From 091b8f1934a5ad71b2a15e8570b6934f7119e44d Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Wed, 21 Oct 2026 09:41:53 -0700 Subject: [PATCH 001/395] Report which packages and modules would run in a Node island Adds the report-only Node island policy, configuration and detector of the scope-B plan (milestone NI-2). Coverage now lists the npm packages a build would run in a Node island and why, gap islands first, without changing code generation. - project-config.ts reads the `scriptc` key of the entry's package.json with a schema; an unknown value in `scriptc.packages` or `scriptc.nodeIslands` is SC8004 instead of being ignored. Package shims and the WebAssembly scanner read through it. - node-islands/: origin (user, npm, shim, builtin; package instances), rules R1.1-R2.5 and the four classes, a syntactic pre-scan for R1/R2 markers, classification of refusals and runtime fences (reason ids at the refusal sites that need them), the policy (auto, explicit, off; forced node and native packages; marked user modules) and the report with island closure. - coverage: a "Node islands" section, `--explain-island=`, a `nodeIslands` field in the diagnostics envelope, and `--fail-on` `node-islands`, `eager-node-islands` and `island-gaps`. - CLI: `--node-islands`, `--node-island`, `--native`, `--node` (validated, report-only) and `--strict-user-js` (SC8101-SC8105 for R2 constructs in user JavaScript). Co-Authored-By: Claude Opus 5.5 --- .gitignore | 1 + packages/cli/src/bootstrap.ts | 23 +- packages/compiler/src/cli/command.ts | 85 +- packages/compiler/src/cli/usage.ts | 34 +- packages/compiler/src/compile-types.ts | 4 + packages/compiler/src/coverage/report.ts | 278 +++++- .../compiler/src/coverage/verdict-cache.ts | 11 +- .../compiler/src/diagnostics/diagnostic.ts | 67 ++ packages/compiler/src/diagnostics/envelope.ts | 53 ++ packages/compiler/src/frontend/analysis.ts | 26 +- .../lowering/builtins/module-resolution.ts | 2 + .../src/frontend/lowering/call-signatures.ts | 2 + .../frontend/lowering/cjs-inline-require.ts | 2 + .../lowering/expressions/identifiers.ts | 12 +- .../frontend/lowering/lower-node-module.ts | 2 + .../src/frontend/lowering/lower-stdlib.ts | 2 + .../compiler/src/frontend/lowering/lowerer.ts | 17 +- .../compiler/src/frontend/package-shims.ts | 33 +- packages/compiler/src/frontend/pipeline.ts | 107 ++- packages/compiler/src/index.ts | 3 + .../compiler/src/node-islands/classify.ts | 131 +++ .../compiler/src/node-islands/detect.test.ts | 165 ++++ packages/compiler/src/node-islands/detect.ts | 849 ++++++++++++++++++ packages/compiler/src/node-islands/origin.ts | 68 ++ packages/compiler/src/node-islands/policy.ts | 167 ++++ .../compiler/src/node-islands/report.test.ts | 201 +++++ packages/compiler/src/node-islands/report.ts | 355 ++++++++ packages/compiler/src/node-islands/rules.ts | 109 +++ packages/compiler/src/node-islands/scan.ts | 155 ++++ packages/compiler/src/node-islands/summary.ts | 110 +++ packages/compiler/src/project-config.test.ts | 126 +++ packages/compiler/src/project-config.ts | 421 +++++++++ packages/compiler/src/wasm/discover.ts | 39 +- .../node-islands-config/main.ts.json | 6 + .../node-islands-user-js.cjs | 22 + tests/coverage-fixtures/node-islands/main.ts | 16 + .../node_modules/ni-ctor/index.d.ts | 4 + .../node_modules/ni-ctor/index.js | 15 + .../node_modules/ni-ctor/package.json | 5 + .../node-islands/node_modules/ni-dep/index.js | 4 + .../node_modules/ni-dep/package.json | 5 + .../node_modules/ni-eval/index.d.ts | 1 + .../node_modules/ni-eval/index.js | 5 + .../node_modules/ni-eval/package.json | 5 + .../node_modules/ni-gap/index.d.ts | 1 + .../node-islands/node_modules/ni-gap/index.js | 8 + .../node_modules/ni-gap/package.json | 5 + .../node_modules/ni-minified/index.d.ts | 1 + .../node_modules/ni-minified/index.js | 1 + .../node_modules/ni-minified/package.json | 5 + .../node_modules/ni-typed/index.d.ts | 1 + .../node_modules/ni-typed/index.js | 4 + .../node_modules/ni-typed/package.json | 5 + .../node-islands/package.json | 5 + tests/diagnostics/node-islands-config/main.ts | 3 + .../node-islands-config/package.json | 14 + .../coverage-node-islands-explain.txt | 13 + .../coverage-node-islands-user-js.txt | 34 + .../__snapshots__/coverage-node-islands.txt | 39 + .../node-islands-config/main.ts.txt | 35 + tests/harness/coverage.test.ts | 87 ++ 61 files changed, 3928 insertions(+), 81 deletions(-) create mode 100644 packages/compiler/src/node-islands/classify.ts create mode 100644 packages/compiler/src/node-islands/detect.test.ts create mode 100644 packages/compiler/src/node-islands/detect.ts create mode 100644 packages/compiler/src/node-islands/origin.ts create mode 100644 packages/compiler/src/node-islands/policy.ts create mode 100644 packages/compiler/src/node-islands/report.test.ts create mode 100644 packages/compiler/src/node-islands/report.ts create mode 100644 packages/compiler/src/node-islands/rules.ts create mode 100644 packages/compiler/src/node-islands/scan.ts create mode 100644 packages/compiler/src/node-islands/summary.ts create mode 100644 packages/compiler/src/project-config.test.ts create mode 100644 packages/compiler/src/project-config.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json create mode 100644 tests/coverage-fixtures/node-islands-user-js.cjs create mode 100644 tests/coverage-fixtures/node-islands/main.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.d.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-ctor/package.json create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-dep/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-dep/package.json create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.d.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-eval/package.json create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.d.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-gap/package.json create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.d.ts create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.js create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-typed/package.json create mode 100644 tests/coverage-fixtures/node-islands/package.json create mode 100644 tests/diagnostics/node-islands-config/main.ts create mode 100644 tests/diagnostics/node-islands-config/package.json create mode 100644 tests/harness/__snapshots__/coverage-node-islands-explain.txt create mode 100644 tests/harness/__snapshots__/coverage-node-islands-user-js.txt create mode 100644 tests/harness/__snapshots__/coverage-node-islands.txt create mode 100644 tests/harness/__snapshots__/node-islands-config/main.ts.txt diff --git a/.gitignore b/.gitignore index fda9414aa..fa352c85e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules/ !tests/corpus/*/node_modules/ !tests/fixtures/npm-static/node_modules/ !tests/coverage-fixtures/wasm/node_modules/ +!tests/coverage-fixtures/node-islands*/node_modules/ !tests/fixtures/npm-static/nested/inner/node_modules/ !tests/fixtures/npm-static/node_modules/*/node_modules/ !tests/fixtures/npm/store/*/node_modules/ diff --git a/packages/cli/src/bootstrap.ts b/packages/cli/src/bootstrap.ts index 1a319f98d..3fb61232f 100644 --- a/packages/cli/src/bootstrap.ts +++ b/packages/cli/src/bootstrap.ts @@ -70,7 +70,9 @@ async function tryFastPath(): Promise { values["profile-generate"] !== undefined || values["profile-use"] !== undefined || values.sparse || - (values["external-types"] ?? []).length > 0 + (values["external-types"] ?? []).length > 0 || + // The full CLI validates the Node island policy flags. + hasNodeIslandFlags(values) ) return null; const backend = values.backend; @@ -176,6 +178,21 @@ async function tryFastPath(): Promise { }); } +function hasNodeIslandFlags( + values: ReturnType< + typeof parseArgs<{ options: typeof CLI_OPTIONS; allowPositionals: true; allowNegative: true }> + >["values"], +): boolean { + return ( + values["node-islands"] !== undefined || + (values["node-island"] ?? []).length > 0 || + (values.native ?? []).length > 0 || + values.node !== undefined || + values["explain-island"] !== undefined || + values["strict-user-js"] + ); +} + /** An unchanged program's coverage verdict replays from the verdict cache * without loading the compiler graph. Anything else (a miss, a comment-only * edit, options the replay does not cover) defers to the full CLI. */ @@ -205,7 +222,9 @@ async function coverageFastPath( values["provenance-sources"] || values["emit-ir"] || (values["npm-static"] ?? []).length > 0 || - (values["external-types"] ?? []).length > 0 + (values["external-types"] ?? []).length > 0 || + // The replay covers the default Node island policy only. + hasNodeIslandFlags(values) ) return null; let startup: typeof import("@scriptc/compiler/startup-cache"); diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 03160c6fa..128dacb26 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -4,12 +4,19 @@ import { parseArgs } from "node:util"; import { isExactExternalTypeSpecifier } from "../frontend/program.js"; import { renderDiagnostics } from "../diagnostics/render.js"; import { + COVERAGE_FAIL_ON, coverageEnvelope, coveragePasses, renderCoverage, renderExplainDynamic, + renderExplainIsland, type CoverageFailOn, } from "../coverage/report.js"; +import { + NODE_ISLANDS_MODES, + type NodeIslandOptions, + type NodeIslandsMode, +} from "../node-islands/policy.js"; import { TIER_MODES, type TierMode } from "../frontend/tier-analysis.js"; import { buildEnvelope } from "../diagnostics/envelope.js"; import { renderWarnings } from "../diagnostics/render.js"; @@ -105,6 +112,7 @@ async function main(args: string[], host: CliHost): Promise { values["emit-ir"] || values.tier !== undefined || values["explain-dynamic"] !== undefined || + hasNodeIslandFlags(values) || !values["keep-llvm"] ) { fail( @@ -162,6 +170,7 @@ async function main(args: string[], host: CliHost): Promise { values["emit-ir"] || values.tier !== undefined || values["explain-dynamic"] !== undefined || + hasNodeIslandFlags(values) || !values["keep-llvm"]; if (values.out === undefined || inputs.length === 0 || others) { fail( @@ -214,7 +223,8 @@ async function main(args: string[], host: CliHost): Promise { (values["npm-static"] ?? []).length > 0 || externalTypeArgs.length > 0 || values.tier !== undefined || - values["explain-dynamic"] !== undefined + values["explain-dynamic"] !== undefined || + hasNodeIslandFlags(values) ) { fail( "scriptc build --lib takes no --backend/--emit/--print/--optimization/--strip/--windows-subsystem/--detached-exit/--profile-generate/--profile-use/--npm-static/--ffi/--external-types/--tier/--explain-dynamic: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, external type mappings belong to coverage, and library artifacts are static-tier only", @@ -284,8 +294,10 @@ async function main(args: string[], host: CliHost): Promise { fail(`--print=diagnostics is a build/coverage option\n\n${USAGE}`); } const failOnRaw = values["fail-on"]; - if (failOnRaw !== undefined && failOnRaw !== "blockers" && failOnRaw !== "divergences") { - fail(`unknown --fail-on value "${failOnRaw}" (supported: blockers, divergences)\n\n${USAGE}`); + if (failOnRaw !== undefined && !(COVERAGE_FAIL_ON as readonly string[]).includes(failOnRaw)) { + fail( + `unknown --fail-on value "${failOnRaw}" (supported: ${COVERAGE_FAIL_ON.join(", ")})\n\n${USAGE}`, + ); } if (failOnRaw !== undefined && command !== "coverage") { fail(`--fail-on is a coverage option (a build already fails on blockers)\n\n${USAGE}`); @@ -300,6 +312,48 @@ async function main(args: string[], host: CliHost): Promise { if (explainDynamic !== undefined && command !== "coverage") { fail(`--explain-dynamic is a coverage option\n\n${USAGE}`); } + const explainIsland = values["explain-island"]; + if (explainIsland !== undefined && command !== "coverage") { + fail(`--explain-island is a coverage option\n\n${USAGE}`); + } + if (explainIsland === "") fail(`--explain-island needs a package name\n\n${USAGE}`); + if (values["strict-user-js"] && command !== "coverage") { + fail(`--strict-user-js is a coverage option\n\n${USAGE}`); + } + // The Node island policy's command-line half. Report-only: build and run + // validate it, coverage reports the islands it would choose. + const islandMode = values["node-islands"]; + if (islandMode !== undefined && !(NODE_ISLANDS_MODES as readonly string[]).includes(islandMode)) { + fail( + `unknown --node-islands value "${islandMode}" (supported: ${NODE_ISLANDS_MODES.join(", ")})\n\n${USAGE}`, + ); + } + const listValues = (raw: string[] | undefined): string[] => + (raw ?? []) + .flatMap((v) => v.split(",")) + .map((v) => v.trim()) + .filter((v) => v !== ""); + const islandArgs = listValues(values["node-island"]); + const nativeArgs = listValues(values.native); + for (const name of nativeArgs) { + if (islandArgs.includes(name)) + fail(`--native=${name} and --node-island=${name} contradict each other\n\n${USAGE}`); + } + if (values.node === "") fail(`--node needs the path of a node executable\n\n${USAGE}`); + const nodeIslands: NodeIslandOptions | undefined = + islandMode === undefined && + islandArgs.length === 0 && + nativeArgs.length === 0 && + values.node === undefined && + !values["strict-user-js"] + ? undefined + : { + ...(islandMode !== undefined ? { mode: islandMode as NodeIslandsMode } : {}), + ...(islandArgs.length > 0 ? { islands: islandArgs } : {}), + ...(nativeArgs.length > 0 ? { native: nativeArgs } : {}), + ...(values.node !== undefined ? { node: resolve(values.node) } : {}), + ...(values["strict-user-js"] ? { strictUserJs: true } : {}), + }; if (printNativeLinkInfo && command !== "build") { fail(`--print=native-link-info is a build option\n\n${USAGE}`); } @@ -429,6 +483,7 @@ async function main(args: string[], host: CliHost): Promise { ...(tier !== undefined ? { tier } : {}), ...(explainDynamic !== undefined ? { explainDynamic: true } : {}), ...(wasmFiles.length > 0 ? { wasm: wasmFiles } : {}), + ...(nodeIslands !== undefined ? { nodeIslands } : {}), }); if (printDiagnostics) { // Keep stdout pure JSON for tooling. @@ -443,6 +498,17 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write( renderCoverage(coverage, { color, sourceTexts, root: process.cwd() }) + "\n", ); + if (explainIsland !== undefined) { + process.stdout.write( + "\n" + + renderExplainIsland(coverage.nodeIslands, explainIsland, { + color, + sourceTexts, + root: process.cwd(), + }) + + "\n", + ); + } if (explainDynamic !== undefined && coverage.tiers !== undefined) { process.stdout.write( "\n" + @@ -564,6 +630,19 @@ async function main(args: string[], host: CliHost): Promise { return 0; } +/** Whether any Node island flag is present (they belong to build, run and + * coverage). */ +function hasNodeIslandFlags(values: ReturnType["values"]): boolean { + return ( + values["node-islands"] !== undefined || + (values["node-island"] ?? []).length > 0 || + (values.native ?? []).length > 0 || + values.node !== undefined || + values["explain-island"] !== undefined || + values["strict-user-js"] + ); +} + /** Both installed and seed commands use this argument and diagnostic contract. */ export async function runCli(args: string[], host: CliHost): Promise { try { diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index aa7408d2a..bd40aa57c 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -31,10 +31,12 @@ 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 + divergences, any Node island (eager-node-islands is the + same until deferred evaluation lands), or a gap island + (a package scriptc should compile but cannot yet) --backend code generator (llvm) --optimization native optimization posture (default: release/-O2). dev @@ -100,6 +102,28 @@ Options: values and the bindings, fields, functions, classes and modules the dynamic tier would take over, with the refusal behind each; a name shows only that unit's chain + --node-islands + Node island policy (default: auto): npm packages outside + the native subset run in Node automatically; explicit + allows only marked islands; off allows none. REPORT-ONLY + in this version: coverage reports the islands a build + would choose; builds compile as before + --node-island + mark an npm package or one of your modules (a path or + glob) to run in a Node island (repeatable, comma lists; + report-only) + --native require an npm package to compile natively; outside the + native subset it is SC8003 (repeatable; report-only) + --node the node executable Node islands use (report-only) + --explain-island + coverage only: why a package would run in a Node island, + with the first reason per module, the native import + sites and the packages inside its closure + --strict-user-js + coverage only: report constructor functions, prototype + and shape mutation, sloppy-mode semantics and builtin + patching in your own .js/.cjs/.mjs modules as + SC8101–SC8105 blockers --wasm coverage only: report a WebAssembly module the program loads whose path its sources do not spell (repeatable; @@ -132,6 +156,12 @@ export const CLI_OPTIONS = { tier: { type: "string" }, "explain-dynamic": { type: "string" }, wasm: { type: "string", multiple: true }, + "node-islands": { type: "string" }, + "node-island": { type: "string", multiple: true }, + native: { type: "string", multiple: true }, + node: { type: "string" }, + "explain-island": { type: "string" }, + "strict-user-js": { type: "boolean", default: false }, lib: { type: "boolean", default: false }, profile: { type: "string" }, help: { type: "boolean", short: "h", default: false }, diff --git a/packages/compiler/src/compile-types.ts b/packages/compiler/src/compile-types.ts index 9ecd75974..39fea3acf 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -4,6 +4,7 @@ import type { NativeOptimization } from "./backend/optimization.js"; import type { ScrDiagnostic } from "./diagnostics/diagnostic.js"; import type { CoverageInput } from "./coverage/report.js"; import type { TierMode } from "./frontend/tier-analysis.js"; +import type { NodeIslandOptions } from "./node-islands/policy.js"; export type CompileOutputKind = "ir" | "llvm" | "asm" | "obj" | "exe"; @@ -183,6 +184,9 @@ export interface AnalyzeOptions { /** `--wasm `: WebAssembly module files the program loads that its * sources do not name (absolute paths); reported with the discovered ones. */ wasm?: readonly string[]; + /** The Node island policy's command-line half (report-only: coverage + * reports which packages and modules would run in a Node island). */ + nodeIslands?: NodeIslandOptions; } export interface AnalyzeResult { diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 89733e255..8fc12a040 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -17,6 +17,7 @@ import { type DiagnosticsEnvelope, type LocatedDiagnostic, type SourcePosition, + type NodeIslandsEnvelope, type TierUnitPosition, type TiersEnvelope, } from "../diagnostics/envelope.js"; @@ -25,6 +26,14 @@ import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { ProvenanceSources } from "../frontend/provenance-registry.js"; import type { DemotionUnit, TierReport } from "../frontend/tier-analysis.js"; import type { WasmModuleReport } from "../wasm/discover.js"; +import { + describe as describeReason, + hasNodeIslandDecisions, + type NodeIslandEntry, + type NodeIslandReport, +} from "../node-islands/summary.js"; +import { classDescription } from "../node-islands/rules.js"; +import type { SrcLoc } from "../ir/ir.js"; export interface CoverageInput { file: string; @@ -61,6 +70,10 @@ export interface CoverageInput { /** The WebAssembly modules the build can see (wasm/discover.ts): their * size, validity and the proposals they use. */ wasmModules?: WasmModuleReport[]; + /** Node islands, report-only: the packages and user modules that would + * run in a Node island, and why. Present when there is something to + * report. */ + nodeIslands?: NodeIslandReport; /** True when tsc preflight failed — no coverage can be computed. */ preflightFailed: boolean; } @@ -144,6 +157,19 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions out.push(` ${c(RED, "not analyzable")}: ${config[0]!.message}`); return out.join("\n"); } + const projectConfig = input.diagnostics.filter((d) => d.code === "SC8004"); + if (projectConfig.length > 0) { + out.push( + ` ${c(RED, "not analyzable")}: the project's scriptc configuration has ${projectConfig.length} unknown setting${projectConfig.length === 1 ? "" : "s"}`, + ); + for (const d of projectConfig) { + const p = locator.position(d.loc.file, d.loc.start); + out.push(` ${c(DIM, "SC8004")} ${d.message}`); + out.push(` ${c(DIM, `at ${where({ file: d.loc.file, ...p })}`)}`); + if (d.hint !== undefined) out.push(` ${c(DIM, `hint: ${d.hint}`)}`); + } + return out.join("\n"); + } const tsc = input.diagnostics.filter((d) => d.code === "SC0001"); if (tsc.length > 0) { const world = tsc.filter((d) => d.typeWorld === true).length; @@ -221,6 +247,22 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions out.push(""); } + // Node islands (report-only): what would run in Node, gap islands + // first (DESIGN.md §1.3: a gap island is compiler work an island hides). + if (input.nodeIslands !== undefined && hasNodeIslandDecisions(input.nodeIslands)) { + renderNodeIslands( + input.nodeIslands, + out, + c, + (loc) => { + const p = locator.position(loc.file, loc.start); + return where({ file: loc.file, ...p }); + }, + opts.root, + ); + out.push(""); + } + // The WebAssembly modules the build can see (wasm/discover.ts). A build // translates the valid ones to native code (wasm/translate.ts) when the // program uses the WebAssembly API; the others fail to load at run time. @@ -450,7 +492,20 @@ function displayPath(file: string, root: string | undefined): string { /** What `coverage --fail-on` checks: reached blockers (they fail a build) * and, optionally, divergences. Unreached blockers never fail. */ -export type CoverageFailOn = "blockers" | "divergences"; +export type CoverageFailOn = + | "blockers" + | "divergences" + | "node-islands" + | "eager-node-islands" + | "island-gaps"; + +export const COVERAGE_FAIL_ON: readonly CoverageFailOn[] = [ + "blockers", + "divergences", + "node-islands", + "eager-node-islands", + "island-gaps", +]; /** Whether the analysis passes the selected gate. Preflight failures * always fail; `failOn` undefined means only preflight fails (the report's @@ -462,6 +517,16 @@ 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; + // 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. + // Would-be policy errors (SC8002, SC8003) fail every island gate. + const islands = input.nodeIslands; + if (islands === undefined) return true; + const anyIsland = failOn === "node-islands" || failOn === "eager-node-islands"; + if ((anyIsland || failOn === "island-gaps") && islands.violations.length > 0) return false; + if (anyIsland && (islands.islands.length > 0 || islands.userModules.length > 0)) return false; + if (failOn === "island-gaps" && islands.islands.some((i) => i.class === "gap")) return false; return true; } @@ -524,6 +589,9 @@ export function coverageEnvelope( }), ...(input.tiers !== undefined ? { tiers: tiersEnvelope(input.tiers, locator) } : {}), ...(input.wasmModules !== undefined ? { wasmModules: input.wasmModules } : {}), + ...(input.nodeIslands !== undefined + ? { nodeIslands: nodeIslandsEnvelope(input.nodeIslands, locator) } + : {}), groups, diagnostics: located, }; @@ -661,3 +729,211 @@ export function renderExplainDynamic( } return out.join("\n"); } + +/* ── Node islands (report-only) ───────────────────────────────────────── */ + +const ISLAND_REASONS_SHOWN = 1; + +function islandName(i: { package: string; version: string | null }): string { + return i.version === null ? i.package : `${i.package}@${i.version}`; +} + +function renderNodeIslands( + report: NodeIslandReport, + out: string[], + c: (code: string, s: string) => string, + at: (loc: SrcLoc) => string, + root: string | undefined, +): void { + const n = report.islands.length + report.userModules.length; + out.push( + ` ${c(BOLD, "Node islands")} ${c(DIM, `(would-be, report-only: the build is unchanged; --node-islands=${report.policy})`)}`, + ); + if (n === 0) out.push(` ${c(DIM, "none")}`); + const widest = Math.max(0, ...report.islands.map((i) => i.class.length)); + for (const island of report.islands) { + const tone = island.class === "gap" ? RED : YELLOW; + const decided = + island.decision === "auto" + ? "" + : c( + DIM, + ` (marked by ${island.decision === "cli" ? "--node-island" : "scriptc.packages"})`, + ); + out.push(` ${c(tone, island.class.padEnd(widest))} ${islandName(island)}${decided}`); + for (const r of island.reasons.slice(0, ISLAND_REASONS_SHOWN)) { + const where = r.loc === null ? "" : `${at(r.loc)}: `; + out.push(` ${c(DIM, capMessage(`${where}${describeReason(r)}`))}`); + } + const more = island.reasons.length - ISLAND_REASONS_SHOWN; + const details = [ + more > 0 + ? `+${more} more reason${more === 1 ? "" : "s"} in ${counted(island.modules, "module")}` + : "", + island.carries.length > 0 ? `carries ${island.carries.join(", ")}` : "", + ].filter((t) => t !== ""); + if (details.length > 0) out.push(` ${c(DIM, details.join("; "))}`); + } + for (const m of report.userModules) { + out.push( + ` ${c(YELLOW, "marked".padEnd(widest))} ${displayPath(m.file, root)} ${c(DIM, `(user module, marked by ${m.source === "cli" ? "--node-island" : "nodeIslands.modules"})`)}`, + ); + } + for (const v of report.violations) { + out.push(` ${c(RED, v.code)} ${capMessage(v.message)}`); + } + const shared = report.packages.filter((p) => p.status === "shared"); + if (shared.length > 0) { + out.push( + ` ${c(DIM, `reached from native code and from an island: ${shared.map((p) => p.package).join(", ")}`)}`, + ); + } + const native = report.packages.filter((p) => p.status === "native"); + if (native.length > 0) { + out.push( + ` ${c(DIM, `${counted(native.length, "package")} stay${native.length === 1 ? "s" : ""} native: ${native.map((p) => p.package).join(", ")}`)}`, + ); + } + if (report.unknownPackages.length > 0) { + out.push( + ` ${c(YELLOW, "note")} not imported by the program: ${report.unknownPackages.join(", ")}`, + ); + } + if (report.islands.length > 0) { + out.push( + ` ${c(DIM, "hint: scriptc coverage --explain-island= lists every reason")}`, + ); + } +} + +/** `scriptc coverage --explain-island=`: why a package would run + * in a Node island — every module's first reason, the native import sites + * (the boundary), the packages inside its closure, and what later + * milestones decide. */ +export function renderExplainIsland( + report: NodeIslandReport | undefined, + query: string, + opts: CoverageRenderOptions = {}, +): string { + const c = (code: string, s: string) => (opts.color ? code + s + RESET : s); + const locator = new SourceLocator(opts.sourceTexts); + const at = (loc: SrcLoc) => { + const p = locator.position(loc.file, loc.start); + return `${displayPath(loc.file, opts.root)}:${p.line}:${p.column}`; + }; + const out: string[] = []; + const matches = (report?.islands ?? []).filter( + (i) => i.package === query || islandName(i) === query, + ); + if (matches.length === 0) { + const pkg = (report?.packages ?? []).find((p) => p.package === query); + if (pkg === undefined) { + out.push(` '${query}' is not a Node island: no npm package of that name is in the program`); + } else if (pkg.status === "inside") { + out.push( + ` '${query}' is not a Node island of its own: only ${pkg.islands.join(", ")} import${pkg.islands.length === 1 ? "s" : ""} it, so it would run inside that island`, + ); + } else if (pkg.status === "shared") { + out.push( + ` '${query}' compiles natively and is also reached from the Node island${pkg.islands.length === 1 ? "" : "s"} ${pkg.islands.join(", ")}`, + ); + } else { + out.push(` '${query}' is not a Node island: it compiles natively`); + } + return out.join("\n"); + } + for (const island of matches) { + out.push( + ` ${c(BOLD, "Node island")} ${islandName(island)} ${c(DIM, "(would-be, report-only)")}`, + ); + if (island.path !== null) out.push(` path ${displayPath(island.path, opts.root)}`); + out.push(` class ${island.class}: ${classDescription(island.class)}`); + out.push( + ` decision ${island.decision === "auto" ? "automatic (npm code outside the native subset)" : island.decision === "cli" ? "marked with --node-island" : 'marked "node" in scriptc.packages'}`, + ); + out.push( + ` evaluation eager at its module position ${c(DIM, "(deferred evaluation is decided by a later milestone)")}`, + ); + if (island.reasons.length > 0) { + out.push(` reasons ${c(DIM, "(first per module; decisive class first)")}`); + const shown = new Set(); + for (const r of island.reasons) { + const key = r.loc === null ? "" : r.loc.file; + if (shown.has(key)) continue; + shown.add(key); + const where = r.loc === null ? "package" : at(r.loc); + out.push(` ${r.class.padEnd(12)} ${where}`); + out.push(` ${c(DIM, capMessage(describeReason(r)))}`); + } + const counts = new Map(); + for (const r of island.reasons) counts.set(r.class, (counts.get(r.class) ?? 0) + 1); + const summary = [...counts].map(([cls, k]) => `${k} ${cls}`).join(", "); + out.push(` all reasons ${island.reasons.length} (${summary})`); + } + if (island.importedFrom.length > 0) { + out.push(` boundary ${counted(island.importedFrom.length, "native import site")}:`); + for (const loc of island.importedFrom.slice(0, SITES_SHOWN)) out.push(` ${at(loc)}`); + if (island.importedFrom.length > SITES_SHOWN) + out.push(` ${c(DIM, `+${island.importedFrom.length - SITES_SHOWN} more`)}`); + out.push( + ` ${c(DIM, "copy and handle decisions per boundary site come with compiler-managed islands")}`, + ); + } else { + out.push( + ` boundary ${c(DIM, "no native import site (reached only from other islands)")}`, + ); + } + if (island.carries.length > 0) out.push(` carries ${island.carries.join(", ")}`); + } + return out.join("\n"); +} + +function nodeIslandsEnvelope( + report: NodeIslandReport, + locator: SourceLocator, +): NodeIslandsEnvelope { + const pos = (loc: SrcLoc): SourcePosition => ({ + file: loc.file, + ...locator.position(loc.file, loc.start), + }); + const entry = (i: NodeIslandEntry) => { + const first = i.reasons[0]; + return { + package: i.package, + version: i.version, + path: i.path, + class: i.class, + reason: i.reason, + diagnostic: i.diagnostic, + decision: i.decision, + evaluation: i.evaluation, + ...(first !== undefined && first.loc !== null ? { at: pos(first.loc) } : {}), + reasons: i.reasons.map((r) => ({ + class: r.class, + rule: r.rule, + reason: r.reason, + code: r.code, + message: r.message, + source: r.source, + ...(r.loc !== null ? { at: pos(r.loc) } : {}), + })), + carries: i.carries, + importedFrom: i.importedFrom.map(pos), + }; + }; + return { + mode: report.mode, + policy: report.policy, + islands: report.islands.map(entry), + userModules: report.userModules.map((m) => ({ file: m.file, decision: m.source })), + npmPackages: report.packages.map((p) => ({ + package: p.package, + version: p.version, + path: p.path, + status: p.status, + islands: p.islands, + })), + violations: report.violations.map((v) => ({ code: v.code, message: v.message, ...pos(v.loc) })), + unknownPackages: report.unknownPackages, + }; +} diff --git a/packages/compiler/src/coverage/verdict-cache.ts b/packages/compiler/src/coverage/verdict-cache.ts index 422bf5e4d..af8269189 100644 --- a/packages/compiler/src/coverage/verdict-cache.ts +++ b/packages/compiler/src/coverage/verdict-cache.ts @@ -15,13 +15,15 @@ import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { NpmStaticStatus } from "./report.js"; import type { WasmModuleReport } from "../wasm/discover.js"; +import type { NodeIslandReport } from "../node-islands/summary.js"; +import { nodeIslandOptionsKey } from "../node-islands/policy.js"; import { FrontendInputTracker, frontendInputsStillMatch, type FrontendInputSnapshot, } from "../frontend/input-tracker.js"; -const SCHEMA = "scriptc.coverage-verdict.v2"; +const SCHEMA = "scriptc.coverage-verdict.v3"; /** Key-value storage for verdict entries (best effort: failures miss). */ export interface VerdictStore { @@ -40,6 +42,7 @@ export interface CachedCoverage { divergences?: ScrDiagnostic[]; npmStatic?: NpmStaticStatus[]; wasmModules?: WasmModuleReport[]; + nodeIslands?: NodeIslandReport; preflightFailed: boolean; } @@ -129,6 +132,10 @@ export function verdictKey( ...(opts.tier === "static-only" ? ["tier=static-only"] : []), // The module files' contents are probes; their names are options. ...(opts.wasm !== undefined && opts.wasm.length > 0 ? [`wasm=${opts.wasm.join("\n")}`] : []), + // The Node island policy's command-line half shapes the report. + ...(opts.nodeIslands !== undefined + ? [`node-islands=${nodeIslandOptionsKey(opts.nodeIslands)}`] + : []), ].join("\0"), ); } @@ -151,6 +158,7 @@ function cacheable(coverage: AnalyzeResult["coverage"]): CachedCoverage | null { if (coverage.divergences !== undefined) out.divergences = coverage.divergences; if (coverage.npmStatic !== undefined) out.npmStatic = coverage.npmStatic; if (coverage.wasmModules !== undefined) out.wasmModules = coverage.wasmModules; + if (coverage.nodeIslands !== undefined) out.nodeIslands = coverage.nodeIslands; return out; } @@ -168,6 +176,7 @@ function restore(cached: CachedCoverage, sources: CachedSource[]): AnalyzeResult if (cached.divergences !== undefined) coverage.divergences = cached.divergences; if (cached.npmStatic !== undefined) coverage.npmStatic = cached.npmStatic; if (cached.wasmModules !== undefined) coverage.wasmModules = cached.wasmModules; + if (cached.nodeIslands !== undefined) coverage.nodeIslands = cached.nodeIslands; return { coverage, sourceTexts }; } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index e4d4006ca..b66efb351 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -62,6 +62,13 @@ * to a cast to an unrelated class (SC6004); SC6005 (Date strings * outside the native parser's grammar) is retired — the runtime * parses date strings with V8's grammar + * SC8xxx Node island policy: packages and modules outside the native + * subset that run (or, in report-only mode, would run) in a + * Node island — unknown project configuration (SC8004), a + * package required to be native (SC8003) or every island + * refused (SC8002) while outside the native subset, and the + * R2 object-model constructs in user JavaScript (SC8101–SC8105, + * behind --strict-user-js) * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; @@ -89,6 +96,11 @@ export interface ScrDiagnostic { * the error may not reproduce under the project's tsc. Machine-readable * output reports these under the "environment" category. */ typeWorld?: boolean; + /** Why the construct is outside the native subset, as a Node island + * reason id (node-islands/rules.ts), at the refusal sites whose code + * alone does not say it (a computed require(), a native addon, `with`). + * Report-only: never part of the rendered message. */ + reason?: string; } /** SC3002–SC3004 — native-helper target, installation, and execution @@ -201,6 +213,60 @@ export function unrelatedClassCastDivergenceDiag( }; } +/* ── SC8xxx: Node island policy ───────────────────────────────────────── */ + +/** SC8004 — an unknown key or value in `scriptc.packages` or + * `scriptc.nodeIslands`. */ +export function projectConfigDiag( + path: string, + detail: string, + hint: string, + loc: SrcLoc, +): ScrDiagnostic { + return { + code: "SC8004", + message: `package.json '${path}' ${detail}`, + loc, + hint, + }; +} + +/** SC8002 — Node islands are off (or limited to marked islands) and an + * npm package is outside the native subset. */ +export function nodeIslandsOffDiag(pkg: string, why: string, loc: SrcLoc): ScrDiagnostic { + return { + code: "SC8002", + message: `npm package '${pkg}' is outside the native subset (${why}) and Node islands are off`, + loc, + hint: `allow Node islands (--node-islands=auto), mark it with --node-island=${pkg}, or replace the dependency`, + }; +} + +/** SC8003 — a package marked "native" is outside the native subset. */ +export function nativePackageDiag(pkg: string, why: string, loc: SrcLoc): ScrDiagnostic { + return { + code: "SC8003", + message: `npm package '${pkg}' is marked native but is outside the native subset: ${why}`, + loc, + hint: `remove the "native" marking to let it run in a Node island, or replace the construct the reason names`, + }; +} + +/** SC8101–SC8105 — an R2 object-model construct in the program's own + * JavaScript (R2.1 constructor functions … R2.5 builtin patching). */ +export function userJsObjectModelDiag( + code: "SC8101" | "SC8102" | "SC8103" | "SC8104" | "SC8105", + construct: string, + loc: SrcLoc, +): ScrDiagnostic { + return { + code, + message: `${construct} needs a mutable JavaScript object model, which native code does not have`, + loc, + hint: "rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules", + }; +} + /* ── SC5xxx: native FFI ───────────────────────────────────────────────── */ /** SC5001 — the outbound native-FFI manifest is malformed or unreadable. */ @@ -766,6 +832,7 @@ export function nativeAddonDiag(specifier: string, loc: SrcLoc): ScrDiagnostic { loc, milestone: "later", hint: "expose the native operation as a plain C ABI function in an object or static archive, then bind a signature-only TypeScript declaration with --ffi; Node-API callbacks themselves require Node (https://scriptc.dev/ffi#replacing-a-node-api-addon)", + reason: "native-addon", }; } diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index c986fc5b1..42b424a7b 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -142,10 +142,60 @@ export interface DiagnosticsEnvelope { /** coverage only: the WebAssembly modules the build can see, with their * size, validity, proposals and the sources that found them. */ wasmModules?: WasmModuleReport[]; + /** coverage only, when there is something to report: the packages and + * user modules that would run in a Node island (report-only). */ + nodeIslands?: NodeIslandsEnvelope; groups: DiagnosticGroup[]; diagnostics: LocatedDiagnostic[]; } +/** One reason a package is outside the native subset. */ +export interface NodeIslandReasonEnvelope { + class: string; + /** R1.1–R2.5, or null for gap and packaging reasons. */ + rule: string | null; + reason: string; + code: string | null; + message: string; + source: string; + at?: SourcePosition; +} + +/** `scriptc coverage --print=diagnostics`: the Node island decisions. In + * this version they are would-be decisions (mode "would-be"): the build + * does not change. */ +export interface NodeIslandsEnvelope { + mode: "would-be"; + policy: "auto" | "explicit" | "off"; + /** Gap islands first, then by class and name. */ + islands: { + package: string; + version: string | null; + path: string | null; + class: string; + reason: string; + diagnostic: string | null; + decision: string; + evaluation: string; + at?: SourcePosition; + reasons: NodeIslandReasonEnvelope[]; + carries: string[]; + importedFrom: SourcePosition[]; + }[]; + userModules: { file: string; decision: string }[]; + /** Every npm package that is not an island: native, inside an island's + * closure, or shared between native code and an island. */ + npmPackages: { + package: string; + version: string | null; + path: string | null; + status: string; + islands: string[]; + }[]; + violations: (SourcePosition & { code: string; message: string })[]; + unknownPackages: string[]; +} + /** The divergence band: compiles, but the program can behave differently * from Node. Warnings, never build failures. */ export function isDivergenceCode(code: string): boolean { @@ -159,6 +209,7 @@ export function categoryOf(diag: ScrDiagnostic): DiagnosticCategory { if (code === "SC0003") return "invalid-source"; if (code === "SC0002" || code === "SC0004") return "environment"; if (/^SC[35]\d{3}$/.test(code)) return "environment"; + if (code === "SC8004" || code === "SC8005" || code === "SC8012") return "environment"; if (/^SC9\d{3}$/.test(code)) return "internal"; return "unsupported"; } @@ -179,6 +230,8 @@ export function hintOf(diag: ScrDiagnostic): string { return "restate the value's type inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')"; if (/^SC2\d{3}$/.test(code)) return "restate the type inside the compilable set (https://scriptc.dev/limitations describes the boundary)"; + if (/^SC8\d{3}$/.test(code)) + return "see the Node island policy: npm code outside the native subset runs in Node, user code must be marked (scriptc coverage --explain-island=)"; if (/^SC9\d{3}$/.test(code)) return "this is a compiler bug — please report it with the program that triggers it"; return "see https://scriptc.dev/limitations"; diff --git a/packages/compiler/src/frontend/analysis.ts b/packages/compiler/src/frontend/analysis.ts index 43d0aa2bb..5fbd96ba6 100644 --- a/packages/compiler/src/frontend/analysis.ts +++ b/packages/compiler/src/frontend/analysis.ts @@ -4,6 +4,10 @@ import type { AnalyzeOptions, AnalyzeResult } from "../compile-types.js"; import { loadFfiProfile, type FfiProfile } from "../ffi/ffi-manifest.js"; import { provenanceSources } from "./provenance-registry.js"; import type { FrontendFactory } from "./pipeline.js"; +import { readProjectConfig } from "../project-config.js"; +import { nodeIslandPolicy } from "../node-islands/policy.js"; +import { buildNodeIslandReport } from "../node-islands/report.js"; +import { hasNodeIslandFindings } from "../node-islands/summary.js"; /** Analysis without codegen: how much of the program compiles statically. * Unlike compile(), lowering diagnostics are data here, not failure. */ @@ -96,6 +100,23 @@ export function analyzeWithFrontend( ...(opts.wasm !== undefined && opts.wasm.length > 0 ? { wasmFiles: opts.wasm } : {}), }); const provenance = provenanceSources(); + // Node islands, report-only: which packages and modules would run in + // Node, from the detector's scan and the policy. Code generation does + // not depend on it. + const policy = nodeIslandPolicy(readProjectConfig(entryPath), opts.nodeIslands); + const islandReport = + fe.nodeIslandScan === undefined + ? null + : buildNodeIslandReport({ + entry: entryPath, + scan: fe.nodeIslandScan({ userJs: policy.strictUserJs }), + npm: fe.npmStatic, + diagnostics: [...lowered.diagnostics, ...(lowered.unreached?.diagnostics ?? [])], + fences: [...lowered.runtimeFences, ...(lowered.unreached?.runtimeFences ?? [])], + policy, + }); + const nodeIslands = + islandReport !== null && hasNodeIslandFindings(islandReport) ? islandReport : null; return { coverage: { file: entryPath, @@ -103,7 +124,9 @@ export function analyzeWithFrontend( // The import fences report as blockers alongside the statement-level // ones (use sites of the fenced bindings emit matching diagnostics, // which the report groups with these). - diagnostics: [...preflight, ...lowered.diagnostics], + // --strict-user-js: R2 constructs in user JavaScript are blockers + // (SC8101–SC8105), as scope B's builds will make them errors. + diagnostics: [...preflight, ...lowered.diagnostics, ...(nodeIslands?.userJs ?? [])], ...(lowered.runtimeFences.length > 0 ? { runtimeFences: lowered.runtimeFences } : {}), ...(lowered.divergences !== undefined ? { divergences: lowered.divergences } : {}), ...(lowered.unreached ? { unreached: lowered.unreached } : {}), @@ -115,6 +138,7 @@ export function analyzeWithFrontend( ...(lowered.provenanceElided ? { provenanceElided: lowered.provenanceElided } : {}), ...(lowered.tiers !== undefined ? { tiers: lowered.tiers } : {}), ...(lowered.wasmModules !== undefined ? { wasmModules: lowered.wasmModules } : {}), + ...(nodeIslands !== null ? { nodeIslands } : {}), preflightFailed: false, }, sourceTexts: fe.sourceTexts(), diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index 6b2e9e589..bbaf15353 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -146,6 +146,8 @@ export function lowerCreateRequireCall( "createRequire's require with this argument shape", call, 'the compiled module graph is fixed at build time — the one lowered form is require("")', + undefined, + "dynamic-require", ); } const spec = cr.spec; diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index e5ce784be..f57d9aedb 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -934,6 +934,8 @@ function appendArgumentsParameter( "SC1090", node, "parameterized 'arguments' outside an ES module (sloppy-mode parameter aliases)", + undefined, + "sloppy-arguments", ); } } diff --git a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts index 2b28c6533..a4df8d910 100644 --- a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts +++ b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts @@ -211,6 +211,8 @@ export function lowerUncompiledRequireCall( "SC1090", call, 'require() with a computed specifier (the compiled module graph is fixed at build time: require("") lowers)', + undefined, + "dynamic-require", ); } if (canonicalBuiltinModule(spec) !== null) return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index b5f99d682..bbf725fe9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -786,7 +786,17 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead", eval: "runtime code evaluation cannot be compiled ahead of time", }; - lowerer.noLowering(expr.text, expr, globalHints[expr.text], sym ?? undefined); + lowerer.noLowering( + expr.text, + expr, + globalHints[expr.text], + sym ?? undefined, + expr.text === "WeakRef" || expr.text === "FinalizationRegistry" + ? "weak-ref" + : expr.text === "eval" + ? "code-from-string" + : undefined, + ); } } // An ambient global nothing defines compiles to exactly what Node does diff --git a/packages/compiler/src/frontend/lowering/lower-node-module.ts b/packages/compiler/src/frontend/lowering/lower-node-module.ts index 37675777c..b383d2aff 100644 --- a/packages/compiler/src/frontend/lowering/lower-node-module.ts +++ b/packages/compiler/src/frontend/lowering/lower-node-module.ts @@ -586,6 +586,8 @@ export function fenceNodeModuleMutation( `${action} through require.cache`, target, "a compiled binary has a fixed module graph; cache lookup and Object.keys(require.cache) are read-only", + undefined, + "loader-hook", ); } if ( diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index e968af638..74b2a5709 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -2203,6 +2203,8 @@ export function lowerDynamicImportCall(lowerer: Lowerer, call: ts.CallExpression call, "dynamic import() of computed specifiers (the module graph resolves at " + "build time — the specifier must be a string literal)", + undefined, + "dynamic-import", ); } if (call.arguments.nodes.length !== 1) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 543149f83..4fd9afbc0 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -8067,8 +8067,11 @@ export class Lowerer { node: ts.Node, featureOverride?: string, hintOverride?: string, + reason?: string, ): never { - this.pushDiag(unsupportedDiag(code, locOf(node), featureOverride, hintOverride)); + const diag = unsupportedDiag(code, locOf(node), featureOverride, hintOverride); + if (reason !== undefined) diag.reason = reason; + this.pushDiag(diag); throw new PoisonError(); } @@ -8294,8 +8297,16 @@ export class Lowerer { * nothing lowers. Poisons the statement like every other rejection. * `sym`, when given, picks the wording: surface declared only by the * adopted @types/node is blamed at @types/node. */ - noLowering(surface: string, node: ts.Node, hint?: string, sym?: ts.Symbol | null): never { - this.pushDiag(noLoweringDiag(surface, locOf(node), hint, this.nodeTypesOnlySymbol(sym))); + noLowering( + surface: string, + node: ts.Node, + hint?: string, + sym?: ts.Symbol | null, + reason?: string, + ): never { + const diag = noLoweringDiag(surface, locOf(node), hint, this.nodeTypesOnlySymbol(sym)); + if (reason !== undefined) diag.reason = reason; + this.pushDiag(diag); throw new PoisonError(); } diff --git a/packages/compiler/src/frontend/package-shims.ts b/packages/compiler/src/frontend/package-shims.ts index 86a41d3a6..1c315a4cc 100644 --- a/packages/compiler/src/frontend/package-shims.ts +++ b/packages/compiler/src/frontend/package-shims.ts @@ -42,6 +42,7 @@ import { dirname, join, resolve } from "node:path"; import { packageShimRoot, tsgoPath } from "./dts-paths.js"; import { trackedDirectoryExists, trackedReadFile, trackedRealpath } from "./input-tracker.js"; +import { readProjectConfig } from "../project-config.js"; import { packageNameOfSpecifier } from "./workspace-registry.js"; import { BUILTIN_ALIAS_PACKAGES, @@ -150,36 +151,12 @@ export function packageShimEntries(): string[] { let unloadedPackages: ReadonlySet = new Set(); /** Reads the project's package-shim configuration: the `scriptc.packages` - * map of the entry's nearest package.json. Called once per program load. */ + * entries marked "unloaded" in the entry's nearest package.json + * (project-config.ts validates the rest). Called once per program load. */ export function configurePackageShims(entryPath: string): void { const packages = new Set(); - for (let dir = dirname(resolve(entryPath)); ;) { - const text = trackedReadFile(join(dir, "package.json")); - if (text !== null) { - let parsed: unknown = null; - try { - parsed = JSON.parse(text); - } catch { - parsed = null; - } - const config = - parsed !== null && typeof parsed === "object" - ? (parsed as Record)["scriptc"] - : undefined; - const map = - config !== null && typeof config === "object" - ? (config as Record)["packages"] - : undefined; - if (map !== null && typeof map === "object") { - for (const [name, kind] of Object.entries(map as Record)) { - if (kind === "unloaded") packages.add(name); - } - } - break; - } - const parent = dirname(dir); - if (parent === dir) break; - dir = parent; + for (const [name, setting] of readProjectConfig(entryPath).packages) { + if (setting.unloaded) packages.add(name); } unloadedPackages = packages; } diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 193e8adc0..5ee2a8bea 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -46,6 +46,14 @@ import { CONTRACT_TYPES_MODULE } from "./contract-types.js"; import { THREADS_MODULE } from "./threads-import.js"; import { erasedTypeOnlyImport } from "./type-only-imports.js"; import { EXPERIMENTAL_MODULE } from "./experimental-import.js"; +import { readProjectConfig } from "../project-config.js"; +import { packageDirOf } from "../node-islands/origin.js"; +import { + scanFiles, + type NodeIslandScan, + type RefusalKind, + type RefusalRecord, +} from "../node-islands/scan.js"; /** Where a package-anchored preflight diagnostic sits, as * `:` — the SC2013 refusal that drops the package @@ -104,6 +112,10 @@ export interface Frontend { npmStatic: NpmStaticStatus[]; /** Each judged npm package's first import site, used to anchor refusals. */ npmImportSites: ReadonlyMap; + /** The Node island detector's input (report-only): the program's + * modules with their pre-scan markers, package import edges, and how + * each refused package was refused. Call before dispose. */ + nodeIslandScan?: (opts: { userJs: boolean }) => NodeIslandScan; /** Releases the frontend's resources (the spawned tsgo server). Call * exactly once, after the last lower(). */ dispose: () => void; @@ -384,6 +396,30 @@ function packagesNamedByDiag(message: string, optedIn: ReadonlySet): Set return hits; } +/** The modules of packages the drop-and-reload loop is about to remove, + * scanned on the program that still holds them. Report-only: a detector + * failure here never changes the build's outcome. */ +function scanDroppedPackages( + load: LoadResult, + packages: readonly string[], + into: { modules: NodeIslandScan["modules"]; edges: NodeIslandScan["edges"] }, +): void { + if (packages.length === 0) return; + try { + const wanted = new Set(packages); + const files = load.program.getImplementationSourceFiles().filter((sf) => { + if (sf.isDeclarationFile) return false; + const at = packageDirOf(sf.fileName); + return at !== null && wanted.has(at.name); + }); + const scanned = scanFiles(load.program, files, { userJs: false, dropped: true, edges: true }); + into.modules.push(...scanned.modules); + into.edges.push(...scanned.edges); + } catch { + /* report-only detection */ + } +} + export function runFrontend( entryPath: string, loadProgram: ProgramLoader, @@ -458,6 +494,16 @@ function loadFrontend( clearResolveCaches(); const statuses: NpmStaticStatus[] = []; const npmSites = new Map(); + // Node island detection (report-only): how each package was refused, and + // the modules of packages removed from the program. + const refusals: RefusalRecord[] = []; + const droppedScan: { modules: NodeIslandScan["modules"]; edges: NodeIslandScan["edges"] } = { + modules: [], + edges: [], + }; + const recordRefusal = (pkg: string, kind: RefusalKind, diag: ScrDiagnostic | null): void => { + refusals.push({ package: pkg, kind, diag }); + }; // Explicit library attempts bypass only the auto-selection heuristics. // The shared preflight/lowering refusal and dependency-closure checks remain. const judged = new Set(libraryNpmStatic); @@ -513,6 +559,7 @@ function loadFrontend( status: "refused", detail: `mapped as an external host module by --external-types (${specs.map((s) => JSON.stringify(s)).join(", ")})`, }); + recordRefusal(pkg, "external", null); return false; }); } @@ -546,19 +593,25 @@ function loadFrontend( const typecheckRefused = new Set(); while (effective.size > 0) { const reasons = new Map(npmStaticOffenders()); + const kinds = new Map(); + for (const pkg of reasons.keys()) kinds.set(pkg, { kind: "offender", diag: null }); // Bundler interop the rewrite could not finish refuses its package // only where the compiled program can load the file. if (npmStaticFileDegrades().size > 0) { const loadable = npmStaticLoadableFiles(load.program, load.moduleOrder); for (const [file, { pkg, reason }] of npmStaticFileDegrades()) { - if (!reasons.has(pkg) && (loadable === null || loadable.has(file))) + if (!reasons.has(pkg) && (loadable === null || loadable.has(file))) { reasons.set(pkg, reason); + kinds.set(pkg, { kind: "degrade", diag: null }); + } } } for (const d of preflight) { const pkg = npmStaticPackageOfPath(d.loc.file); - if (pkg !== null && !reasons.has(pkg)) + if (pkg !== null && !reasons.has(pkg)) { reasons.set(pkg, `${d.code} at ${packageDiagWhere(load, d.loc)}: ${d.message}`); + kinds.set(pkg, { kind: "preflight", diag: d }); + } } if (![...reasons.keys()].some((p) => effective.has(p))) { const named = new Map(); @@ -570,6 +623,7 @@ function loadFrontend( } for (const [pkg, count] of named) { typecheckRefused.add(pkg); + kinds.set(pkg, { kind: "typecheck", diag: null }); reasons.set( pkg, `its inferred export surface breaks ${count} import site${count === 1 ? "" : "s"} in program files (bundler-emitted surfaces type only as far as inference reaches)`, @@ -578,9 +632,12 @@ function loadFrontend( } const dropping = [...reasons.keys()].filter((p) => effective.has(p)); if (dropping.length === 0) break; + scanDroppedPackages(load, dropping, droppedScan); for (const p of dropping) { effective.delete(p); statuses.push({ package: p, status: "refused", detail: reasons.get(p)! }); + const kind = kinds.get(p); + recordRefusal(p, kind?.kind ?? "offender", kind?.diag ?? null); } load.dispose(); load = loadProgram(entryPath, { npmStatic: [...effective], externalTypes }); @@ -600,9 +657,12 @@ function loadFrontend( // graceful per-package degradation, never a failed gate the user cannot // act on (the note carries the why). if (effective.size > 0 && preflight.some((d) => d.code === "SC0001")) { + const lastResortDropped: string[] = []; const dropWithNote = (p: string): void => { effective.delete(p); typecheckRefused.add(p); + lastResortDropped.push(p); + recordRefusal(p, "typecheck", null); statuses.push({ package: p, status: "refused", @@ -624,11 +684,13 @@ function loadFrontend( probe.dispose(); if (probeDiags.some((d) => d.code === "SC0001")) dropWithNote(p); } + scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); load.dispose(); load = loadProgram(entryPath, { npmStatic: [...effective], externalTypes }); preflight = checkPreflight(load); if (preflight.some((d) => d.code === "SC0001") && effective.size > 0) { for (const p of [...effective]) dropWithNote(p); + scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); load.dispose(); load = loadProgram(entryPath, { npmStatic: [...effective], externalTypes }); preflight = checkPreflight(load); @@ -649,8 +711,12 @@ function loadFrontend( } const finalLoad = load; + // An unknown key or value in the project's scriptc configuration fails + // the build before anything else (SC8004). + const projectConfig = readProjectConfig(entryPath); + const configDiagnostics = projectConfig.diagnostics; return { - preflight, + preflight: configDiagnostics.length > 0 ? [...configDiagnostics, ...preflight] : preflight, entryText: () => finalLoad.entry.text, entryExports: () => entryFunctionExports(finalLoad.entry), // The contract scans the PROGRAM's source files, not the runtime @@ -677,14 +743,23 @@ function loadFrontend( // are in moduleOrder like any program module, so their bytes join the // library identity hashes (source_hash/build_id) — compiled code is // identity, whatever directory it came from. - sourceTexts: () => - new Map( + sourceTexts: () => { + const texts = new Map( [ finalLoad.entry, ...finalLoad.moduleOrder, ...finalLoad.program.getImplementationSourceFiles().filter((sf) => !sf.isDeclarationFile), ].map((sf) => [sf.fileName, sf.text]), - ), + ); + // SC8004 diagnostics point into the project's package.json. + if ( + configDiagnostics.length > 0 && + projectConfig.manifest !== null && + projectConfig.manifestText !== null + ) + texts.set(projectConfig.manifest, projectConfig.manifestText); + return texts; + }, lower: (opts) => lowerToIr(finalLoad.program, finalLoad.entry, finalLoad.moduleOrder, { ...opts, @@ -698,6 +773,26 @@ function loadFrontend( }), npmStatic: statuses, npmImportSites: npmSites, + nodeIslandScan: ({ userJs }) => { + const files = new Set([finalLoad.entry, ...finalLoad.moduleOrder]); + addDynamicImportGraph(finalLoad, files); + // Package edges matter only when the program has npm packages: one + // that loads, one that was refused, or one judged and dropped. + const npm = + statuses.length > 0 || + droppedScan.modules.length > 0 || + [...files].some((sf) => sf.fileName.includes("/node_modules/")); + const scanned = scanFiles(finalLoad.program, [...files], { + userJs, + dropped: false, + edges: npm, + }); + return { + modules: [...droppedScan.modules, ...scanned.modules], + edges: [...droppedScan.edges, ...scanned.edges], + refusals, + }; + }, dispose: finalLoad.dispose, }; } diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e9b8514af..78a6a1e24 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -182,11 +182,14 @@ export { export { renderCoverage, renderExplainDynamic, + renderExplainIsland, coverageEnvelope, coveragePasses, type CoverageInput, type CoverageFailOn, } from "./coverage/report.js"; +export type { NodeIslandReport, NodeIslandEntry } from "./node-islands/summary.js"; +export type { NodeIslandOptions, NodeIslandsMode } from "./node-islands/policy.js"; export { TIER_MODES, type DemotedUnit, diff --git a/packages/compiler/src/node-islands/classify.ts b/packages/compiler/src/node-islands/classify.ts new file mode 100644 index 000000000..c41b9437c --- /dev/null +++ b/packages/compiler/src/node-islands/classify.ts @@ -0,0 +1,131 @@ +/* Diagnostic → Node island class (DESIGN.md §1.3). A refusal in a module + * of an npm package is a reason for that package to run in a Node island; + * its class says why: the runtime itself (R1), a mutable object model (R2), + * a shipping problem (packaging), or a construct inside the native subset + * that scriptc cannot compile yet (gap). The reason id on the diagnostic + * (ScrDiagnostic.reason) decides first; the code and message decide for + * refusal sites that do not carry one. */ +import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { NodeIslandMarker } from "./detect.js"; +import { classOfRule, ruleInfo, ruleOfReason } from "./rules.js"; +import type { RefusalRecord } from "./scan.js"; +import type { NodeIslandReason, NodeIslandReasonSource } from "./summary.js"; + +/** Message shapes of refusal sites that predate reason ids. */ +const MESSAGE_REASONS: { code: string; pattern: RegExp; reason: string }[] = [ + { code: "SC2010", pattern: /./, reason: "code-from-string" }, + { code: "SC1010", pattern: /'(?:node:)?vm'/, reason: "vm" }, + { code: "SC1090", pattern: /^dynamic import\(\) of computed/, reason: "dynamic-import" }, + { code: "SC1090", pattern: /require\(\) with a computed specifier/, reason: "dynamic-require" }, + { code: "SC1090", pattern: /'with' statement/, reason: "with" }, + { code: "SC1090", pattern: /sloppy-mode parameter aliases/, reason: "sloppy-arguments" }, + { code: "SC2020", pattern: /^'(?:eval|Function)'/, reason: "code-from-string" }, + { + code: "SC2020", + pattern: /createRequire's require with this argument shape/, + reason: "dynamic-require", + }, + { code: "SC2020", pattern: /native addon/, reason: "native-addon" }, + { + code: "SC2020", + pattern: /through require\.cache|require\.extensions|Module\._|module\.register/, + reason: "loader-hook", + }, + { + code: "SC2020", + pattern: /process\.binding|_linkedBinding|internalBinding|setFlagsFromString/, + reason: "engine-internal", + }, + { code: "SC2020", pattern: /\bWeakRef\b|\bFinalizationRegistry\b/, reason: "weak-ref" }, + { code: "SC1100", pattern: /\bWeakRef\b|\bFinalizationRegistry\b/, reason: "weak-ref" }, +]; + +function reasonIdOf(d: ScrDiagnostic): string | null { + if (d.reason !== undefined) return d.reason; + for (const entry of MESSAGE_REASONS) { + if (entry.code === d.code && entry.pattern.test(d.message)) return entry.reason; + } + return null; +} + +/** Codes that are not reasons of the package they appear in: a cascade + * marker repeats a root cause, an import of another refused package is + * that package's island boundary, and divergences compile. */ +export function isIslandReasonCode(code: string): boolean { + if (code === "SC2004" || code === "SC2013") return false; + if (/^SC6\d{3}$/.test(code)) return false; + return true; +} + +export function classifyDiagnostic( + d: ScrDiagnostic, + source: NodeIslandReasonSource, +): NodeIslandReason { + const id = reasonIdOf(d); + const rule = id === null ? null : ruleOfReason(id); + if (rule !== null && id !== null) { + return { + class: classOfRule(rule), + rule, + reason: id, + code: d.code, + message: d.message, + loc: d.loc, + source, + }; + } + return { + class: "gap", + rule: null, + reason: "gap", + code: d.code, + message: d.message, + loc: d.loc, + source, + }; +} + +export function classifyMarker(m: NodeIslandMarker): NodeIslandReason { + return { + class: classOfRule(m.rule), + rule: m.rule, + reason: m.reason, + code: null, + message: `${ruleInfo(m.rule).title}: ${m.construct}`, + loc: m.loc, + source: "pre-scan", + }; +} + +/** A refused package's status (pipeline.ts): the drop-and-reload record + * when there is one, else the declaration and shipped-JS eligibility bar, + * which is a packaging reason. */ +export function classifyRefusal( + detail: string, + record: RefusalRecord | null, +): NodeIslandReason | null { + if (record !== null && record.kind === "external") return null; + if (record !== null && record.kind === "preflight" && record.diag !== null) { + return classifyDiagnostic(record.diag, "package"); + } + if (record !== null && record.kind === "offender" && /require|createRequire/.test(detail)) { + return { + class: "runtime", + rule: "R1.2", + reason: "dynamic-require", + code: "SC2013", + message: detail, + loc: null, + source: "package", + }; + } + return { + class: "packaging", + rule: null, + reason: "packaging", + code: "SC2013", + message: detail, + loc: null, + source: "package", + }; +} diff --git a/packages/compiler/src/node-islands/detect.test.ts b/packages/compiler/src/node-islands/detect.test.ts new file mode 100644 index 000000000..2a21395da --- /dev/null +++ b/packages/compiler/src/node-islands/detect.test.ts @@ -0,0 +1,165 @@ +import { afterAll, describe, expect, test } from "vitest"; +import { closeSourceParser, parseSourceFile } from "../frontend/ts7/source-parser-node.js"; +import { scanModule } from "./detect.js"; + +afterAll(() => closeSourceParser()); + +let serial = 0; +/** The markers of one JavaScript module as `rule reason` strings. */ +function markers(source: string, ext = "cjs"): string[] { + const sf = parseSourceFile(`/virtual/m${serial++}.${ext}`, source, "js"); + return scanModule(sf).map((m) => `${m.rule} ${m.reason}`); +} + +describe("R1 runtime markers", () => { + test("R1.1 code from strings", () => { + expect(markers("eval(code);")).toEqual(["R1.1 code-from-string"]); + expect(markers("(0, eval)(code);")).toEqual(["R1.1 code-from-string"]); + expect(markers("const f = new Function('a', 'return a');")).toEqual(["R1.1 code-from-string"]); + expect(markers("const f = Function('a', body);")).toEqual(["R1.1 code-from-string"]); + expect(markers("const vm = require('node:vm');")).toEqual(["R1.1 vm"]); + expect(markers("import vm from 'vm';", "mjs")).toEqual(["R1.1 vm"]); + }); + + test("R1.1 exceptions: the global-object idiom and a capability probe in try", () => { + expect(markers("const g = Function('return this')();")).toEqual([]); + expect(markers("let ok; try { new Function(''); ok = true; } catch { ok = false; }")).toEqual( + [], + ); + }); + + test("R1.2 dynamic module loading", () => { + expect(markers("require(name);")).toEqual(["R1.2 dynamic-require"]); + expect(markers("import(`./${x}.js`);", "mjs")).toEqual(["R1.2 dynamic-import"]); + expect(markers("const r = createRequire(import.meta.url); r(spec);", "mjs")).toEqual([ + "R1.2 dynamic-require", + ]); + expect(markers("require('./a' + '.js'); require.resolve(name); import('./b.js');")).toEqual([]); + }); + + test("R1.3 native addons", () => { + expect(markers("require('./build/addon.node');")).toEqual(["R1.3 native-addon"]); + expect(markers("const b = require('bindings')('addon');")).toEqual(["R1.3 native-addon"]); + expect(markers("process.dlopen(module, path);")).toEqual(["R1.3 native-addon"]); + }); + + test("R1.4 loader and require hooks", () => { + expect(markers("require.extensions['.coffee'] = compile;")).toEqual(["R1.4 loader-hook"]); + expect(markers("delete require.cache[key];")).toEqual(["R1.4 loader-hook"]); + expect(markers("const Module = require('module'); Module._load = load;")).toEqual([ + "R1.4 loader-hook", + ]); + expect(markers("const m = require('node:module'); m.registerHooks({});")).toEqual([ + "R1.4 loader-hook", + ]); + expect(markers("require('require-in-the-middle');")).toEqual(["R1.4 loader-hook"]); + }); + + test("R1.5 engine internals", () => { + expect(markers("process.binding('fs');")).toEqual(["R1.5 engine-internal"]); + expect(markers("const v8 = require('v8'); v8.setFlagsFromString('--x');")).toEqual([ + "R1.5 engine-internal", + ]); + expect(markers("global.gc();")).toEqual(["R1.5 engine-internal"]); + }); + + test("R1.6 runtime-only globals", () => { + expect(markers("const r = new WeakRef(o);")).toEqual(["R1.6 weak-ref"]); + expect(markers("const f = new FinalizationRegistry(cb);")).toEqual(["R1.6 weak-ref"]); + expect(markers("if (typeof WeakRef !== 'undefined') {}")).toEqual([]); + expect(markers("const o = { WeakRef: 1 }; o.WeakRef;")).toEqual([]); + }); +}); + +describe("R2 object-model markers", () => { + test("R2.1 constructor functions", () => { + expect(markers("function F() {} const f = new F();")).toEqual(["R2.1 constructor-function"]); + expect(markers("function F() {} F.prototype.m = function () {};")).toEqual([ + "R2.1 constructor-function", + ]); + expect(markers("const util = require('util'); util.inherits(A, B);")).toEqual([ + "R2.1 constructor-function", + ]); + expect(markers("function Child() { Parent.call(this, 1); }")).toEqual([ + "R2.1 constructor-function", + ]); + expect(markers("class C {} const c = new C();")).toEqual([]); + expect( + markers("function wrap(fn) { return function () { return fn.apply(this, arguments); }; }"), + ).toEqual([]); + }); + + test("R2.2 prototype mutation", () => { + expect(markers("Object.setPrototypeOf(a, b);")).toEqual(["R2.2 prototype-write"]); + expect(markers("o.__proto__ = p;")).toEqual(["R2.2 prototype-write"]); + expect(markers("const o = { __proto__: base };")).toEqual(["R2.2 prototype-write"]); + // The transpiled-Error idiom and null-prototype dictionaries stay. + expect( + markers( + "class E extends Error { constructor() { super(); Object.setPrototypeOf(this, E.prototype); } }", + ), + ).toEqual([]); + expect(markers("const d = { __proto__: null };")).toEqual([]); + }); + + test("R2.3 shape mutation outside the checker's view", () => { + expect(markers("Object.defineProperty(o, 'k', { get() { return 1; } });")).toEqual([ + "R2.3 define-property", + ]); + expect(markers("class C {} C.prototype.extra = 1;")).toEqual(["R2.3 define-property"]); + // Module objects and interop helpers build module records. + expect(markers("Object.defineProperty(exports, '__esModule', { value: true });")).toEqual([]); + expect( + markers( + "var __createBinding = function (o, m, k) { Object.defineProperty(o, k, { enumerable: true, get: function () { return m[k]; } }); };", + ), + ).toEqual([]); + }); + + test("R2.4 sloppy-mode semantics", () => { + expect(markers("with (o) { x; }")).toEqual(["R2.4 with"]); + expect(markers("function f(a) { arguments[0] = 1; return a; }")).toEqual([ + "R2.4 sloppy-arguments", + ]); + expect(markers("function f(a) { a = 2; return arguments[0]; }")).toEqual([ + "R2.4 sloppy-arguments", + ]); + expect(markers("function f() { return arguments.callee; }")).toEqual(["R2.4 sloppy-arguments"]); + expect(markers("var n = 010;")).toEqual(["R2.4 sloppy-mode"]); + expect(markers("var x = 1; delete x;")).toEqual(["R2.4 sloppy-mode"]); + // Strict code: a prologue, an ES module, a class body. + expect(markers("'use strict'; function f(a) { a = 2; return arguments[0]; }")).toEqual([]); + expect(markers("export function f(a) { a = 2; return arguments[0]; }", "mjs")).toEqual([]); + expect(markers("class C { m(a) { a = 2; return arguments[0]; } }")).toEqual([]); + }); + + test("R2.5 builtin and global patching", () => { + expect(markers("Array.prototype.last = function () {};")).toEqual(["R2.5 builtin-write"]); + expect(markers("Promise = Bluebird;")).toEqual(["R2.5 builtin-write"]); + expect(markers("globalThis.fetch = f;")).toEqual(["R2.5 builtin-write"]); + expect(markers("const fs = require('fs'); fs.close = patched;")).toEqual([ + "R2.5 builtin-write", + ]); + expect(markers("const p = new Proxy(t, h);")).toEqual(["R2.5 proxy"]); + // Modeled process state and tuning knobs stay; so do redeclared names. + expect( + markers("process.exitCode = 1; process.env.X = 'y'; Error.stackTraceLimit = 50;"), + ).toEqual([]); + expect(markers("var Promise = require('bluebird'); Promise.config = {};")).toEqual([]); + }); +}); + +test("markers come back in source order with their locations", () => { + const source = "Object.setPrototypeOf(a, b);\neval(x);\n"; + const sf = parseSourceFile(`/virtual/order${serial++}.cjs`, source, "js"); + const found = scanModule(sf); + expect(found.map((m) => [m.rule, source.slice(m.loc.start, m.loc.end)])).toEqual([ + ["R2.2", "Object.setPrototypeOf(a, b)"], + ["R1.1", "eval(x)"], + ]); +}); + +test("computed writes keep the member they write through", () => { + expect(markers("process.env[key] = value;")).toEqual([]); + expect(markers("Array.prototype[name] = fn;")).toEqual(["R2.5 builtin-write"]); +}); diff --git a/packages/compiler/src/node-islands/detect.ts b/packages/compiler/src/node-islands/detect.ts new file mode 100644 index 000000000..f612d58e5 --- /dev/null +++ b/packages/compiler/src/node-islands/detect.ts @@ -0,0 +1,849 @@ +/* The pre-scan (DESIGN.md §1.4, phase 1): a syntactic walk over one + * JavaScript module that finds the R1 (runtime) and R2 (object-model) + * markers of rules.ts. It is a fast path and a source of good reasons; it + * never decides alone that a module is native, and the authoritative signal + * stays the lowering's refusals (classify.ts). + * + * Every check is local to the file: a name is a "function" when the file + * declares it as a function, a builtin global when the file does not + * declare it at all, and a builtin module object when the file binds it to + * require("") or imports it from one. Checker-dependent parts of + * R2.3 (writes the declared type does not cover, freeze followed by writes) + * and R2.4 (assignments to undeclared identifiers, `this` as the global + * object) are left to the lowering. */ +import type { SrcLoc } from "../ir/ir.js"; +import * as ts from "../frontend/ts7/adapter.js"; +import { SUPPORTED_BUILTIN_MODULES } from "../frontend/builtin-modules.js"; +import type { NodeIslandRule } from "./rules.js"; + +export interface NodeIslandMarker { + rule: NodeIslandRule; + /** The reason id (rules.ts). */ + reason: string; + /** The construct, as a short phrase for messages. */ + construct: string; + loc: SrcLoc; +} + +/** Markers past this count in one module add nothing to a report. */ +const MARKER_CAP = 64; + +const BUILTIN_GLOBALS = new Set([ + "Object", + "Array", + "String", + "Number", + "Boolean", + "Function", + "Symbol", + "BigInt", + "Promise", + "Error", + "TypeError", + "RangeError", + "SyntaxError", + "ReferenceError", + "EvalError", + "URIError", + "AggregateError", + "Map", + "Set", + "WeakMap", + "WeakSet", + "RegExp", + "Date", + "Math", + "JSON", + "Reflect", + "Intl", + "ArrayBuffer", + "SharedArrayBuffer", + "DataView", + "Uint8Array", + "Int8Array", + "Uint16Array", + "Int16Array", + "Uint32Array", + "Int32Array", + "Float32Array", + "Float64Array", + "BigInt64Array", + "BigUint64Array", + "Uint8ClampedArray", + "Buffer", + "process", + "console", + "globalThis", + "global", + "URL", + "URLSearchParams", + "TextEncoder", + "TextDecoder", + "AbortController", + "AbortSignal", + "EventTarget", + "Event", + "fetch", + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + "queueMicrotask", + "structuredClone", +]); + +/** Writes to builtin members that the native subset models (process + * state, tuning knobs). */ +const ALLOWED_BUILTIN_WRITES = new Set([ + "process.exitCode", + "process.title", + "process.env", + "Error.stackTraceLimit", + "Buffer.poolSize", + "events.defaultMaxListeners", + "events.EventEmitter.defaultMaxListeners", + "EventEmitter.defaultMaxListeners", +]); + +const ADDON_LOADERS = new Set([ + "bindings", + "node-gyp-build", + "node-pre-gyp", + "@mapbox/node-pre-gyp", + "prebuild-install", + "node-gyp-build-optional-packages", +]); + +const LOADER_HOOK_PACKAGES = new Set(["import-in-the-middle", "require-in-the-middle"]); + +function builtinModuleName(spec: string): string | null { + const bare = spec.startsWith("node:") ? spec.slice(5) : spec; + if ((SUPPORTED_BUILTIN_MODULES as readonly string[]).includes(bare)) return bare; + if (bare === "vm" || bare === "v8" || bare === "module" || bare === "inspector") return bare; + return null; +} + +function literalText(node: ts.Node | undefined): string | null { + if (node === undefined) return null; + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text; + return null; +} + +/** A specifier that folds at build time: a literal, or a concatenation of + * literals. */ +function isFoldableSpecifier(node: ts.Node): boolean { + if (literalText(node) !== null) return true; + if (ts.isParenthesizedExpression(node)) return isFoldableSpecifier(node.expression); + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) + return isFoldableSpecifier(node.left) && isFoldableSpecifier(node.right); + return false; +} + +function isAssignmentOperator(kind: ts.SyntaxKind): boolean { + return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment; +} + +function propertyName(node: ts.Node): string | null { + if (ts.isPropertyAccessExpression(node)) { + const name = node.name; + return ts.isIdentifier(name) ? name.text : null; + } + if (ts.isElementAccessExpression(node)) return literalText(node.argumentExpression); + return null; +} + +/** `a.b.c` as "a.b.c" (element accesses with literal keys included), or + * null when the chain has a computed key or does not start at a name. */ +function chainText(node: ts.Node): string | null { + if (ts.isIdentifier(node)) return node.text; + if (node.kind === ts.SyntaxKind.ThisKeyword) return "this"; + if (ts.isParenthesizedExpression(node)) return chainText(node.expression); + if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) { + const base = chainText(node.expression); + const name = propertyName(node); + if (base === null || name === null) return null; + return `${base}.${name}`; + } + return null; +} + +/** Like chainText, with computed keys spelled `[…]`: `process.env[…]`. */ +function memberText(node: ts.Node): string | null { + const text = chainText(node); + if (text !== null) return text; + if (ts.isElementAccessExpression(node)) { + const base = memberText(node.expression); + return base === null ? null : `${base}[…]`; + } + if (ts.isPropertyAccessExpression(node)) { + const base = memberText(node.expression); + const name = propertyName(node); + return base === null || name === null ? null : `${base}.${name}`; + } + return null; +} + +/** The identifier a member chain starts at (`a` of `a.b[c].d`). */ +function chainRoot(node: ts.Node): ts.Identifier | null { + let cur = node; + for (;;) { + if (ts.isIdentifier(cur)) return cur; + if (ts.isPropertyAccessExpression(cur) || ts.isElementAccessExpression(cur)) { + cur = cur.expression; + continue; + } + if (ts.isParenthesizedExpression(cur)) { + cur = cur.expression; + continue; + } + return null; + } +} + +function hasUseStrict(statements: readonly ts.Node[]): boolean { + for (const stmt of statements) { + if (!ts.isExpressionStatement(stmt)) return false; + const text = literalText(stmt.expression); + if (text === null) return false; + if (text === "use strict") return true; + } + return false; +} + +/** ES module syntax at the top level (static import or export): the + * module is strict. The parser's module indicator is not used, since it + * also marks CommonJS files under forced module detection. */ +function hasEsmSyntax(sf: ts.SourceFile): boolean { + for (const stmt of sf.statements.nodes) { + if (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt)) return true; + if (ts.isExportAssignment(stmt)) return true; + const head = sf.text.slice(ts.tokenStartOfNode(stmt), ts.tokenStartOfNode(stmt) + 7); + if (head === "export ") return true; + } + return false; +} + +function isFunctionBoundary(node: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isMethodDeclaration(node) || + ts.isConstructorDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + ); +} + +/** One module's pre-scan. */ +class ModuleScanner { + readonly markers: NodeIslandMarker[] = []; + /** Every name the file declares (any scope): a builtin global the file + * redeclares is not the builtin. */ + private readonly declared = new Set(); + /** Names declared as functions (declarations and function-valued + * variables). */ + private readonly functions = new Set(); + private readonly classes = new Set(); + /** Local name → the builtin module (or `module.member`) it is bound to. */ + private readonly builtinBindings = new Map(); + /** Local names bound to a createRequire(...) result. */ + private readonly createdRequires = new Set(); + private readonly strictness = new Map(); + private readonly moduleStrict: boolean; + + constructor(private readonly sf: ts.SourceFile) { + const name = sf.fileName; + this.moduleStrict = + name.endsWith(".mjs") || + name.endsWith(".mts") || + hasEsmSyntax(sf) || + hasUseStrict(sf.statements.nodes); + } + + scan(): NodeIslandMarker[] { + this.collectDeclarations(); + ts.walkPreorder(this.sf, (node) => { + if (this.markers.length >= MARKER_CAP) return "stop"; + this.visit(node); + return undefined; + }); + return this.markers; + } + + private add(rule: NodeIslandRule, reason: string, construct: string, node: ts.Node): void { + if (this.markers.length >= MARKER_CAP) return; + this.markers.push({ + rule, + reason, + construct, + loc: { file: this.sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }, + }); + } + + private bindName(name: ts.Node | undefined, init: ts.Node | undefined): void { + if (name === undefined) return; + if (ts.isIdentifier(name)) { + this.declared.add(name.text); + if (init === undefined) return; + let value = init; + while (ts.isParenthesizedExpression(value)) value = value.expression; + if (ts.isFunctionExpression(value) || ts.isArrowFunction(value)) + this.functions.add(name.text); + else if (ts.isClassExpression(value)) this.classes.add(name.text); + const bound = this.builtinOf(value); + if (bound !== null) this.builtinBindings.set(name.text, bound); + if (ts.isCallExpression(value) && this.isCreateRequireCallee(value.expression)) + this.createdRequires.add(name.text); + return; + } + if (ts.isObjectBindingPattern(name)) { + const base = init === undefined ? null : this.builtinOf(init); + for (const element of name.elements.nodes) { + if (!ts.isBindingElement(element)) continue; + const local = element.name; + if (local !== undefined && ts.isIdentifier(local) && base !== null) { + const key = element.propertyName; + const member = key !== undefined && ts.isIdentifier(key) ? key.text : local.text; + this.builtinBindings.set(local.text, `${base}.${member}`); + } + this.bindName(local, undefined); + } + return; + } + if (ts.isArrayBindingPattern(name)) { + for (const element of name.elements.nodes) { + if (ts.isBindingElement(element)) this.bindName(element.name, undefined); + } + } + } + + /** `require("fs")`, `require("node:util").inherits` → "fs", "util.inherits". */ + private builtinOf(node: ts.Node): string | null { + let value = node; + while (ts.isParenthesizedExpression(value)) value = value.expression; + if (ts.isCallExpression(value) && this.isRequireCallee(value.expression)) { + const spec = literalText(value.arguments.nodes[0]); + return spec === null ? null : builtinModuleName(spec); + } + if (ts.isPropertyAccessExpression(value)) { + const base = this.builtinOf(value.expression); + const name = propertyName(value); + return base === null || name === null ? null : `${base}.${name}`; + } + if (ts.isIdentifier(value)) return this.builtinBindings.get(value.text) ?? null; + return null; + } + + private isRequireCallee(node: ts.Node): boolean { + return ts.isIdentifier(node) && node.text === "require"; + } + + private isCreateRequireCallee(node: ts.Node): boolean { + if (ts.isIdentifier(node)) { + return ( + node.text === "createRequire" || + this.builtinBindings.get(node.text) === "module.createRequire" + ); + } + return propertyName(node) === "createRequire"; + } + + private collectDeclarations(): void { + ts.walkPreorder(this.sf, (node) => { + if (ts.isFunctionDeclaration(node) && node.name !== undefined) { + this.declared.add(node.name.text); + this.functions.add(node.name.text); + } else if (ts.isClassDeclaration(node) && node.name !== undefined) { + this.declared.add(node.name.text); + this.classes.add(node.name.text); + } else if (ts.isVariableDeclaration(node)) { + this.bindName(node.name, node.initializer); + } else if (ts.isParameter(node)) { + this.bindName(node.name, undefined); + } else if (ts.isImportDeclaration(node)) { + const spec = literalText(node.moduleSpecifier); + const module = spec === null ? null : builtinModuleName(spec); + const clause = node.importClause; + if (clause !== undefined) { + if (clause.name !== undefined) { + this.declared.add(clause.name.text); + if (module !== null) this.builtinBindings.set(clause.name.text, module); + } + const bindings = clause.namedBindings; + if (bindings !== undefined && ts.isNamespaceImport(bindings)) { + this.declared.add(bindings.name.text); + if (module !== null) this.builtinBindings.set(bindings.name.text, module); + } else if (bindings !== undefined && ts.isNamedImports(bindings)) { + for (const element of bindings.elements.nodes) { + this.declared.add(element.name.text); + const imported = element.propertyName; + const member = + imported !== undefined && ts.isIdentifier(imported) + ? imported.text + : element.name.text; + if (module !== null) + this.builtinBindings.set(element.name.text, `${module}.${member}`); + } + } + } + } + return undefined; + }); + } + + /** Whether the code at `node` runs in strict mode. */ + private isStrict(node: ts.Node): boolean { + if (this.moduleStrict) return true; + let cur: ts.Node | undefined = node.parent; + const visited: ts.Node[] = []; + let strict = false; + while (cur !== undefined) { + const known = this.strictness.get(cur); + if (known !== undefined) { + strict = known; + break; + } + visited.push(cur); + if (ts.isClassDeclaration(cur) || ts.isClassExpression(cur)) { + strict = true; + break; + } + if (isFunctionBoundary(cur) || ts.isArrowFunction(cur)) { + const body = (cur as ts.FunctionLikeDeclaration).body; + if (body !== undefined && ts.isBlock(body) && hasUseStrict(body.statements.nodes)) { + strict = true; + break; + } + } + cur = cur.parent; + } + for (const n of visited) this.strictness.set(n, strict); + return strict; + } + + private isGlobal(name: string): boolean { + return BUILTIN_GLOBALS.has(name) && !this.declared.has(name); + } + + private visit(node: ts.Node): void { + if (ts.isCallExpression(node)) this.visitCall(node); + else if (ts.isNewExpression(node)) this.visitNew(node); + else if (ts.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) + this.visitWrite(node.left, node, "write"); + else if (ts.isDeleteExpression(node)) this.visitDelete(node); + else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) { + if ( + node.operator === ts.SyntaxKind.PlusPlusToken || + node.operator === ts.SyntaxKind.MinusMinusToken + ) + this.visitWrite(node.operand, node, "write"); + } else if (ts.isWithStatement(node)) { + this.add("R2.4", "with", "a 'with' statement", node); + } else if (ts.isImportDeclaration(node)) { + const spec = literalText(node.moduleSpecifier); + if (spec !== null) this.visitSpecifier(spec, node); + } else if (ts.isIdentifier(node)) { + this.visitIdentifier(node); + } else if (ts.isNumericLiteral(node)) { + const text = this.sf.text.slice(ts.tokenStartOfNode(node), node.end); + if (/^0[0-9]+$/.test(text) && !this.isStrict(node)) + this.add("R2.4", "sloppy-mode", "a legacy octal literal", node); + } else if (ts.isPropertyAccessExpression(node)) { + const text = chainText(node); + if ((text === "arguments.callee" || text === "arguments.caller") && !this.isStrict(node)) + this.add("R2.4", "sloppy-arguments", `'${text}'`, node); + } else if (ts.isObjectLiteralExpression(node)) { + for (const prop of node.properties.nodes) { + if (!ts.isPropertyAssignment(prop)) continue; + const name = prop.name; + const key = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : null; + if (key === "__proto__" && prop.initializer.kind !== ts.SyntaxKind.NullKeyword) + this.add("R2.2", "prototype-write", "an object literal with a '__proto__' value", prop); + } + } else if (isFunctionBoundary(node)) { + this.visitFunction(node as ts.FunctionLikeDeclaration); + } + } + + private visitSpecifier(spec: string, node: ts.Node): void { + const builtin = builtinModuleName(spec); + if (builtin === "vm") this.add("R1.1", "vm", "the 'vm' module", node); + else if (spec.endsWith(".node")) + this.add("R1.3", "native-addon", `the native addon '${spec}'`, node); + else if (ADDON_LOADERS.has(spec)) + this.add("R1.3", "native-addon", `the native addon loader '${spec}'`, node); + else if (LOADER_HOOK_PACKAGES.has(spec)) + this.add("R1.4", "loader-hook", `the loader hook package '${spec}'`, node); + } + + private visitCall(call: ts.CallExpression): void { + const callee = call.expression; + const args = call.arguments.nodes; + const first = args[0]; + // import(e) and require(e) + if (callee.kind === ts.SyntaxKind.ImportKeyword) { + if (first !== undefined && !isFoldableSpecifier(first)) + this.add("R1.2", "dynamic-import", "import() with a computed specifier", call); + else if (first !== undefined) this.visitSpecifier(literalText(first) ?? "", call); + return; + } + if ( + this.isRequireCallee(callee) || + (ts.isIdentifier(callee) && this.createdRequires.has(callee.text)) + ) { + if (first !== undefined && !isFoldableSpecifier(first)) + this.add("R1.2", "dynamic-require", "require() with a computed specifier", call); + else if (first !== undefined) this.visitSpecifier(literalText(first) ?? "", call); + return; + } + if (ts.isCallExpression(callee) && this.isCreateRequireCallee(callee.expression)) { + if (first !== undefined && !isFoldableSpecifier(first)) + this.add("R1.2", "dynamic-require", "createRequire(...)() with a computed specifier", call); + return; + } + // eval, Function + let target = callee; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.CommaToken) + target = target.right; + const text = chainText(target); + if (text === null) return; + if ( + (text === "eval" && !this.declared.has("eval")) || + text === "globalThis.eval" || + text === "global.eval" || + text === "window.eval" + ) { + this.add("R1.1", "code-from-string", "eval()", call); + return; + } + if (text === "Function" && this.isGlobal("Function")) { + this.visitFunctionConstructor(call, args); + return; + } + const root = chainRoot(target); + const rootBinding = root === null ? undefined : this.builtinBindings.get(root.text); + const resolved = + rootBinding !== undefined && root !== null + ? rootBinding + text.slice(root.text.length) + : text; + // engine internals + if ( + (text === "process.binding" || text === "process._linkedBinding") && + this.isGlobal("process") + ) { + this.add("R1.5", "engine-internal", `'${text}()'`, call); + return; + } + if (text === "internalBinding" && !this.declared.has("internalBinding")) { + this.add("R1.5", "engine-internal", "'internalBinding()'", call); + return; + } + if (resolved === "v8.setFlagsFromString" || text.endsWith(".setFlagsFromString")) { + this.add("R1.5", "engine-internal", "'v8.setFlagsFromString()'", call); + return; + } + if ((text === "global.gc" || text === "globalThis.gc") && this.isGlobal(text.split(".")[0]!)) { + this.add("R1.5", "engine-internal", `'${text}()'`, call); + return; + } + if (text === "process.dlopen" && this.isGlobal("process")) { + this.add("R1.3", "native-addon", "'process.dlopen()'", call); + return; + } + // loader hooks + if (resolved === "module.register" || resolved === "module.registerHooks") { + this.add("R1.4", "loader-hook", `'${resolved}()'`, call); + return; + } + // prototype mutation + if ( + (text === "Object.setPrototypeOf" && this.isGlobal("Object")) || + (text === "Reflect.setPrototypeOf" && this.isGlobal("Reflect")) + ) { + // The transpiled-Error idiom `Object.setPrototypeOf(this, …)` stays. + if (first !== undefined && first.kind !== ts.SyntaxKind.ThisKeyword) + this.add("R2.2", "prototype-write", `'${text}()'`, call); + return; + } + // defineProperty outside the module object and interop helpers + if ( + ((text === "Object.defineProperty" || text === "Object.defineProperties") && + this.isGlobal("Object")) || + (text === "Reflect.defineProperty" && this.isGlobal("Reflect")) + ) { + if (first !== undefined && !this.isModuleObject(first) && !this.inInteropHelper(call)) + this.add("R2.3", "define-property", `'${text}()'`, call); + return; + } + if (text.endsWith(".__defineGetter__") || text.endsWith(".__defineSetter__")) { + this.add("R2.3", "define-property", `'${text.slice(text.lastIndexOf(".") + 1)}()'`, call); + return; + } + // constructor functions + if ( + args.length === 2 && + (resolved === "util.inherits" || + (text === "inherits" && !this.functions.has("inherits")) || + text.endsWith(".inherits")) + ) { + this.add("R2.1", "constructor-function", `'${text}()'`, call); + return; + } + if ( + ts.isPropertyAccessExpression(target) && + (propertyName(target) === "call" || propertyName(target) === "apply") && + first !== undefined && + first.kind === ts.SyntaxKind.ThisKeyword && + ts.isExpressionStatement(call.parent!) + ) { + const fn = target.expression; + const name = ts.isIdentifier(fn) ? fn.text : null; + if (name !== null && /^[A-Z]/.test(name) && !this.classes.has(name)) + this.add( + "R2.1", + "constructor-function", + `'${name}.${propertyName(target)}(this, …)'`, + call, + ); + } + } + + private visitFunctionConstructor(node: ts.Node, args: readonly ts.Node[]): void { + // The global-object idiom Function("return this")() folds to globalThis. + if (args.length === 1) { + const body = literalText(args[0]); + if (body !== null && body.trim() === "return this") return; + } + // A capability probe tested inside try (zod 4's allowsEval) keeps + // today's EvalError lowering. + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (isFunctionBoundary(cur) || ts.isArrowFunction(cur)) break; + if (ts.isBlock(cur) && cur.parent !== undefined && ts.isTryStatement(cur.parent)) { + if (cur.parent.tryBlock === cur) return; + } + } + this.add("R1.1", "code-from-string", "new Function()", node); + } + + private visitNew(node: ts.NewExpression): void { + const callee = node.expression; + if (!ts.isIdentifier(callee)) return; + const name = callee.text; + if (name === "Function" && this.isGlobal("Function")) { + this.visitFunctionConstructor(node, node.arguments?.nodes ?? []); + } else if (name === "Proxy" && !this.declared.has("Proxy")) { + this.add("R2.5", "proxy", "new Proxy()", node); + } else if (this.functions.has(name) && !this.classes.has(name)) { + this.add("R2.1", "constructor-function", `'new ${name}()' of a function`, node); + } + } + + private isModuleObject(node: ts.Node): boolean { + const text = chainText(node); + return text === "exports" || text === "module.exports" || text === "module"; + } + + /** Inside a module-interop helper (tsc's __createBinding/__exportStar, + * Babel's _interopRequireWildcard, esbuild's __export): their + * defineProperty calls build module objects the native subset models. */ + private inInteropHelper(node: ts.Node): boolean { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + let name: string | null = null; + if (ts.isFunctionDeclaration(cur) && cur.name !== undefined) name = cur.name.text; + else if (ts.isVariableDeclaration(cur) && ts.isIdentifier(cur.name)) name = cur.name.text; + else if (ts.isBinaryExpression(cur) && isAssignmentOperator(cur.operatorToken.kind)) + name = chainText(cur.left); + if (name !== null && /(^|\.)_/.test(name)) return true; + } + return false; + } + + private visitWrite(target: ts.Node, node: ts.Node, what: "write" | "delete"): void { + let left = target; + while (ts.isParenthesizedExpression(left)) left = left.expression; + if (ts.isIdentifier(left)) { + if (this.isGlobal(left.text) && left.text !== "global" && left.text !== "globalThis") + this.add("R2.5", "builtin-write", `a ${what} of the global '${left.text}'`, node); + return; + } + if (!ts.isPropertyAccessExpression(left) && !ts.isElementAccessExpression(left)) return; + const text = chainText(left); + const root = chainRoot(left); + const base = left.expression; + const baseText = chainText(base); + // require.extensions / require.cache + if ( + text !== null && + (text.startsWith("require.extensions") || text.startsWith("require.cache")) + ) { + this.add( + "R1.4", + "loader-hook", + `a ${what} of '${text.startsWith("require.cache") ? "require.cache" : "require.extensions"}'`, + node, + ); + return; + } + if (baseText === "require.cache" || baseText === "require.extensions") { + this.add("R1.4", "loader-hook", `a ${what} of '${baseText}'`, node); + return; + } + // __proto__ writes + if (propertyName(left) === "__proto__") { + this.add("R2.2", "prototype-write", `a ${what} of '__proto__'`, node); + return; + } + if (root === null) return; + const binding = this.builtinBindings.get(root.text); + // Module internals: Module._load = …, Module.prototype.require = … + if (text !== null) { + const resolved = binding !== undefined ? binding + text.slice(root.text.length) : text; + if ( + /^(module|Module)\.(_load|_resolveFilename|_extensions|wrap|_compile|prototype\.require|prototype\._compile)$/.test( + resolved, + ) && + (binding !== undefined || !this.declared.has(root.text) || root.text === "Module") + ) { + this.add("R1.4", "loader-hook", `a ${what} of '${resolved}'`, node); + return; + } + // F.prototype… + const parts = text.split("."); + if (parts.length >= 2 && parts[1] === "prototype") { + const name = parts[0]!; + if (this.isGlobal(name)) { + this.add("R2.5", "builtin-write", `a ${what} of '${text}'`, node); + return; + } + if (binding === undefined && this.classes.has(name) && !this.functions.has(name)) { + this.add("R2.3", "define-property", `a ${what} of the class prototype '${text}'`, node); + return; + } + if (binding === undefined && name !== "this") { + this.add("R2.1", "constructor-function", `a ${what} of '${text}'`, node); + return; + } + } + } + // Builtin globals and builtin module objects. + const spelled = memberText(left) ?? `${root.text}[…]`; + if (binding !== undefined) { + const member = binding + spelled.slice(root.text.length); + if (!this.allowedWrite(member)) { + this.add( + "R2.5", + "builtin-write", + `a ${what} of the builtin module member '${member}'`, + node, + ); + } + return; + } + if (this.isGlobal(root.text)) { + const member = spelled; + if (!this.allowedWrite(member)) + this.add("R2.5", "builtin-write", `a ${what} of the builtin '${member}'`, node); + } + } + + private allowedWrite(member: string): boolean { + for (const allowed of ALLOWED_BUILTIN_WRITES) { + if ( + member === allowed || + member.startsWith(`${allowed}.`) || + member.startsWith(`${allowed}[`) + ) + return true; + } + return false; + } + + private visitDelete(node: ts.DeleteExpression): void { + let target = node.expression; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (ts.isIdentifier(target)) { + if (!this.isStrict(node)) + this.add("R2.4", "sloppy-mode", `'delete ${target.text}' of an identifier`, node); + return; + } + this.visitWrite(target, node, "delete"); + } + + private visitIdentifier(node: ts.Identifier): void { + const name = node.text; + if (name !== "WeakRef" && name !== "FinalizationRegistry") return; + if (this.declared.has(name)) return; + const parent = node.parent; + if (parent === undefined) return; + // Only value uses: not a property name, declaration name or typeof probe. + if (ts.isPropertyAccessExpression(parent) && parent.name === node) return; + if (ts.isTypeOfExpression(parent)) return; + if ((ts.isPropertyAssignment(parent) || ts.isMethodDeclaration(parent)) && parent.name === node) + return; + this.add("R1.6", "weak-ref", `'${name}'`, node); + } + + /** Observable `arguments` aliasing in a sloppy function: writes to + * arguments[i], or to a parameter while `arguments` is read. */ + private visitFunction(fn: ts.FunctionLikeDeclaration): void { + const body = fn.body; + if (body === undefined || !ts.isBlock(body)) return; + if (this.isStrict(body.statements.nodes[0] ?? body)) return; + if (hasUseStrict(body.statements.nodes)) return; + const params = new Set(); + for (const p of fn.parameters.nodes) { + if (ts.isIdentifier(p.name)) params.add(p.name.text); + } + const seen: ArgumentsUse = { reads: false, argumentsWrite: null, paramWrite: null }; + ts.walkPreorder(body, (node) => { + if ( + node !== body && + (isFunctionBoundary(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) + ) + return "skip"; + if (ts.isIdentifier(node) && node.text === "arguments") seen.reads = true; + if (ts.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) { + const left = node.left; + if ( + ts.isElementAccessExpression(left) && + ts.isIdentifier(left.expression) && + left.expression.text === "arguments" + ) { + if (seen.argumentsWrite === null) seen.argumentsWrite = node; + } else if (ts.isIdentifier(left) && params.has(left.text)) { + if (seen.paramWrite === null) seen.paramWrite = node; + } + } + return undefined; + }); + if (seen.argumentsWrite !== null) + this.add( + "R2.4", + "sloppy-arguments", + "a write to 'arguments[i]' in sloppy mode", + seen.argumentsWrite, + ); + else if (seen.reads && seen.paramWrite !== null) + this.add( + "R2.4", + "sloppy-arguments", + "a parameter write while 'arguments' is read in sloppy mode", + seen.paramWrite, + ); + } +} + +interface ArgumentsUse { + reads: boolean; + argumentsWrite: ts.Node | null; + paramWrite: ts.Node | null; +} + +/** The R1/R2 markers of one JavaScript module, in source order. */ +export function scanModule(sf: ts.SourceFile): NodeIslandMarker[] { + const markers = new ModuleScanner(sf).scan(); + return markers.sort((a, b) => a.loc.start - b.loc.start); +} diff --git a/packages/compiler/src/node-islands/origin.ts b/packages/compiler/src/node-islands/origin.ts new file mode 100644 index 000000000..65beebba0 --- /dev/null +++ b/packages/compiler/src/node-islands/origin.ts @@ -0,0 +1,68 @@ +/* Where a source file comes from. User code (the program's own tree and + * workspace packages compiled from source) is strict; npm code (installed + * packages under node_modules) is lenient and is decided per package + * instance; shims are scriptc's own registry implementations; builtin + * covers declaration files of the type world. */ +import { join } from "node:path"; +import { isPackageShimFile } from "../frontend/package-shims.js"; +import { trackedReadFile } from "../frontend/input-tracker.js"; + +export type Origin = "user" | "npm" | "shim" | "builtin"; + +export function originOf(file: string): Origin { + const path = file.split("\\").join("/"); + if (isPackageShimFile(path)) return "shim"; + if (/\.d\.[cm]?ts$/.test(path)) return "builtin"; + return path.includes("/node_modules/") ? "npm" : "user"; +} + +/** One installed package: the decision unit for npm code. pnpm keeps + * versions apart in different directories, so the directory identifies the + * instance. */ +export interface PackageInstance { + name: string; + /** The package directory (…/node_modules/). */ + dir: string; + version: string | null; +} + +/** The package instance of a file under node_modules: the LAST + * node_modules segment (nested installs belong to the innermost package), + * scope-aware. Null outside node_modules. */ +export function packageDirOf(file: string): { name: string; dir: string } | null { + const path = file.split("\\").join("/"); + const parts = path.split("/"); + const i = parts.lastIndexOf("node_modules"); + if (i < 0 || i + 1 >= parts.length - 1) return null; + const first = parts[i + 1]!; + if (first.startsWith("@")) { + if (i + 2 >= parts.length - 1) return null; + const name = `${first}/${parts[i + 2]!}`; + return { name, dir: parts.slice(0, i + 3).join("/") }; + } + return { name: first, dir: parts.slice(0, i + 2).join("/") }; +} + +/** The `version` field of a package directory's package.json. */ +export function packageVersionOf(dir: string): string | null { + let version: string | null = null; + const text = trackedReadFile(join(dir, "package.json")); + if (text !== null) { + try { + const parsed: unknown = JSON.parse(text); + if (parsed !== null && typeof parsed === "object") { + const v = (parsed as Record)["version"]; + if (typeof v === "string") version = v; + } + } catch { + version = null; + } + } + return version; +} + +export function packageInstanceOf(file: string): PackageInstance | null { + const at = packageDirOf(file); + if (at === null) return null; + return { name: at.name, dir: at.dir, version: packageVersionOf(at.dir) }; +} diff --git a/packages/compiler/src/node-islands/policy.ts b/packages/compiler/src/node-islands/policy.ts new file mode 100644 index 000000000..72dbf3cd0 --- /dev/null +++ b/packages/compiler/src/node-islands/policy.ts @@ -0,0 +1,167 @@ +/* The Node island policy (DESIGN.md §1.5–§1.6): user code strict, npm + * lenient. npm code outside the native subset becomes a Node island + * automatically (`nodeIslands.auto`, `--node-islands=auto`); user modules + * become islands only when marked (`nodeIslands.modules`, + * `--node-island=`); `"native"` (`--native=`) makes one + * dependency strict; `--node-islands=off` makes the whole program strict. + * + * This version is report-only: the policy decides what WOULD run in Node, + * and coverage reports it, but code generation does not change. */ +import { isAbsolute, join, resolve, dirname } from "node:path"; +import type { ProjectConfig } from "../project-config.js"; + +export type NodeIslandsMode = "auto" | "explicit" | "off"; +export const NODE_ISLANDS_MODES: readonly NodeIslandsMode[] = ["auto", "explicit", "off"]; + +/** The command-line half of the policy (`build`, `run` and `coverage` + * accept these). */ +export interface NodeIslandOptions { + /** `--node-islands=auto|explicit|off`. */ + mode?: NodeIslandsMode; + /** `--node-island=`: packages and user modules forced into a + * Node island. Paths are absolute. */ + islands?: readonly string[]; + /** `--native=`: packages that must compile natively. */ + native?: readonly string[]; + /** `--node=`: the node executable recorded for islands. */ + node?: string; + /** `--strict-user-js`: report R2 constructs in user JavaScript as + * SC8101–SC8105. */ + strictUserJs?: boolean; +} + +export type DecisionSource = "config" | "cli"; + +export interface UserModulePattern { + /** An absolute path or glob. */ + pattern: string; + source: DecisionSource; +} + +export interface NodeIslandPolicy { + mode: NodeIslandsMode; + /** Where the mode came from (an explicit `"auto": false` reads as + * "explicit"). */ + modeSource: DecisionSource | "default"; + forcedNode: Map; + forcedNative: Map; + /** `"unloaded"` packages: never loaded, never islands. */ + unloaded: Set; + userModules: UserModulePattern[]; + node: string | null; + strictUserJs: boolean; +} + +/** Whether a `--node-island` value names a module rather than a package. */ +export function isModuleIslandArgument(value: string): boolean { + return ( + value.startsWith(".") || + value.startsWith("/") || + /\.(?:[cm]?[jt]s|[jt]sx)$/.test(value) || + value.includes("*") + ); +} + +export function nodeIslandPolicy( + config: ProjectConfig, + options: NodeIslandOptions = {}, +): NodeIslandPolicy { + const forcedNode = new Map(); + const forcedNative = new Map(); + const unloaded = new Set(); + for (const [name, setting] of config.packages) { + if (setting.unloaded) unloaded.add(name); + else if (setting.run === "node") forcedNode.set(name, "config"); + else if (setting.run === "native") forcedNative.set(name, "config"); + } + const userModules: UserModulePattern[] = []; + const base = config.manifest === null ? process.cwd() : dirname(config.manifest); + for (const pattern of config.nodeIslands.modules) { + userModules.push({ + pattern: isAbsolute(pattern) ? pattern : join(base, pattern), + source: "config", + }); + } + for (const value of options.islands ?? []) { + if (isModuleIslandArgument(value)) { + userModules.push({ pattern: resolve(value), source: "cli" }); + } else { + forcedNative.delete(value); + forcedNode.set(value, "cli"); + } + } + for (const name of options.native ?? []) { + forcedNode.delete(name); + forcedNative.set(name, "cli"); + } + let mode: NodeIslandsMode = "auto"; + let modeSource: DecisionSource | "default" = "default"; + if (options.mode !== undefined) { + mode = options.mode; + modeSource = "cli"; + } else if (!config.nodeIslands.auto) { + mode = "explicit"; + modeSource = "config"; + } + return { + mode, + modeSource, + forcedNode, + forcedNative, + unloaded, + userModules, + node: options.node ?? config.nodeIslands.node, + strictUserJs: options.strictUserJs === true, + }; +} + +/** Whether a user module is marked to run in a Node island. */ +export function isMarkedUserModule(policy: NodeIslandPolicy, file: string): DecisionSource | null { + const path = file.split("\\").join("/"); + for (const entry of policy.userModules) { + if (globMatch(entry.pattern.split("\\").join("/"), path)) return entry.source; + } + return null; +} + +/** Glob matching over `/`-separated paths: `**` spans directories, `*` and + * `?` stay inside one segment. */ +export function globMatch(pattern: string, path: string): boolean { + return matchSegments(pattern.split("/"), 0, path.split("/"), 0); +} + +function matchSegments(pat: string[], pi: number, segs: string[], si: number): boolean { + if (pi === pat.length) return si === segs.length; + const p = pat[pi]!; + if (p === "**") { + for (let k = si; k <= segs.length; k++) if (matchSegments(pat, pi + 1, segs, k)) return true; + return false; + } + if (si === segs.length) return false; + return matchSegment(p, 0, segs[si]!, 0) && matchSegments(pat, pi + 1, segs, si + 1); +} + +function matchSegment(p: string, pi: number, s: string, si: number): boolean { + if (pi === p.length) return si === s.length; + const ch = p.charAt(pi); + if (ch === "*") { + for (let k = si; k <= s.length; k++) if (matchSegment(p, pi + 1, s, k)) return true; + return false; + } + if (si === s.length) return false; + if (ch === "?" || ch === s.charAt(si)) return matchSegment(p, pi + 1, s, si + 1); + return false; +} + +/** The policy's identity for caches: two analyses with the same identity + * decide the same islands. */ +export function nodeIslandOptionsKey(options: NodeIslandOptions | undefined): string { + if (options === undefined) return ""; + return [ + options.mode ?? "", + (options.islands ?? []).join(","), + (options.native ?? []).join(","), + options.node ?? "", + options.strictUserJs === true ? "strict-user-js" : "", + ].join("|"); +} diff --git a/packages/compiler/src/node-islands/report.test.ts b/packages/compiler/src/node-islands/report.test.ts new file mode 100644 index 000000000..a11d9fb8b --- /dev/null +++ b/packages/compiler/src/node-islands/report.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, test } from "vitest"; +import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import { emptyProjectConfig, parseProjectConfig } from "../project-config.js"; +import type { NodeIslandMarker } from "./detect.js"; +import { + globMatch, + isMarkedUserModule, + nodeIslandPolicy, + type NodeIslandOptions, +} from "./policy.js"; +import { buildNodeIslandReport, type NpmOutcome } from "./report.js"; +import type { NodeIslandScan, PackageEdge, ScannedModule } from "./scan.js"; + +const NM = "/app/node_modules"; + +function mod(pkg: string | null, file: string, markers: NodeIslandMarker[] = []): ScannedModule { + return { + file, + origin: pkg === null ? "user" : "npm", + packageDir: pkg === null ? null : `${NM}/${pkg}`, + packageName: pkg, + markers, + dropped: false, + }; +} + +function marker(file: string, rule: NodeIslandMarker["rule"], reason: string): NodeIslandMarker { + return { rule, reason, construct: reason, loc: { file, start: 0, end: 1 } }; +} + +function edge(from: string | null, to: string): PackageEdge { + return { + fromDir: from === null ? null : `${NM}/${from}`, + fromFile: from === null ? "/app/main.ts" : `${NM}/${from}/index.js`, + to, + toDir: `${NM}/${to}`, + shim: false, + loc: { file: from === null ? "/app/main.ts" : `${NM}/${from}/index.js`, start: 0, end: 1 }, + }; +} + +function gapFence(pkg: string): ScrDiagnostic { + return { + code: "SC1090", + message: "a builtin function alias as an escaping value", + loc: { file: `${NM}/${pkg}/index.js`, start: 5, end: 9 }, + }; +} + +/** A program: main.ts imports a (constructor functions), b (a gap fence) + * and c (clean); a imports d (clean, only from a) and c. */ +function program(): { scan: NodeIslandScan; fences: ScrDiagnostic[] } { + return { + scan: { + modules: [ + mod(null, "/app/main.ts"), + mod("a", `${NM}/a/index.js`, [marker(`${NM}/a/index.js`, "R2.1", "constructor-function")]), + mod("b", `${NM}/b/index.js`), + mod("c", `${NM}/c/index.js`), + mod("d", `${NM}/d/index.js`), + ], + edges: [edge(null, "a"), edge(null, "b"), edge(null, "c"), edge("a", "d"), edge("a", "c")], + refusals: [], + }, + fences: [gapFence("b")], + }; +} + +function report( + options: NodeIslandOptions = {}, + npm: NpmOutcome[] = [], + config = emptyProjectConfig(), +) { + const { scan, fences } = program(); + return buildNodeIslandReport({ + entry: "/app/main.ts", + scan, + npm, + diagnostics: [], + fences, + policy: nodeIslandPolicy(config, options), + }); +} + +describe("decisions", () => { + test("auto: packages with reasons are islands, gap islands first; closure and sharing", () => { + const r = report(); + expect(r.islands.map((i) => [i.package, i.class, i.decision])).toEqual([ + ["b", "gap", "auto"], + ["a", "object-model", "auto"], + ]); + expect(r.islands[0]!.reason).toBe("SC1090: a builtin function alias as an escaping value"); + expect(r.islands[1]!.carries).toEqual(["d"]); + expect(r.packages.map((p) => [p.package, p.status, p.islands])).toEqual([ + ["c", "shared", ["a"]], + ["d", "inside", ["a"]], + ]); + expect(r.violations).toEqual([]); + }); + + test("a refused package is a packaging island, classified by its refusal", () => { + const r = report({}, [ + { package: "c", status: "refused", detail: "auto: its shipped JS looks minified" }, + ]); + const c = r.islands.find((i) => i.package === "c")!; + expect([c.class, c.reason, c.diagnostic]).toEqual([ + "packaging", + "SC2013: auto: its shipped JS looks minified", + "SC2013", + ]); + }); + + test("forced native turns reasons into SC8003; forced node islands a clean package", () => { + const r = report({ native: ["a"], islands: ["c"] }); + expect(r.islands.map((i) => [i.package, i.class, i.decision])).toEqual([ + ["b", "gap", "auto"], + ["c", "marked", "cli"], + ]); + expect(r.violations.map((v) => v.code)).toEqual(["SC8003"]); + expect(r.violations[0]!.message).toContain("'a' is marked native"); + }); + + test("off: no islands, every package outside the native subset is SC8002", () => { + const r = report({ mode: "off" }); + expect(r.islands).toEqual([]); + expect(r.violations.map((v) => [v.code, v.message.split(" (")[0]])).toEqual([ + ["SC8002", "npm package 'a' is outside the native subset"], + ["SC8002", "npm package 'b' is outside the native subset"], + ]); + }); + + test('explicit (or "auto": false): only marked islands', () => { + const config = parseProjectConfig( + "/app/package.json", + JSON.stringify({ scriptc: { nodeIslands: { auto: false }, packages: { b: "node" } } }), + ); + const r = report({}, [], config); + expect(r.policy).toBe("explicit"); + expect(r.islands.map((i) => [i.package, i.decision])).toEqual([["b", "config"]]); + expect(r.violations.map((v) => v.code)).toEqual(["SC8002"]); + }); + + test("command-line names the program does not import are reported", () => { + expect(report({ native: ["nope"] }).unknownPackages).toEqual(["nope"]); + }); + + test("unloaded packages are never islands", () => { + const config = parseProjectConfig( + "/app/package.json", + JSON.stringify({ scriptc: { packages: { a: "unloaded" } } }), + ); + expect(report({}, [], config).islands.map((i) => i.package)).toEqual(["b"]); + }); +}); + +describe("user modules", () => { + test("marked by glob in the config or by path on the command line", () => { + const config = parseProjectConfig( + "/app/package.json", + JSON.stringify({ scriptc: { nodeIslands: { modules: ["src/plugins/**/*.ts"] } } }), + ); + const policy = nodeIslandPolicy(config, { islands: ["/app/src/legacy.js"] }); + expect(isMarkedUserModule(policy, "/app/src/plugins/a/loader.ts")).toBe("config"); + expect(isMarkedUserModule(policy, "/app/src/legacy.js")).toBe("cli"); + expect(isMarkedUserModule(policy, "/app/src/main.ts")).toBe(null); + }); + + test("SC81xx for R2 markers in user JavaScript, only with --strict-user-js", () => { + const file = "/app/legacy.js"; + const scan: NodeIslandScan = { + modules: [ + mod(null, file, [ + marker(file, "R2.1", "constructor-function"), + marker(file, "R2.5", "builtin-write"), + marker(file, "R1.1", "code-from-string"), + ]), + ], + edges: [], + refusals: [], + }; + const build = (strictUserJs: boolean) => + buildNodeIslandReport({ + entry: file, + scan, + npm: [], + diagnostics: [], + fences: [], + policy: nodeIslandPolicy(emptyProjectConfig(), { strictUserJs }), + }); + expect(build(false).userJs).toEqual([]); + expect(build(true).userJs.map((d) => d.code)).toEqual(["SC8101", "SC8105"]); + }); +}); + +test("globMatch", () => { + expect(globMatch("/a/**/*.ts", "/a/b/c/d.ts")).toBe(true); + expect(globMatch("/a/**/*.ts", "/a/d.ts")).toBe(true); + expect(globMatch("/a/*.ts", "/a/b/d.ts")).toBe(false); + expect(globMatch("/a/?.js", "/a/x.js")).toBe(true); + expect(globMatch("/a/x.js", "/a/x.js")).toBe(true); +}); diff --git a/packages/compiler/src/node-islands/report.ts b/packages/compiler/src/node-islands/report.ts new file mode 100644 index 000000000..a6898e855 --- /dev/null +++ b/packages/compiler/src/node-islands/report.ts @@ -0,0 +1,355 @@ +/* The Node island report: which packages and user modules would run in a + * Node island, and why (DESIGN.md §1.3–§1.7). Report-only: the decisions + * are computed from the detector and the policy, and nothing about the + * build changes. + * + * 1. Reasons per package instance: pre-scan markers in its modules, + * refusals and runtime fences the lowering produced in its modules, and + * the drop-and-reload refusal of a package that did not compile. + * 2. Decision per package: the policy (forced node or native, the mode) + * applied to its reasons. + * 3. Closure: a package imported only by island packages runs inside the + * island (it is Node's business); one reached from both sides is + * reported as shared (DESIGN.md §2.6 decides it later). */ +import { + nativePackageDiag, + nodeIslandsOffDiag, + userJsObjectModelDiag, + type ScrDiagnostic, +} from "../diagnostics/diagnostic.js"; +import type { SrcLoc } from "../ir/ir.js"; +import { + classifyDiagnostic, + classifyMarker, + classifyRefusal, + isIslandReasonCode, +} from "./classify.js"; +import { + describe, + type IslandDecision, + type NativePackage, + type NodeIslandEntry, + type NodeIslandReason, + type NodeIslandReasonSource, + type NodeIslandReport, + type UserModuleIsland, +} from "./summary.js"; +import { packageDirOf, packageVersionOf } from "./origin.js"; +import { isMarkedUserModule, type NodeIslandPolicy } from "./policy.js"; +import { classRank, reportRank, ruleInfo } from "./rules.js"; +import type { NodeIslandScan, RefusalRecord } from "./scan.js"; + +/** One dependency's native compilation outcome (coverage/report.ts's + * NpmStaticStatus, restated to keep this module free of coverage). */ +export interface NpmOutcome { + package: string; + status: "static" | "refused"; + detail?: string; +} + +export interface NodeIslandReportInput { + entry: string; + scan: NodeIslandScan; + npm: readonly NpmOutcome[]; + /** Lowering diagnostics of the analysis (reached and unreached), runtime + * fences separately. */ + diagnostics: readonly ScrDiagnostic[]; + fences: readonly ScrDiagnostic[]; + policy: NodeIslandPolicy; +} + +/** Reasons kept per package (the report renders the first per module). */ +const REASON_CAP = 200; + +interface PackageState { + key: string; + name: string; + dir: string | null; + reasons: NodeIslandReason[]; + importers: Set; + nativeSites: SrcLoc[]; + island: boolean; + decision: IslandDecision; + carriedBy: string | null; + violation: boolean; +} + +export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandReport { + const policy = input.policy; + const states = new Map(); + const byName = new Map(); + const state = (name: string, dir: string | null): PackageState => { + const key = dir ?? `name:${name}`; + let s = states.get(key); + if (s === undefined) { + s = { + key, + name, + dir, + reasons: [], + importers: new Set(), + nativeSites: [], + island: false, + decision: "auto", + carriedBy: null, + violation: false, + }; + states.set(key, s); + const keys = byName.get(name) ?? []; + keys.push(key); + byName.set(name, keys); + } + return s; + }; + const addReason = (s: PackageState, r: NodeIslandReason): void => { + if (s.reasons.length < REASON_CAP) s.reasons.push(r); + }; + const ignored = (name: string): boolean => policy.unloaded.has(name); + + // Modules and their markers. + for (const m of input.scan.modules) { + if (m.origin !== "npm" || m.packageName === null) continue; + if (ignored(m.packageName)) continue; + const s = state(m.packageName, m.packageDir); + for (const marker of m.markers) addReason(s, classifyMarker(marker)); + } + // Import edges. + for (const e of input.scan.edges) { + if (e.shim || ignored(e.to)) continue; + const s = state(e.to, e.toDir); + if (e.fromDir === null) { + s.importers.add(""); + s.nativeSites.push(e.loc); + } else if (e.fromDir !== s.key) { + s.importers.add(e.fromDir); + } + } + // Lowering refusals and fences inside npm modules. + const addDiagnostics = (diags: readonly ScrDiagnostic[], source: NodeIslandReasonSource) => { + for (const d of diags) { + if (!isIslandReasonCode(d.code)) continue; + const at = packageDirOf(d.loc.file); + if (at === null || ignored(at.name)) continue; + addReason(state(at.name, at.dir), classifyDiagnostic(d, source)); + } + }; + addDiagnostics(input.diagnostics, "refusal"); + addDiagnostics(input.fences, "fence"); + // Packages that did not compile. + const records = new Map(); + for (const r of input.scan.refusals) if (!records.has(r.package)) records.set(r.package, r); + for (const outcome of input.npm) { + if (outcome.status !== "refused" || ignored(outcome.package)) continue; + const reason = classifyRefusal( + outcome.detail ?? "its static compilation was refused", + records.get(outcome.package) ?? null, + ); + if (reason === null) continue; + const keys = byName.get(outcome.package); + if (keys === undefined || keys.length === 0) { + addReason(state(outcome.package, null), reason); + continue; + } + for (const key of keys) addReason(states.get(key)!, reason); + } + + // Decisions. + const violations: ScrDiagnostic[] = []; + const fallbackLoc: SrcLoc = { file: input.entry, start: 0, end: 0 }; + for (const s of states.values()) { + sortReasons(s.reasons); + const forcedNode = policy.forcedNode.get(s.name); + const forcedNative = policy.forcedNative.get(s.name); + const outside = s.reasons.length > 0; + const loc = s.nativeSites[0] ?? fallbackLoc; + if (forcedNative !== undefined) { + if (outside) { + s.violation = true; + violations.push(nativePackageDiag(s.name, describe(s.reasons[0]!), loc)); + } + continue; + } + if (policy.mode === "off") { + if (outside || forcedNode !== undefined) { + s.violation = true; + violations.push( + nodeIslandsOffDiag( + s.name, + outside ? describe(s.reasons[0]!) : "it is marked to run in Node", + loc, + ), + ); + } + continue; + } + if (forcedNode !== undefined) { + s.island = true; + s.decision = forcedNode; + continue; + } + if (!outside) continue; + if (policy.mode === "explicit") { + s.violation = true; + violations.push(nodeIslandsOffDiag(s.name, describe(s.reasons[0]!), loc)); + continue; + } + s.island = true; + s.decision = "auto"; + } + + // Closure: packages imported only by islands (or by packages inside + // them) run inside the island. + for (let changed = true; changed;) { + changed = false; + for (const s of states.values()) { + if (s.island || s.carriedBy !== null || s.violation) continue; + if (s.importers.size === 0 || s.importers.has("")) continue; + let carrier: string | null = null; + let all = true; + for (const from of s.importers) { + const importer = states.get(from); + const owner = + importer === undefined ? null : importer.island ? importer.key : importer.carriedBy; + if (owner === null) { + all = false; + break; + } + carrier ??= owner; + } + if (all && carrier !== null) { + s.carriedBy = carrier; + changed = true; + } + } + } + + // Entries. + const islands: NodeIslandEntry[] = []; + const packages: NativePackage[] = []; + for (const s of states.values()) { + const version = s.dir === null ? null : packageVersionOf(s.dir); + if (s.island) { + const first = s.reasons[0]; + const modules = new Set(); + for (const r of s.reasons) if (r.loc !== null) modules.add(r.loc.file); + const carries: string[] = []; + for (const other of states.values()) { + if (other.carriedBy === s.key) carries.push(other.name); + } + carries.sort(); + islands.push({ + package: s.name, + version, + path: s.dir, + class: first === undefined ? "marked" : first.class, + reason: first === undefined ? markedReason(s.decision) : describe(first), + diagnostic: first === undefined ? null : first.code, + decision: s.decision, + reasons: s.reasons, + modules: modules.size, + carries, + importedFrom: s.nativeSites, + evaluation: "eager", + }); + continue; + } + if (s.violation) continue; + // A package that resolves to no installed JavaScript never loads (an + // optional dependency that is absent): nothing to compile or island. + if (s.dir === null && s.reasons.length === 0) continue; + const reachingIslands: string[] = []; + for (const from of s.importers) { + const importer = states.get(from); + if (importer === undefined) continue; + const owner = importer.island ? importer.key : importer.carriedBy; + if (owner !== null) { + const name = states.get(owner)!.name; + if (!reachingIslands.includes(name)) reachingIslands.push(name); + } + } + reachingIslands.sort(); + packages.push({ + package: s.name, + version, + path: s.dir, + status: s.carriedBy !== null ? "inside" : reachingIslands.length > 0 ? "shared" : "native", + islands: s.carriedBy !== null ? [states.get(s.carriedBy)!.name] : reachingIslands, + }); + } + islands.sort( + (a, b) => + reportRank(a.class) - reportRank(b.class) || + compareText(a.package, b.package) || + compareText(a.path ?? "", b.path ?? ""), + ); + packages.sort( + (a, b) => compareText(a.package, b.package) || compareText(a.path ?? "", b.path ?? ""), + ); + + // Marked user modules and SC81xx in user JavaScript. + const userModules: UserModuleIsland[] = []; + const userJs: ScrDiagnostic[] = []; + for (const m of input.scan.modules) { + if (m.origin !== "user") continue; + const marked = isMarkedUserModule(policy, m.file); + if (marked !== null) { + userModules.push({ file: m.file, source: marked }); + continue; + } + if (!policy.strictUserJs) continue; + for (const marker of m.markers) { + const code = ruleInfo(marker.rule).userJsCode; + if (code === null) continue; + userJs.push(userJsObjectModelDiag(code, capitalize(marker.construct), marker.loc)); + } + } + userModules.sort((a, b) => compareText(a.file, b.file)); + + const unknownPackages: string[] = []; + // Only command-line names: a project's configuration may list packages + // for other entries. + for (const forced of [policy.forcedNode, policy.forcedNative]) { + for (const [name, source] of forced) { + if (source === "cli" && !byName.has(name) && !unknownPackages.includes(name)) + unknownPackages.push(name); + } + } + unknownPackages.sort(); + + return { + mode: "would-be", + policy: policy.mode, + islands, + packages, + userModules, + violations, + userJs, + unknownPackages, + }; +} + +function compareText(a: string, b: string): number { + return a < b ? -1 : a > b ? 1 : 0; +} + +function capitalize(text: string): string { + return text.length === 0 ? text : text.charAt(0).toUpperCase() + text.slice(1); +} + +function markedReason(decision: IslandDecision): string { + return decision === "cli" ? "marked with --node-island" : 'marked "node" in scriptc.packages'; +} + +/** Decisive class first; within a class, refusals before pre-scan markers + * (the lowering is authoritative), then source order. */ +function sortReasons(reasons: NodeIslandReason[]): void { + const sourceRank = (r: NodeIslandReason): number => + r.source === "package" ? 0 : r.source === "refusal" ? 1 : r.source === "fence" ? 2 : 3; + reasons.sort( + (a, b) => + classRank(a.class) - classRank(b.class) || + sourceRank(a) - sourceRank(b) || + compareText(a.loc?.file ?? "", b.loc?.file ?? "") || + (a.loc?.start ?? 0) - (b.loc?.start ?? 0), + ); +} diff --git a/packages/compiler/src/node-islands/rules.ts b/packages/compiler/src/node-islands/rules.ts new file mode 100644 index 000000000..25f686314 --- /dev/null +++ b/packages/compiler/src/node-islands/rules.ts @@ -0,0 +1,109 @@ +/* The refusal rules of scope B (results/design-scope-b/DESIGN.md §1.2): + * the constructs that put a module outside the native subset, grouped in + * eleven rule families, and the four classes the detector reports. + * + * runtime (R1) needs the JavaScript runtime itself + * object-model (R2) needs a mutable object model + * gap inside the native subset, but scriptc cannot compile it yet + * packaging not compilable as shipped (no declarations, minified, ...) + * + * A reason id names one construct; refusal sites carry it on their + * diagnostic (ScrDiagnostic.reason) and the pre-scan (detect.ts) reports it + * with the rule. */ + +export type NodeIslandClass = "runtime" | "object-model" | "gap" | "packaging" | "marked"; + +export type NodeIslandRule = + | "R1.1" + | "R1.2" + | "R1.3" + | "R1.4" + | "R1.5" + | "R1.6" + | "R2.1" + | "R2.2" + | "R2.3" + | "R2.4" + | "R2.5"; + +export interface RuleInfo { + rule: NodeIslandRule; + title: string; + /** The SC81xx code the construct is in user JavaScript (R2 only). */ + userJsCode: "SC8101" | "SC8102" | "SC8103" | "SC8104" | "SC8105" | null; +} + +const RULES: RuleInfo[] = [ + { rule: "R1.1", title: "code from strings", userJsCode: null }, + { rule: "R1.2", title: "dynamic module loading", userJsCode: null }, + { rule: "R1.3", title: "native addons", userJsCode: null }, + { rule: "R1.4", title: "loader and require hooks", userJsCode: null }, + { rule: "R1.5", title: "engine internals", userJsCode: null }, + { rule: "R1.6", title: "runtime-only globals", userJsCode: null }, + { rule: "R2.1", title: "constructor functions", userJsCode: "SC8101" }, + { rule: "R2.2", title: "prototype mutation", userJsCode: "SC8102" }, + { rule: "R2.3", title: "shape mutation outside the checker's view", userJsCode: "SC8103" }, + { rule: "R2.4", title: "sloppy-mode semantics", userJsCode: "SC8104" }, + { rule: "R2.5", title: "builtin and global patching", userJsCode: "SC8105" }, +]; + +export function ruleInfo(rule: NodeIslandRule): RuleInfo { + for (const info of RULES) if (info.rule === rule) return info; + throw new Error(`unknown Node island rule ${rule}`); +} + +/** Reason id → rule. Ids without a rule (gap, packaging) are absent. */ +const REASON_RULES: Record = { + "code-from-string": "R1.1", + vm: "R1.1", + "dynamic-require": "R1.2", + "dynamic-import": "R1.2", + "unresolved-import": "R1.2", + "native-addon": "R1.3", + "loader-hook": "R1.4", + "engine-internal": "R1.5", + "weak-ref": "R1.6", + "constructor-function": "R2.1", + "prototype-write": "R2.2", + "define-property": "R2.3", + with: "R2.4", + "sloppy-arguments": "R2.4", + "sloppy-mode": "R2.4", + "builtin-write": "R2.5", + proxy: "R2.5", +}; + +export function ruleOfReason(reason: string): NodeIslandRule | null { + return REASON_RULES[reason] ?? null; +} + +export function classOfRule(rule: NodeIslandRule): NodeIslandClass { + return rule.startsWith("R1.") ? "runtime" : "object-model"; +} + +/** The decisive class first: a runtime need outranks an object-model need, + * which outranks shipping problems; a gap is a package's class only when + * nothing else keeps it out of the native subset (fixing the gap would make + * it native). */ +export function classRank(cls: NodeIslandClass): number { + if (cls === "runtime") return 0; + if (cls === "object-model") return 1; + if (cls === "packaging") return 2; + if (cls === "gap") return 3; + return 4; +} + +/** Report order: gap islands first (compiler work hidden behind an + * island), then the rest by rank. */ +export function reportRank(cls: NodeIslandClass): number { + if (cls === "gap") return 0; + return classRank(cls) + 1; +} + +export function classDescription(cls: NodeIslandClass): string { + if (cls === "runtime") return "needs the JavaScript runtime"; + if (cls === "object-model") return "needs a mutable object model"; + if (cls === "gap") return "inside the native subset, but scriptc cannot compile it yet"; + if (cls === "packaging") return "not compilable as shipped"; + return "marked to run in Node"; +} diff --git a/packages/compiler/src/node-islands/scan.ts b/packages/compiler/src/node-islands/scan.ts new file mode 100644 index 000000000..0ac082e73 --- /dev/null +++ b/packages/compiler/src/node-islands/scan.ts @@ -0,0 +1,155 @@ +/* The program-level half of the detector: which modules a program loads, + * the pre-scan markers in each, and the package import edges the island + * closure needs. The frontend calls it on its final program (and on the + * program that still held a package the drop-and-reload loop removed). */ +import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { SrcLoc } from "../ir/ir.js"; +import * as ts from "../frontend/ts7/adapter.js"; +import { canonicalBuiltinModule, npmBuiltinAliasOf } from "../frontend/builtin-modules.js"; +import { erasedTypeOnlyImport } from "../frontend/type-only-imports.js"; +import { isShimmedImport } from "../frontend/package-shims.js"; +import { resolveBareModule } from "../frontend/resolve.js"; +import { isJsSourceFileName } from "../frontend/tsc-codes.js"; +import { isRelativeSpecifier, packageNameOfSpecifier } from "../frontend/workspace-registry.js"; +import { moduleSourceCandidates } from "../frontend/module-source-candidates.js"; +import { scanModule, type NodeIslandMarker } from "./detect.js"; +import { originOf, packageDirOf, type Origin } from "./origin.js"; + +export interface ScannedModule { + file: string; + origin: Origin; + /** The npm package directory, for npm modules. */ + packageDir: string | null; + packageName: string | null; + markers: NodeIslandMarker[]; + /** True for a module of a package the drop-and-reload loop removed. */ + dropped: boolean; +} + +/** One import of an npm package. */ +export interface PackageEdge { + /** The importing package's directory; null for user code. */ + fromDir: string | null; + fromFile: string; + /** The imported package's name. */ + to: string; + /** Its directory when the import resolves to installed JavaScript. */ + toDir: string | null; + /** Served by a registry shim, a builtin alias, or marked unloaded. */ + shim: boolean; + loc: SrcLoc; +} + +/** How the drop-and-reload loop refused a package. */ +export type RefusalKind = "preflight" | "offender" | "degrade" | "typecheck" | "external"; + +export interface RefusalRecord { + package: string; + kind: RefusalKind; + /** The package-anchored preflight diagnostic, for kind "preflight". */ + diag: ScrDiagnostic | null; +} + +export interface NodeIslandScan { + modules: ScannedModule[]; + edges: PackageEdge[]; + refusals: RefusalRecord[]; +} + +export function emptyScan(): NodeIslandScan { + return { modules: [], edges: [], refusals: [] }; +} + +function locOfNode(node: ts.Node): SrcLoc { + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; +} + +/** The literal module specifiers of a file with their sites: top-level + * import and export declarations, and the require() and import() calls the + * program's shared module-source scan already collected (no second walk). */ +function specifiersOf(program: ts.Program, sf: ts.SourceFile): { spec: string; node: ts.Node }[] { + const out: { spec: string; node: ts.Node }[] = []; + for (const node of sf.statements.nodes) { + if ( + (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && + node.moduleSpecifier !== undefined && + ts.isStringLiteral(node.moduleSpecifier) + ) { + // Type-only imports load nothing. + const typeOnly = ts.isImportDeclaration(node) ? erasedTypeOnlyImport(node) : node.isTypeOnly; + if (!typeOnly) out.push({ spec: node.moduleSpecifier.text, node }); + } + } + for (const call of moduleSourceCandidates(program, sf).calls) { + const arg = call.arguments.nodes[0]; + if ( + arg !== undefined && + ts.isStringLiteralLike(arg) && + (call.expression.kind === ts.SyntaxKind.ImportKeyword || + (ts.isIdentifier(call.expression) && call.expression.text === "require")) + ) + out.push({ spec: arg.text, node: call }); + } + return out; +} + +/** Scans `files`: markers for npm JavaScript (and for user JavaScript when + * `userJs`), and package edges for every file when `edges` (a program with + * no npm package has none to find). */ +export function scanFiles( + program: ts.Program, + files: readonly ts.SourceFile[], + opts: { userJs: boolean; dropped: boolean; edges: boolean }, +): { modules: ScannedModule[]; edges: PackageEdge[] } { + const modules: ScannedModule[] = []; + const edges: PackageEdge[] = []; + const seen = new Set(); + for (const sf of files) { + if (seen.has(sf.fileName)) continue; + seen.add(sf.fileName); + const origin = originOf(sf.fileName); + if (origin === "builtin" || origin === "shim") continue; + const at = origin === "npm" ? packageDirOf(sf.fileName) : null; + const js = isJsSourceFileName(sf.fileName); + const markers = js && (origin === "npm" || opts.userJs) ? scanModule(sf) : []; + modules.push({ + file: sf.fileName, + origin, + packageDir: at === null ? null : at.dir, + packageName: at === null ? null : at.name, + markers, + dropped: opts.dropped, + }); + if (!opts.edges) continue; + for (const { spec, node } of specifiersOf(program, sf)) { + if (isRelativeSpecifier(spec) || spec.startsWith("/") || spec.startsWith("#")) continue; + // scriptc's own packages lower at their use sites. + if (spec.startsWith("@scriptc/")) continue; + const alias = npmBuiltinAliasOf(spec); + if (alias === null && (spec.startsWith("node:") || canonicalBuiltinModule(spec) !== null)) + continue; + const name = packageNameOfSpecifier(spec); + const shim = alias !== null || isShimmedImport(sf.fileName, spec); + let toDir: string | null = null; + if (!shim) { + const resolved = resolveBareModule(sf.fileName, spec, "js-only"); + if (resolved !== null) { + const target = packageDirOf(resolved.typesFile); + // A workspace package compiled from source is user code. + if (target === null) continue; + toDir = target.dir; + } + } + edges.push({ + fromDir: at === null ? null : at.dir, + fromFile: sf.fileName, + to: name, + toDir, + shim, + loc: locOfNode(node), + }); + } + } + return { modules, edges }; +} diff --git a/packages/compiler/src/node-islands/summary.ts b/packages/compiler/src/node-islands/summary.ts new file mode 100644 index 000000000..8108d985c --- /dev/null +++ b/packages/compiler/src/node-islands/summary.ts @@ -0,0 +1,110 @@ +/* The Node island report's data model and the small helpers coverage + * rendering needs. Kept free of compiler imports: the coverage report (and + * the CLI's verdict-replay fast path behind it) loads this module. */ +import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { SrcLoc } from "../ir/ir.js"; +import type { DecisionSource, NodeIslandPolicy } from "./policy.js"; +import type { NodeIslandClass, NodeIslandRule } from "./rules.js"; + +export type NodeIslandReasonSource = "pre-scan" | "refusal" | "fence" | "package"; + +export interface NodeIslandReason { + class: NodeIslandClass; + rule: NodeIslandRule | null; + /** Reason id: a rules.ts id, "gap" or "packaging". */ + reason: string; + /** The SC code of the refusal, when the reason is a diagnostic. */ + code: string | null; + message: string; + loc: SrcLoc | null; + source: NodeIslandReasonSource; +} + +export type IslandDecision = "auto" | "config" | "cli"; + +export interface NodeIslandEntry { + package: string; + version: string | null; + /** The package directory (the instance), when it is installed. */ + path: string | null; + class: NodeIslandClass; + /** The first decisive reason, rendered. */ + reason: string; + /** The SC code of the first decisive reason, when it is a refusal. */ + diagnostic: string | null; + decision: IslandDecision; + /** Every reason, in report order (decisive class first, then source + * order). */ + reasons: NodeIslandReason[]; + /** Modules of the package with at least one reason. */ + modules: number; + /** Packages imported only through this island, which run inside it. */ + carries: string[]; + /** Where native code imports the package: the island boundary. */ + importedFrom: SrcLoc[]; + /** Report-only: deferral (NI-7) is not decided yet, so every island + * would evaluate eagerly at its module position. */ + evaluation: "eager"; +} + +export interface UserModuleIsland { + file: string; + source: DecisionSource; +} + +export interface NativePackage { + package: string; + version: string | null; + path: string | null; + /** "native": compiles natively; "inside": runs inside an island's + * closure; "shared": reached from native code and from an island. */ + status: "native" | "inside" | "shared"; + /** For inside and shared: the islands that reach it. */ + islands: string[]; +} + +export interface NodeIslandReport { + mode: "would-be"; + policy: NodeIslandPolicy["mode"]; + islands: NodeIslandEntry[]; + packages: NativePackage[]; + userModules: UserModuleIsland[]; + /** Would-be errors: SC8002 (islands off) and SC8003 (marked native). */ + violations: ScrDiagnostic[]; + /** SC8101–SC8105 for user JavaScript (only with --strict-user-js). */ + userJs: ScrDiagnostic[]; + /** --native / --node-island / config package names the program does + * not import. */ + unknownPackages: string[]; +} + +/** "R2.1 constructor functions: 'new Foo()' of a function" or + * "SC2012: ". */ +export function describe(r: NodeIslandReason): string { + if (r.source === "pre-scan" && r.rule !== null) return `${r.rule} ${r.message}`; + const rule = r.rule === null ? "" : `${r.rule} `; + return r.code === null ? `${rule}${r.message}` : `${rule}${r.code}: ${r.message}`; +} + +/** True when the report has decisions to show (islands, marked modules, + * policy errors or unknown names); SC81xx findings alone render as + * blockers. */ +export function hasNodeIslandDecisions(report: NodeIslandReport): boolean { + return ( + report.islands.length > 0 || + report.userModules.length > 0 || + report.violations.length > 0 || + report.unknownPackages.length > 0 + ); +} + +/** True when the report has anything to show. */ +export function hasNodeIslandFindings(report: NodeIslandReport): boolean { + return ( + report.islands.length > 0 || + report.userModules.length > 0 || + report.violations.length > 0 || + report.userJs.length > 0 || + report.unknownPackages.length > 0 + ); +} diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts new file mode 100644 index 000000000..b9750f24e --- /dev/null +++ b/packages/compiler/src/project-config.test.ts @@ -0,0 +1,126 @@ +import { expect, test } from "vitest"; +import { parseProjectConfig } from "./project-config.js"; + +const manifest = "/project/package.json"; + +function config(scriptc: unknown) { + return parseProjectConfig(manifest, JSON.stringify({ name: "app", scriptc }, null, 2)); +} + +test("a package.json without a scriptc key has the default configuration", () => { + const c = parseProjectConfig(manifest, '{ "name": "app" }'); + expect(c.diagnostics).toEqual([]); + expect(c.packages.size).toBe(0); + expect(c.nodeIslands).toEqual({ + auto: true, + modules: [], + node: null, + nodeVersion: null, + packaging: "embedded", + speculativeStart: false, + }); + expect(parseProjectConfig(manifest, "{ not json").diagnostics).toEqual([]); +}); + +test("package settings: strings and objects", () => { + const c = config({ + packages: { + encoding: "unloaded", + "dd-trace": "node", + "hot-shots": "native", + semver: "auto", + "proper-lockfile": { run: "node", evaluate: "lazy", share: "duplicate" }, + }, + }); + expect(c.diagnostics).toEqual([]); + expect(c.packages.get("encoding")?.unloaded).toBe(true); + expect(c.packages.get("dd-trace")?.run).toBe("node"); + expect(c.packages.get("hot-shots")?.run).toBe("native"); + expect(c.packages.get("semver")?.run).toBe("auto"); + expect(c.packages.get("proper-lockfile")).toEqual({ + unloaded: false, + run: "node", + evaluate: "lazy", + share: "duplicate", + marshal: "copy", + schedule: "lockstep", + }); +}); + +test("nodeIslands settings", () => { + const c = config({ + nodeIslands: { + auto: false, + modules: ["src/plugins/*.ts"], + node: "/usr/local/bin/node", + nodeVersion: ">=24.13.1", + packaging: "beside", + speculativeStart: true, + }, + wasm: ["build/a.wasm", 3], + }); + expect(c.diagnostics).toEqual([]); + expect(c.nodeIslands).toEqual({ + auto: false, + modules: ["src/plugins/*.ts"], + node: "/usr/local/bin/node", + nodeVersion: ">=24.13.1", + packaging: "beside", + speculativeStart: true, + }); + // scriptc.wasm keeps its lenient reading. + expect(c.wasm).toEqual(["/project/build/a.wasm"]); +}); + +test("unknown keys and values are SC8004, located at the key", () => { + const text = JSON.stringify( + { + name: "app", + scriptc: { + packages: { + encoding: "unloded", + debug: { run: "node", evaluate: "later", color: true }, + ms: 3, + }, + nodeIslands: { auto: "yes", mode: "auto", modules: "src/x.ts", packaging: "zip" }, + }, + }, + null, + 2, + ); + const c = parseProjectConfig(manifest, text); + expect(c.diagnostics.map((d) => [d.code, d.message])).toEqual([ + ["SC8004", `package.json 'scriptc.packages.encoding' has the unknown value "unloded"`], + ["SC8004", `package.json 'scriptc.packages.debug.evaluate' has the unknown value "later"`], + ["SC8004", "package.json 'scriptc.packages.debug.color' is not a known package setting"], + ["SC8004", "package.json 'scriptc.packages.ms' must be a string or an object, not 3"], + ["SC8004", `package.json 'scriptc.nodeIslands.auto' must be true or false, not "yes"`], + ["SC8004", "package.json 'scriptc.nodeIslands.mode' is not a known Node island setting"], + [ + "SC8004", + `package.json 'scriptc.nodeIslands.modules' must be an array of module paths or globs, not "src/x.ts"`, + ], + ["SC8004", `package.json 'scriptc.nodeIslands.packaging' has the unknown value "zip"`], + ]); + // Locations point at the offending key inside the scriptc object. + const at = (i: number) => text.slice(c.diagnostics[i]!.loc.start).split(":")[0]; + expect(at(0)).toBe('"encoding"'); + expect(at(1)).toBe('"evaluate"'); + expect(at(5)).toBe('"mode"'); + for (const d of c.diagnostics) expect(d.loc.file).toBe(manifest); + // Invalid entries are not applied; valid siblings are. + expect(c.packages.has("encoding")).toBe(false); + expect(c.packages.has("debug")).toBe(false); + expect(c.nodeIslands.auto).toBe(true); +}); + +test("a non-object packages or nodeIslands value is SC8004", () => { + expect(config({ packages: ["a"] }).diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.packages' must be an object mapping package names to settings, not an array", + ]); + expect(config({ nodeIslands: true }).diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.nodeIslands' must be an object, not true", + ]); + // Other keys of the scriptc object belong to other readers. + expect(config({ somethingElse: 1 }).diagnostics).toEqual([]); +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts new file mode 100644 index 000000000..cc13a868d --- /dev/null +++ b/packages/compiler/src/project-config.ts @@ -0,0 +1,421 @@ +/* The project configuration: the `scriptc` key of the entry's nearest + * package.json. One reader for every consumer (package shims, the + * WebAssembly scanner, the Node island policy), with a schema: a value + * this compiler does not know in `scriptc.packages` or + * `scriptc.nodeIslands` is SC8004 instead of being ignored, because an + * ignored setting is a silent divergence from what the project asked for. + * + * "scriptc": { + * "packages": { + * "encoding": "unloaded", // never loaded (package-shims.ts) + * "dd-trace": "node", // run in a Node island + * "hot-shots": "native", // must compile natively (SC8003) + * "proper-lockfile": { "run": "node", "evaluate": "lazy" } + * }, + * "nodeIslands": { "auto": true, "modules": ["src/plugins/*.ts"] }, + * "wasm": ["build/module.wasm"] + * } + * + * Node islands are report-only in this version: the policy decides which + * packages and modules WOULD run in Node, and coverage reports it, but code + * generation does not change. Other keys of the `scriptc` object are left + * to their own readers. */ +import { dirname, join, resolve } from "node:path"; +import { projectConfigDiag, type ScrDiagnostic } from "./diagnostics/diagnostic.js"; +import { trackedReadFile } from "./frontend/input-tracker.js"; + +/** How a package runs: compiled natively, in a Node island, or decided by + * the detector. */ +export type PackageRun = "native" | "node" | "auto"; +export type PackageEvaluate = "auto" | "lazy" | "eager"; +export type PackageShare = "auto" | "shared" | "duplicate"; +export type PackageMarshal = "copy" | "handles"; +export type PackageSchedule = "lockstep" | "free"; + +/** One `scriptc.packages.` entry. `unloaded` packages never load; + * the other settings choose where the package runs. */ +export interface PackageSetting { + unloaded: boolean; + run: PackageRun; + evaluate: PackageEvaluate; + share: PackageShare; + marshal: PackageMarshal; + schedule: PackageSchedule; +} + +export interface NodeIslandsSetting { + /** Automatic Node islands for npm code outside the native subset. */ + auto: boolean; + /** User modules (paths or globs relative to the package.json) that run + * in Node. */ + modules: string[]; + /** The `node` executable recorded into the binary. */ + node: string | null; + /** A semver range checked at the helper handshake. */ + nodeVersion: string | null; + packaging: "embedded" | "beside"; + speculativeStart: boolean; +} + +export interface ProjectConfig { + /** The package.json the configuration came from, or null. */ + manifest: string | null; + /** Its text, for diagnostic code frames. */ + manifestText: string | null; + packages: Map; + nodeIslands: NodeIslandsSetting; + /** `scriptc.wasm`: module paths, resolved against the manifest. */ + wasm: string[]; + /** SC8004 for every unknown key or value. */ + diagnostics: ScrDiagnostic[]; +} + +const PACKAGE_STRINGS = ["unloaded", "node", "native", "auto"]; +const PACKAGE_OBJECT_KEYS: Record = { + run: ["native", "node", "auto"], + evaluate: ["auto", "lazy", "eager"], + share: ["auto", "shared", "duplicate"], + marshal: ["copy", "handles"], + schedule: ["lockstep", "free"], +}; +const NODE_ISLANDS_KEYS = [ + "auto", + "modules", + "node", + "nodeVersion", + "packaging", + "speculativeStart", +]; + +function defaultPackageSetting(): PackageSetting { + return { + unloaded: false, + run: "auto", + evaluate: "auto", + share: "auto", + marshal: "copy", + schedule: "lockstep", + }; +} + +function defaultNodeIslands(): NodeIslandsSetting { + return { + auto: true, + modules: [], + node: null, + nodeVersion: null, + packaging: "embedded", + speculativeStart: false, + }; +} + +/** The empty configuration of a project without a package.json. */ +export function emptyProjectConfig(): ProjectConfig { + return { + manifest: null, + manifestText: null, + packages: new Map(), + nodeIslands: defaultNodeIslands(), + wasm: [], + diagnostics: [], + }; +} + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function quoted(values: string[]): string { + return values.map((v) => `"${v}"`).join(", "); +} + +/** A short rendering of a JSON value for a message. */ +function shown(value: unknown): string { + if (typeof value === "string") return `"${value}"`; + if (value === null) return "null"; + if (Array.isArray(value)) return "an array"; + if (typeof value === "object") return "an object"; + return String(value); +} + +/** Reads and validates one package.json text. Exported for tests. */ +export function parseProjectConfig(manifest: string, text: string): ProjectConfig { + const config = emptyProjectConfig(); + config.manifest = manifest; + config.manifestText = text; + let parsed: unknown = null; + try { + parsed = JSON.parse(text); + } catch { + // Node reports a malformed package.json itself; the configuration is + // simply absent here, as before. + return config; + } + if (!isRecord(parsed)) return config; + const root = parsed["scriptc"]; + if (!isRecord(root)) return config; + const locate = new JsonLocator(text); + const problem = (path: string[], detail: string, hint: string): void => { + config.diagnostics.push( + projectConfigDiag(`scriptc.${path.join(".")}`, detail, hint, { + file: manifest, + start: locate.offsetOf(["scriptc", ...path]), + end: locate.offsetOf(["scriptc", ...path]), + }), + ); + }; + + const packages = root["packages"]; + if (packages !== undefined) { + if (!isRecord(packages)) { + problem( + ["packages"], + `must be an object mapping package names to settings, not ${shown(packages)}`, + `write "packages": { "": "auto" }`, + ); + } else { + for (const name of Object.keys(packages)) { + const value = packages[name]; + const setting = defaultPackageSetting(); + if (typeof value === "string") { + if (!PACKAGE_STRINGS.includes(value)) { + problem( + ["packages", name], + `has the unknown value ${shown(value)}`, + `use one of ${quoted(PACKAGE_STRINGS)}, or an object with ${Object.keys(PACKAGE_OBJECT_KEYS).join(", ")}`, + ); + continue; + } + if (value === "unloaded") setting.unloaded = true; + else setting.run = value as PackageRun; + config.packages.set(name, setting); + continue; + } + if (!isRecord(value)) { + problem( + ["packages", name], + `must be a string or an object, not ${shown(value)}`, + `use one of ${quoted(PACKAGE_STRINGS)}, or an object with ${Object.keys(PACKAGE_OBJECT_KEYS).join(", ")}`, + ); + continue; + } + let valid = true; + for (const key of Object.keys(value)) { + const allowed = PACKAGE_OBJECT_KEYS[key]; + if (allowed === undefined) { + problem( + ["packages", name, key], + "is not a known package setting", + `the settings are ${Object.keys(PACKAGE_OBJECT_KEYS).join(", ")}`, + ); + valid = false; + continue; + } + const v = value[key]; + if (typeof v !== "string" || !allowed.includes(v)) { + problem( + ["packages", name, key], + `has the unknown value ${shown(v)}`, + `use one of ${quoted(allowed)}`, + ); + valid = false; + continue; + } + if (key === "run") setting.run = v as PackageRun; + else if (key === "evaluate") setting.evaluate = v as PackageEvaluate; + else if (key === "share") setting.share = v as PackageShare; + else if (key === "marshal") setting.marshal = v as PackageMarshal; + else setting.schedule = v as PackageSchedule; + } + if (valid) config.packages.set(name, setting); + } + } + } + + const islands = root["nodeIslands"]; + if (islands !== undefined) { + if (!isRecord(islands)) { + problem( + ["nodeIslands"], + `must be an object, not ${shown(islands)}`, + `write "nodeIslands": { "auto": true }`, + ); + } else { + const setting = config.nodeIslands; + for (const key of Object.keys(islands)) { + const v = islands[key]; + if (!NODE_ISLANDS_KEYS.includes(key)) { + problem( + ["nodeIslands", key], + "is not a known Node island setting", + `the settings are ${NODE_ISLANDS_KEYS.join(", ")}`, + ); + } else if (key === "auto" || key === "speculativeStart") { + if (typeof v !== "boolean") { + problem( + ["nodeIslands", key], + `must be true or false, not ${shown(v)}`, + "use a boolean", + ); + } else if (key === "auto") setting.auto = v; + else setting.speculativeStart = v; + } else if (key === "modules") { + const list: string[] = []; + let ok = Array.isArray(v); + if (Array.isArray(v)) { + for (const entry of v as unknown[]) { + if (typeof entry === "string" && entry !== "") list.push(entry); + else ok = false; + } + } + if (!ok) { + problem( + ["nodeIslands", key], + `must be an array of module paths or globs, not ${shown(v)}`, + `write "modules": ["src/plugins/loader.ts"]`, + ); + } else setting.modules = list; + } else if (key === "packaging") { + if (v !== "embedded" && v !== "beside") { + problem( + ["nodeIslands", key], + `has the unknown value ${shown(v)}`, + `use "embedded" or "beside"`, + ); + } else setting.packaging = v; + } else if (typeof v !== "string" || v === "") { + problem( + ["nodeIslands", key], + `must be a non-empty string, not ${shown(v)}`, + key === "node" + ? "give the path of the node executable" + : `write a range such as ">=24.13.1"`, + ); + } else if (key === "node") setting.node = v; + else setting.nodeVersion = v; + } + } + } + + // `scriptc.wasm` keeps its lenient reading: string entries of an array. + const wasm = root["wasm"]; + if (Array.isArray(wasm)) { + for (const entry of wasm as unknown[]) { + if (typeof entry === "string") config.wasm.push(resolve(dirname(manifest), entry)); + } + } + return config; +} + +let cachedManifest: string | null = null; +let cachedText: string | null = null; +let cachedConfig: ProjectConfig | null = null; + +/** The configuration of the entry's nearest package.json (the first one + * found walking up from the entry's directory, readable or not). */ +export function readProjectConfig(entryPath: string): ProjectConfig { + for (let dir = dirname(resolve(entryPath)); ;) { + const manifest = join(dir, "package.json"); + const text = trackedReadFile(manifest); + if (text !== null) { + if (cachedConfig !== null && cachedManifest === manifest && cachedText === text) + return cachedConfig; + const config = parseProjectConfig(manifest, text); + cachedManifest = manifest; + cachedText = text; + cachedConfig = config; + return config; + } + const parent = dirname(dir); + if (parent === dir) return emptyProjectConfig(); + dir = parent; + } +} + +/** Offsets of keys in a JSON text, for diagnostic locations. It follows + * object nesting so `["scriptc", "packages", "x"]` finds the key `"x"` + * inside `scriptc.packages`, not an earlier `"x"` elsewhere. */ +class JsonLocator { + constructor(private readonly text: string) {} + + /** The offset of the last key of `path` that could be found (0 when + * none). */ + offsetOf(path: string[]): number { + let start = 0; + let end = this.text.length; + let found = 0; + for (const key of path) { + const at = this.keyIn(key, start, end); + if (at < 0) break; + found = at; + // The key's value: from after the colon to the end of its object. + const colon = this.text.indexOf(":", at); + if (colon < 0) break; + start = colon + 1; + end = this.valueEnd(start, end); + } + return found; + } + + /** A direct key of the object starting at or after `start`. */ + private keyIn(key: string, start: number, end: number): number { + const text = this.text; + let depth = 0; + let i = start; + while (i < end) { + const ch = text.charCodeAt(i); + if (ch === 34 /* " */) { + const close = this.stringEnd(i); + if (depth === 1 && text.slice(i + 1, close) === key) { + let j = close + 1; + while (j < end && /\s/.test(text.charAt(j))) j++; + if (text.charAt(j) === ":") return i; + } + i = close + 1; + continue; + } + if (ch === 123 /* { */ || ch === 91 /* [ */) depth++; + else if (ch === 125 /* } */ || ch === 93 /* ] */) { + depth--; + if (depth <= 0) return -1; + } + i++; + } + return -1; + } + + private stringEnd(open: number): number { + const text = this.text; + let i = open + 1; + while (i < text.length) { + const ch = text.charCodeAt(i); + if (ch === 92 /* \ */) i += 2; + else if (ch === 34) return i; + else i++; + } + return text.length; + } + + /** The end of the JSON value that starts at or after `start`. */ + private valueEnd(start: number, end: number): number { + const text = this.text; + let i = start; + while (i < end && /\s/.test(text.charAt(i))) i++; + let depth = 0; + while (i < end) { + const ch = text.charCodeAt(i); + if (ch === 34) { + i = this.stringEnd(i) + 1; + if (depth === 0) return i; + continue; + } + if (ch === 123 || ch === 91) depth++; + else if (ch === 125 || ch === 93) { + depth--; + if (depth <= 0) return i + 1; + } else if (depth === 0 && ch === 44 /* , */) return i; + i++; + } + return end; + } +} diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index de38bf64f..3cdc3eb96 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -24,11 +24,8 @@ import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { dirname, isAbsolute, join, resolve } from "node:path"; import * as ts from "../frontend/ts7/adapter.js"; -import { - trackedAccessibleEntries, - trackedFileExists, - trackedReadFile, -} from "../frontend/input-tracker.js"; +import { trackedAccessibleEntries, trackedReadFile } from "../frontend/input-tracker.js"; +import { readProjectConfig } from "../project-config.js"; import { analyzeWasm, isWasmHeader, type WasmModuleSummary } from "./binary.js"; import { decodeBase64 } from "./base64.js"; @@ -282,24 +279,10 @@ class Discovery { // ── source 4: configuration ───────────────────────────────────────── scanConfig(): void { - const manifest = nearestPackageJson(dirname(this.input.entry)); - if (manifest !== null) { - const text = trackedReadFile(manifest); - if (text !== null) { - let listed: unknown; - try { - const parsed = JSON.parse(text) as { scriptc?: { wasm?: unknown } }; - listed = parsed.scriptc?.wasm; - } catch { - listed = undefined; - } - if (Array.isArray(listed)) { - for (const entry of listed) { - if (typeof entry === "string") { - this.addFile(resolve(dirname(manifest), entry), { kind: "config", file: manifest }); - } - } - } + const config = readProjectConfig(this.input.entry); + if (config.manifest !== null) { + for (const path of config.wasm) { + this.addFile(path, { kind: "config", file: config.manifest }); } } for (const path of this.input.configured) { @@ -396,16 +379,6 @@ function collectWasmFiles(dir: string, depth: number, out: string[]): void { } } -function nearestPackageJson(dir: string): string | null { - for (;;) { - const candidate = join(dir, "package.json"); - if (trackedFileExists(candidate)) return candidate; - const parent = dirname(dir); - if (parent === dir) return null; - dir = parent; - } -} - /** Every WebAssembly module the program's sources, packages and * configuration make visible to the build, in discovery order. */ export function discoverWasmModules(input: WasmDiscoveryInput): WasmModuleReport[] { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json new file mode 100644 index 000000000..6b86e7f9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/node-islands-config/main.ts" + ], + "diags": [] +} diff --git a/tests/coverage-fixtures/node-islands-user-js.cjs b/tests/coverage-fixtures/node-islands-user-js.cjs new file mode 100644 index 000000000..c01f8c255 --- /dev/null +++ b/tests/coverage-fixtures/node-islands-user-js.cjs @@ -0,0 +1,22 @@ +// User JavaScript with one R2 construct per rule. With --strict-user-js, +// coverage reports each as the SC81xx error scope B makes it; without the +// flag the report is unchanged. +function Point(x) { + this.x = x; +} +Point.prototype.norm = function norm() { + return Math.abs(this.x); +}; +const base = { kind: "base" }; +const child = {}; +Object.setPrototypeOf(child, base); +const box = {}; +Object.defineProperty(box, "size", { value: 3, enumerable: false }); +function legacy(a) { + arguments[0] = 2; + return a; +} +Array.prototype.last = function last() { + return this[this.length - 1]; +}; +console.log(new Point(-2).norm(), child.kind, box.size, legacy(1)); diff --git a/tests/coverage-fixtures/node-islands/main.ts b/tests/coverage-fixtures/node-islands/main.ts new file mode 100644 index 000000000..ac6037d1a --- /dev/null +++ b/tests/coverage-fixtures/node-islands/main.ts @@ -0,0 +1,16 @@ +// Node islands, report-only: one npm package per island class, a package +// that runs inside an island's closure, one reached from both sides, and +// one that stays native. Coverage reports the islands a build would +// choose; the build itself is unchanged. +import { describe } from "ni-gap"; +import { compile } from "ni-eval"; +import { Counter } from "ni-ctor"; +import { shout } from "ni-minified"; +import { twice } from "ni-typed"; + +console.log(describe()); +console.log(compile("a + 1")(2)); +const counter = new Counter(); +counter.add(twice(2)); +console.log(counter.total()); +console.log(shout("hi")); diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.d.ts new file mode 100644 index 000000000..2b70027a9 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.d.ts @@ -0,0 +1,4 @@ +export declare class Counter { + add(n: number): void; + total(): number; +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js new file mode 100644 index 000000000..39992107e --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js @@ -0,0 +1,15 @@ +"use strict"; +// A constructor function with prototype methods needs a mutable object +// model (R2.1). Its helper package runs inside its island. +const { step } = require("ni-dep"); +const { twice } = require("ni-typed"); +function Counter() { + this.count = 0; +} +Counter.prototype.add = function add(n) { + this.count = step(this.count, twice(n)); +}; +Counter.prototype.total = function total() { + return this.count; +}; +exports.Counter = Counter; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/package.json new file mode 100644 index 000000000..096b98d20 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-ctor/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-ctor", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-dep/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-dep/index.js new file mode 100644 index 000000000..71a0eec99 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-dep/index.js @@ -0,0 +1,4 @@ +"use strict"; +exports.step = function step(a, b) { + return a + b; +}; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-dep/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-dep/package.json new file mode 100644 index 000000000..24b33b1ef --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-dep/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-dep", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.d.ts new file mode 100644 index 000000000..4bdbf94f6 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.d.ts @@ -0,0 +1 @@ +export declare function compile(body: string): (a: number) => number; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.js new file mode 100644 index 000000000..c549c058e --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.js @@ -0,0 +1,5 @@ +"use strict"; +// Code from strings needs the JavaScript runtime (R1.1). +exports.compile = function compile(body) { + return new Function("a", "return " + body); +}; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-eval/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/package.json new file mode 100644 index 000000000..449239a05 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-eval/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-eval", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.d.ts new file mode 100644 index 000000000..6e1b743c2 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.d.ts @@ -0,0 +1 @@ +export declare function describe(): string; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js new file mode 100644 index 000000000..2148e51fd --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js @@ -0,0 +1,8 @@ +"use strict"; +// A construct inside the native subset that scriptc does not compile yet: +// a builtin function alias used as an escaping value. +const { extname } = require("node:path"); +const ext = extname; +exports.describe = function describe() { + return typeof ext; +}; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-gap/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/package.json new file mode 100644 index 000000000..9804ad187 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-gap/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-gap", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts new file mode 100644 index 000000000..ccebdab51 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts @@ -0,0 +1 @@ +export declare function shout(s: string): string; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js new file mode 100644 index 000000000..25c0e11ac --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js @@ -0,0 +1 @@ +"use strict";exports.shout=function shout(s){return s.toUpperCase()+"!"}; \ No newline at end of file diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json new file mode 100644 index 000000000..cc819dfc8 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-minified", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.d.ts new file mode 100644 index 000000000..bb1a852b1 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.d.ts @@ -0,0 +1 @@ +export declare function twice(n: number): number; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.js new file mode 100644 index 000000000..082402bfd --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/index.js @@ -0,0 +1,4 @@ +"use strict"; +exports.twice = function twice(n) { + return n * 2; +}; diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-typed/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/package.json new file mode 100644 index 000000000..c3048156c --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-typed/package.json @@ -0,0 +1,5 @@ +{ + "name": "ni-typed", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/coverage-fixtures/node-islands/package.json b/tests/coverage-fixtures/node-islands/package.json new file mode 100644 index 000000000..f18eb6012 --- /dev/null +++ b/tests/coverage-fixtures/node-islands/package.json @@ -0,0 +1,5 @@ +{ + "name": "node-islands-coverage-fixture", + "private": true, + "type": "module" +} diff --git a/tests/diagnostics/node-islands-config/main.ts b/tests/diagnostics/node-islands-config/main.ts new file mode 100644 index 000000000..cc7b3735f --- /dev/null +++ b/tests/diagnostics/node-islands-config/main.ts @@ -0,0 +1,3 @@ +// An unknown value in scriptc.packages or scriptc.nodeIslands is SC8004: +// the configuration the project asked for is never silently ignored. +console.log("configured"); diff --git a/tests/diagnostics/node-islands-config/package.json b/tests/diagnostics/node-islands-config/package.json new file mode 100644 index 000000000..1a01afa30 --- /dev/null +++ b/tests/diagnostics/node-islands-config/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "type": "module", + "scriptc": { + "packages": { + "encoding": "unloded", + "debug": { "run": "island" } + }, + "nodeIslands": { + "auto": "yes", + "mode": "auto" + } + } +} diff --git a/tests/harness/__snapshots__/coverage-node-islands-explain.txt b/tests/harness/__snapshots__/coverage-node-islands-explain.txt new file mode 100644 index 000000000..f416b9781 --- /dev/null +++ b/tests/harness/__snapshots__/coverage-node-islands-explain.txt @@ -0,0 +1,13 @@ + Node island ni-ctor@1.0.0 (would-be, report-only) + path tests/coverage-fixtures/node-islands/node_modules/ni-ctor + class object-model: needs a mutable object model + decision automatic (npm code outside the native subset) + evaluation eager at its module position (deferred evaluation is decided by a later milestone) + reasons (first per module; decisive class first) + object-model tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js:9:1 + R2.1 constructor functions: a write of 'Counter.prototype.add' + all reasons 2 (2 object-model) + boundary 1 native import site: + tests/coverage-fixtures/node-islands/main.ts:7:1 + copy and handle decisions per boundary site come with compiler-managed islands + carries ni-dep \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-node-islands-user-js.txt b/tests/harness/__snapshots__/coverage-node-islands-user-js.txt new file mode 100644 index 000000000..61cb8bb33 --- /dev/null +++ b/tests/harness/__snapshots__/coverage-node-islands-user-js.txt @@ -0,0 +1,34 @@ +scriptc coverage tests/coverage-fixtures/node-islands-user-js.cjs + + statements analyzed 13 + compile statically 10 (76%) + in dynamic tier 10 (operate on values typed only at runtime; --explain-dynamic lists them) + (1 function not analyzed — signature blocked; their statements count as not static) + + deferred to runtime 2 sites (JS statements that throw their fence if executed) + ×1 SC1090 parameterized 'arguments' outside an ES module (sloppy-mode parameter aliases) + at tests/coverage-fixtures/node-islands-user-js.cjs:15:1 + hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) + ×1 SC2004 uses of 'legacy' inherit the blocker on its declaration + at tests/coverage-fixtures/node-islands-user-js.cjs:22:57 + hint: the declaration of 'legacy' did not compile — fix the diagnostic reported there and these sites clear with it + + blockers: + ×1 SC8101 A write of 'Point.prototype.norm' needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:7:1 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules + ×1 SC8101 'new Point()' of a function needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:22:13 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules + ×1 SC8102 'Object.setPrototypeOf()' needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:12:1 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules + ×1 SC8103 'Object.defineProperty()' needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:14:1 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules + ×1 SC8104 A write to 'arguments[i]' in sloppy mode needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:16:3 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules + ×1 SC8105 A write of 'Array.prototype.last' needs a mutable JavaScript object model, which native code does not have + at tests/coverage-fixtures/node-islands-user-js.cjs:19:1 + hint: rewrite it as a class or typed object, or mark this module as a Node island in scriptc.nodeIslands.modules \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-node-islands.txt b/tests/harness/__snapshots__/coverage-node-islands.txt new file mode 100644 index 000000000..e8876bb0d --- /dev/null +++ b/tests/harness/__snapshots__/coverage-node-islands.txt @@ -0,0 +1,39 @@ +scriptc coverage tests/coverage-fixtures/node-islands/main.ts + + statements analyzed 29 + compile statically 27 (93%) + in dynamic tier 17 (operate on values typed only at runtime; --explain-dynamic lists them) + + npm package compilation: + ni-minified refused (its shipped JS looks minified) + ni-gap static + ni-eval static + ni-ctor static + ni-typed static + ni-dep static + + Node islands (would-be, report-only: the build is unchanged; --node-islands=auto) + gap ni-gap@1.0.0 + tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js:7:17: SC1090: a builtin function alias as an escaping value (call 'ext' directly) is not supported yet + runtime ni-eval@1.0.0 + tests/coverage-fixtures/node-islands/node_modules/ni-eval/index.js:4:10: R1.1 code from strings: new Function() + object-model ni-ctor@1.0.0 + tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js:9:1: R2.1 constructor functions: a write of 'Counter.prototype.add' + +1 more reason in 1 module; carries ni-dep + packaging ni-minified@1.0.0 + SC2013: its shipped JS looks minified + reached from native code and from an island: ni-typed + hint: scriptc coverage --explain-island= lists every reason + + deferred to runtime 1 site (JS statements that throw their fence if executed) + ×1 SC1090 a builtin function alias as an escaping value (call 'ext' directly) + at tests/coverage-fixtures/node-islands/node_modules/ni-gap/index.js:7:17 + hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) + + blockers: + ×2 SC2013 cannot compile npm package 'ni-minified' statically: its implementation is not part of the compiled module graph + at tests/coverage-fixtures/node-islands/main.ts:8:1, tests/coverage-fixtures/node-islands/main.ts:16:13 + hint: use a dependency with supported source, or replace the unsupported operation; scriptc has no interpreter fallback + ×1 SC2013 cannot compile npm package 'ni-minified' statically: its shipped JS looks minified + at tests/coverage-fixtures/node-islands/main.ts:8:1 + hint: to compile it anyway, name it with --npm-static=ni-minified (or --npm-static=ni-minified... to include its dependencies) \ No newline at end of file diff --git a/tests/harness/__snapshots__/node-islands-config/main.ts.txt b/tests/harness/__snapshots__/node-islands-config/main.ts.txt new file mode 100644 index 000000000..03cc714aa --- /dev/null +++ b/tests/harness/__snapshots__/node-islands-config/main.ts.txt @@ -0,0 +1,35 @@ +node-islands-config/package.json:6:7 - error SC8004: package.json 'scriptc.packages.encoding' has the unknown value "unloded" + + 5 | "packages": { + 6 | "encoding": "unloded", + | ^ + 7 | "debug": { "run": "island" } + + hint: use one of "unloaded", "node", "native", "auto", or an object with run, evaluate, share, marshal, schedule + +node-islands-config/package.json:7:18 - error SC8004: package.json 'scriptc.packages.debug.run' has the unknown value "island" + + 6 | "encoding": "unloded", + 7 | "debug": { "run": "island" } + | ^ + 8 | }, + + hint: use one of "native", "node", "auto" + +node-islands-config/package.json:10:7 - error SC8004: package.json 'scriptc.nodeIslands.auto' must be true or false, not "yes" + + 9 | "nodeIslands": { + 10 | "auto": "yes", + | ^ + 11 | "mode": "auto" + + hint: use a boolean + +node-islands-config/package.json:11:7 - error SC8004: package.json 'scriptc.nodeIslands.mode' is not a known Node island setting + + 10 | "auto": "yes", + 11 | "mode": "auto" + | ^ + 12 | } + + hint: the settings are auto, modules, node, nodeVersion, packaging, speculativeStart \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index cc91c0922..954294821 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -9,6 +9,7 @@ import { coveragePasses, renderCoverage, renderExplainDynamic, + renderExplainIsland, } from "@scriptc/compiler"; import { shardSelect, shardSuffix } from "./shard.js"; @@ -704,6 +705,92 @@ test("a cycle whose top level calls a builtin with a builtin function value is a expect(out).toContain("statements analyzed"); }); +test("Node islands (report-only): one package per class, gap islands first", async () => { + // ni-gap (a fence inside the native subset), ni-eval (new Function), + // ni-ctor (a constructor function, carrying ni-dep), ni-minified (not + // compilable as shipped), ni-typed (native, also reached from ni-ctor). + const entry = fixture("node-islands/main.ts"); + await expect(report(entry)).toMatchFileSnapshot("__snapshots__/coverage-node-islands.txt"); + const { coverage, sourceTexts } = analyze(entry); + const islands = coverage.nodeIslands!; + expect(islands.islands.map((i) => [i.package, i.class])).toEqual([ + ["ni-gap", "gap"], + ["ni-eval", "runtime"], + ["ni-ctor", "object-model"], + ["ni-minified", "packaging"], + ]); + expect(islands.packages.map((p) => [p.package, p.status, p.islands])).toEqual([ + ["ni-dep", "inside", ["ni-ctor"]], + ["ni-typed", "shared", ["ni-ctor"]], + ]); + await expect( + renderExplainIsland(coverage.nodeIslands, "ni-ctor", { sourceTexts, root: repoRoot }), + ).toMatchFileSnapshot("__snapshots__/coverage-node-islands-explain.txt"); + expect(renderExplainIsland(coverage.nodeIslands, "ni-dep", { root: repoRoot })).toContain( + "only ni-ctor imports it", + ); + // The machine-readable form carries the same decisions. + const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }); + expect(envelope.nodeIslands?.mode).toBe("would-be"); + expect(envelope.nodeIslands?.islands.map((i) => [i.package, i.class, i.at?.line])).toEqual([ + ["ni-gap", "gap", 7], + ["ni-eval", "runtime", 4], + ["ni-ctor", "object-model", 9], + ["ni-minified", "packaging", undefined], + ]); + // The gates: the blocker gate is today's build (which refuses + // ni-minified with SC2013, and nothing else); the island gates fail on + // the would-be islands. + expect(coverage.diagnostics.every((d) => d.code === "SC2013")).toBe(true); + expect(coveragePasses(coverage, "island-gaps")).toBe(false); + expect(coveragePasses(coverage, "node-islands")).toBe(false); +}); + +test("Node island policy from the command line: native, forced, off", () => { + const entry = fixture("node-islands/main.ts"); + const forced = analyze(entry, { + nodeIslands: { native: ["ni-eval"], islands: ["ni-typed", "not-a-dependency"] }, + }).coverage.nodeIslands!; + expect(forced.islands.map((i) => [i.package, i.class, i.decision])).toEqual([ + ["ni-gap", "gap", "auto"], + ["ni-ctor", "object-model", "auto"], + ["ni-minified", "packaging", "auto"], + ["ni-typed", "marked", "cli"], + ]); + expect(forced.violations.map((v) => v.code)).toEqual(["SC8003"]); + expect(forced.unknownPackages).toEqual(["not-a-dependency"]); + const off = analyze(entry, { nodeIslands: { mode: "off" } }).coverage; + expect(off.nodeIslands!.islands).toEqual([]); + expect(off.nodeIslands!.violations.map((v) => v.code)).toEqual([ + "SC8002", + "SC8002", + "SC8002", + "SC8002", + ]); + expect(coveragePasses(off, "island-gaps")).toBe(false); +}); + +test("--strict-user-js reports R2 constructs in user JavaScript as SC8101–SC8105", async () => { + const entry = fixture("node-islands-user-js.cjs"); + // Without the flag the user module is not part of any island report. + expect(analyze(entry).coverage.nodeIslands).toBeUndefined(); + const { coverage } = analyze(entry, { nodeIslands: { strictUserJs: true } }); + expect(coverage.diagnostics.map((d) => d.code).filter((c) => c.startsWith("SC81"))).toEqual([ + "SC8101", + "SC8102", + "SC8103", + "SC8104", + "SC8105", + "SC8101", + ]); + await expect( + renderCoverage( + { ...coverage, file: coverage.file.replace(repoRoot + "/", "") }, + { root: repoRoot }, + ), + ).toMatchFileSnapshot("__snapshots__/coverage-node-islands-user-js.txt"); +}); + test(`every corpus program is 100% static (corpus and coverage agree${shardSuffix()})`, async () => { // The differential corpus compiles by definition; coverage must agree. From ba80dc254d2b6355bbdcef44ae1f76f577db63d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 13:56:21 -0500 Subject: [PATCH 002/395] Iterate classes with object-literal iterators and close them on early exits --- .../builtin-apis/synchronous-iteration.mdx | 2 +- .../language-features/async-iteration.mdx | 1 + .../lowering/class-dynamic-dispatch.ts | 10 +- .../src/frontend/lowering/dispatch-sites.ts | 1 + .../lowering/expressions/array-literals.ts | 8 + .../src/frontend/lowering/lower-classes.ts | 126 +++++++++++++-- .../src/frontend/lowering/lower-stmts.ts | 146 +++++++++++++++--- .../src/frontend/lowering/lower-stream.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 10 +- .../6928-class-iterator-protocol.ts.json | 6 + .../6929-class-iterator-protocol.mjs.json | 6 + ...6935-class-async-iterator-protocol.ts.json | 6 + ...936-class-async-iterator-protocol.mjs.json | 6 + packages/runtime/src/scr_dyn_for_await.c | 8 +- tests/corpus/6928-class-iterator-protocol.ts | 93 +++++++++++ tests/corpus/6929-class-iterator-protocol.mjs | 78 ++++++++++ .../6935-class-async-iterator-protocol.ts | 60 +++++++ .../6936-class-async-iterator-protocol.mjs | 60 +++++++ .../6928-class-iterator-protocol.ts.json | 1 + .../6929-class-iterator-protocol.mjs.json | 1 + ...6935-class-async-iterator-protocol.ts.json | 1 + ...936-class-async-iterator-protocol.mjs.json | 1 + 22 files changed, 582 insertions(+), 51 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6928-class-iterator-protocol.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6929-class-iterator-protocol.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6935-class-async-iterator-protocol.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-class-async-iterator-protocol.mjs.json create mode 100644 tests/corpus/6928-class-iterator-protocol.ts create mode 100644 tests/corpus/6929-class-iterator-protocol.mjs create mode 100644 tests/corpus/6935-class-async-iterator-protocol.ts create mode 100644 tests/corpus/6936-class-async-iterator-protocol.mjs create mode 100644 tests/harness/corpus-costs/6928-class-iterator-protocol.ts.json create mode 100644 tests/harness/corpus-costs/6929-class-iterator-protocol.mjs.json create mode 100644 tests/harness/corpus-costs/6935-class-async-iterator-protocol.ts.json create mode 100644 tests/harness/corpus-costs/6936-class-async-iterator-protocol.mjs.json diff --git a/docs/content/limitations/builtin-apis/synchronous-iteration.mdx b/docs/content/limitations/builtin-apis/synchronous-iteration.mdx index 564f9af45..38cc9c475 100644 --- a/docs/content/limitations/builtin-apis/synchronous-iteration.mdx +++ b/docs/content/limitations/builtin-apis/synchronous-iteration.mdx @@ -1,4 +1,4 @@ - **Synchronous iteration:** Checked `for...of` follows live iterator methods, including compiled class methods and abrupt-completion cleanup. Mapper-less checked `Array.from` accepts those iterators and plain array-like objects. Sparse checked arrays and general borrowed iterator factories are unsupported. - Direct statically typed class loops also restrict iterator `return` and `throw` methods. + Direct statically typed class loops accept iterators that are class instances or object literals; a loop that exits early calls the iterator's zero-parameter `return()`, as Node's IteratorClose does. diff --git a/docs/content/limitations/language-features/async-iteration.mdx b/docs/content/limitations/language-features/async-iteration.mdx index 863c9be90..b15dca7fc 100644 --- a/docs/content/limitations/language-features/async-iteration.mdx +++ b/docs/content/limitations/language-features/async-iteration.mdx @@ -1,6 +1,7 @@ - **Async iteration:** Async `yield*` delegation, stored Web or Node iterator handles, and general structural async-iterator objects are unsupported. Typed async generators support lazy execution, `await`, direct `yield`, queued `.next()`/`.return()`/`.throw()` requests, and `for await`. `for await` also accepts Node `Readable` streams, Web `ReadableStream` values, and compiled class iterators whose zero-parameter `next()` returns a promise of a `{ value, done? }` record. + A class instance whose `[Symbol.asyncIterator]()` returns an object literal, or that has only `[Symbol.iterator]()`, iterates through the checked-dynamic async protocol, and an early exit awaits the iterator's `return()`. Abrupt completion runs cleanup, including literal `destroyOnReturn` and `preventCancel` options. The `node:timers/promises` `setInterval(delay, value)` iterator compiles without AbortSignal options. `Array.fromAsync` accepts arrays of values or of promises and typed async generators, with an optional mapping function; a `thisArg` and arrays that mix values and promises are unsupported. diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index af3212ef0..86ad05386 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -2367,15 +2367,19 @@ export class ClassDynamicDispatch { } const iterator = expr.kind === "libCall" && - (expr.fn === "dyn.iterator" || expr.fn === "dyn.arrayFromIterator") + (expr.fn === "dyn.iterator" || + expr.fn === "dyn.arrayFromIterator" || + expr.fn === "dyn.asyncIteratorOpen") ? expr : null; + // `for await` opens a class instance's [Symbol.asyncIterator](). + const asyncOpen = iterator?.fn === "dyn.asyncIteratorOpen"; const invoke: Invoke | null = iterator ? { kind: "dynInvoke", recv: iterator.args[0]!, - method: "sym:iterator", - calleeName: "value[Symbol.iterator]", + method: asyncOpen ? "sym:asyncIterator" : "sym:iterator", + calleeName: asyncOpen ? "value[Symbol.asyncIterator]" : "value[Symbol.iterator]", args: [], type: DYN, loc: iterator.loc, diff --git a/packages/compiler/src/frontend/lowering/dispatch-sites.ts b/packages/compiler/src/frontend/lowering/dispatch-sites.ts index 2618dacf9..49d0bd243 100644 --- a/packages/compiler/src/frontend/lowering/dispatch-sites.ts +++ b/packages/compiler/src/frontend/lowering/dispatch-sites.ts @@ -59,6 +59,7 @@ const DISPATCH_LIB_CALLS = new Set([ "dyn.keySetComputed", "dyn.iterator", "dyn.arrayFromIterator", + "dyn.asyncIteratorOpen", "dyn.toString", "dyn.stringConstructor", "dyn.forInKeys", diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index 8f119504c..6153b87f2 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -425,6 +425,14 @@ export function lowerArrayLiteral( if (src.type.kind === "object") { const drained = lowerer.classIteratorDrainCall(src, locOf(el), type.elem); if (drained) src = drained; + // In JavaScript an iterable class whose inferred iterator shape has + // no static protocol spreads through the runtime's iterator walk. + else if ( + isJsSourceFile(ts.getSourceFileOfNode(el)) && + lowerer.classes.get(src.type.className) && + lowerer.findMethodOn(lowerer.classes.get(src.type.className)!, "sym:iterator") + ) + src = lowerer.coerceInto(el.expression, src, DYN); } // `[...s]` on a STRING spreads its code-point characters (the // string iterator's walk — astral chars whole) through the same diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index d8ccc15b7..9d994b76b 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -7923,31 +7923,83 @@ export function classMemberNameOf(lowerer: Lowerer, name: ts.PropertyName): stri export interface ClassIteratorInfo { /** The `[Symbol.iterator]()` call's receiver class + its declarer. */ className: string; - /** The iterator object's type (the method's return). */ - iterT: IrType & { kind: "object" }; + /** The iterator's type (the method's return): a class instance, or a + * record (an object literal) whose `next` field is a function. */ + iterT: IrType & ({ kind: "object" } | { kind: "record" }); /** next()'s result record type. */ resultT: IrType & { kind: "record" }; valueT: IrType; /** False: no done field — the protocol never terminates. */ hasDone: boolean; + /** The iterator declares a zero-parameter return(), which a consumer + * that stops before the end calls (IteratorClose). Only consumers that + * ask for `close` see such iterators. */ + hasReturn: boolean; } -export function classIteratorOf(lowerer: Lowerer, t: IrType): ClassIteratorInfo | null { +/** A return() result type that is always an object: IteratorClose after a + * break throws a TypeError for any other result, which these desugars do + * not model. */ +function objectResult(t: IrType): boolean { + return t.kind === "record" || t.kind === "object"; +} + +/** What a class iterable's consumer handles: `records` accepts an + * object-literal iterator; `close` accepts an iterator with return(), + * which the consumer calls when it stops early (or never stops early). */ +export interface ClassIteratorOptions { + records?: boolean; + close?: boolean; +} +export function classIteratorOf( + lowerer: Lowerer, + t: IrType, + options: ClassIteratorOptions = {}, +): ClassIteratorInfo | null { if (t.kind !== "object") return null; const info = lowerer.classes.get(t.className); if (!info) return null; const iter = findMethodOn(lowerer, info, "sym:iterator"); if (!iter || iter.sig.params.length !== 0 || iter.sig.abstract === true) return null; const iterT = iter.sig.ret; - if (iterT.kind !== "object") return null; - const itInfo = lowerer.classes.get(iterT.className); - if (!itInfo) return null; - // IteratorClose honesty: a declared return()/throw() would be called - // by JS on abrupt completion; these desugars never close. - if (findMethodOn(lowerer, itInfo, "return") || findMethodOn(lowerer, itInfo, "throw")) - return null; - const next = findMethodOn(lowerer, itInfo, "next"); - if (!next || next.sig.params.length !== 0 || next.sig.abstract === true) return null; - const resultT = next.sig.ret; + let resultT: IrType; + let hasReturn: boolean; + if (iterT.kind === "object") { + const itInfo = lowerer.classes.get(iterT.className); + if (!itInfo) return null; + // IteratorClose honesty: a declared return() would be called by JS on + // abrupt completion, so only a consumer that closes takes it; for-of + // never calls throw(), the other consumers refuse it. + const close = findMethodOn(lowerer, itInfo, "return"); + if ( + close && + (!options.close || + close.sig.abstract === true || + close.sig.params.length !== 0 || + !objectResult(close.sig.ret)) + ) + return null; + if (findMethodOn(lowerer, itInfo, "throw") && !options.close) return null; + const next = findMethodOn(lowerer, itInfo, "next"); + if (!next || next.sig.params.length !== 0 || next.sig.abstract === true) return null; + resultT = next.sig.ret; + hasReturn = !!close; + } else if (iterT.kind === "record" && options.records) { + const fields = lowerer.shapes.get(iterT.shapeId)?.fields; + const next = fields?.find((f) => f.name === "next")?.type; + if (!next || next.kind !== "func" || next.params.length !== 0 || next.rest) return null; + const close = fields?.find((f) => f.name === "return")?.type; + if ( + close && + (!options.close || + close.kind !== "func" || + close.params.length !== 0 || + close.rest || + !objectResult(close.ret)) + ) + return null; + resultT = next.ret; + hasReturn = close !== undefined; + } else return null; if (resultT.kind !== "record") return null; const shape = lowerer.shapes.get(resultT.shapeId); const value = shape?.fields.find((f) => f.name === "value"); @@ -7960,6 +8012,7 @@ export function classIteratorOf(lowerer: Lowerer, t: IrType): ClassIteratorInfo resultT, valueT: value.type, hasDone: done !== undefined, + hasReturn, }; } @@ -7971,7 +8024,43 @@ export function classIteratorNextCall( itRef: IrExpr, loc: SrcLoc, ): IrExpr { - return accessorCall(lowerer, cit.iterT.className, "next", itRef, [], cit.resultT, loc); + return classIteratorMethodCall(lowerer, cit, "next", itRef, loc); +} + +/** `it.next()` or `it.return()` of a class iterator: a (possibly virtual) + * method call of an iterator class, or the call of an object-literal + * iterator's function field. */ +export function classIteratorMethodCall( + lowerer: Lowerer, + cit: ClassIteratorInfo, + method: "next" | "return", + itRef: IrExpr, + loc: SrcLoc, +): IrExpr { + const iterT = cit.iterT; + if (iterT.kind === "object") { + const ret = + method === "next" + ? cit.resultT + : findMethodOn(lowerer, lowerer.classes.get(iterT.className)!, "return")!.sig.ret; + return accessorCall(lowerer, iterT.className, method, itRef, [], ret, loc); + } + const field = lowerer.shapes.get(iterT.shapeId)!.fields.find((f) => f.name === method)!; + const fnT = field.type as IrType & { kind: "func" }; + return { + kind: "callValue", + callee: { + kind: "recordGet", + obj: itRef, + shapeId: iterT.shapeId, + field: method, + type: fnT, + loc, + }, + args: [], + type: fnT.ret, + loc, + }; } /** `recv[Symbol.iterator]()` as an ordinary method call. */ @@ -7999,12 +8088,15 @@ export function classIteratorDrainCall( loc: SrcLoc, elemT?: IrType, ): IrExpr | null { - const cit = classIteratorOf(lowerer, src.type); + // A spread takes every value, so it never closes the iterator. + const cit = classIteratorOf(lowerer, src.type, { records: true, close: true }); if (!cit) return null; const outElem = elemT ?? cit.valueT; // Probe the element coercion purely: identical types, or an arm of a // union destination (the wrap coerceToExpected applies below). - if (!typeEquals(outElem, cit.valueT)) { + // A checked-dynamic value (`IteratorResult`'s value is `T | any`) + // converts to the destination element with a check. + if (!typeEquals(outElem, cit.valueT) && cit.valueT.kind !== "dyn") { if (outElem.kind !== "union" || lowerer.armTag(outElem.unionId, cit.valueT) < 0) return null; } const outT = arrayOf(outElem); @@ -8122,7 +8214,7 @@ export function classIteratorRestDrainCall( loc: SrcLoc, ): IrExpr { const outT = arrayOf(cit.valueT); - const key = `it:${cit.iterT.className}`; + const key = `it:${cit.iterT.kind === "object" ? cit.iterT.className : typeKey(cit.iterT)}`; let name = lowerer.iterDrainHelpers.get(key); if (!name) { name = keyedHelperName(lowerer, "%iter.drain", key); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 213abfec6..bee887e0f 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -9467,8 +9467,23 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { // iterator are awaited as the async-from-sync iterator awaits them, and // a value that is not iterable throws Node's TypeError. const headT = lowerer.mapTypeOf(lowerer.typeOf(stmt.expression)); + // A class instance whose [Symbol.asyncIterator]() (or + // [Symbol.iterator]()) returns an iterator object takes the same + // checked-dynamic protocol over the boxed instance. + const headClass = headT?.kind === "object" ? lowerer.classes.get(headT.className) : undefined; + const classAsync = + headClass !== undefined && lowerer.findMethodOn(headClass, "sym:asyncIterator") !== null; + // Without [Symbol.asyncIterator], the loop opens [Symbol.iterator]() and + // awaits its values, as the async-from-sync iterator does. + const classSync = + headClass !== undefined && + !classAsync && + lowerer.findMethodOn(headClass, "sym:iterator") !== null; + const classIterable = classAsync || classSync; const syncGenerator = - (headT?.kind === "generator" && headT.async !== true) || isJsSourceFile(stmt.getSourceFile()); + (headT?.kind === "generator" && headT.async !== true) || + classIterable || + isJsSourceFile(stmt.getSourceFile()); if (lowerer.ctx.isAsync && (syncGenerator || isUntypedIterable(lowerer, stmt.expression))) { const iterable = lowerer.lowerExpr(stmt.expression); if (iterable.type.kind === "generator" && iterable.type.async) @@ -9480,10 +9495,19 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ); if (iterable.type.kind === "dyn") return lowerForOfDyn(lowerer, stmt, iterable, labels, true); if ( - (iterable.type.kind === "generator" || isJsSourceFile(stmt.getSourceFile())) && + (iterable.type.kind === "generator" || + classIterable || + isJsSourceFile(stmt.getSourceFile())) && lowerer.dynConvertible(iterable.type) ) - return lowerForOfDyn(lowerer, stmt, lowerer.coerceToExpected(iterable, DYN), labels, true); + return lowerForOfDyn( + lowerer, + stmt, + lowerer.coerceToExpected(iterable, DYN), + labels, + true, + classSync && iterable.type.kind === "object", + ); } // A NodeJS.ReadableStream iterates as the general Readable. requestGeneralStreamSurface(lowerer, stmt.expression); @@ -9732,17 +9756,30 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ); // Class iterables ([Symbol.iterator]() + next() — classIteratorOf) // drive the same protocol with ordinary method calls. - const cit = lowerer.classIteratorOf(iterable.type); + const cit = lowerer.classIteratorOf(iterable.type, { records: true, close: true }); if (cit) return lowerForOfClassIterator(lowerer, stmt, iterable, cit, labels); // A declared [Symbol.iterator] whose shape classIteratorOf refused // (an `any`-typed iterator or result, a declared return()/throw()): // name the protocol, not the class. const info = lowerer.classes.get(iterable.type.className); + // In JavaScript the inferred shape is often a union of result + // objects: the loop runs the iterator protocol over the boxed value. + if ( + info && + isJsSourceFile(ts.getSourceFileOfNode(stmt)) && + lowerer.findMethodOn(info, "sym:iterator") + ) + return lowerForOfDyn( + lowerer, + stmt, + lowerer.coerceInto(stmt.expression, iterable, DYN), + labels, + ); if (info && lowerer.findMethodOn(info, "sym:iterator")) { lowerer.unsupported( "SC1090", stmt.expression, - `for-of over this [Symbol.iterator] shape (the method must take no parameters and return an object whose zero-parameter next() returns a { value, done? } record with a boolean done; iterator classes declaring return()/throw() stay out — IteratorClose has no lowering)`, + `for-of over this [Symbol.iterator] shape (the method must take no parameters and return an object whose zero-parameter next() returns a { value, done? } record with a boolean done, and whose return(), if any, takes no parameters and returns an object)`, ); } } @@ -10641,11 +10678,13 @@ function lowerForOfMatchAll( * } } * * A result record with NO done field never terminates — exactly JS - * (undefined is falsy forever; Node loops forever too). No - * IteratorClose: classIteratorOf already refused iterator classes - * declaring return()/throw(), so a break exits with nothing to close. - * `done: true` results bind nothing (the loop breaks before the value - * read), matching JS's ignore-the-final-value rule. */ + * (undefined is falsy forever; Node loops forever too). An iterator with + * return() is closed when the loop leaves after a value arrived (break, + * return, or a throw from the body), as IteratorClose does: a done flag + * set around each step guards the call in a finally, and after a throw + * from the body an error from return() is dropped. A throw from next() + * does not close. `done: true` results bind nothing (the loop breaks + * before the value read), matching JS's ignore-the-final-value rule. */ function lowerForOfClassIterator( lowerer: Lowerer, stmt: ts.ForOfStatement, @@ -10688,6 +10727,19 @@ function lowerForOfClassIterator( const recvRef: IrExpr = { kind: "varRef", localId: recv.id, type: iterable.type, loc }; const itRef = (): IrExpr => ({ kind: "varRef", localId: it.id, type: cit.iterT, loc }); const rRef = (): IrExpr => ({ kind: "varRef", localId: r.id, type: cit.resultT, loc }); + const done = cit.hasReturn ? lowerer.declareHiddenLocal("%cid", BOOL) : null; + if (done) done.mutable = true; + const setDone = (value: boolean): IrStmt[] => + done + ? [ + { + kind: "assign", + localId: done.id, + value: { kind: "boolLit", value, type: BOOL, loc }, + loc, + }, + ] + : []; const varTarget = exprTarget ?? (decl ? forOfVarTarget(lowerer, decl) : null); const x = varTarget ? lowerer.declareHiddenLocal("%vof", cit.valueT) @@ -10700,6 +10752,7 @@ function lowerForOfClassIterator( const xRef: IrExpr = { kind: "varRef", localId: x.id, type: cit.valueT, loc }; const blame: ts.Node = decl?.name ?? exprTargetNode ?? stmt.initializer; const head: IrStmt[] = [ + ...setDone(true), { kind: "varDecl", localId: r.id, @@ -10747,8 +10800,16 @@ function lowerForOfClassIterator( } satisfies IrStmt, ] : []), + ...setDone(false), ]; const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels); + const loop: IrStmt = { + kind: "while", + cond: { kind: "boolLit", value: true, type: BOOL, loc }, + body: [...head, ...body], + ...(labels && { labels }), + loc, + }; return { kind: "block", body: [ @@ -10759,13 +10820,45 @@ function lowerForOfClassIterator( init: lowerer.classIteratorOpenCall(cit, recvRef, loc), loc, }, - { - kind: "while", - cond: { kind: "boolLit", value: true, type: BOOL, loc }, - body: [...head, ...body], - ...(labels && { labels }), - loc, - }, + ...(done + ? [ + { + kind: "varDecl", + localId: done.id, + init: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + } satisfies IrStmt, + { + kind: "tryCatch", + tryBody: [loop], + catchBody: null, + catchLocalId: null, + dropFinallyErrors: true, + finallyBody: [ + { + kind: "if", + cond: { + kind: "unary", + op: "!", + operand: { kind: "varRef", localId: done.id, type: BOOL, loc }, + type: BOOL, + loc, + }, + then: [ + { + kind: "exprStmt", + expr: lowerer.classIteratorReturnCall(cit, itRef(), loc), + loc, + }, + ], + else_: null, + loc, + }, + ], + loc, + } satisfies IrStmt, + ] + : [loop]), ], loc, }; @@ -10783,6 +10876,7 @@ function lowerForOfDyn( iterable: IrExpr, labels?: string[], asyncLoop = false, + syncOpen = false, ): IrStmt { const loc = locOf(stmt); const spell = notIterableSpelling(stmt.expression, asyncLoop ? "async iterable" : "iterable"); @@ -11010,7 +11104,7 @@ function lowerForOfDyn( localId: iterator.id, init: check({ kind: "libCall", - fn: asyncLoop ? "dyn.asyncIteratorOpen" : "dyn.iterator", + fn: asyncLoop && !syncOpen ? "dyn.asyncIteratorOpen" : "dyn.iterator", args: [iterable, { kind: "strLit", value: spell, type: STRING, loc }], type: DYN, loc, @@ -11022,13 +11116,15 @@ function lowerForOfDyn( { kind: "varDecl", localId: syncIterator.id, - init: { - kind: "libCall", - fn: "dyn.asyncIteratorWasSync", - args: [], - type: BOOL, - loc, - }, + init: syncOpen + ? { kind: "boolLit", value: true, type: BOOL, loc } + : { + kind: "libCall", + fn: "dyn.asyncIteratorWasSync", + args: [], + type: BOOL, + loc, + }, loc, } satisfies IrStmt, ] diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 6540b3a99..90bdf917e 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -1954,7 +1954,7 @@ function classIteratorObject( iter: IrExpr, loc: SrcLoc, ): IrExpr { - const key = `streamFromIter:${cit.iterT.className}`; + const key = `streamFromIter:${cit.iterT.kind === "object" ? cit.iterT.className : typeKey(cit.iterT)}`; let name = lowerer.valueHelpers.get(key); if (!name) { name = keyedHelperName(lowerer, "%stream.fromIter", key); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 747e396af..ea503e1f1 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -348,6 +348,7 @@ import { type ClassInfo, type ClassMethodSignature, type ClassIteratorInfo, + type ClassIteratorOptions, type GenericClassInfo, registerBuiltinErrorClasses, defineClassValueConstruct, @@ -359,6 +360,7 @@ import { builtinStreamInfoOf, analyzeClassDecoration, classIteratorDrainCall, + classIteratorMethodCall, classIteratorNextCall, classIteratorOf, classIteratorOpenCall, @@ -13049,8 +13051,12 @@ export class Lowerer { return accessorCall(this, className, member, obj, extraArgs, ret, loc); } - classIteratorOf(t: IrType): ClassIteratorInfo | null { - return classIteratorOf(this, t); + classIteratorOf(t: IrType, options?: ClassIteratorOptions): ClassIteratorInfo | null { + return classIteratorOf(this, t, options); + } + + classIteratorReturnCall(cit: ClassIteratorInfo, it: IrExpr, loc: SrcLoc): IrExpr { + return classIteratorMethodCall(this, cit, "return", it, loc); } classIteratorOpenCall(cit: ClassIteratorInfo, recv: IrExpr, loc: SrcLoc): IrExpr { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6928-class-iterator-protocol.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6928-class-iterator-protocol.ts.json new file mode 100644 index 000000000..2efd8521a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6928-class-iterator-protocol.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6928-class-iterator-protocol.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6929-class-iterator-protocol.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6929-class-iterator-protocol.mjs.json new file mode 100644 index 000000000..49e5e4304 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6929-class-iterator-protocol.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6929-class-iterator-protocol.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6935-class-async-iterator-protocol.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6935-class-async-iterator-protocol.ts.json new file mode 100644 index 000000000..93775b96b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6935-class-async-iterator-protocol.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6935-class-async-iterator-protocol.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-class-async-iterator-protocol.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-class-async-iterator-protocol.mjs.json new file mode 100644 index 000000000..378f7d18b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-class-async-iterator-protocol.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6936-class-async-iterator-protocol.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn_for_await.c b/packages/runtime/src/scr_dyn_for_await.c index 25183c4ee..2d39885c0 100644 --- a/packages/runtime/src/scr_dyn_for_await.c +++ b/packages/runtime/src/scr_dyn_for_await.c @@ -39,7 +39,11 @@ ScrDyn *scr_dyn_async_iterator_open(ScrDyn *value, ScrStr *spell) { } /* Whether the iterator the last scr_dyn_async_iterator_open returned is a - * synchronous one. Read immediately after the open. */ + * synchronous one. Read immediately after the open; the read clears it, so + * an iterator a class's [Symbol.asyncIterator]() returned (opened without + * the runtime) reads false. */ bool scr_dyn_async_iterator_was_sync(void) { - return scr_for_await_sync; + bool sync = scr_for_await_sync; + scr_for_await_sync = false; + return sync; } diff --git a/tests/corpus/6928-class-iterator-protocol.ts b/tests/corpus/6928-class-iterator-protocol.ts new file mode 100644 index 000000000..f097368de --- /dev/null +++ b/tests/corpus/6928-class-iterator-protocol.ts @@ -0,0 +1,93 @@ +// for-of and spread over classes whose [Symbol.iterator]() returns an iterator object, +// with return() called on early exits as Node's IteratorClose does. +class Bag { + readonly items: string[]; + constructor(items: string[]) { this.items = items; } + [Symbol.iterator]() { + let i = 0; + const items = this.items; + return { + next(): IteratorResult { + return i < items.length ? { value: items[i++]!, done: false } : { value: undefined, done: true }; + }, + return(): IteratorResult { + console.log("return at", i); + return { value: undefined, done: true }; + }, + }; + } +} +const bag = new Bag(["a", "b", "c", "d"]); +function firstLong(): string | undefined { + for (const s of bag) if (s >= "b") return s; + return undefined; +} +console.log(firstLong()); +try { + for (const s of bag) if (s === "c") throw new Error("stop at " + s); +} catch (e) { + console.log((e as Error).message); +} +outer: for (const n of [1, 2]) { + for (const s of bag) { + if (s === "b") continue outer; + console.log(n, s); + } +} +let all = ""; +for (const s of bag) all += s; +console.log(all); + +class Counter { + n = 0; + [Symbol.iterator](): CounterIterator { return new CounterIterator(3); } +} +class CounterIterator { + i = 0; + readonly limit: number; + constructor(limit: number) { this.limit = limit; } + next(): { value: number; done: boolean } { + return this.i < this.limit ? { value: this.i++, done: false } : { value: -1, done: true }; + } + return(): { value: number; done: boolean } { + console.log("class return", this.i); + return { value: -1, done: true }; + } +} +for (const v of new Counter()) { if (v === 1) break; console.log("v", v); } +console.log([...new Counter()].join()); + +class Fussy { + [Symbol.iterator]() { + let i = 0; + return { + next(): IteratorResult { return { value: i++, done: i > 5 }; }, + return(): IteratorResult { throw new Error("return failed"); }, + }; + } +} +try { + for (const v of new Fussy()) if (v === 1) break; +} catch (e) { + console.log("break:", (e as Error).message); +} +try { + for (const v of new Fussy()) if (v === 1) throw new Error("body failed"); +} catch (e) { + console.log("throw:", (e as Error).message); +} + +class Range { + readonly n: number; + constructor(n: number) { this.n = n; } + [Symbol.iterator]() { + let i = 0; + const n = this.n; + return { + next(): IteratorResult { return i < n ? { value: i++, done: false } : { value: undefined, done: true }; }, + }; + } +} +let total = 0; +for (const v of new Range(4)) total += v; +console.log(total, [...new Range(3)].join()); diff --git a/tests/corpus/6929-class-iterator-protocol.mjs b/tests/corpus/6929-class-iterator-protocol.mjs new file mode 100644 index 000000000..b5255ba35 --- /dev/null +++ b/tests/corpus/6929-class-iterator-protocol.mjs @@ -0,0 +1,78 @@ +// for-of and spread in JavaScript over classes whose [Symbol.iterator]() returns an +// iterator object, with return() called on early exits as Node's IteratorClose does. +class Bag { + + constructor(items) { this.items = items; } + [Symbol.iterator]() { + let i = 0; + const items = this.items; + return { + next() { + return i < items.length ? { value: items[i++], done: false } : { value: undefined, done: true }; + }, + return() { + console.log("return at", i); + return { value: undefined, done: true }; + }, + }; + } +} +const bag = new Bag(["a", "b", "c", "d"]); +function firstLong() { + for (const s of bag) if (s >= "b") return s; + return undefined; +} +console.log(firstLong()); +try { + for (const s of bag) if (s === "c") throw new Error("stop at " + s); +} catch (e) { + console.log(e.message); +} +outer: for (const n of [1, 2]) { + for (const s of bag) { + if (s === "b") continue outer; + console.log(n, s); + } +} +let all = ""; +for (const s of bag) all += s; +console.log(all); + +class Counter { + n = 0; + [Symbol.iterator]() { return new CounterIterator(3); } +} +class CounterIterator { + i = 0; + + constructor(limit) { this.limit = limit; } + next() { + return this.i < this.limit ? { value: this.i++, done: false } : { value: -1, done: true }; + } + return() { + console.log("class return", this.i); + return { value: -1, done: true }; + } +} +for (const v of new Counter()) { if (v === 1) break; console.log("v", v); } +console.log([...new Counter()].join()); + +class Fussy { + [Symbol.iterator]() { + let i = 0; + return { + next() { return { value: i++, done: i > 5 }; }, + return() { if (i >= 0) throw new Error("return failed"); return { value: undefined, done: true }; }, + }; + } +} +try { + for (const v of new Fussy()) if (v === 1) break; +} catch (e) { + console.log("break:", e.message); +} +try { + for (const v of new Fussy()) if (v === 1) throw new Error("body failed"); +} catch (e) { + console.log("throw:", e.message); +} diff --git a/tests/corpus/6935-class-async-iterator-protocol.ts b/tests/corpus/6935-class-async-iterator-protocol.ts new file mode 100644 index 000000000..9ec5a61a5 --- /dev/null +++ b/tests/corpus/6935-class-async-iterator-protocol.ts @@ -0,0 +1,60 @@ +// for await over classes whose [Symbol.asyncIterator]() or [Symbol.iterator]() +// returns an iterator object: early exits await return(), as in Node. +class Ticks { + readonly n: number; + constructor(n: number) { this.n = n; } + [Symbol.asyncIterator]() { + let i = 0; + const n = this.n; + return { + async next(): Promise> { + return i < n ? { value: i++, done: false } : { value: undefined, done: true }; + }, + async return(): Promise> { + console.log("closed at", i); + return { value: undefined, done: true }; + }, + }; + } +} +class Pages { + [Symbol.asyncIterator](): PageCursor { return new PageCursor(); } +} +class PageCursor { + page = 0; + async next(): Promise<{ value: string; done: boolean }> { + this.page++; + return this.page <= 3 ? { value: "page " + this.page, done: false } : { value: "", done: true }; + } +} +class Letters { + [Symbol.iterator]() { + const letters = ["x", "y", "z"]; + let i = 0; + return { + next(): IteratorResult { return i < letters.length ? { value: letters[i++]!, done: false } : { value: undefined, done: true }; }, + }; + } +} +async function main(): Promise { + let sum = 0; + for await (const v of new Ticks(4)) sum += v * 2; + console.log("sum", sum); + for await (const v of new Ticks(5)) { + if (v === 2) break; + console.log("early", v); + } + try { + for await (const v of new Ticks(5)) if (v === 1) throw new Error("body " + v); + } catch (e) { + console.log("caught", (e as Error).message); + } + async function find(): Promise { + for await (const v of new Ticks(9)) if (v > 2) return v; + return -1; + } + console.log("found", await find()); + for await (const p of new Pages()) console.log(p); + for await (const l of new Letters()) console.log("letter", l); +} +main(); diff --git a/tests/corpus/6936-class-async-iterator-protocol.mjs b/tests/corpus/6936-class-async-iterator-protocol.mjs new file mode 100644 index 000000000..cb7b1e68a --- /dev/null +++ b/tests/corpus/6936-class-async-iterator-protocol.mjs @@ -0,0 +1,60 @@ +// for await in JavaScript over classes whose [Symbol.asyncIterator]() or [Symbol.iterator]() +// returns an iterator object: early exits await return(), as in Node. +class Ticks { + + constructor(n) { this.n = n; } + [Symbol.asyncIterator]() { + let i = 0; + const n = this.n; + return { + async next() { + return i < n ? { value: i++, done: false } : { value: undefined, done: true }; + }, + async return() { + console.log("closed at", i); + return { value: undefined, done: true }; + }, + }; + } +} +class Pages { + [Symbol.asyncIterator]() { return new PageCursor(); } +} +class PageCursor { + page = 0; + async next() { + this.page++; + return this.page <= 3 ? { value: "page " + this.page, done: false } : { value: "", done: true }; + } +} +class Letters { + [Symbol.iterator]() { + const letters = ["x", "y", "z"]; + let i = 0; + return { + next() { return i < letters.length ? { value: letters[i++], done: false } : { value: undefined, done: true }; }, + }; + } +} +async function main() { + let sum = 0; + for await (const v of new Ticks(4)) sum += v * 2; + console.log("sum", sum); + for await (const v of new Ticks(5)) { + if (v === 2) break; + console.log("early", v); + } + try { + for await (const v of new Ticks(5)) if (v === 1) throw new Error("body " + v); + } catch (e) { + console.log("caught", e.message); + } + async function find() { + for await (const v of new Ticks(9)) if (v > 2) return v; + return -1; + } + console.log("found", await find()); + for await (const p of new Pages()) console.log(p); + for await (const l of new Letters()) console.log("letter", l); +} +main(); diff --git a/tests/harness/corpus-costs/6928-class-iterator-protocol.ts.json b/tests/harness/corpus-costs/6928-class-iterator-protocol.ts.json new file mode 100644 index 000000000..577adfbd9 --- /dev/null +++ b/tests/harness/corpus-costs/6928-class-iterator-protocol.ts.json @@ -0,0 +1 @@ +{ "plain": 3913, "san": 3785 } diff --git a/tests/harness/corpus-costs/6929-class-iterator-protocol.mjs.json b/tests/harness/corpus-costs/6929-class-iterator-protocol.mjs.json new file mode 100644 index 000000000..a653f7709 --- /dev/null +++ b/tests/harness/corpus-costs/6929-class-iterator-protocol.mjs.json @@ -0,0 +1 @@ +{ "plain": 3934, "san": 3488 } diff --git a/tests/harness/corpus-costs/6935-class-async-iterator-protocol.ts.json b/tests/harness/corpus-costs/6935-class-async-iterator-protocol.ts.json new file mode 100644 index 000000000..facd86f6d --- /dev/null +++ b/tests/harness/corpus-costs/6935-class-async-iterator-protocol.ts.json @@ -0,0 +1 @@ +{ "plain": 3179, "san": 2683 } diff --git a/tests/harness/corpus-costs/6936-class-async-iterator-protocol.mjs.json b/tests/harness/corpus-costs/6936-class-async-iterator-protocol.mjs.json new file mode 100644 index 000000000..cbe2cb7d2 --- /dev/null +++ b/tests/harness/corpus-costs/6936-class-async-iterator-protocol.mjs.json @@ -0,0 +1 @@ +{ "plain": 3547, "san": 2898 } From c271aa85aa4ed98cae5602ccc17393fdbc53b882 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 16:45:21 -0500 Subject: [PATCH 003/395] Count holes written through dynamic views of typed arrays --- .../compiler/src/frontend/lowering/lowerer.ts | 103 ++++++++++++++++++ .../lowering/runtime-optional-elements.ts | 62 +++++++++-- .../test/runtime-optional-precision.test.ts | 25 +++++ .../6067-view-holes-in-typed-arrays.ts.json | 6 + .../corpus/6067-view-holes-in-typed-arrays.ts | 57 ++++++++++ .../6067-view-holes-in-typed-arrays.ts.json | 1 + 6 files changed, 247 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json create mode 100644 tests/corpus/6067-view-holes-in-typed-arrays.ts create mode 100644 tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ae91a87d5..e737eca64 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6309,7 +6309,110 @@ export class Lowerer { }); return keys; }; + // Element ABIs of the native arrays a value converted to a dynamic type + // can reach: the conversion keeps each such array as a live view, so + // stores through the dynamic value write the native storage. + const collectNativeViews = (): Set => { + const keys = new Set(); + const seen = new Set(); + const collect = (type: IrType | null): void => { + if (!type) return; + const id = typeKey(type); + if (seen.has(id)) return; + seen.add(id); + if (type.kind === "array") { + if (type.elem.kind !== "dyn") keys.add(typeKey(type.elem)); + collect(type.elem); + } else if (type.kind === "union") { + for (const arm of this.unions.get(type.unionId)?.arms ?? []) collect(arm); + } else if (type.kind === "record") { + for (const field of this.shapes.get(type.shapeId)?.fields ?? []) collect(field.type); + } else if (type.kind === "object") { + for (const field of this.classes.get(type.className)?.fields.values() ?? []) + collect(field); + } else if (type.kind === "map") { + collect(type.key); + collect(type.value); + } else if (type.kind === "set") collect(type.elem); + else if (type.kind === "promise") collect(type.inner); + }; + const holdsDynamic = (type: IrType | null, depth: number): boolean => { + if (!type || depth > 4) return false; + if (type.kind === "dyn") return true; + if (type.kind === "array" || type.kind === "set") return holdsDynamic(type.elem, depth + 1); + if (type.kind === "map") return holdsDynamic(type.value, depth + 1); + if (type.kind === "promise") return holdsDynamic(type.inner, depth + 1); + if (type.kind === "union") + return (this.unions.get(type.unionId)?.arms ?? []).some((arm) => + holdsDynamic(arm, depth + 1), + ); + if (type.kind === "record") + return (this.shapes.get(type.shapeId)?.fields ?? []).some((field) => + holdsDynamic(field.type, depth + 1), + ); + return false; + }; + const mapped = (type: ts.Type | null | undefined): IrType | null => + type ? panicSafe(() => this.mapTypeOf(type), null) : null; + const flow = (source: ts.Expression, target: ts.Type | null | undefined): void => { + if (target === undefined) return; + const into = target === null ? null : mapped(target); + // An unanswerable target may be dynamic. + if (target !== null && into !== null && !holdsDynamic(into, 0)) return; + const from = typeOrNull(source); + if (from === null) keys.add("*"); + else collect(mapped(from)); + }; + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) || ts.isTypeAssertion(node)) + flow(node.expression, typeOrNull(node)); + else if (ts.isVariableDeclaration(node) && node.type && node.initializer) + flow(node.initializer, typeOrNull(node.name)); + else if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + flow(node.right, typeOrNull(node.left)); + else if (ts.isReturnStatement(node) && node.expression) + flow(node.expression, contextualOrNull(node.expression)); + else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + for (const arg of node.arguments?.nodes ?? []) + flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); + } else if (ts.isPropertyAssignment(node)) + flow(node.initializer, contextualOrNull(node.initializer)); + else if (ts.isShorthandPropertyAssignment(node)) { + const name = node.name as ts.Expression; + flow(name, contextualOrNull(name)); + } else if (ts.isArrayLiteralExpression(node)) { + for (const element of node.elements.nodes) + if (!ts.isSpreadElement(element) && !ts.isOmittedExpression(element)) + flow(element, contextualOrNull(element)); + } else if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) + flow(node.body as ts.Expression, contextualOrNull(node.body as ts.Expression)); + }; + for (const sf of sourceFiles) + ts.walkPreorder(sf, (node) => { + // A checker panic hides this conversion: any array. + if (!panicSafe(() => (visit(node), true), false)) keys.add("*"); + return undefined; + }); + return keys; + }; + const dynamicType = (type: ts.Type): boolean => { + if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return true; + if (this.checker.isArrayType(type) && !this.checker.isTupleType(type)) { + const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; + if (element && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) + return true; + } + return panicSafe(() => this.mapTypeOf(type)?.kind === "dyn", false); + }; const elementStates = new ArrayElementStates({ + isDynamicArray: (node) => { + const type = typeOrNull(node); + return type === null || dynamicType(type); + }, + nativeViewKeys: collectNativeViews, elementAdmitsUndefined: (key) => { siteDependencies?.read(key); return optionalElementKeys.has(key); diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 96d20d2c4..8fc0c4a95 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -37,6 +37,13 @@ export interface ElementStateHost { ownerOf(node: ts.Expression): ts.Symbol | null; /** The owning slot a newly built array is stored into. */ allocationOwner(node: ts.Expression): ts.Symbol | null; + /** `node` is a dynamic value (`any`, `unknown`, `unknown[]`): when it is + * a live view of a native array, a store through it writes that array's + * storage. */ + isDynamicArray(node: ts.Expression): boolean; + /** Element keys of the native arrays a value converted to a dynamic type + * can reach, as live views ("*" for any array). */ + nativeViewKeys(): ReadonlySet; /** Debug provenance for a new fact. */ note: ((fact: "undefined" | "holes", key: string, site: ts.Node | undefined) => void) | null; /** The scan read, or added to, the facts of an element key, of an owning @@ -48,6 +55,8 @@ export interface ElementStateHost { /** The fact name of the facts shared by every array. */ const ANY_ARRAY = "*"; +/** The fact name of holes stored through dynamic views. */ +const VIEW_HOLES = "%view"; /** What an array value can contain besides present values. */ interface Contents { @@ -81,6 +90,10 @@ export class ArrayElementStates { * whose element ABI is decided per generic instantiation. */ private anyUndefined = false; private anyHoley = false; + /** A store through a dynamic value can leave holes; they land in every + * native array such a value views. */ + private viewHoles = false; + private viewKeys: ReadonlySet | null = null; /** Facts of arrays owned by one storage slot. */ private readonly undefinedOwners = new Set(); private readonly holeyOwners = new Set(); @@ -330,14 +343,32 @@ export class ArrayElementStates { this.host.read?.(ANY_ARRAY); if (keys === null) return null; for (const key of keys) this.host.read?.(key); + this.host.read?.(VIEW_HOLES); return { undefined: this.anyUndefined || keys.some((key) => this.undefinedKeys.has(key) || this.host.elementAdmitsUndefined(key)), - holes: this.anyHoley || keys.some((key) => this.holeyKeys.has(key)), + holes: this.anyHoley || keys.some((key) => this.holeyKeys.has(key) || this.viewed(key)), }; } + /** Holes stored through a dynamic view can be in arrays of this key. */ + private viewed(key: string): boolean { + if (!this.viewHoles) return false; + this.viewKeys ??= this.host.nativeViewKeys(); + return this.viewKeys.has(key) || this.viewKeys.has(ANY_ARRAY); + } + + /** A store through `receiver`, which has no native element key, can + * leave holes. Only a dynamic value can be a view of a native array. */ + private addViewHoles(receiver: ts.Expression): boolean { + if (this.viewHoles || !this.host.isDynamicArray(receiver)) return false; + this.viewHoles = true; + this.host.changed?.(VIEW_HOLES); + this.host.note?.("holes", VIEW_HOLES, this.site); + return true; + } + /** Reading every index of `node` (a spread, an iteration, `Array.from`) * can produce `undefined`. Only native arrays can: other iterables reject * an undefined value when it is stored. */ @@ -427,7 +458,11 @@ export class ArrayElementStates { /** A store through `receiver` can leave holes. */ private addHolesTo(receiver: ts.Expression): boolean { const owner = this.host.ownerOf(receiver); - if (owner === null) return this.addHoles(this.host.arrayKeys(receiver)); + if (owner === null) { + const keys = this.host.arrayKeys(receiver); + if (keys !== null && keys.length === 0) return this.addViewHoles(receiver); + return this.addHoles(keys); + } if (this.holeyOwners.has(owner)) return false; this.holeyOwners.add(owner); this.host.changed?.(owner); @@ -490,7 +525,8 @@ export class ArrayElementStates { const undefinedValue = storesValue && this.host.mayBeUndefined(node.right); if (!holes && !undefinedValue) return false; const keys = this.host.arrayKeys(target.expression); - if (keys !== null && keys.length === 0) return false; + if (keys !== null && keys.length === 0) + return holes && !namesProperty(target) && this.addViewHoles(target.expression); let changed = false; if (holes && this.addHolesTo(target.expression)) changed = true; if (undefinedValue && this.addUndefinedTo(target.expression)) changed = true; @@ -501,8 +537,6 @@ export class ArrayElementStates { target.name.text === "length" && !isShrinkingLength(node, target) ) { - const keys = this.host.arrayKeys(target.expression); - if (keys !== null && keys.length === 0) return false; return this.addHolesTo(target.expression); } return false; @@ -574,10 +608,16 @@ export class ArrayElementStates { )); if (!reflective) return false; let changed = false; - for (const arg of args) { + // The written object: `Reflect.apply`'s receiver argument, otherwise + // the first argument. + const written = this.host.isGlobal(owner, "Reflect") && method === "apply" ? 1 : 0; + for (const [index, arg] of args.entries()) { const target = ts.isSpreadElement(arg) ? arg.expression : arg; const keys = this.host.arrayKeys(target); - if (keys !== null && keys.length === 0) continue; + if (keys !== null && keys.length === 0) { + if (index === written && this.addViewHoles(target)) changed = true; + continue; + } if (this.addUndefinedTo(target)) changed = true; if (this.addHolesTo(target)) changed = true; } @@ -761,6 +801,14 @@ function isAppendIndex(target: ts.ElementAccessExpression): boolean { ); } +/** `o["name"]`: a string literal key that is not an array index writes a + * named property, never an element. */ +function namesProperty(target: ts.ElementAccessExpression): boolean { + const key = peel(target.argumentExpression); + if (!ts.isStringLiteral(key) && !ts.isNoSubstitutionTemplateLiteral(key)) return false; + return !/^(0|[1-9][0-9]*)$/.test(key.text) || Number(key.text) >= 2 ** 32 - 1; +} + /** `xs.length = 0`, `xs.length = xs.length - k` and `xs.length -= k` * truncate; truncation never leaves holes. */ function isShrinkingLength( diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 7840b0c62..3a3251b5a 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -102,6 +102,31 @@ console.log(copy.map((item) => weigh(item)).join(",")); expect(signature(spread, "weigh")).toBe("weigh(Item|undefined): f64"); }); +test("holes stored through a dynamic view widen callbacks over the arrays it views", async () => { + const viewed = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown[] = items; +view[3] = new Item(2); +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(viewed, "weigh")).toBe("weigh(Item|undefined): f64"); + // A dynamic store cannot reach an element type no dynamic value views. + const other = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const values: unknown[] = [1]; +values[3] = 2; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(other, "weigh")).toBe("weigh(Item): f64"); + // A view without stores that leave holes keeps the parameters. + const read = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown[] = items; +console.log(view.length, items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(read, "weigh")).toBe("weigh(Item): f64"); +}); + test("holes that only their owning slot can observe stay out of callbacks", async () => { const module = await lower(`${ITEM} class Cache { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json new file mode 100644 index 000000000..9d257fd49 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6067-view-holes-in-typed-arrays.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6067-view-holes-in-typed-arrays.ts b/tests/corpus/6067-view-holes-in-typed-arrays.ts new file mode 100644 index 000000000..b59437646 --- /dev/null +++ b/tests/corpus/6067-view-holes-in-typed-arrays.ts @@ -0,0 +1,57 @@ +// Holes written through an `unknown[]` or `any` view of a typed array are +// holes of the typed array: for...of visits them as undefined and the find +// family passes them to its callback, as in Node. The views reach the array +// through a binding, an `any` parameter, a generic instantiated with +// `unknown`, a record field, a class field and a nested array. + +const numbers: number[] = [1, 2]; +const numbersView: unknown[] = numbers; +numbersView[3] = 5; +for (const value of numbers) console.log("numbers", value, typeof value); +console.log(numbers.findIndex((value) => value === undefined)); +console.log(numbers.findLastIndex((value) => value === undefined)); +console.log(numbers.find((value) => value !== undefined && value > 1)); + +const grown: string[] = ["a"]; +const grownView: unknown[] = grown; +grownView.length = 3; +for (const value of grown) console.log("grown", value); + +function growAny(values: any): void { + values[2] = 9; +} +const passed: number[] = [7]; +growAny(passed); +for (const value of passed) console.log("passed", value); + +function keep(value: T): T { + return value; +} +const kept: number[] = [3]; +(keep(kept) as unknown[])[2] = 4; +for (const value of kept) console.log("kept", value); + +const boxed: number[] = [1]; +const box: unknown = { values: boxed }; +(box as { values: unknown[] }).values[2] = 3; +for (const value of boxed) console.log("boxed", value); + +class Holder { + values: number[] = [1, 2]; +} +const holder = new Holder(); +const holderView: unknown = holder; +(holderView as { values: unknown[] }).values[3] = 4; +for (const value of holder.values) console.log("holder", value); + +const nested: number[][] = [[1]]; +const nestedView: unknown[][] = nested; +nestedView[0]![2] = 3; +for (const value of nested[0]!) console.log("nested", value); + +// A named property written through a view is not an element. +const named: number[] = [1, 2]; +const bag = named as unknown as Record; +bag["label"] = "x"; +for (const value of named) console.log("named", value); +console.log(named.length, bag["label"]); diff --git a/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json b/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json new file mode 100644 index 000000000..b8b740bf8 --- /dev/null +++ b/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json @@ -0,0 +1 @@ +{ "plain": 3967, "san": 4623 } From 3bbb88c43c8c5e0957047c6035c0a9ff42c66aba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 16:51:25 -0500 Subject: [PATCH 004/395] Construct through arrays of class values that mix a class and its subclasses `const kinds = [Dog, Cat, Animal]; new kinds[0]()` now works as in Node: - TypeScript: the checker types the element as the common base (`typeof Animal`), and an element read keeps the `undefined` an index past the end reads. The construction now narrows the class value out of that union, and an index past the end throws Node's TypeError ("kinds[5] is not a constructor"). Before, it refused with SC2001. - JavaScript: such a literal is a checked-dynamic array stored in a slot of the base's class values, and the check accepted only the base's own class value, so storing it threw "expected class constructor". A class value of a subclass now checks into its base class's slot, as a subclass instance already checks into a base-class instance slot. Classes without subclasses keep the exact check. --- packages/compiler/src/backend/llvm/dyn.ts | 18 ++++ .../src/frontend/lowering/lower-classes.ts | 93 ++++++++++++++++++- .../corpus/6937-class-value-arrays.mjs.json | 6 ++ .../corpus/6938-class-value-arrays.ts.json | 6 ++ tests/corpus/6937-class-value-arrays.mjs | 30 ++++++ tests/corpus/6938-class-value-arrays.ts | 28 ++++++ .../6937-class-value-arrays.mjs.json | 1 + .../6938-class-value-arrays.ts.json | 1 + 8 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6937-class-value-arrays.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6938-class-value-arrays.ts.json create mode 100644 tests/corpus/6937-class-value-arrays.mjs create mode 100644 tests/corpus/6938-class-value-arrays.ts create mode 100644 tests/harness/corpus-costs/6937-class-value-arrays.mjs.json create mode 100644 tests/harness/corpus-costs/6938-class-value-arrays.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 5c9d8e60f..f0278bce6 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -1530,6 +1530,24 @@ export class LlDyn { const B = new BlockBuilder(); if (t.kind === "classval") { host.declare(`declare ptr @scr_dyn_class_check(ptr, ptr, ptr)`); + // A subclass's class value checks into the base class's slot. + const subclasses = host.classSubtypes(t.className).filter((name) => name !== t.className); + if (subclasses.length > 0) { + host.declare(`declare zeroext i1 @scr_dyn_class_is(ptr, ptr)`); + for (const className of subclasses) { + const subKey = host.cstr(typeKey({ kind: "classval", className })); + const matched = B.tmp(); + B.line(`${matched} = call zeroext i1 @scr_dyn_class_is(ptr %d, ptr ${subKey})`); + const yes = B.newLabel("sub.yes"); + const no = B.newLabel("sub.no"); + B.condBr(matched, yes, no); + B.startBlock(yes); + const sub = B.tmp(); + B.line(`${sub} = call ptr @scr_dyn_class_check(ptr %d, ptr ${subKey}, ptr %path)`); + B.terminate(`ret ptr ${sub}`); + B.startBlock(no); + } + } const checked = B.tmp(); B.line( `${checked} = call ptr @scr_dyn_class_check(ptr %d, ptr ${host.cstr(key)}, ptr %path)`, diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index d8ccc15b7..34631cf53 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -10769,8 +10769,99 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { "constructing through a class value of an uninstantiated generic type (annotate the slot with a concrete instantiation — e.g. 'new (v: number) => Box')", ); } - const callee = lowerer.lowerExpr(expr.expression); + let callee = lowerer.lowerExpr(expr.expression); if (callee.type.kind === "dyn") return lowerCheckedConstructor(lowerer, expr, callee); + // An array element read (`new T[0]()` over `[Dog, Animal]`, whose + // checker type is the common base) keeps the undefined an index past + // the end reads: the class value is narrowed out with a check, which + // throws a TypeError for undefined. + if (callee.type.kind === "union") { + const arms = lowerer.unions.get(callee.type.unionId)?.arms ?? []; + const classArms = arms.filter((arm) => arm.kind === "classval"); + if ( + classArms.length === 1 && + classArms[0]!.kind === "classval" && + classArms[0]!.className === calleeT.className && + arms.every((arm) => arm.kind === "classval" || arm.kind === "undefinedT") + ) { + const unionT = callee.type; + const helper = lowerer.narrowedArmHelper(unionT.unionId, calleeT, callee.loc); + if (helper) { + const loc = callee.loc; + const name = `%new.callee:${unionT.unionId}`; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const undefinedTag = lowerer.armTag(unionT.unionId, UNDEFINED_T); + const value: IrExpr = { kind: "varRef", localId: "value", type: unionT, loc }; + lowerer.liftedFns.push({ + name, + params: [ + { localId: "value", name: "value", type: unionT }, + { localId: "message", name: "message", type: STRING }, + ], + locals: [ + { id: "value", name: "value", type: unionT, mutable: false }, + { id: "message", name: "message", type: STRING, mutable: false }, + ], + returnType: calleeT, + body: [ + ...(undefinedTag >= 0 + ? [ + { + kind: "if", + cond: { + kind: "unionIsTag", + unionId: unionT.unionId, + tag: undefinedTag, + negated: false, + value, + type: BOOL, + loc, + }, + then: [ + { + kind: "throw", + value: { + kind: "libCall", + fn: "error.new", + args: [{ kind: "varRef", localId: "message", type: STRING, loc }], + type: { kind: "object", className: "%TypeError" }, + loc, + }, + loc, + }, + ], + else_: null, + loc, + } satisfies IrStmt, + ] + : []), + { + kind: "return", + value: lowerer.checkedNarrowCall(helper, value, calleeT, loc), + loc, + }, + ], + loc, + }); + } + callee = { + kind: "call", + callee: name, + args: [ + callee, + { + kind: "strLit", + value: `${expr.expression.getText()} is not a constructor`, + type: STRING, + loc, + }, + ], + type: calleeT, + loc, + }; + } + } + } if (callee.type.kind !== "classval") lowerer.badType(expr.expression, lowerer.typeOf(expr.expression)); lowerer.noteEdge(classValueConstructor(info.def).fn); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6937-class-value-arrays.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6937-class-value-arrays.mjs.json new file mode 100644 index 000000000..eefa82916 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6937-class-value-arrays.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6937-class-value-arrays.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6938-class-value-arrays.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6938-class-value-arrays.ts.json new file mode 100644 index 000000000..ff2a51546 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6938-class-value-arrays.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6938-class-value-arrays.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6937-class-value-arrays.mjs b/tests/corpus/6937-class-value-arrays.mjs new file mode 100644 index 000000000..a563d7514 --- /dev/null +++ b/tests/corpus/6937-class-value-arrays.mjs @@ -0,0 +1,30 @@ +// Arrays of class values that mix a base class and its subclasses: the +// elements construct, compare and name like the classes themselves. +class Animal { + speak() { + return "..."; + } +} +class Dog extends Animal { + speak() { + return "woof"; + } +} +class Cat extends Animal { + speak() { + return "meow"; + } +} +const kinds = [Dog, Cat, Animal]; +console.log(kinds.length, kinds.map((C) => C.name).join()); +console.log(new kinds[0]().speak(), new kinds[1]().speak(), new kinds[2]().speak()); +console.log(kinds[0] === Dog, kinds.indexOf(Cat), kinds.includes(Animal)); +const made = kinds.map((C) => new C()); +console.log(made.map((a) => a.speak()).join(), made[0] instanceof Animal, made[1] instanceof Dog); +try { + new kinds[5](); +} catch (e) { + console.log(e.name, e.message); +} +const byName = { dog: Dog, cat: Cat }; +console.log(new byName.dog().speak(), new byName["cat"]().speak()); diff --git a/tests/corpus/6938-class-value-arrays.ts b/tests/corpus/6938-class-value-arrays.ts new file mode 100644 index 000000000..672700cbf --- /dev/null +++ b/tests/corpus/6938-class-value-arrays.ts @@ -0,0 +1,28 @@ +// Arrays of class values that mix a base class and its subclasses, with the +// element type the checker infers (the base class): elements construct and +// dispatch to the subclass, and an index past the end throws Node's TypeError. +class Shape { + area(): number { + return 0; + } +} +class Square extends Shape { + area(): number { + return 4; + } +} +class Circle extends Shape { + area(): number { + return 3; + } +} +const shapes = [Square, Circle, Shape]; +console.log(shapes.length, new shapes[0]().area(), new shapes[1]().area(), new shapes[2]().area()); +let total = 0; +for (const S of shapes) total += new S().area(); +console.log(total, shapes[1] === Circle, shapes.map((S) => S.name).join()); +try { + new shapes[3](); +} catch (e) { + console.log((e as Error).name, (e as Error).message); +} diff --git a/tests/harness/corpus-costs/6937-class-value-arrays.mjs.json b/tests/harness/corpus-costs/6937-class-value-arrays.mjs.json new file mode 100644 index 000000000..7e905d65a --- /dev/null +++ b/tests/harness/corpus-costs/6937-class-value-arrays.mjs.json @@ -0,0 +1 @@ +{ "plain": 4211, "san": 6383 } diff --git a/tests/harness/corpus-costs/6938-class-value-arrays.ts.json b/tests/harness/corpus-costs/6938-class-value-arrays.ts.json new file mode 100644 index 000000000..76098ce8d --- /dev/null +++ b/tests/harness/corpus-costs/6938-class-value-arrays.ts.json @@ -0,0 +1 @@ +{ "plain": 2399, "san": 4080 } From aaca1f1e2b838e0ef15d91743b06bfd5803d6dc0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 18:17:48 -0500 Subject: [PATCH 005/395] Count the arguments a function receives when it is called through a value `arguments.length` in a typed function folds to a constant. In a function declaration it was the declared parameter count even when the function is called through a value with more arguments: Node prints 4 for `[1].map(f)` with `function f(v) { return v + arguments.length; }`, the compiled program printed 2. A top-level function declaration that reads `arguments` and that the program uses as a value now gets a count slot. Its value is an adapter that stores the parameter count of the function type the value is passed as (3 for map, 4 for reduce, 2 for a sort comparator) and forwards; the function reads the slot on entry and clears it, so a direct call sees the declared count. Function expressions and declarations passed where the expected function type has optional or rest parameters (a replace callback) are called with a varying count and are refused with SC1090 instead of folding to a wrong count, as are methods, nested functions, and arrows inside a function used as a value. --- .../src/frontend/lowering/argument-counts.ts | 230 ++++++++++++++++++ .../lowering/expressions/identifiers.ts | 13 +- .../lowering/expressions/property-access.ts | 50 ++-- .../src/frontend/lowering/function-context.ts | 3 + .../src/frontend/lowering/lower-functions.ts | 2 + .../compiler/src/frontend/lowering/lowerer.ts | 3 + ...81-function-value-arguments-length.ts.json | 6 + .../function-value-arguments-length.ts.json | 6 + .../7381-function-value-arguments-length.ts | 27 ++ .../function-value-arguments-length.ts | 36 +++ .../function-value-arguments-length.ts.txt | 43 ++++ ...81-function-value-arguments-length.ts.json | 1 + 12 files changed, 398 insertions(+), 22 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/argument-counts.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7381-function-value-arguments-length.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/function-value-arguments-length.ts.json create mode 100644 tests/corpus/7381-function-value-arguments-length.ts create mode 100644 tests/diagnostics/function-value-arguments-length.ts create mode 100644 tests/harness/__snapshots__/function-value-arguments-length.ts.txt create mode 100644 tests/harness/corpus-costs/7381-function-value-arguments-length.ts.json diff --git a/packages/compiler/src/frontend/lowering/argument-counts.ts b/packages/compiler/src/frontend/lowering/argument-counts.ts new file mode 100644 index 000000000..ec1753160 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/argument-counts.ts @@ -0,0 +1,230 @@ +import * as ts from "../ts7/adapter.js"; +import { bodyReadsArguments } from "../arguments-usage.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { BOOL, F64, type IrExpr, type IrStmt, type IrType } from "../../ir/ir.js"; +import { varRef } from "../../ir/build.js"; +import { typeKey } from "../type-mapper.js"; +import type { Lowerer } from "./lowerer.js"; +import { isThisParameter } from "./call-signatures.js"; +import { buildFunctionAdapter } from "./coercions/builders.js"; +import { keyedHelperName } from "./stable-names.js"; + +/** `arguments.length` in a top-level function declaration that the program + * also uses as a value. A direct call passes the declared parameters (tsc + * checks the arity), while a call through the value passes the parameter + * count of the function type the value was passed as (`[1].map(f)` calls f + * with 3). The value is an adapter that stores that count in a global slot + * before forwarding; the function reads the slot on entry and clears it, + * so a direct call (the cleared slot) sees the declared count. */ + +/** The count slot of `decl`, or null when its `arguments.length` folds to + * the declared count (or the declaration does not qualify). */ +export function argumentCountSlot(lowerer: Lowerer, decl: ts.FunctionDeclaration): string | null { + const cached = lowerer.argumentCountSlots.get(decl); + if (cached !== undefined) return cached; + let slot: string | null = null; + if ( + decl.body !== undefined && + decl.name !== undefined && + decl.asteriskToken === undefined && + !decl.typeParameters?.nodes.length && + lowerer.isTopLevelFnSymbol(decl.name) && + decl.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) !== true && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + fixedArity(decl) && + bodyReadsArguments(decl) && + usedAsValue(lowerer, decl) + ) { + const sig = lowerer.fnSigOf(decl.name); + if (sig) { + slot = `%g.argc.${sig.name}`; + lowerer.globalsList.push({ id: slot, name: "%argc", type: F64, mutable: true }); + } + } + lowerer.argumentCountSlots.set(decl, slot); + return slot; +} + +/** Reads `decl`'s count slot into a hidden local on entry and clears it; the + * local holds the declared count when the slot was clear. */ +export function declareArgumentCount( + lowerer: Lowerer, + decl: ts.FunctionDeclaration, + prologue: IrStmt[], +): void { + const slot = argumentCountSlot(lowerer, decl); + if (slot === null) return; + const loc = locOf(decl); + const local = lowerer.declareHiddenLocal("%argc", F64); + const stored = varRef(slot, F64, loc); + prologue.push( + { + kind: "varDecl", + localId: local.id, + init: { + kind: "ternary", + cond: { + kind: "bin", + op: "===", + left: stored, + right: { kind: "numLit", value: 0, type: F64, loc }, + type: BOOL, + loc, + }, + then: { kind: "numLit", value: declaredCount(decl), type: F64, loc }, + else_: stored, + type: F64, + loc, + }, + loc, + }, + { kind: "assign", localId: slot, value: { kind: "numLit", value: 0, type: F64, loc }, loc }, + ); + lowerer.ctx.argumentCount = local; +} + +/** `decl` as a value at `reference`: the plain closure when every call + * through the expected function type passes the declared count, otherwise + * an adapter that stores the expected type's count in the slot first. */ +export function argumentCountValue( + lowerer: Lowerer, + decl: ts.FunctionDeclaration, + reference: ts.Expression, + closure: IrExpr & { type: IrType & { kind: "func" } }, +): IrExpr { + const slot = argumentCountSlot(lowerer, decl); + if (slot === null) return closure; + const count = contextualCallCount(lowerer, reference); + if (count === null || count === "varies") { + lowerer.unsupported( + "SC1090", + reference, + count === null + ? "functions that read 'arguments.length' used as values where no function type is expected (the count each call passes is not known)" + : "functions that read 'arguments.length' used as values where the expected function type has optional or rest parameters (the count varies per call)", + ); + } + const declared = declaredCount(decl); + if (count <= declared) return closure; + const loc = locOf(reference); + const type = closure.type; + const key = `argc:${slot}:${count}:${typeKey(type)}`; + let name = lowerer.arrHofHelpers.get(key); + if (name === undefined) { + name = keyedHelperName(lowerer, "%argc.adapter", key); + lowerer.arrHofHelpers.set(key, name); + const params = type.params.map((t, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: t })); + lowerer.liftedFns.push( + ...buildFunctionAdapter( + name, + type, + type, + params, + [], + [ + { + kind: "assign", + localId: slot, + value: { kind: "numLit", value: count, type: F64, loc }, + loc, + }, + { + kind: "return", + value: { + kind: "callValue", + callee: varRef("f.0", type, loc), + args: params.map((p) => varRef(p.localId, p.type, loc)), + type: type.ret, + loc, + }, + loc, + }, + ], + loc, + ), + ); + } + return { kind: "call", callee: name, args: [closure], type, loc }; +} + +/** The argument count every call through the function type `node` is + * expected as passes: that type's parameter count when it has one call + * signature with a fixed parameter list (a `this` parameter is type-only), + * "varies" when it has optional or rest parameters, null when there is no + * such type. */ +export function contextualCallCount( + lowerer: Lowerer, + node: ts.Expression, +): number | "varies" | null { + const contextual = lowerer.checker.getContextualType(node); + if (contextual === undefined) return null; + const signatures = lowerer.checker.getCallSignatures( + lowerer.checker.getNonNullableType(contextual), + ); + if (signatures.length !== 1) return null; + const declaration = signatures[0]!.declaration; + if (declaration === undefined) return null; + let count = 0; + for (const parameter of (declaration.parameters?.nodes ?? []) as ts.ParameterDeclaration[]) { + if (isThisParameter(parameter)) continue; + if (parameter.questionToken || parameter.initializer || parameter.dotDotDotToken) + return "varies"; + count++; + } + return count; +} + +/** Whether a program reference to the function declaration or method `fn` + * reads it as a value (`xs.map(f)`, `const g = f`, `xs.map(C.parse)`) rather + * than calling it. Export specifiers and default exports only name a + * function; the importing files' references resolve to it and are checked + * the same way. */ +export function usedAsValue( + lowerer: Lowerer, + fn: ts.FunctionDeclaration | ts.MethodDeclaration, +): boolean { + const name = fn.name; + if (!name || !(ts.isIdentifier(name) || ts.isPrivateIdentifier(name))) return false; + const method = ts.isMethodDeclaration(fn); + const symbolAt = (n: ts.Node): ts.Symbol | null | undefined => + method + ? lowerer.checker.getSymbolAtLocation(n) + : lowerer.resolveValueSymbol(n as ts.Identifier); + const symbol = symbolAt(name); + if (!symbol) return false; + let found = false; + for (const sf of lowerer.program.getSourceFiles()) { + if (sf.isDeclarationFile) continue; + ts.walkPreorder(sf, (n) => { + if (found) return "stop"; + if (ts.isTypeNode(n)) return "skip"; + if (n === name || !(ts.isIdentifier(n) || ts.isPrivateIdentifier(n))) return undefined; + if (n.text !== name.text || !n.parent) return undefined; + const parent = n.parent; + // A method is read through its member name (`C.parse`, `this.#m`). + const read = ts.isPropertyAccessExpression(parent) && parent.name === n ? parent : n; + if (method && read === n && !ts.isBindingElement(parent)) return undefined; + const use = read.parent; + if (use && ts.isCallExpression(use) && use.expression === read) return undefined; + if (ts.isExportSpecifier(parent) || ts.isExportAssignment(parent)) return undefined; + if (ts.isImportSpecifier(parent) || ts.isImportClause(parent)) return undefined; + if (symbolAt(n) === symbol) found = true; + return undefined; + }); + if (found) break; + } + return found; +} + +function fixedArity(fn: ts.FunctionLikeDeclaration): boolean { + return fn.parameters.nodes.every( + (p) => + p.questionToken === undefined && + p.initializer === undefined && + p.dotDotDotToken === undefined, + ); +} + +function declaredCount(fn: ts.FunctionLikeDeclaration): number { + return fn.parameters.nodes.filter((p) => !isThisParameter(p)).length; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 63175d50a..ff6f65f26 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -25,6 +25,7 @@ import { lowerNodeModuleIdentifier } from "../lower-node-module.js"; import { lowerCjsExportsObjectValue } from "../cjs-exports-object.js"; import { captureContextArguments } from "../function-context.js"; import { argumentsOwner, typedArgumentsCandidate } from "../typed-arguments.js"; +import { argumentCountValue } from "../argument-counts.js"; import { isCjsExportTableLiteral, isCjsJsFile, @@ -424,7 +425,17 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // semantics); the rest marker carries the trailing dyn-array ABI. const funcType: IrType = funcTypeFromParamShapes(sig.params, sig.returnType); lowerer.requireExactArityValue(expr, expr, sig.params, funcType); - return { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc }; + const closure = { + kind: "closure" as const, + fnName: sig.name, + captures: [], + type: funcType, + loc, + }; + const decl = lowerer.resolveValueSymbol(expr)?.valueDeclaration; + return decl && ts.isFunctionDeclaration(decl) && funcType.kind === "func" + ? argumentCountValue(lowerer, decl, expr, { ...closure, type: funcType }) + : closure; } { // A generic function as a VALUE: monomorphized by flow — the diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 5df3c7fe0..5f1683ff7 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -6,6 +6,7 @@ import { lowerHistogramProperty } from "../builtins/performance.js"; import { staticFunctionNameRead } from "../static-function-names.js"; import { typedArgumentsArity } from "../typed-arguments.js"; import { isThisParameter } from "../call-signatures.js"; +import { argumentCountSlot, contextualCallCount, usedAsValue } from "../argument-counts.js"; import { checkedPrivateValue, localPrivateBrandClass, @@ -669,7 +670,31 @@ export function lowerPropertyAccessExpr( const declared = (fn as ts.FunctionLikeDeclaration).parameters.nodes.filter( (p) => !isThisParameter(p), ).length; - const count = Math.max(declared, contextualArgumentCount(lowerer, fn) ?? 0); + const contextual = contextualArgumentCount(lowerer, fn); + if (contextual === "varies") { + lowerer.unsupported( + "SC1090", + expr, + "'arguments.length' in function expressions passed where the expected function type has optional or rest parameters (the count varies per call)", + ); + } + // A top-level declaration also used as a value reads the count its + // caller stored (argument-counts.ts). + if (ts.isFunctionDeclaration(fn) && lowerer.ctx.argumentCount !== undefined) + return varRef(lowerer.ctx.argumentCount.id, F64, loc); + if ( + (ts.isFunctionDeclaration(fn) || ts.isMethodDeclaration(fn)) && + usedAsValue(lowerer, fn) + ) { + lowerer.unsupported( + "SC1090", + expr, + ts.isFunctionDeclaration(fn) && argumentCountSlot(lowerer, fn) !== null + ? "'arguments.length' in an arrow function inside a function that is also used as a value" + : "'arguments.length' in methods, nested, async and generator functions used as values (a caller of the value can pass more arguments than the function declares)", + ); + } + const count = Math.max(declared, contextual ?? 0); // Calls through the expression's own name pass the declared count, // calls through the wider slot pass more: no constant answers both. if (count > declared && referencesOwnName(lowerer, fn)) { @@ -4469,24 +4494,7 @@ function referencesOwnName(lowerer: Lowerer, fn: ts.Node): boolean { } /** The argument count every caller passes a function expression whose - * value flows into one fixed-arity function type, an array method's - * callback or an annotated slot: that type's parameter count. Null when - * there is no such type or its arity varies. */ -function contextualArgumentCount(lowerer: Lowerer, fn: ts.Node): number | null { - if (!ts.isFunctionExpression(fn)) return null; - const contextual = lowerer.checker.getContextualType(fn); - if (contextual === undefined) return null; - const signatures = lowerer.checker.getCallSignatures( - lowerer.checker.getNonNullableType(contextual), - ); - if (signatures.length !== 1) return null; - const declaration = signatures[0]!.declaration; - if (declaration === undefined) return null; - let count = 0; - for (const parameter of (declaration.parameters?.nodes ?? []) as ts.ParameterDeclaration[]) { - if (isThisParameter(parameter)) continue; - if (parameter.questionToken || parameter.initializer || parameter.dotDotDotToken) return null; - count++; - } - return count; + * value flows into a function type (argument-counts.ts contextualCallCount). */ +function contextualArgumentCount(lowerer: Lowerer, fn: ts.Node): number | "varies" | null { + return ts.isFunctionExpression(fn) ? contextualCallCount(lowerer, fn) : null; } diff --git a/packages/compiler/src/frontend/lowering/function-context.ts b/packages/compiler/src/frontend/lowering/function-context.ts index 92042b7d7..702574b63 100644 --- a/packages/compiler/src/frontend/lowering/function-context.ts +++ b/packages/compiler/src/frontend/lowering/function-context.ts @@ -46,6 +46,9 @@ export interface FnCtx { * rest param): the synthetic trailing dyn-array param `arguments` * reads resolve to. */ argumentsLocal?: IrLocal | null; + /** The count `arguments.length` reads in a top-level function declaration + * that is also used as a value (argument-counts.ts). */ + argumentCount?: IrLocal; /** Arrows inherit the nearest ordinary function's arguments binding. */ inheritsArguments?: boolean; /** A function lowered on demand on top of an unrelated caller's frames diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index c1cca9c64..c1682f7c8 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -1,5 +1,6 @@ import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; import { declareTypedArguments } from "./typed-arguments.js"; +import { declareArgumentCount } from "./argument-counts.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { everyStmtList, transformStmtList } from "../../ir/traverse.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; @@ -551,6 +552,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I (sig.params[sig.params.length - 1]!.mode === "dynRest" || sig.params[sig.params.length - 1]!.mode === "arguments"), ); + declareArgumentCount(lowerer, decl, prologue); const bodyBlock = blockBodyOf(decl); if (!bodyBlock) { lowerer.unsupported( diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 4add3193c..4b60d9e1b 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2335,6 +2335,9 @@ export class Lowerer { * every name destructured from a present source optional. */ readonly runtimeOptionalPatternTypes = new Map(); readonly runtimeOptionalReduceTypes = new Map(); + /** Each function declaration's `arguments.length` count slot, or null + * (argument-counts.ts argumentCountSlot). */ + readonly argumentCountSlots = new Map(); /** Non-null assertions whose value is forwarded into a slot promoted to * hold undefined. `x!` has no runtime effect, so these keep the stored * undefined state instead of trapping before any dereference. */ diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7381-function-value-arguments-length.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7381-function-value-arguments-length.ts.json new file mode 100644 index 000000000..b9e5eacd1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7381-function-value-arguments-length.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7381-function-value-arguments-length.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/function-value-arguments-length.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/function-value-arguments-length.ts.json new file mode 100644 index 000000000..6aa894918 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/function-value-arguments-length.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/function-value-arguments-length.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7381-function-value-arguments-length.ts b/tests/corpus/7381-function-value-arguments-length.ts new file mode 100644 index 000000000..059ff1487 --- /dev/null +++ b/tests/corpus/7381-function-value-arguments-length.ts @@ -0,0 +1,27 @@ +// `arguments.length` in a function declaration counts the arguments its +// caller passes: a direct call passes the declared parameters, and a call +// through the function as a value passes the parameters of the function +// type it was passed as (3 for map and filter, 4 for reduce, 2 for sort). +function scaled(value: number): number { + return value * 10 + arguments.length; +} +function total(acc: number, value: number): number { + return acc + value + arguments.length; +} +function compare(a: number, b: number): number { + return arguments.length === 2 ? a - b : 0; +} +console.log([1, 2].map(scaled).join(), scaled(3)); +console.log([5, 6].filter(scaled).length, [1, 2].reduce(total, 0), total(1, 2)); +console.log([3, 1, 2].sort(compare).join()); + +function apply(f: (a: number, b: number, c: number) => number): number { + return f(1, 2, 3); +} +console.log(apply(scaled)); + +// A direct call inside a call through the value sees its own count. +function depth(n: number): number { + return n <= 0 ? arguments.length : depth(n - 1) + arguments.length; +} +console.log([2].map(depth).join(), depth(2)); diff --git a/tests/diagnostics/function-value-arguments-length.ts b/tests/diagnostics/function-value-arguments-length.ts new file mode 100644 index 000000000..ab0b53f82 --- /dev/null +++ b/tests/diagnostics/function-value-arguments-length.ts @@ -0,0 +1,36 @@ +// `arguments.length` has no single value when the calls pass varying +// counts: a function expression or declaration passed where the expected +// function type has optional or rest parameters. Methods and nested +// functions used as values, and arrows inside a function used as a value, +// keep the fence. +const replaced = "a-b".replace(/-/, function (match) { + return String(arguments.length) + match; +}); +function call(f: (a: number, b?: number) => number): number { + return f(1, 2); +} +const counted = call(function (a) { + return arguments.length + a; +}); +function offset(value: number): number { + return value + arguments.length; +} +const offsets = call(offset); +class Scale { + static by(value: number): number { + return value * arguments.length; + } +} +const scaled = [2].map(Scale.by); +function outer(): number[] { + function inner(value: number): number { + return value + arguments.length; + } + return [1].map(inner); +} +function deferred(value: number): number { + const read = (): number => arguments.length; + return value + read(); +} +const late = [1].map(deferred); +export { replaced, counted, offsets, scaled, outer, late }; diff --git a/tests/harness/__snapshots__/function-value-arguments-length.ts.txt b/tests/harness/__snapshots__/function-value-arguments-length.ts.txt new file mode 100644 index 000000000..adcb48d2a --- /dev/null +++ b/tests/harness/__snapshots__/function-value-arguments-length.ts.txt @@ -0,0 +1,43 @@ +function-value-arguments-length.ts:7:17 - error SC1090: 'arguments.length' in function expressions passed where the expected function type has optional or rest parameters (the count varies per call) are not supported yet + + 6 | const replaced = "a-b".replace(/-/, function (match) { + 7 | return String(arguments.length) + match; + | ^~~~~~~~~~~~~~~~ + 8 | }); + +function-value-arguments-length.ts:13:10 - error SC1090: 'arguments.length' in function expressions passed where the expected function type has optional or rest parameters (the count varies per call) are not supported yet + + 12 | const counted = call(function (a) { + 13 | return arguments.length + a; + | ^~~~~~~~~~~~~~~~ + 14 | }); + +function-value-arguments-length.ts:18:22 - error SC1090: functions that read 'arguments.length' used as values where the expected function type has optional or rest parameters (the count varies per call) are not supported yet + + 17 | } + 18 | const offsets = call(offset); + | ^~~~~~ + 19 | class Scale { + +function-value-arguments-length.ts:21:20 - error SC1090: 'arguments.length' in methods, nested, async and generator functions used as values (a caller of the value can pass more arguments than the function declares) are not supported yet + + 20 | static by(value: number): number { + 21 | return value * arguments.length; + | ^~~~~~~~~~~~~~~~ + 22 | } + +function-value-arguments-length.ts:32:30 - error SC1090: 'arguments.length' in an arrow function inside a function that is also used as a value is not supported yet + + 31 | function deferred(value: number): number { + 32 | const read = (): number => arguments.length; + | ^~~~~~~~~~~~~~~~ + 33 | return value + read(); + +function-value-arguments-length.ts:33:18 - error SC2004: uses of 'read' inherit the blocker on its declaration + + 32 | const read = (): number => arguments.length; + 33 | return value + read(); + | ^~~~ + 34 | } + + hint: the declaration of 'read' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file diff --git a/tests/harness/corpus-costs/7381-function-value-arguments-length.ts.json b/tests/harness/corpus-costs/7381-function-value-arguments-length.ts.json new file mode 100644 index 000000000..b4659b9a0 --- /dev/null +++ b/tests/harness/corpus-costs/7381-function-value-arguments-length.ts.json @@ -0,0 +1 @@ +{ "plain": 3400, "san": 3300 } From 7cbb6d3b371003b188201fdff61a6145dad7ed5a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 18:28:44 -0500 Subject: [PATCH 006/395] Count only stores and flows that can reach a typed array's view --- .../compiler/src/frontend/lowering/lowerer.ts | 41 +++++++++++++++---- .../test/runtime-optional-precision.test.ts | 18 ++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e737eca64..ccb91291c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6312,6 +6312,27 @@ export class Lowerer { // Element ABIs of the native arrays a value converted to a dynamic type // can reach: the conversion keeps each such array as a live view, so // stores through the dynamic value write the native storage. + // A standard library function only reads an array argument it takes as + // `any` (console.log, JSON.stringify, String), except the reflective + // writes: Object.assign and defineProperty, Reflect.set and the like, + // and Array.prototype methods through call or apply. + const reflectiveWriters = new Set([ + "assign", + "defineProperty", + "defineProperties", + "setPrototypeOf", + "set", + "apply", + "call", + ]); + const standardLibraryReader = (node: ts.CallExpression | ts.NewExpression): boolean => + panicSafe(() => { + const callee = peel(node.expression); + if (ts.isPropertyAccessExpression(callee) && reflectiveWriters.has(callee.name.text)) + return false; + const declaration = this.checker.getResolvedSignature(node)?.declaration; + return declaration !== undefined && this.isStdlibFile(ts.getSourceFileOfNode(declaration)); + }, false); const collectNativeViews = (): Set => { const keys = new Set(); const seen = new Set(); @@ -6376,8 +6397,9 @@ export class Lowerer { else if (ts.isReturnStatement(node) && node.expression) flow(node.expression, contextualOrNull(node.expression)); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - for (const arg of node.arguments?.nodes ?? []) - flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); + if (!standardLibraryReader(node)) + for (const arg of node.arguments?.nodes ?? []) + flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); } else if (ts.isPropertyAssignment(node)) flow(node.initializer, contextualOrNull(node.initializer)); else if (ts.isShorthandPropertyAssignment(node)) { @@ -6398,14 +6420,17 @@ export class Lowerer { }); return keys; }; + // A receiver typed `any`, `unknown` or an array of either can be a live + // view of a native array. A receiver of another type (`Worker[]`, + // `Record`) is one only after a lying assertion, even + // when its values are dynamic. const dynamicType = (type: ts.Type): boolean => { if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return true; - if (this.checker.isArrayType(type) && !this.checker.isTupleType(type)) { - const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; - if (element && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) - return true; - } - return panicSafe(() => this.mapTypeOf(type)?.kind === "dyn", false); + if (!this.checker.isArrayType(type) || this.checker.isTupleType(type)) return false; + const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; + return ( + element !== undefined && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 + ); }; const elementStates = new ArrayElementStates({ isDynamicArray: (node) => { diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 3a3251b5a..64f0bd86e 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -125,6 +125,24 @@ const view: unknown[] = items; console.log(view.length, items.findIndex((item) => weigh(item) > 1)); `); expect(signature(read, "weigh")).toBe("weigh(Item): f64"); + // Standard library functions that take arrays as `any` only read them. + const readers = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +console.log(items.length, JSON.stringify(items)); +const values: unknown[] = [1]; +values[3] = 2; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(readers, "weigh")).toBe("weigh(Item): f64"); + // A store through a Record type is not a store through a view. + const record = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown = items; +const bag: Record = {}; +bag["3"] = view; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(record, "weigh")).toBe("weigh(Item): f64"); }); test("holes that only their owning slot can observe stay out of callbacks", async () => { From a3b9cfb1466649db971d5562405a8a01f028c048 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 18:51:14 -0500 Subject: [PATCH 007/395] Find checker reply element ends in C and list dynamic imports from module references --- packages/compiler/native/ts7-json.test.c | 33 +++++ packages/compiler/native/ts7-process.c | 42 +++++++ packages/compiler/native/ts7-process.ffi.json | 3 +- packages/compiler/native/ts7-process.h | 4 + .../src/frontend/lowering/lower-modules.ts | 30 +++-- .../src/frontend/ts7/native-process.ts | 6 + .../src/frontend/ts7/semantic-json.ts | 26 +++- .../test/native-json-value-end.test.ts | 118 ++++++++++++++++++ 8 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 packages/compiler/native/ts7-json.test.c create mode 100644 packages/compiler/test/native-json-value-end.test.ts diff --git a/packages/compiler/native/ts7-json.test.c b/packages/compiler/native/ts7-json.test.c new file mode 100644 index 000000000..b696d77c2 --- /dev/null +++ b/packages/compiler/native/ts7-json.test.c @@ -0,0 +1,33 @@ +/* Reads cases from standard input, each a little-endian u32 length, a u32 + * start and the bytes, and prints scriptc_ts7_json_value_end for each. */ +#include "ts7-process.h" +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +static int read_u32(uint32_t *value) { + unsigned char bytes[4]; + if (fread(bytes, 1, 4, stdin) != 4) return 0; + *value = (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 | (uint32_t)bytes[2] << 16 | + (uint32_t)bytes[3] << 24; + return 1; +} + +int main(void) { +#ifdef _WIN32 + /* The cases are bytes: text mode would rewrite line endings. */ + _setmode(_fileno(stdin), _O_BINARY); +#endif + uint32_t length, start; + while (read_u32(&length)) { + if (!read_u32(&start)) return 1; + uint8_t *bytes = malloc(length == 0 ? 1 : length); + if (bytes == NULL || fread(bytes, 1, length, stdin) != length) return 1; + printf("%d\n", (int)scriptc_ts7_json_value_end(bytes, length, start)); + free(bytes); + } + return 0; +} diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index ea451345d..3333cf4e8 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -405,3 +405,45 @@ void scriptc_ts7_close(uint32_t handle) { void scriptc_ts7_close_all(void) { while (processes != NULL) scriptc_ts7_close(processes->id); } + +static int json_space(uint8_t byte) { + return byte == ' ' || byte == '\n' || byte == '\r' || byte == '\t'; +} + +int32_t scriptc_ts7_json_value_end(const uint8_t *bytes, size_t size, uint32_t start) { + if (size > INT32_MAX) return -1; + size_t depth = 0; + size_t index = start; + while (index < size) { + uint8_t byte = bytes[index]; + if (byte == '"') { + index++; + for (;;) { + if (index >= size) return -1; + const uint8_t *quote = memchr(bytes + index, '"', size - index); + if (quote == NULL) return -1; + const uint8_t *escape = memchr(bytes + index, '\\', (size_t)(quote - (bytes + index))); + if (escape == NULL) { + index = (size_t)(quote - bytes); + break; + } + index = (size_t)(escape - bytes) + 2; + } + index++; + if (depth == 0) return (int32_t)index; + continue; + } + if (byte == '{' || byte == '[') { + depth++; + } else if (byte == '}' || byte == ']') { + if (depth == 0) return (int32_t)index; + depth--; + if (depth == 0) return (int32_t)(index + 1); + } else if (depth == 0 && (byte == ',' || json_space(byte))) { + return (int32_t)index; + } + index++; + } + if (depth != 0) return -1; + return (int32_t)index; +} diff --git a/packages/compiler/native/ts7-process.ffi.json b/packages/compiler/native/ts7-process.ffi.json index 86f3cb4d2..9a9e39526 100644 --- a/packages/compiler/native/ts7-process.ffi.json +++ b/packages/compiler/native/ts7-process.ffi.json @@ -7,6 +7,7 @@ { "name": "ts7NativePid", "symbol": "scriptc_ts7_pid", "params": ["u32"], "returns": "u32" }, { "name": "ts7NativeClose", "symbol": "scriptc_ts7_close", "params": ["u32"], "returns": "void" }, { "name": "ts7NativeCloseAll", "symbol": "scriptc_ts7_close_all", "params": [], "returns": "void" }, - { "name": "ts7NativeError", "symbol": "scriptc_ts7_error", "params": ["mutable-bytes"], "returns": "i32" } + { "name": "ts7NativeError", "symbol": "scriptc_ts7_error", "params": ["mutable-bytes"], "returns": "i32" }, + { "name": "ts7NativeJsonValueEnd", "symbol": "scriptc_ts7_json_value_end", "params": ["bytes", "u32"], "returns": "i32" } ] } diff --git a/packages/compiler/native/ts7-process.h b/packages/compiler/native/ts7-process.h index 3d2a0ba05..5bc0d11c4 100644 --- a/packages/compiler/native/ts7-process.h +++ b/packages/compiler/native/ts7-process.h @@ -21,5 +21,9 @@ uint32_t scriptc_ts7_pid(uint32_t handle); void scriptc_ts7_close(uint32_t handle); void scriptc_ts7_close_all(void); int32_t scriptc_ts7_error(uint8_t *bytes, size_t size); +/* The index after the JSON value that starts at `start` in a checker reply, + * as jsonValueEnd in semantic-json.ts computes it, or -1 when the reply is + * malformed there. */ +int32_t scriptc_ts7_json_value_end(const uint8_t *bytes, size_t size, uint32_t start); #endif diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 5238333ad..1301a152f 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -346,24 +346,28 @@ function dynamicImportProgramTargetOf( } /** Every distinct program module a file's `import("literal")` sites name - * (source order, deduped). */ + * (source order, deduped). The parser lists the specifier of every such call + * in the file's module references, in source order after the static imports, + * so the file's nodes need no walk. */ function dynamicProgramImportsOf(program: ts.Program, sf: ts.SourceFile): ts.SourceFile[] { const out: ts.SourceFile[] = []; const seen = new Set(); - ts.walkPreorder(sf, (node) => { + for (const specifier of sf.imports) { + const call = specifier.parent; if ( - ts.isCallExpression(node) && - node.expression.kind === ts.SyntaxKind.ImportKeyword && - node.arguments.nodes[0] !== undefined && - ts.isStringLiteralLike(node.arguments.nodes[0]) - ) { - const dep = dynamicImportProgramTargetOf(program, sf, node.arguments.nodes[0].text); - if (dep && !seen.has(dep)) { - seen.add(dep); - out.push(dep); - } + call === undefined || + !ts.isCallExpression(call) || + call.expression.kind !== ts.SyntaxKind.ImportKeyword || + call.arguments.nodes[0] !== specifier || + !ts.isStringLiteralLike(specifier) + ) + continue; + const dep = dynamicImportProgramTargetOf(program, sf, specifier.text); + if (dep && !seen.has(dep)) { + seen.add(dep); + out.push(dep); } - }); + } return out; } diff --git a/packages/compiler/src/frontend/ts7/native-process.ts b/packages/compiler/src/frontend/ts7/native-process.ts index 7bb523d74..b36189e64 100644 --- a/packages/compiler/src/frontend/ts7/native-process.ts +++ b/packages/compiler/src/frontend/ts7/native-process.ts @@ -1,5 +1,6 @@ import { Ts7Wire, type Ts7WireIo } from "./rpc-wire.js"; import { TS7_FILE_SYSTEM_CALLBACKS } from "./rpc-filesystem.js"; +import { useNativeJsonValueEnd } from "./semantic-json.js"; // Bound by native/ts7-process.ffi.json. This module is a native entry point; // the Node host continues to use rpc-process.ts's public spawn boundary. @@ -25,6 +26,11 @@ declare function ts7NativePid(handle: number): number; declare function ts7NativeClose(handle: number): void; declare function ts7NativeCloseAll(): void; declare function ts7NativeError(bytes: Uint8Array): number; +declare function ts7NativeJsonValueEnd(bytes: Uint8Array, start: number): number; + +// Reply decoding finds element ends with the C scanner. FFI functions can +// only be called, not passed as values, so an arrow function calls it. +useNativeJsonValueEnd((bytes, start) => ts7NativeJsonValueEnd(bytes, start)); // process.exit skips C atexit handlers. Its ordinary exit listener closes // servers too, while the C atexit sweep covers normal native termination. diff --git a/packages/compiler/src/frontend/ts7/semantic-json.ts b/packages/compiler/src/frontend/ts7/semantic-json.ts index 3a04c741f..1a4d93bd6 100644 --- a/packages/compiler/src/frontend/ts7/semantic-json.ts +++ b/packages/compiler/src/frontend/ts7/semantic-json.ts @@ -76,8 +76,8 @@ function malformedReply(): never { /** The index after the JSON value that starts at `start`: an object, an * array, a string or a bare literal. Strings are skipped with their escapes, - * so brackets inside them do not count. */ -function jsonValueEnd(bytes: Uint8Array, start: number): number { + * so brackets inside them do not count. Throws when the value is unclosed. */ +export function jsonValueEnd(bytes: Uint8Array, start: number): number { let depth = 0; let index = start; while (index < bytes.length) { @@ -103,6 +103,22 @@ function jsonValueEnd(bytes: Uint8Array, start: number): number { return index; } +/** The scanner the decoders find value ends with: jsonValueEnd, or the + * native compiler's scanner (useNativeJsonValueEnd). */ +let valueEnd: (bytes: Uint8Array, start: number) => number = jsonValueEnd; + +/** Has the decoders find value ends with `scan`, which returns what + * jsonValueEnd returns, or -1 where jsonValueEnd throws. The native compiler + * installs its C scanner (native/ts7-process.c), which skips the strings that + * make up most reply bytes with memchr. */ +export function useNativeJsonValueEnd(scan: (bytes: Uint8Array, start: number) => number): void { + valueEnd = (bytes, start) => { + const end = scan(bytes, start); + if (end < 0) malformedReply(); + return end; + }; +} + /** The id an object reply starts with (`{"id":N`), or -1 when it starts * otherwise. */ function leadingId(bytes: Uint8Array, start: number, end: number): number { @@ -158,7 +174,7 @@ export function decodeSemanticObject( ): R | null { const start = skipJsonSpace(bytes, 0); if (start === bytes.length) return null; - const end = jsonValueEnd(bytes, start); + const end = valueEnd(bytes, start); if (skipJsonSpace(bytes, end) !== bytes.length) malformedReply(); return replyElement(bytes, start, end, cached, build); } @@ -174,7 +190,7 @@ export function decodeSemanticObjects( let index = skipJsonSpace(bytes, 0); if (index === bytes.length) return null; if (bytes[index] !== OPEN_BRACKET) { - const end = jsonValueEnd(bytes, index); + const end = valueEnd(bytes, index); if ( skipJsonSpace(bytes, end) !== bytes.length || parseJsonSlice(bytes, index, end) !== null @@ -189,7 +205,7 @@ export function decodeSemanticObjects( return result; } for (;;) { - const end = jsonValueEnd(bytes, index); + const end = valueEnd(bytes, index); if (end === index) malformedReply(); result.push(replyElement(bytes, index, end, cached, build)); index = skipJsonSpace(bytes, end); diff --git a/packages/compiler/test/native-json-value-end.test.ts b/packages/compiler/test/native-json-value-end.test.ts new file mode 100644 index 000000000..9d9cc3950 --- /dev/null +++ b/packages/compiler/test/native-json-value-end.test.ts @@ -0,0 +1,118 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { jsonValueEnd } from "../src/frontend/ts7/semantic-json.js"; + +const sources = join(import.meta.dirname, "../native"); +const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; + +/** jsonValueEnd's result, or -1 where it throws. */ +function expectedEnd(bytes: Uint8Array, start: number): number { + try { + return jsonValueEnd(bytes, start); + } catch { + return -1; + } +} + +/** A deterministic generator, so a failing case can be reproduced. */ +function random(seed: number): () => number { + let state = seed; + return () => { + state = (state * 1103515245 + 12345) % 2147483648; + return state / 2147483648; + }; +} + +const ALPHABET = ["{", "}", "[", "]", '"', "\\", ",", " ", "\n", "\t", "a", "1", "n", ":", "é"]; + +function cases(): { bytes: Uint8Array; start: number }[] { + const next = random(1); + const out: { bytes: Uint8Array; start: number }[] = []; + const texts = [ + "", + '""', + '"unclosed', + '"\\"', + '"\\\\"', + '"a\\"b"', + '"\\\\\\""', + "null", + "null,", + "123 ", + '{"id":1}', + '{"id":1,"name":"a}b"}', + '[{"id":1},null,{"id":2,"declarations":["1.2./a.ts","3.4./b \\"c\\".ts"]}]', + '{"id":1,"nested":{"list":[1,{"x":"]"}]}}, next', + "{", + "[[]", + "}", + "]", + ",", + " ", + ]; + for (const text of texts) { + const bytes = Buffer.from(text, "utf8"); + for (let start = 0; start <= bytes.length; start++) out.push({ bytes, start }); + } + for (let n = 0; n < 4000; n++) { + let text = ""; + const length = Math.floor(next() * 40); + for (let k = 0; k < length; k++) text += ALPHABET[Math.floor(next() * ALPHABET.length)]; + const bytes = Buffer.from(text, "utf8"); + out.push({ bytes, start: Math.floor(next() * (bytes.length + 1)) }); + } + const reply = JSON.stringify( + Array.from({ length: 200 }, (_, id) => + id % 7 === 0 + ? null + : { id, name: `name ${id} "q" \\ {[`, declarations: [`${id}.80./src/a ${id}.ts`] }, + ), + ); + const bytes = Buffer.from(reply, "utf8"); + for (let start = 0; start < bytes.length; start += 3) out.push({ bytes, start }); + return out; +} + +test("the native scanner finds the value ends jsonValueEnd finds", () => { + const dir = mkdtempSync(join(tempRoot, "scriptc-ts7-json-")); + try { + const harness = join(dir, "scan" + (process.platform === "win32" ? ".exe" : "")); + execFileSync("clang", [ + "-std=c11", + "-Wall", + "-Wextra", + "-Werror", + ...(process.env["SCRIPTC_SAN"] === "1" ? ["-fsanitize=address,undefined"] : []), + join(sources, "ts7-json.test.c"), + join(sources, "ts7-process.c"), + "-o", + harness, + ]); + const all = cases(); + const chunks: Buffer[] = []; + for (const { bytes, start } of all) { + const header = Buffer.alloc(8); + header.writeUInt32LE(bytes.length, 0); + header.writeUInt32LE(start, 4); + chunks.push(header, Buffer.from(bytes)); + } + const output = execFileSync(harness, { input: Buffer.concat(chunks), encoding: "utf8" }); + const actual = output.trim().split(/\r?\n/).map(Number); + expect(actual.length).toBe(all.length); + for (let k = 0; k < all.length; k++) { + const { bytes, start } = all[k]!; + const expected = expectedEnd(bytes, start); + if (actual[k] !== expected) + expect({ text: Buffer.from(bytes).toString("utf8"), start, end: actual[k] }).toEqual({ + text: Buffer.from(bytes).toString("utf8"), + start, + end: expected, + }); + } + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); From 0d016b7554ebdeb9c537ee768a5a89c5fd482704 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 19:57:46 -0500 Subject: [PATCH 008/395] util: runtime format strings, inspect.custom and colors, untyped promisify --- .../limitations/builtin-function-values.mdx | 2 +- .../console-and-process-streams.mdx | 7 + .../src/generated/node-v24-compatibility.json | 12 +- .../generated/node-v24-internal.json | 35 +- internal/compatibility/static-support.json | 8 +- .../ambient/scriptc-node-fallback.d.ts | 57 +- .../compiler/src/backend/llvm/lib-shared.ts | 16 + .../lowering/builtins/promisify-callback.ts | 26 +- .../src/frontend/lowering/call-arguments.ts | 159 ++- .../lowering/expressions/object-literals.ts | 20 +- .../lowering/expressions/property-access.ts | 11 + .../src/frontend/lowering/lower-calls.ts | 30 + .../src/frontend/lowering/lower-classes.ts | 13 + .../src/frontend/lowering/lower-console.ts | 38 +- .../src/frontend/lowering/lower-inspect.ts | 779 +++++++++++- .../src/frontend/lowering/lower-modules.ts | 13 +- .../compiler/src/frontend/lowering/lowerer.ts | 4 + .../src/frontend/lowering/surfaces.ts | 3 +- .../src/frontend/lowering/symbol-fields.ts | 29 + packages/compiler/src/frontend/type-mapper.ts | 24 + packages/compiler/src/ir/builtin-effects.ts | 11 + .../compiler/src/ir/builtin-signatures.ts | 37 +- .../corpus/7520-util-format-runtime.ts.json | 6 + .../corpus/7521-util-format-runtime.cjs.json | 6 + .../corpus/7522-promisify-untyped.cjs.json | 6 + .../corpus/7523-promisify-untyped.ts.json | 6 + .../tests/corpus/7524-inspect-custom.ts.json | 6 + .../tests/corpus/7525-inspect-custom.cjs.json | 6 + .../7526-inspect-options-runtime.ts.json | 6 + .../diagnostics/inspect-custom-fences.ts.json | 6 + packages/runtime/src/scr_console_native.c | 6 +- packages/runtime/src/scr_dyn.h | 4 + packages/runtime/src/scr_inspect.c | 1123 ++++++++++++++++- packages/runtime/src/scr_inspect.h | 71 +- packages/runtime/src/scr_json.c | 18 + packages/runtime/src/scr_symbol.c | 5 + packages/runtime/src/scr_util_callable.c | 166 +-- tests/corpus/7520-util-format-runtime.ts | 110 ++ tests/corpus/7521-util-format-runtime.cjs | 64 + tests/corpus/7522-promisify-untyped.cjs | 64 + tests/corpus/7523-promisify-untyped.ts | 38 + tests/corpus/7524-inspect-custom.ts | 91 ++ tests/corpus/7525-inspect-custom.cjs | 75 ++ tests/corpus/7526-inspect-options-runtime.ts | 62 + tests/diagnostics/inspect-custom-fences.ts | 44 + .../inspect-custom-fences.ts.txt | 49 + .../7520-util-format-runtime.ts.json | 1 + .../7521-util-format-runtime.cjs.json | 1 + .../7522-promisify-untyped.cjs.json | 1 + .../7523-promisify-untyped.ts.json | 1 + .../corpus-costs/7524-inspect-custom.ts.json | 1 + .../corpus-costs/7525-inspect-custom.cjs.json | 1 + .../7526-inspect-options-runtime.ts.json | 1 + tests/harness/errors.test.ts | 47 + 54 files changed, 3101 insertions(+), 325 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json create mode 100644 tests/corpus/7520-util-format-runtime.ts create mode 100644 tests/corpus/7521-util-format-runtime.cjs create mode 100644 tests/corpus/7522-promisify-untyped.cjs create mode 100644 tests/corpus/7523-promisify-untyped.ts create mode 100644 tests/corpus/7524-inspect-custom.ts create mode 100644 tests/corpus/7525-inspect-custom.cjs create mode 100644 tests/corpus/7526-inspect-options-runtime.ts create mode 100644 tests/diagnostics/inspect-custom-fences.ts create mode 100644 tests/harness/__snapshots__/inspect-custom-fences.ts.txt create mode 100644 tests/harness/corpus-costs/7520-util-format-runtime.ts.json create mode 100644 tests/harness/corpus-costs/7521-util-format-runtime.cjs.json create mode 100644 tests/harness/corpus-costs/7522-promisify-untyped.cjs.json create mode 100644 tests/harness/corpus-costs/7523-promisify-untyped.ts.json create mode 100644 tests/harness/corpus-costs/7524-inspect-custom.ts.json create mode 100644 tests/harness/corpus-costs/7525-inspect-custom.cjs.json create mode 100644 tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 0fa62e558..efd45af2f 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -14,7 +14,7 @@ A class can extend `require("events")` directly. Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere. `util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call. -`util.promisify` over an untyped (`any`) function returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`. +`util.promisify` over an untyped function (a value typed `any`, a function whose parameters are untyped, or any other target in JavaScript) returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`, and throws Node's `ERR_INVALID_ARG_TYPE` for values that are not functions. Such a function with other own properties, `util.promisify.custom` among them, throws `SC2020` where `util.promisify` runs, and the wrapper does not report Node's DEP0174 warning when the original returns a promise. `util.styleText` supports standard formats and aliases, `#RGB` and `#RRGGBB` hex colors, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`. diff --git a/docs/content/limitations/console-and-process-streams.mdx b/docs/content/limitations/console-and-process-streams.mdx index c62b82f0c..946cb8061 100644 --- a/docs/content/limitations/console-and-process-streams.mdx +++ b/docs/content/limitations/console-and-process-streams.mdx @@ -1,6 +1,13 @@ `console.log`, `info`, and `debug` write to stdout; `error` and `warn` write to stderr. They accept `unknown`, printing strings verbatim and other values through native `util.inspect`. Direct calls support global `console` and `node:console` imports. +Console output, `util.format`, and `util.formatWithOptions` with a format string known only at run time, spread arguments, or an options object format their arguments at run time. +There, `%s` of a function or of a class instance whose class defines `toString` and `%o` of an object throw `SC2020`. +`util.inspect` with `colors`, with option values known only at run time, or in the `inspect(value, showHidden, depth, colors)` form renders the value's untyped view in the runtime with Node's styles. +Colored output of errors, promises, regular expressions, `DataView`, `ArrayBuffer`, weak collections, iterators, and other native objects throws `SC2020`. +Inspect options other than `depth`, `colors`, `customInspect`, `compact` (a number or `false`), `breakLength`, and `sorted` (a boolean) throw `SC2020` when they are set to a value other than Node's default. +`util.inspect` calls `[util.inspect.custom]` methods of classes and objects with Node's depth, options, and `util.inspect` arguments, and `console.dir` and `customInspect: false` skip them. +A class instance with such a method that is held as an untyped value throws `SC2020` when it is inspected, and a class whose subclasses declare the method without declaring it itself is refused. Stored global console values and detached output methods retain identity and accept ordinary arguments. Stored methods do not support format specifiers with substitution arguments. Other stored console APIs, custom `Console` instances, and console mutation are unsupported. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index c341da852..b94ac47be 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -73027,9 +73027,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -73050,9 +73050,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 9393fcaa8..f95cf3aa7 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -101265,6 +101265,13 @@ "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/5884-inspect-layout-options.ts", "tests/corpus/6428-promisify-callback-functions.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs", + "tests/corpus/7522-promisify-untyped.cjs", + "tests/corpus/7523-promisify-untyped.ts", + "tests/corpus/7524-inspect-custom.ts", + "tests/corpus/7525-inspect-custom.cjs", + "tests/corpus/7526-inspect-options-runtime.ts", "tests/corpus/node24-deep-equality-properties.js", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality.ts", @@ -102369,7 +102376,9 @@ "tests": [ "tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", - "tests/corpus/5835-util-format-integer-specifiers.ts" + "tests/corpus/5835-util-format-integer-specifiers.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs" ] }, "anchorSource": "exact" @@ -102397,7 +102406,9 @@ "tests": [ "tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", - "tests/corpus/5835-util-format-integer-specifiers.ts" + "tests/corpus/5835-util-format-integer-specifiers.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs" ] }, "anchorSource": "exact" @@ -102588,7 +102599,8 @@ "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", - "tests/corpus/5884-inspect-layout-options.ts" + "tests/corpus/5884-inspect-layout-options.ts", + "tests/corpus/7526-inspect-options-runtime.ts" ] }, "anchorSource": "exact" @@ -102611,8 +102623,11 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:util" + "status": "partial", + "evidence": "compiler-feature:util.util.inspect", + "tests": [ + "tests/corpus/7526-inspect-options-runtime.ts" + ] }, "anchorSource": "exact" }, @@ -102634,8 +102649,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:util" + "status": "partial", + "evidence": "compiler-feature:util.custom", + "tests": [ + "tests/corpus/7524-inspect-custom.ts", + "tests/corpus/7525-inspect-custom.cjs" + ] }, "anchorSource": "ancestor" }, @@ -102952,6 +102971,8 @@ "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", + "tests/corpus/7522-promisify-untyped.cjs", + "tests/corpus/7523-promisify-untyped.ts", "tests/harness/project-config.test.ts" ] }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 06aad26cf..b5211c967 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -244,9 +244,11 @@ {"chapter": "worker_threads", "symbols": ["resourceLimits"], "status": "partial", "evidence": ["tests/corpus/5806-worker-resource-limits.ts"]}, {"chapter": "worker_threads", "symbols": ["BroadcastChannel extends EventTarget", "broadcastChannel.close", "broadcastChannel.postMessage", "broadcastChannel.ref", "broadcastChannel.unref", "onmessage"], "status": "partial", "evidence": ["tests/corpus/5800-broadcast-channel.ts", "tests/corpus/5801-broadcast-channel.cjs", "tests/corpus/5802-broadcast-channel-workers.ts"]}, {"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject"], "status": "partial", "evidence": ["tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]}, - {"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts"]}, + {"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/7520-util-format-runtime.ts", "tests/corpus/7521-util-format-runtime.cjs"]}, {"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject", "util.types.isDate", "util.types.isSharedArrayBuffer", "util.types.isMapIterator", "util.types.isSetIterator", "util.types.isKeyObject", "util.types.isNumberObject", "util.types.isStringObject", "util.types.isBooleanObject", "util.types.isBigIntObject", "util.types.isSymbolObject", "util.types.isBoxedPrimitive", "util.types.isBigInt64Array", "util.types.isBigUint64Array", "util.types.isFloat16Array", "util.types.isCryptoKey", "util.types.isExternal"], "status": "partial", "evidence": ["tests/corpus/5831-util-types-brands.ts", "tests/corpus/5832-util-types-brands-dynamic.js", "tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]}, - { "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts"] }, + { "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts", "tests/corpus/7526-inspect-options-runtime.ts"] }, + { "chapter": "util", "signatures": ["`util.inspect(object[, showHidden[, depth[, colors]]])`"], "status": "partial", "evidence": ["tests/corpus/7526-inspect-options-runtime.ts"] }, + { "chapter": "util", "signatures": ["`custom` Type: {symbol} that can be used to declare custom inspect functions."], "status": "partial", "evidence": ["tests/corpus/7524-inspect-custom.ts", "tests/corpus/7525-inspect-custom.cjs"] }, { "chapter": "util", "symbols": ["util.styleText"], "status": "partial", "evidence": ["tests/corpus/node24-style-text.ts", "tests/corpus/node24-style-text-validation.js", "tests/corpus/node24-style-text-environment.js", "tests/corpus/node24-style-text.cjs", "tests/fixtures/node-types/style-text.ts"] }, { "chapter": "util", "symbols": ["util.isDeepStrictEqual"], "status": "partial", "evidence": ["tests/corpus/node24-deep-equality.ts", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality-properties.js", "tests/fixtures/node-types/deep-equality.ts", "packages/compiler/test/deep-equality.test.ts"] }, { "chapter": "deprecations", "symbols": ["util.toUSVString"], "status": "not-applicable" }, @@ -400,7 +402,7 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] }, - { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/corpus/7522-promisify-untyped.cjs", "tests/corpus/7523-promisify-untyped.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] }, { "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] }, { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index b195c3fbb..80054b4fa 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3838,7 +3838,13 @@ declare module "util" { export type TextDecoder = globalThis.TextDecoder; export var TextEncoder: typeof globalThis.TextEncoder; export var TextDecoder: typeof globalThis.TextDecoder; - /* stream.pipeline's overload comes first: its __promisify__ member keeps + /* An untyped function: an `any` value, or a function whose parameters + * are untyped (JavaScript's `(value, callback) => ...`), the only + * parameters that accept this object type. The promisified function is + * untyped too. It comes first because such functions match every other + * overload. */ + export function promisify(fn: (...args: { readonly "scriptc:untyped": true }[]) => unknown): any; + /* stream.pipeline's overload comes next: its __promisify__ member keeps * every other target out of it. */ export function promisify( fn: typeof import("node:stream").pipeline, @@ -3959,15 +3965,56 @@ declare module "util" { ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; /* Declared surface without full lowering (the type-level tolerance * story: guarded/diagnostic-path code must TYPECHECK; a reached use - * without a lowering fences at its site). inspect's options carry the - * documented knobs through an index signature. */ + * without a lowering fences at its site). */ + export interface InspectOptions { + showHidden?: boolean | undefined; + depth?: number | null | undefined; + colors?: boolean | undefined; + customInspect?: boolean | undefined; + showProxy?: boolean | undefined; + maxArrayLength?: number | null | undefined; + maxStringLength?: number | null | undefined; + breakLength?: number | undefined; + compact?: boolean | number | undefined; + sorted?: boolean | ((a: string, b: string) => number) | undefined; + getters?: "get" | "set" | boolean | undefined; + numericSeparator?: boolean | undefined; + } + export type Style = + | "special" + | "number" + | "bigint" + | "boolean" + | "undefined" + | "null" + | "string" + | "symbol" + | "date" + | "regexp" + | "module"; + export interface InspectOptionsStylized extends InspectOptions { + stylize(text: string, styleType: Style): string; + } + export type CustomInspectFunction = ( + depth: number, + options: InspectOptionsStylized, + inspect: typeof import("node:util").inspect, + ) => any; export function inspect( object: unknown, - options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + showHidden?: boolean, + depth?: number | null, + color?: boolean, ): string; + export function inspect(object: unknown, options?: InspectOptions): string; + export namespace inspect { + /* The key of an object's custom inspect function: + * Symbol.for('nodejs.util.inspect.custom'). */ + const custom: unique symbol; + } export function format(format?: unknown, ...args: unknown[]): string; export function formatWithOptions( - inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + inspectOptions: InspectOptions, format?: unknown, ...args: unknown[] ): string; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 0b6041034..b02cf44f3 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1034,6 +1034,22 @@ export const LIB_FN_SYMS: Record = { "dyn.generatorThis": "scr_gen_receiver", "insp.dyn": "scr_insp_dyn", "insp.dynS": "scr_insp_dyn_s", + "insp.format": "scr_insp_format_pack", + "insp.formatPrimitives": "scr_insp_format_primitives", + "insp.formatWithOptions": "scr_insp_format_with_options", + "insp.inspect": "scr_insp_inspect", + "insp.customEnabled": "scr_insp_custom_enabled", + "insp.registerCustom": "scr_insp_register_custom", + "insp.customInspect": "scr_insp_custom_inspect", + "insp.depthKind": "scr_insp_depth_kind", + "insp.customDepth": "scr_insp_custom_depth", + "insp.customOptions": "scr_insp_custom_options", + "insp.inspectFunction": "scr_insp_inspect_function", + "insp.customText": "scr_insp_custom_text", + "insp.customIsSelf": "scr_insp_custom_is_self", + "insp.customResult": "scr_insp_custom_result", + "insp.formatSpecifies": "scr_insp_format_specifies", + "insp.formatSpecifiesDyn": "scr_insp_format_specifies_dyn", "fs.readFileSyncDyn": "scr_fs_read_file_sync_dyn", "fs.readFileEncoded": "scr_fs_read_file_encoded", "fs.readFdEncoded": "scr_fs_read_fd_encoded", diff --git a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts index b4dbbf513..d3378c8ec 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts @@ -1,7 +1,8 @@ import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; -import { varRef } from "../../../ir/build.js"; +import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; +import { isJsSourceFile } from "../../program.js"; import { BOOL, DYN, @@ -92,12 +93,22 @@ function callbackShape( return { fnT, resultT, args, callbackT, callbackParamT: callbackParamT!, inner, error }; } -/** Whether `promisify(fn)`'s one argument is typed `any`: the function is - * only known at run time, so the promisified function is built there. */ +/** Whether `promisify(fn)`'s one argument is only known at run time, so + * the promisified function is built there: a value typed `any`, a function + * with untyped parameters (the declarations type that call `any`), or any + * argument in JavaScript, whose inferred types do not describe the + * function Node receives (it validates the argument and reads its own + * properties). */ export function untypedPromisifyArgument(lowerer: Lowerer, call: ts.CallExpression): boolean { - if (call.questionDotToken || call.arguments.nodes.length !== 1) return false; - const argNode = call.arguments.nodes[0]!; - return !ts.isSpreadElement(argNode) && (lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0; + if (call.questionDotToken || call.arguments.nodes.length > 1) return false; + const argNode = call.arguments.nodes[0]; + if (argNode !== undefined && ts.isSpreadElement(argNode)) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(call))) return true; + if (argNode === undefined) return false; + return ( + (lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0 || + (lowerer.typeOf(call).flags & ts.TypeFlags.Any) !== 0 + ); } /** The callback shape of a `promisify(fn)` call, or null. */ @@ -144,10 +155,11 @@ export function lowerCallbackPromisify( if (shape === null) { // An untyped (`any`) function: Node's promisify at run time. if (!untypedPromisifyArgument(lowerer, call)) return null; + const argNode = call.arguments.nodes[0]; return { kind: "libCall", fn: "util.promisifyDyn", - args: [lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN)], + args: [argNode ? lowerer.lowerExprExpecting(argNode, DYN) : dynUndefinedExpr(loc)], type: DYN, loc, }; diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 060ffe9d2..7dc302365 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -56,6 +56,95 @@ function emptyTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { return restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx)?.length === 0; } +/** A call's arguments as one checked-dynamic array, built in source order + * into a fresh hidden local: each argument converts to dyn, and a spread + * contributes the values its iteration yields (holes as undefined). + * `lowerSpread` lowers a spread's operand; the default lowers it here. */ +export function dynArgumentPack( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, + options: { + /** Pre-lowered values pushed before the arguments. */ + leading?: readonly IrExpr[] | undefined; + blame?: ts.Node | undefined; + lowerSpread?: (arg: ts.SpreadElement) => IrExpr; + } = {}, +): { stmts: IrStmt[]; pack: () => IrExpr } { + const blame = options.blame ?? argNodes[0]!; + const lowerSpread = + options.lowerSpread ?? ((arg: ts.SpreadElement) => lowerer.lowerExpr(arg.expression)); + const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN); + const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc }); + const stmts: IrStmt[] = [ + { + kind: "varDecl", + localId: pack.id, + init: { kind: "dynArrLit", elems: [], type: DYN, loc }, + loc, + }, + ]; + const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => { + stmts.push({ + kind: "exprStmt", + expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc }, + loc: value.loc, + }); + }; + for (const value of options.leading ?? []) + push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush"); + const spreads = argNodes.filter(ts.isSpreadElement).length; + argNodes.forEach((arg, index) => { + if (!ts.isSpreadElement(arg)) { + push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush"); + return; + } + let value = lowerSpread(arg); + if (value.type.kind === "set") { + value = { + kind: "setIntrinsic", + method: "toArray", + receiver: value, + args: [], + type: arrayOf(value.type.elem), + loc: value.loc, + }; + } else if (value.type.kind === "object") { + value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value; + } + if (value.type.kind === "array") { + // Argument iteration materializes holes as present undefined + // before conversion to the dense checked-dynamic argument pack. + value = { + kind: "arrayLit", + elems: [value], + spreads: [0], + type: value.type, + loc: value.loc, + }; + } + value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); + if (spreads === 1 && index === argNodes.length - 1) { + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.packPushSpread", + args: [ + ref(), + value, + { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, + ], + type: VOID, + loc, + }, + loc, + }); + } else push(value, "dyn.packPushSpreadIter"); + }); + return { stmts, pack: ref }; +} + export function completeArgs( lowerer: Lowerer, argNodes: readonly ts.Expression[], @@ -96,72 +185,10 @@ export function completeArgs( // Runtime-length spreads determine the complete argument list before // any parameter default runs. Build it once in source order, then // extract the fixed native ABI slots; missing elements are undefined. - const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN); - const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc }); - const stmts: IrStmt[] = [ - { - kind: "varDecl", - localId: pack.id, - init: { kind: "dynArrLit", elems: [], type: DYN, loc }, - loc, - }, - ]; - const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => { - stmts.push({ - kind: "exprStmt", - expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc }, - loc: value.loc, - }); - }; - for (const value of leading ?? []) push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush"); - const spreads = argNodes.filter(ts.isSpreadElement).length; - argNodes.forEach((arg, index) => { - if (!ts.isSpreadElement(arg)) { - push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush"); - return; - } - let value = lowerSpread(arg); - if (value.type.kind === "set") { - value = { - kind: "setIntrinsic", - method: "toArray", - receiver: value, - args: [], - type: arrayOf(value.type.elem), - loc: value.loc, - }; - } else if (value.type.kind === "object") { - value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value; - } - if (value.type.kind === "array") { - // Argument iteration materializes holes as present undefined - // before conversion to the dense checked-dynamic argument pack. - value = { - kind: "arrayLit", - elems: [value], - spreads: [0], - type: value.type, - loc: value.loc, - }; - } - value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); - if (spreads === 1 && index === argNodes.length - 1) { - stmts.push({ - kind: "exprStmt", - expr: { - kind: "libCall", - fn: "dyn.packPushSpread", - args: [ - ref(), - value, - { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, - ], - type: VOID, - loc, - }, - loc, - }); - } else push(value, "dyn.packPushSpreadIter"); + const { stmts, pack: ref } = dynArgumentPack(lowerer, argNodes, loc, { + leading, + blame, + lowerSpread, }); const args = shapes.map((shape, index): IrExpr => { if (shape.mode === "arguments") return ref(); diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 4d04be30a..43322246c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -33,7 +33,11 @@ import { } from "../../type-mapper.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { tryLowerExpression } from "./try-lower-expression.js"; -import { fenceSymbolFieldCopy } from "../symbol-fields.js"; +import { + classSymbolKeyOf, + fenceSymbolFieldCopy, + symbolFieldDisplayName, +} from "../symbol-fields.js"; import { planUnionRetag } from "../union-retag.js"; import { jsHoldsDynObject, runtimeOptionalStorageOperand } from "../lower-exprs.js"; import { builtinGlobalValue } from "./property-access.js"; @@ -194,7 +198,19 @@ function propNameText(lowerer: Lowerer, name: ts.PropertyName): string { * (NamedEvaluation): an identifier, string or numeric key, or a computed * key that folds to one string. Null for other keys. */ function propertyFunctionName(lowerer: Lowerer, name: ts.PropertyName): string | null { - if (ts.isComputedPropertyName(name)) return literalComputedKey(lowerer, name); + if (ts.isComputedPropertyName(name)) { + const literal = literalComputedKey(lowerer, name); + if (literal !== null) return literal; + // A symbol key names the function `[description]`. + let key = name.expression; + while (ts.isParenthesizedExpression(key)) key = key.expression; + const symbol = classSymbolKeyOf(lowerer, key); + if (symbol) return `[${symbolFieldDisplayName(symbol.fieldName).slice("Symbol(".length, -1)}]`; + const wellKnown = ts.isPropertyAccessExpression(key) + ? lowerer.stdlibGlobalMember(key, "Symbol") + : null; + return wellKnown ? `[Symbol.${wellKnown}]` : null; + } if (ts.isNumericLiteral(name)) return String(Number(name.text)); if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text; return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 5df3c7fe0..9157c551d 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -5,6 +5,7 @@ import * as ts from "../../ts7/adapter.js"; import { lowerHistogramProperty } from "../builtins/performance.js"; import { staticFunctionNameRead } from "../static-function-names.js"; import { typedArgumentsArity } from "../typed-arguments.js"; +import { INSPECT_CUSTOM_DESCRIPTION, isInspectCustomAccess } from "../symbol-fields.js"; import { isThisParameter } from "../call-signatures.js"; import { checkedPrivateValue, @@ -310,6 +311,16 @@ export function lowerPropertyAccessExpr( const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION); if (navigator) return navigator; } + // util.inspect.custom: Symbol.for('nodejs.util.inspect.custom'). + if (isInspectCustomAccess(lowerer, expr)) { + return { + kind: "libCall", + fn: "sym.for", + args: [{ kind: "strLit", value: INSPECT_CUSTOM_DESCRIPTION, type: STRING, loc }], + type: SYMBOL_T, + loc, + }; + } // A dgram socket's method read as a value in JavaScript (`emit: // socket.emit.bind(socket)`): the socket crosses as its checked-dynamic // handle, whose methods are function values that dispatch on their diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c0406e77b..711cd7379 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -267,6 +267,28 @@ import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generati const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); +/** The node:util member a `(0, member)` callee names, or null. */ +function indirectUtilCalleeOf( + lowerer: Lowerer, + callee: ts.Expression, +): { module: string; member: string } | null { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.CommaToken || + !ts.isNumericLiteral(e.left) + ) + return null; + const target = e.right; + const bi = ts.isIdentifier(target) + ? lowerer.builtinImportOf(target) + : ts.isPropertyAccessExpression(target) && !target.questionDotToken + ? lowerer.builtinMemberOf(target) + : null; + return bi?.module === "util" ? bi : null; +} + export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); // Object's checker return type is `any`. Claim known object inputs @@ -295,6 +317,14 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression); if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration); } + // `(0, util_1.inspect)(value)`: TypeScript's CommonJS output calls a + // named import through a comma expression, which leaves the call's + // receiver undefined. node:util's functions do not read their receiver, + // so the call lowers as the direct one. + { + const util = indirectUtilCalleeOf(lowerer, expr.expression); + if (util) return lowerBuiltinCall(lowerer, expr, util, loc); + } // A package shim's runtime intrinsic (a bodiless `declare function` // named scriptcUnixDgram* in a shipped shim): a direct runtime call. if (ts.isIdentifier(expr.expression) && isShimIntrinsicName(expr.expression.text)) { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b23697cab..d4875c9df 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -155,6 +155,7 @@ import { } from "./lower-event-emitter.js"; import { declSymbolOf } from "./lower-modules.js"; import { + INSPECT_CUSTOM_MEMBER, classSymbolKeyOf, classSymbolKeyOfSymbol, symbolFieldDisplayName, @@ -3208,6 +3209,15 @@ export function collectClassShapeInner( symbolMethods.set(key.identity, mName); } } + // Error values held as untyped values render through their error + // encoding, which keeps no compiled methods to call. + if (mName === INSPECT_CUSTOM_MEMBER && errorRooted) { + lowerer.unsupported( + "SC1090", + member.name, + "[util.inspect.custom] methods on Error subclasses", + ); + } // Generator calls use their spawn wrapper. Public methods are // direct calls only when no subclass overrides the method; // override chains involving generators fence below. @@ -4422,6 +4432,9 @@ export function collectClassShapeInner( ...(methods.size > 0 ? { methods: [...methods.keys()] } : {}), ...((): { prototypeNames?: string[] } => { const names = classPrototypeNames(decl); + // A [util.inspect.custom] method, by its reserved slot name: the + // checked-dynamic inspect walk refuses instances that have one. + if (methods.has(INSPECT_CUSTOM_MEMBER)) names.push(INSPECT_CUSTOM_MEMBER); return names.length > 0 ? { prototypeNames: names } : {}; })(), ...(abstractClass ? { abstract: true as const } : {}), diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index eec308342..db4d39e70 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -14,7 +14,14 @@ import { boolLit, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/buil import { locOf } from "../program.js"; import { type ConsoleMethodMember } from "./builtins/console.js"; import { jsFuncNameOf, type Lowerer } from "./lowerer.js"; -import { lowerConsoleInspectArg, lowerFormatCall, lowerInspectValue } from "./lower-inspect.js"; +import { + heldFormatArgs, + lowerConsoleInspectArg, + lowerFormatCall, + lowerInspectValue, + lowerRuntimeFormat, + runtimeFormatString, +} from "./lower-inspect.js"; import { streamSidesOf } from "./lower-stream.js"; /** node:console Console instances: a native handle (the checked-dynamic @@ -357,9 +364,32 @@ export function lowerConsoleOutput( ) { return consoleWrite(stdout, [lowerFormatCall(lowerer, expr, loc, false)], loc); } - const args = expr.arguments.nodes.map((a) => - lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc), - ); + const nodes = expr.arguments.nodes; + // Spread arguments and format strings that may hold a substitution at + // run time go through the runtime formatter. A string first argument + // without a '%' keeps the space-joined write below. + if (nodes.some(ts.isSpreadElement)) + return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); + const runtime = + nodes.length > 1 && !isStringLiteralLike(first) ? runtimeFormatString(lowerer, first!) : null; + if (runtime === "other") + return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); + if (runtime !== null) { + const held = heldFormatArgs(lowerer, nodes, loc); + if (!held.joinable) return seq(held.stmts, consoleWrite(stdout, [held.format()], loc), loc); + const joined = held.args.map((arg) => + lowerConsoleArg(lowerer, arg.node, arg.ref(), surface, loc), + ); + const write: IrStmt = { + kind: "if", + cond: held.specifies, + then: [{ kind: "exprStmt", expr: consoleWrite(stdout, [held.format()], loc), loc }], + else_: [{ kind: "exprStmt", expr: consoleWrite(stdout, joined, loc), loc }], + loc, + }; + return seq([...held.stmts, write], noop(loc), loc); + } + const args = nodes.map((a) => lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc)); return consoleWrite(stdout, args, loc); } diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 47f490c7b..dd950a2a2 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -14,13 +14,15 @@ import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { unsignedHex } from "../../format-integer.js"; import type { Lowerer } from "./lowerer.js"; -import { isJsSourceFile } from "../program.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { noLoweringDiag } from "../../diagnostics/diagnostic.js"; import { BOOL, DYN, F64, type IrExpr, type IrFunction, + type IrLocal, type IrStmt, type IrStrIntrinsicMethod, type IrType, @@ -32,6 +34,7 @@ import { type SrcLoc, canConvertToDyn, canDynCheckTo, + funcOf, CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, @@ -42,15 +45,16 @@ import { shapeHasAccessorSlots, typeKey, } from "../../ir/ir.js"; -import type { ClassInfo } from "./lower-classes.js"; +import { accessorCall, type ClassInfo } from "./lower-classes.js"; import { isSafeToDiscard, isSafeToRepeat } from "./expressions/evaluation-safety.js"; -import { symbolFieldDisplayName } from "./symbol-fields.js"; +import { INSPECT_CUSTOM_MEMBER, symbolFieldDisplayName } from "./symbol-fields.js"; import { eventTargetRootOf } from "./lower-event-target.js"; import { dateRootOf } from "./builtins/date-classes.js"; import { lowerSystemErrorMap } from "./lower-system-errors.js"; import { isPerfEntryType, perfEntryJson } from "./builtins/performance.js"; import { recordMayKeepSource, recordSourceTest } from "./record-sources.js"; import { nullProtoRecordShape, recordNullProtoTest } from "./null-prototype.js"; +import { dynArgumentPack } from "./call-arguments.js"; /* ── IR construction shorthand ───────────────────────────────────────── */ function concatAll(parts: IrExpr[], loc: SrcLoc): IrExpr { @@ -805,6 +809,8 @@ function inspectSupport( case "object": { const info = lowerer.classes.get(t.className); if (!info) return `class '${t.className}' has no inspect lowering`; + const custom = customInspectReason(lowerer, info); + if (custom !== null) return custom; // EventTarget and Event subclasses render through Node's inspectors // for those classes (the runtime's capsule view), and so do classes // declared `extends Date`. @@ -900,6 +906,8 @@ export function inspectExpr( recurse: IrExpr, depth: IrExpr, loc: SrcLoc, + /** The instance's own rendering, without its custom inspect method. */ + ownRendering = false, ): IrExpr { // A Dirent row renders through its dyn view: Node's `Dirent { ... }` // with the Symbol(type) key. @@ -960,6 +968,15 @@ export function inspectExpr( loc, }; case "object": + if (!ownRendering && customInspectMethodOf(lowerer, t.className)) { + return { + kind: "call", + callee: customInspectHelper(lowerer, t, loc), + args: [value, recurse, depth], + type: STRING, + loc, + }; + } if ( eventTargetRootOf(lowerer.classes.get(t.className)) !== null || dateRootOf(lowerer.classes.get(t.className)) !== null @@ -1139,6 +1156,258 @@ function classInspectsAddedProperties(lowerer: Lowerer, info: ClassInfo): boolea ); } +/* ── custom inspect methods ([util.inspect.custom]) ──────────────────── */ + +/** The `[util.inspect.custom]` method a class's instances have, declared + * on the class or a base; null when they have none. */ +function customInspectMethodOf(lowerer: Lowerer, className: string) { + const info = lowerer.classes.get(className); + return info ? (lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER) ?? null) : null; +} + +/** Why instances of a class with a custom inspect method have no static + * rendering, or null. Node calls the method with the remaining depth, the + * options and util.inspect; the method's parameters receive them as + * checked-dynamic values, and its result renders in the instance's place. */ +function customInspectReason(lowerer: Lowerer, info: ClassInfo): string | null { + const found = lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER); + if (!found) { + const below = (c: ClassInfo): boolean => + c.subclasses.some((s) => s.methods.has(INSPECT_CUSTOM_MEMBER) || below(s)); + return below(info) + ? "classes whose subclasses declare [util.inspect.custom] have no inspect lowering yet (declare the method on the class itself)" + : null; + } + if (found.sig.params.length > 3) + return "[util.inspect.custom] methods with more than three parameters have no inspect lowering"; + for (const param of found.sig.params) { + if ( + param.type.kind !== "dyn" && + !canDynCheckTo( + param.type, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return `[util.inspect.custom] methods with a '${lowerer.fmt(param.type)}' parameter have no inspect lowering`; + } + const ret = found.sig.ret; + if (ret.kind === "object") + return "[util.inspect.custom] methods that return a class instance have no inspect lowering"; + if ( + ret.kind !== "string" && + ret.kind !== "dyn" && + ret.kind !== "void" && + inspectSupport(lowerer, ret, new Set(), { recursive: false }) !== null + ) + return `[util.inspect.custom] methods returning '${lowerer.fmt(ret)}' have no inspect lowering`; + return null; +} + +/** Whether values of `t` can hold a checked-dynamic value, whose own custom + * inspect functions the runtime walk calls. */ +function typeReachesDyn(lowerer: Lowerer, t: IrType): boolean { + const seen = new Set(); + const walk = (u: IrType): boolean => { + if (u.kind === "dyn") return true; + const k = typeKey(u); + if (seen.has(k)) return false; + seen.add(k); + return typeConstituents(lowerer, u).some(walk); + }; + return walk(t); +} + +/** Whether values of `t` can hold an instance of a class with a custom + * inspect method. */ +export function typeReachesCustomInspect(lowerer: Lowerer, t: IrType): boolean { + const seen = new Set(); + const walk = (u: IrType): boolean => { + const k = typeKey(u); + if (seen.has(k)) return false; + seen.add(k); + if (u.kind === "object" && customInspectMethodOf(lowerer, u.className)) return true; + return typeConstituents(lowerer, u).some(walk); + }; + return walk(t); +} + +/** `%util.inspCustom.(v, r, d): string` — formatValue's custom hook + * over a class whose instances have a `[util.inspect.custom]` method: + * with ctx.customInspect set, the method's result (a string re-indented + * at the current level, the instance itself rendered normally, any other + * value rendered in its place); otherwise the instance's own rendering. */ +function customInspectHelper( + lowerer: Lowerer, + t: IrType & { kind: "object" }, + loc: SrcLoc, +): string { + const key = `inspCustom:${typeKey(t)}`; + const existing = lowerer.inspectHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%util.inspCustom", key); + lowerer.inspectHelpers.set(key, name); + const found = customInspectMethodOf(lowerer, t.className)!; + const v = (): IrExpr => varRef("v.0", t, loc); + const r = (): IrExpr => varRef("r.0", F64, loc); + const d = (): IrExpr => varRef("d.0", F64, loc); + const ret = (value: IrExpr): IrStmt => ({ kind: "return", value, loc }); + const normal = (): IrExpr => inspectExpr(lowerer, t, v(), r(), d(), loc, true); + const runtimeArgs: IrExpr[] = [ + { kind: "libCall", fn: "insp.customDepth", args: [r(), d()], type: DYN, loc }, + { kind: "libCall", fn: "insp.customOptions", args: [d()], type: DYN, loc }, + { kind: "libCall", fn: "insp.inspectFunction", args: [], type: DYN, loc }, + ]; + const args = found.sig.params.map((param, i) => + lowerer.coerceToExpected(runtimeArgs[i]!, param.type), + ); + const resultT = found.sig.ret; + const call = accessorCall(lowerer, t.className, INSPECT_CUSTOM_MEMBER, v(), args, resultT, loc); + const locals: IrLocal[] = [ + { id: "v.0", name: "v", type: t, mutable: false }, + { id: "r.0", name: "r", type: F64, mutable: false }, + { id: "d.0", name: "d", type: F64, mutable: false }, + ]; + const render: IrStmt[] = []; + if (resultT.kind === "string") { + render.push(ret({ kind: "libCall", fn: "insp.customText", args: [call], type: STRING, loc })); + } else if (resultT.kind === "dyn") { + locals.push({ id: "res.0", name: "res", type: DYN, mutable: false }); + const res = (): IrExpr => varRef("res.0", DYN, loc); + render.push( + { kind: "varDecl", localId: "res.0", init: call, loc }, + { + kind: "if", + cond: { + kind: "libCall", + fn: "insp.customIsSelf", + args: [res(), { kind: "dynFrom", value: v(), liveRef: true, type: DYN, loc }], + type: BOOL, + loc, + }, + then: [ret(normal())], + else_: null, + loc, + }, + ret({ kind: "libCall", fn: "insp.customResult", args: [res(), r(), d()], type: STRING, loc }), + ); + } else if (resultT.kind === "void") { + render.push({ kind: "exprStmt", expr: call, loc }, ret(strLit("undefined", loc))); + } else { + render.push(ret(inspectExpr(lowerer, resultT, call, r(), d(), loc))); + } + lowerer.liftedFns.push({ + name, + params: [ + { localId: "v.0", name: "v", type: t }, + { localId: "r.0", name: "r", type: F64 }, + { localId: "d.0", name: "d", type: F64 }, + ], + returnType: STRING, + locals, + body: [ + { + kind: "if", + cond: { kind: "libCall", fn: "insp.customEnabled", args: [], type: BOOL, loc }, + then: render, + else_: null, + loc, + }, + ret(normal()), + ], + loc, + }); + return name; +} + +/** `%util.inspCustomDyn.(self, depth, options, inspect): dyn` — a + * class's custom inspect method for its instances held as checked-dynamic + * values: the runtime's inspect walk calls it with the instance's typed + * ref and the method's three arguments. */ +function customInspectDynHelper( + lowerer: Lowerer, + t: IrType & { kind: "object" }, + loc: SrcLoc, +): string { + const name = keyedHelperName(lowerer, "%util.inspCustomDyn", typeKey(t)); + const found = customInspectMethodOf(lowerer, t.className)!; + const param = (id: string): IrExpr => varRef(id, DYN, loc); + const ids = ["depth.0", "options.0", "inspect.0"]; + const call = accessorCall( + lowerer, + t.className, + INSPECT_CUSTOM_MEMBER, + varRef("v.0", t, loc), + found.sig.params.map((p, i) => lowerer.coerceToExpected(param(ids[i]!), p.type)), + found.sig.ret, + loc, + ); + const body: IrStmt[] = [ + { kind: "varDecl", localId: "v.0", init: lowerer.coerceToExpected(param("self.0"), t), loc }, + ]; + if (found.sig.ret.kind === "void") { + body.push( + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: dynUndefinedExpr(loc), loc }, + ); + } else { + body.push({ kind: "return", value: lowerer.coerceToExpected(call, DYN), loc }); + } + const params = ["self.0", ...ids]; + lowerer.liftedFns.push({ + name, + params: params.map((id) => ({ localId: id, name: id.slice(0, -2), type: DYN })), + returnType: DYN, + locals: [ + ...params.map((id) => ({ id, name: id.slice(0, -2), type: DYN, mutable: false })), + { id: "v.0", name: "v", type: t, mutable: false }, + ], + body, + loc, + }); + return name; +} + +/** The statements %main runs first, in every thread: each class whose + * instances have a custom inspect method registers its compiled call + * under its instances' typed-ref type key (scr_insp_register_custom). */ +export function customInspectRegistrations(lowerer: Lowerer, loc: SrcLoc): IrStmt[] { + const stmts: IrStmt[] = []; + for (const info of lowerer.classes.values()) { + if (info.def.runtime || info.def.abstract || isErrorClass(lowerer, info.def.name)) continue; + if (!lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER)) continue; + if (customInspectReason(lowerer, info) !== null) continue; + const t = { kind: "object" as const, className: info.def.name }; + const fn = customInspectDynHelper(lowerer, t, loc); + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "insp.registerCustom", + args: [ + strLit(typeKey(t), loc), + { + kind: "dynFrom", + value: { + kind: "closure", + fnName: fn, + captures: [], + type: funcOf([DYN, DYN, DYN, DYN], DYN), + loc, + }, + type: DYN, + loc, + }, + ], + type: VOID, + loc, + }, + loc, + }); + } + return stmts; +} + /* ── the interned per-type helper ────────────────────────────────────── */ /** `%util.insp.(v, r, d): string` — formatValue over the static type: @@ -2213,13 +2482,22 @@ function isDataViewTypeName(tname: string): boolean { /* ── the callsite: options, direct function/class names ──────────────── */ /** The parsed options literal: the resolved depth (numeric; Infinity for - * `null`), breakLength (numeric; default 80) and compact (numeric, 0 for + * `null`), breakLength (numeric; default 80) and compact (numeric, -1 for * false; default 3), with every other non-default knob fenced by name. */ interface InspectOptions { depth: number; breakLength: number; compact: number; sorted: boolean; + /** Set by the `atRunTime` parse: colors or a computed knob value, which + * the runtime reads. */ + runtime: boolean; + /** How the depth option was spelled, for custom inspect methods: 0 a + * number (also when absent), 1 null, 2 undefined. */ + depthKind: number; + /** customInspect: false skips [util.inspect.custom] methods (console.dir's + * default). */ + customInspect: boolean; } function parseInspectOptions( @@ -2227,9 +2505,18 @@ function parseInspectOptions( node: ts.Expression | undefined, recursive: boolean, dir = false, + atRunTime = false, ): InspectOptions { const surface = dir ? "console.dir" : "util.inspect"; - const options: InspectOptions = { depth: 2, breakLength: 80, compact: 3, sorted: false }; + const options: InspectOptions = { + depth: 2, + breakLength: 80, + compact: 3, + sorted: false, + runtime: false, + depthKind: 0, + customInspect: !dir, + }; if (!node || (ts.isIdentifier(node) && node.text === "undefined")) return options; if (!ts.isObjectLiteralExpression(node)) { lowerer.noLowering( @@ -2240,6 +2527,11 @@ function parseInspectOptions( } for (const prop of node.properties.nodes) { if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) { + // `{ depth }` and `{ ...base }` hold values known at run time. + if (atRunTime && (ts.isShorthandPropertyAssignment(prop) || ts.isSpreadAssignment(prop))) { + options.runtime = true; + continue; + } lowerer.noLowering( `${surface} options in this form`, prop, @@ -2269,8 +2561,17 @@ function parseInspectOptions( // bounded by the (finite) object graph — exactly Node. void recursive; options.depth = Infinity; - } else if (!(ts.isIdentifier(value) && value.text === "undefined")) { + options.depthKind = value.kind === ts.SyntaxKind.NullKeyword ? 1 : 0; + } else if (ts.isIdentifier(value) && value.text === "undefined") { + // Node's ctx.depth is undefined: no depth limit. + options.depth = Infinity; + options.depthKind = 2; + } else { const n = numeric(); + if (n === null && atRunTime) { + options.runtime = true; + continue; + } if (n === null) { lowerer.noLowering( `${surface} with a non-literal depth option`, @@ -2281,7 +2582,8 @@ function parseInspectOptions( options.depth = n; } } else if (key === "colors") { - if (value.kind !== ts.SyntaxKind.FalseKeyword) { + if (value.kind !== ts.SyntaxKind.FalseKeyword && atRunTime) options.runtime = true; + else if (value.kind !== ts.SyntaxKind.FalseKeyword) { lowerer.noLowering( `${surface} with colors: true`, value, @@ -2293,6 +2595,10 @@ function parseInspectOptions( // compact of at least 1; false and smaller numbers break every // composite onto its own lines. compact: true is the older layout. const n = value.kind === ts.SyntaxKind.FalseKeyword ? 0 : numeric(); + if (n === null && atRunTime && value.kind !== ts.SyntaxKind.TrueKeyword) { + options.runtime = true; + continue; + } if (n === null || Number.isNaN(n)) { lowerer.noLowering( `${surface} with this compact option`, @@ -2300,9 +2606,22 @@ function parseInspectOptions( "compact must be false or a numeric literal (compact: true has no lowering)", ); } - options.compact = Math.max(n, 0); + // false is -1: the same layout as 0, and custom inspect functions + // read it back as false. + options.compact = value.kind === ts.SyntaxKind.FalseKeyword ? -1 : Math.max(n, 0); } else if (key === "sorted") { - if (value.kind !== ts.SyntaxKind.TrueKeyword && value.kind !== ts.SyntaxKind.FalseKeyword) { + const sortedLiteral = + value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword; + if ( + !sortedLiteral && + atRunTime && + !ts.isArrowFunction(value) && + !ts.isFunctionExpression(value) + ) { + options.runtime = true; + continue; + } + if (!sortedLiteral) { lowerer.noLowering( `${surface} with this sorted option`, value, @@ -2312,6 +2631,10 @@ function parseInspectOptions( options.sorted = value.kind === ts.SyntaxKind.TrueKeyword; } else if (key === "breakLength") { const n = ts.isIdentifier(value) && value.text === "Infinity" ? Infinity : numeric(); + if (n === null && atRunTime) { + options.runtime = true; + continue; + } if (n === null || Number.isNaN(n)) { lowerer.noLowering( `${surface} with a non-literal breakLength option`, @@ -2320,6 +2643,12 @@ function parseInspectOptions( ); } options.breakLength = n; + } else if ( + !dir && + key === "customInspect" && + (value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword) + ) { + options.customInspect = value.kind === ts.SyntaxKind.TrueKeyword; } else if (dir && key === "customInspect") { // console.dir's own default; only that value is lowered. if (value.kind !== ts.SyntaxKind.FalseKeyword) { @@ -2417,16 +2746,86 @@ function directCallableInspect(lowerer: Lowerer, node: ts.Expression, loc: SrcLo return null; } -/** util.inspect(value[, options]) — the spoke's inspect entry. */ +/** util.inspect(value[, options]) and inspect(value, showHidden, depth, + * colors) — the spoke's inspect entry. Options the compiler resolves + * render through the static helpers; colors, option values known only at + * run time and the legacy forms render the value's checked-dynamic view + * in the runtime (insp.inspect). */ function lowerInspectCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { - if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { + const args = expr.arguments.nodes; + if (args.length < 1 || args.length > 4 || args.some(ts.isSpreadElement)) { lowerer.noLowering( - `util.inspect with ${expr.arguments.nodes.length} arguments`, + `util.inspect with ${args.some(ts.isSpreadElement) ? "spread" : args.length} arguments`, expr, - "the supported forms are inspect(value) and inspect(value, options)", + "the supported forms are inspect(value), inspect(value, options) and inspect(value, showHidden, depth, colors)", ); } - return lowerInspectValue(lowerer, expr.arguments.nodes[0]!, expr.arguments.nodes[1], loc, false); + if (args.length <= 2 && !inspectOptionsAtRunTime(lowerer, args[1])) + return lowerInspectValue(lowerer, args[0]!, args[1], loc, false); + return lowerRuntimeInspect(lowerer, args, loc); +} + +/** Whether an options argument needs the runtime reader: an options value + * that is not an object literal, or a literal with colors or a computed + * depth, compact, breakLength or sorted value. Literal options otherwise + * keep the static parse, which also reports the literal options neither + * path lowers. */ +function inspectOptionsAtRunTime(lowerer: Lowerer, node: ts.Expression | undefined): boolean { + if (!node || (ts.isIdentifier(node) && node.text === "undefined")) return false; + if (!ts.isObjectLiteralExpression(node)) return true; + return parseInspectOptions(lowerer, node, false, false, true).runtime; +} + +/** util.inspect over the value's checked-dynamic view, with every argument + * evaluated in order and read in the runtime as Node's inspect reads it. */ +function lowerRuntimeInspect( + lowerer: Lowerer, + args: readonly ts.Expression[], + loc: SrcLoc, +): IrExpr { + const valueNode = args[0]!; + if ( + directCallableInspect(lowerer, valueNode, loc) !== null || + ts.isArrowFunction(valueNode) || + ts.isFunctionExpression(valueNode) + ) { + lowerer.noLowering( + "util.inspect of a function or class with these options", + valueNode, + "pass the options as a literal without colors", + ); + } + const value = lowerer.lowerExpr(valueNode); + if (value.type.kind !== "dyn") { + const reason = inspectSupport(lowerer, value.type, new Set(), { recursive: false }); + if (reason !== null) + lowerer.noLowering(`util.inspect of '${lowerer.fmt(value.type)}' values`, valueNode, reason); + if (!lowerer.dynConvertible(value.type)) + lowerer.noLowering( + `util.inspect with these options of '${lowerer.fmt(value.type)}' values`, + valueNode, + "the options are read at run time, where the value renders through its checked-dynamic view", + ); + } + const stmts: IrStmt[] = []; + const held = (init: IrExpr): IrExpr => { + const local = lowerer.declareHiddenLocal("%inspectArg", DYN); + stmts.push({ kind: "varDecl", localId: local.id, init, loc }); + return varRef(local.id, DYN, loc); + }; + const operands: IrExpr[] = [held(lowerer.coerceToExpected(value, DYN))]; + for (let i = 1; i < 4; i++) { + const node = args[i]; + operands.push(node ? held(lowerer.lowerExprExpecting(node, DYN)) : dynUndefinedExpr(loc)); + } + operands.push(numLit(args.length, loc)); + return { + kind: "seqExpr", + stmts, + result: { kind: "libCall", fn: "insp.inspect", args: operands, type: STRING, loc }, + type: STRING, + loc, + }; } /** `inspect(value, options)` over one value node: the shared body of @@ -2512,6 +2911,46 @@ export function lowerInspectValue( } const options = parseInspectOptions(lowerer, optionsNode, walk.recursive, dir); const depth = options.depth; + // Custom inspect functions read the options as Node spelled them: an + // unbounded depth given as Infinity or undefined, and customInspect: + // false (console.dir's default), apply around the render when the value + // can reach a custom method; the previous settings return afterwards. + const reachesCustom = + typeReachesCustomInspect(lowerer, value.type) || typeReachesDyn(lowerer, value.type); + const depthContext = reachesCustom && depth === Infinity && options.depthKind !== 1; + const customContext = reachesCustom && !options.customInspect; + if (depthContext || customContext) { + const stmts: IrStmt[] = []; + const restore: IrStmt[] = []; + const setting = (fn: "insp.customInspect" | "insp.depthKind", arg: IrExpr, type: IrType) => { + const previous = lowerer.declareHiddenLocal("%inspectContext", type); + const call = (a: IrExpr): IrExpr => ({ kind: "libCall", fn, args: [a], type, loc }); + stmts.push({ kind: "varDecl", localId: previous.id, init: call(arg), loc }); + restore.push({ kind: "exprStmt", expr: call(varRef(previous.id, type, loc)), loc }); + }; + if (customContext) setting("insp.customInspect", boolLit(false, loc), BOOL); + if (depthContext) setting("insp.depthKind", numLit(options.depthKind, loc), F64); + const text = lowerer.declareHiddenLocal("%inspectText", STRING); + stmts.push({ + kind: "varDecl", + localId: text.id, + init: withInspectLayout( + lowerer, + options, + value, + (input) => inspectExpr(lowerer, value.type, input, numLit(0, loc), numLit(depth, loc), loc), + loc, + ), + loc, + }); + return { + kind: "seqExpr", + stmts: [...stmts, ...restore.reverse()], + result: varRef(text.id, STRING, loc), + type: STRING, + loc, + }; + } if (dir && value.type.kind === "dyn") { return withInspectLayout( lowerer, @@ -2542,7 +2981,17 @@ export function lowerInspectValue( * formatNumber, strings verbatim, objects through inspect at the given * depth). Null = fence with the given reason. */ function formatSArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: SrcLoc): IrExpr { - const value = lowerer.lowerExpr(node); + return formatSValue(lowerer, node, lowerer.lowerExpr(node), depth, loc); +} + +/** formatSArg over the already-lowered value of `node`. */ +function formatSValue( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + depth: number, + loc: SrcLoc, +): IrExpr { const t = value.type; if (t.kind === "string") return value; if (t.kind === "f64") @@ -2626,6 +3075,13 @@ function formatOArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: S node, reason, ); + if (depth === 4 && typeReachesCustomInspect(lowerer, value.type)) { + lowerer.noLowering( + "util.format %o over values with [util.inspect.custom] methods", + node, + "%o passes showHidden to the methods — use %O", + ); + } if (depth === 4 && typeTreeHasArray(lowerer, value.type, new Set())) { lowerer.noLowering( "util.format %o over arrays", @@ -2708,8 +3164,9 @@ function formatIntegerFixed(value: IrExpr, spec: "%d" | "%i", loc: SrcLoc): IrEx /** util.format(...) / util.formatWithOptions({}, ...): the compile-time * %-substitution over a literal format string (formatWithOptionsInternal * ported exactly — the arg cursor, the args-exhausted guard, %c's - * consume-and-drop), with per-static-type conversions. Non-literal - * format strings lower only in the substitution-free shapes. */ + * consume-and-drop), with per-static-type conversions. Format strings + * known only at run time, spread arguments and non-empty options lower to + * the runtime formatter (lowerRuntimeFormat, heldFormatArgs). */ export function lowerFormatCall( lowerer: Lowerer, expr: ts.CallExpression, @@ -2721,30 +3178,60 @@ export function lowerFormatCall( const argNodes = [...(argNodesOverride ?? expr.arguments.nodes)]; if (withOptions) { const opts = argNodes.shift(); + // The empty literal {} is the defaults: exactly util.format. if (!opts || !ts.isObjectLiteralExpression(opts) || opts.properties.nodes.length > 0) { - lowerer.noLowering( - "util.formatWithOptions with a non-empty options literal", - opts ?? expr, - "only the empty literal {} (the defaults — exactly util.format) is lowered", - ); + if (opts && ts.isSpreadElement(opts)) { + lowerer.noLowering( + "util.formatWithOptions with a spread options argument", + opts, + "pass the options object as the first argument", + ); + } + return lowerRuntimeFormat(lowerer, argNodes, loc, opts ?? null); } } if (argNodes.length === 0) return strLit("", loc); const first = argNodes[0]!; const firstIsLiteral = ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first); + if (argNodes.some(ts.isSpreadElement)) { + if (literalPrefix !== "" && !firstIsLiteral) + throw new InternalCompilerError("a format prefix needs a literal format string"); + return lowerRuntimeFormat(lowerer, argNodes, loc, undefined, literalPrefix); + } /** The rest-args tail: ` ` + (strings verbatim, everything else * inspected at the default depth). */ const restArg = (node: ts.Expression): IrExpr => formatSArg(lowerer, node, 2, loc); if (!firstIsLiteral) { - const firstT = lowerer.mapTypeOf(lowerer.typeOf(first)); - if (firstT?.kind === "string" && argNodes.length > 1) { - lowerer.noLowering( - "util.format with a runtime format string and further arguments", - first, - "the %-substitution positions are runtime-dependent — use a string literal", - ); + const runtime = argNodes.length > 1 ? runtimeFormatString(lowerer, first) : null; + if (runtime === "other") return lowerRuntimeFormat(lowerer, argNodes, loc); + if (runtime !== null) { + // A string that substitutes formats at run time; any other string + // joins with the remaining arguments, as below. + const held = heldFormatArgs(lowerer, argNodes, loc); + if (!held.joinable) { + return { kind: "seqExpr", stmts: held.stmts, result: held.format(), type: STRING, loc }; + } + const joined: IrExpr[] = []; + held.args.forEach((arg, i) => { + if (i > 0) joined.push(strLit(" ", loc)); + joined.push(formatRestValue(lowerer, arg.node, arg.ref(), loc)); + }); + return { + kind: "seqExpr", + stmts: held.stmts, + result: { + kind: "ternary", + cond: held.specifies, + then: held.format(), + else_: concatAll(joined, loc), + type: STRING, + loc, + }, + type: STRING, + loc, + }; } // No substitutions possible: every argument joins with spaces. const parts: IrExpr[] = []; @@ -3030,6 +3517,240 @@ export function lowerFormatCall( return concatAll(parts, loc); } +/** One space-joined argument of util.format, as Node's formatter renders + * it after the format string: strings verbatim, everything else through + * inspect at the default depth. */ +function formatRestValue( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + loc: SrcLoc, +): IrExpr { + switch (value.type.kind) { + case "string": + return value; + case "f64": + return { kind: "libCall", fn: "insp.f64", args: [value], type: STRING, loc }; + case "bool": + return { kind: "toString", operand: value, type: STRING, loc }; + case "dyn": + return { kind: "libCall", fn: "insp.dynS", args: [value, numLit(2, loc)], type: STRING, loc }; + default: + return lowerConsoleInspectArg(lowerer, node, value, "util.format", loc); + } +} + +const UNCONVERTIBLE_HINT = + "the runtime formatter reads its arguments as checked-dynamic values — use a string literal format"; + +/** A string-valued expression that throws the SC2020 refusal where it runs + * (a JavaScript construct fence deferred to the branch that needs it). */ +function runtimeFenceValue( + lowerer: Lowerer, + surface: string, + node: ts.Node, + hint: string, + loc: SrcLoc, +): IrExpr { + const diag = noLoweringDiag(surface, locOf(node), hint); + lowerer.runtimeFences.push(diag); + const sf = ts.getSourceFileOfNode(node); + const line = ts.getLineAndCharacterOfPosition(sf, diag.loc.start).line + 1; + const slot = lowerer.declareHiddenLocal("%never", STRING); + slot.mutable = true; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: null, loc }, + { + kind: "runtimeFence", + code: diag.code, + message: `${diag.message} [${diag.code} at ${diag.loc.file}:${line}]`, + loc, + }, + ], + result: varRef(slot.id, STRING, loc), + diverges: true, + type: STRING, + loc, + }; +} + +/** How a non-literal format string is known: "string" and "dyn" values + * test for a substitution at run time, "other" types (unions with a string + * arm) format at run time, and null types never hold a string. */ +export function runtimeFormatString( + lowerer: Lowerer, + first: ts.Expression, +): "string" | "dyn" | "other" | null { + const t = lowerer.mapTypeOf(lowerer.typeOf(first)); + if (t === null) return "other"; + if (t.kind === "string") return "string"; + if (t.kind === "dyn") return "dyn"; + if (t.kind === "union") { + const arms = lowerer.unions.get(t.unionId)?.arms ?? []; + return arms.some((arm) => arm.kind === "string" || arm.kind === "dyn") ? "other" : null; + } + return t.kind === "jsval" || t.kind === "caught" ? "other" : null; +} + +/** The runtime formatter over a call's arguments: one checked-dynamic + * pack in source order (spreads contribute their elements), after the + * options when `options` is given (null: an omitted options argument, + * which Node's validation rejects). A literal prefix joins a literal + * format string. */ +export function lowerRuntimeFormat( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, + options?: ts.Expression | null, + literalPrefix = "", +): IrExpr { + const stmts: IrStmt[] = []; + let optionsRef: IrExpr | null = null; + if (options !== undefined) { + const held = lowerer.declareHiddenLocal("%formatOptions", DYN); + stmts.push({ + kind: "varDecl", + localId: held.id, + init: options === null ? dynUndefinedExpr(loc) : lowerer.lowerExprExpecting(options, DYN), + loc, + }); + optionsRef = varRef(held.id, DYN, loc); + } + let nodes = argNodes; + const leading: IrExpr[] = []; + if (literalPrefix !== "") { + const first = argNodes[0] as ts.StringLiteralLike; + leading.push(strLit(literalPrefix + first.text, loc)); + nodes = argNodes.slice(1); + } + const pack = dynArgumentPack(lowerer, nodes, loc, { + leading, + blame: argNodes[0] ?? options ?? undefined, + }); + stmts.push(...pack.stmts); + const result: IrExpr = + optionsRef === null + ? { kind: "libCall", fn: "insp.format", args: [pack.pack()], type: STRING, loc } + : { + kind: "libCall", + fn: "insp.formatWithOptions", + args: [optionsRef, pack.pack()], + type: STRING, + loc, + }; + return { kind: "seqExpr", stmts, result, type: STRING, loc }; +} + +/** The arguments of a call whose first argument is a string or dyn value + * known only at run time, each evaluated once into a hidden local: + * `specifies` tests that string for a '%' before its last character, and + * `format()` is the runtime formatter over the held values. The caller + * renders the other branch (the space-joined arguments) from `args` when + * every argument is `joinable` there. */ +export function heldFormatArgs( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, +): { + stmts: IrStmt[]; + args: { node: ts.Expression; ref: () => IrExpr }[]; + specifies: IrExpr; + format: () => IrExpr; + joinable: boolean; +} { + const stmts: IrStmt[] = []; + let unconvertible: { node: ts.Expression; type: IrType } | null = null; + const unconvertibleSurface = (t: IrType): string => + `util.format with a runtime format string over '${lowerer.fmt(t)}' values`; + const args = argNodes.map((node) => { + const value = lowerer.lowerExpr(node); + if (value.type.kind === "void") { + lowerer.unsupported( + "SC1090", + node, + "util.format of a void call result (call it as its own statement, then pass undefined)", + ); + } + if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) { + // JavaScript keeps the space-joined branch, which printed these + // values before, and refuses only a string that substitutes. + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { + lowerer.noLowering(unconvertibleSurface(value.type), node, UNCONVERTIBLE_HINT); + } + unconvertible ??= { node, type: value.type }; + } + // Unit values have no local: their effects run here and a literal + // stands in. + const k = value.type.kind; + if (k === "undefinedT" || k === "nullT") { + if (value.kind !== "unitLit") stmts.push({ kind: "exprStmt", expr: value, loc }); + const unit = k === "nullT" ? ("null" as const) : ("undefined" as const); + return { node, ref: (): IrExpr => ({ kind: "unitLit", unit, type: value.type, loc }) }; + } + const held = lowerer.declareHiddenLocal("%formatArg", value.type); + stmts.push({ kind: "varDecl", localId: held.id, init: value, loc }); + return { node, ref: () => varRef(held.id, value.type, loc) }; + }); + const first = args[0]!.ref(); + const specifies: IrExpr = + first.type.kind === "string" + ? { kind: "libCall", fn: "insp.formatSpecifies", args: [first], type: BOOL, loc } + : { + kind: "libCall", + fn: "insp.formatSpecifiesDyn", + args: [lowerer.coerceToExpected(first, DYN)], + type: BOOL, + loc, + }; + const format = (): IrExpr => { + if (unconvertible !== null) { + const fenced = unconvertible; + return runtimeFenceValue( + lowerer, + unconvertibleSurface(fenced.type), + fenced.node, + UNCONVERTIBLE_HINT, + loc, + ); + } + // Strings, numbers, booleans, null and undefined format without the + // checked-dynamic walk other values need, which keeps it out of the + // executable. + const primitive = (t: IrType): boolean => + t.kind === "string" || + t.kind === "f64" || + t.kind === "bool" || + t.kind === "undefinedT" || + t.kind === "nullT" || + (t.kind === "union" && (lowerer.unions.get(t.unionId)?.arms.every(primitive) ?? false)); + return { + kind: "libCall", + fn: args.every((arg) => primitive(arg.ref().type)) ? "insp.formatPrimitives" : "insp.format", + args: [ + { + kind: "dynArrLit", + elems: args.map((arg) => lowerer.coerceToExpected(arg.ref(), DYN)), + type: DYN, + loc, + }, + ], + type: STRING, + loc, + }; + }; + // The space-joined branch renders each argument statically; arguments + // the static rendering refuses take the runtime formatter in both cases. + const joinable = args.every(({ ref }) => { + const t = ref().type; + if (t.kind === "func") return false; + if (t.kind === "bytes") return true; + return inspectSupport(lowerer, t, new Set(), { recursive: false }) === null; + }); + return { stmts, args, specifies, format, joinable }; +} + /** Arrays anywhere in the tree — the %o (showHidden) gate. */ function typeTreeHasArray(lowerer: Lowerer, t: IrType, visiting: Set): boolean { switch (t.kind) { diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 5238333ad..8013b842f 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -93,6 +93,7 @@ import { createRequireProgramModuleDecl, } from "./builtins/module-bindings.js"; import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js"; +import { customInspectRegistrations } from "./lower-inspect.js"; import { registerBuiltinClassAlias } from "./builtins/class-values.js"; import { stripTypeCasts } from "./builtins/arguments.js"; import { isWebCryptoMemberType } from "./builtins/webcrypto.js"; @@ -1947,18 +1948,21 @@ export function collectGlobals( // symbol registers HERE, before any use site can lower, and the // statement lowering skips the declaration by the same check. A // bad promisify target reports here (recorded once; the statement - // lowering poisons too, like every collectGlobals failure). + // lowering poisons too, like every collectGlobals failure). Targets + // that promisify to a function value (typed callback-last functions + // and untyped functions) keep an ordinary global. if ( ts.isIdentifier(decl.name) && decl.initializer && isPromisifyCall(lowerer, decl.initializer) ) { + let registered = true; try { - lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer); + registered = lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer); } catch (e) { if (!(e instanceof PoisonError)) throw e; } - continue; + if (registered) continue; } // `const inspect = require("util").inspect` at file scope: a // named import in const clothing (builtinImportOf resolves uses) @@ -3523,6 +3527,9 @@ export function buildMain(lowerer: Lowerer): IrFunction { loc, }); } + // Classes with custom inspect methods register their compiled calls for + // the runtime's inspect walk before any module runs, in every thread. + body.unshift(...customInspectRegistrations(lowerer, loc)); // Node's startup refusal (a resolution the graph carries that Node // rejects — preflight's Node-order resolution walk — or a module-LINK // SyntaxError from either named-import checker): the graph is refused diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 93c6246d9..c2c60abdc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -13677,6 +13677,10 @@ export class Lowerer { * are only dispatch sentinels for call-site-specific lowering. */ lowerBuiltinCallableValue(bi: { module: string; member: string }, loc: SrcLoc): IrExpr | null { if (bi.module === "util" && bi.member === "types") return utilTypesModuleValue(this, loc); + // util.inspect: the function custom inspect methods receive, one + // identity per thread. + if (bi.module === "util" && bi.member === "inspect") + return { kind: "libCall", fn: "insp.inspectFunction", args: [], type: DYN, loc }; if (bi.module === "util/types" && builtinModuleFnOf(this, bi.module, bi.member)) return lowerUtilTypeValue(this, bi.member, loc); if (bi.module === "util" && bi.member === "getSystemErrorMap") diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 8b826304b..80da35ad3 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1686,7 +1686,8 @@ export const BUILTIN_MODULE_FNS: Record< // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and // inspect/format/formatWithOptions plus parseArgs through the util spoke // (lower-inspect.ts: per-type synthesized traversal helpers, - // compile-time format strings, checked-dynamic config/result for + // compile-time format strings and the runtime formatter for the others, + // checked-dynamic config/result for // parseArgs), which both dispatch paths try before the generic table. // parseArgs is tabled as well so the generated surface manifest records // the dedicated static member; the spoke owns its optional arity and diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index 56718615e..ea495d364 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -76,7 +76,36 @@ export function fenceSymbolFieldCopy( } } +/** The registry name util.inspect.custom denotes. */ +export const INSPECT_CUSTOM_DESCRIPTION = "nodejs.util.inspect.custom"; +/** The method slot of a class's `[util.inspect.custom]` member. */ +export const INSPECT_CUSTOM_MEMBER = `${FIELD_PREFIX}Symbol(${INSPECT_CUSTOM_DESCRIPTION})`; + +/** `inspect.custom` over node:util's inspect: a named import, `util.inspect` + * or the CommonJS `util_1.inspect`. */ +export function isInspectCustomAccess(lowerer: Lowerer, expr: ts.Expression): boolean { + while (ts.isParenthesizedExpression(expr)) expr = expr.expression; + if (!ts.isPropertyAccessExpression(expr) || expr.questionDotToken) return false; + if (!ts.isIdentifier(expr.name) || expr.name.text !== "custom") return false; + let target = expr.expression; + while (ts.isParenthesizedExpression(target)) target = target.expression; + const bi = ts.isIdentifier(target) + ? lowerer.builtinImportOf(target) + : ts.isPropertyAccessExpression(target) && !target.questionDotToken + ? lowerer.builtinMemberOf(target) + : null; + return bi?.module === "util" && bi.member === "inspect"; +} + export function classSymbolKeyOf(lowerer: Lowerer, key: ts.Expression): ClassSymbolKey | null { + // util.inspect.custom is the registered Symbol.for(INSPECT_CUSTOM_DESCRIPTION), + // one identity with the program's own Symbol.for calls of that name. + if (isInspectCustomAccess(lowerer, key)) { + let e = key; + while (ts.isParenthesizedExpression(e)) e = e.expression; + const sym = lowerer.checker.getSymbolAtLocation((e as ts.PropertyAccessExpression).name); + return sym ? symbolKeyOf(lowerer, sym, true, INSPECT_CUSTOM_DESCRIPTION) : null; + } if ( !ts.isIdentifier(key) && (!ts.isPropertyAccessExpression(key) || key.questionDotToken || !ts.isIdentifier(key.name)) diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index aef61c743..3db647f64 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -2955,6 +2955,30 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // spawn consumes a presence-sensitive option bag, including optional // stdio and env values. Keep it checked-dynamic for native normalization. if (isStdlibInterface("SpawnOptions")) return DYN; + // util.inspect as a value (a custom inspect method's third parameter is + // `typeof inspect`): the runtime's inspect function, called through the + // checked-dynamic boundary. + if ( + psym !== undefined && + ts.symbolName(psym) === "inspect" && + psym.declarations.some( + (d) => + ts.isFunctionDeclaration(d) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientModule(d, "util"), + ) + ) + return DYN; + // util.inspect's options bag, which inspect and formatWithOptions read + // at run time (its sorted option can be a comparator function), and the + // options custom inspect functions receive. + if ( + (isStdlibInterface("InspectOptions") || isStdlibInterface("InspectOptionsStylized")) && + psym!.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientModule(d, "util"), + ) + ) + return DYN; // dns.LookupAllOptions (`all: true`, the overload whose callback takes // an address list): a lookup option bag dns.lookup reads at run time. // It rides checked-dynamic storage, so the lookup function type that diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index eb3f8c6ab..d676b05dd 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -180,6 +180,17 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "num.toStringRadix", "num.toStringRadixF64", "insp.jsonDyn", + // The runtime formatter calls toString, valueOf and toJSON, and refuses + // renderings it has no lowering for. + "insp.format", + "insp.formatWithOptions", + "insp.inspect", + // The options object reads the caller's options (getters included), and + // the checked-dynamic walk calls custom inspect functions. + "insp.customOptions", + "insp.customResult", + "insp.dyn", + "insp.dynS", // diagnostics_channel: publish runs subscribers synchronously (a throw // propagates — the documented divergence from triggerUncaughtException); // the subscribe/unsubscribe forms throw Node's ERR_INVALID_ARG_TYPE for diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 8edb223b3..6e07e1467 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4370,8 +4370,43 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, "insp.dynS": { argTypes: [DYN, F64], result: STRING }, + /** util.format over a dyn array of its arguments, and + * util.formatWithOptions(options, pack): the runtime formatter for format + * strings known at run time and spread arguments. */ + "insp.format": { argTypes: [DYN], result: STRING }, + /** insp.format over arguments that are all strings, numbers, booleans, + * null or undefined, without the checked-dynamic walk. */ + "insp.formatPrimitives": { argTypes: [DYN], result: STRING }, + "insp.formatWithOptions": { argTypes: [DYN, DYN], result: STRING }, + /** util.inspect(value, options, depth, colors) with the call's argument + * count: the runtime reads the options and renders the checked-dynamic + * view. */ + "insp.inspect": { argTypes: [DYN, DYN, DYN, DYN, F64], result: STRING }, + /** A [util.inspect.custom] method's call: whether ctx.customInspect is + * set, its depth argument (the remaining depth at recursion r of depth + * d), its options object and util.inspect, then its result rendered: a + * string re-indented at the current level, or a checked-dynamic value + * (customIsSelf tests for the instance itself). customInspect sets the + * flag for the renders that follow and answers the previous value, and + * depthKind does the same for how an unbounded depth was given (0 + * Infinity, 1 null, 2 undefined). */ + "insp.customEnabled": { argTypes: [], result: BOOL }, + /** Registers a class's compiled custom inspect function under its + * instances' typed-ref type key, for the checked-dynamic walk. */ + "insp.registerCustom": { argTypes: [STRING, DYN], result: VOID }, + "insp.customInspect": { argTypes: [BOOL], result: BOOL }, + "insp.depthKind": { argTypes: [F64], result: F64 }, + "insp.customDepth": { argTypes: [F64, F64], result: DYN }, + "insp.customOptions": { argTypes: [F64], result: DYN }, + "insp.inspectFunction": { argTypes: [], result: DYN }, + "insp.customText": { argTypes: [STRING], result: STRING }, + "insp.customIsSelf": { argTypes: [DYN, DYN], result: BOOL }, + "insp.customResult": { argTypes: [DYN, F64, F64], result: STRING }, + /** Whether a runtime format string has a '%' before its last character. */ + "insp.formatSpecifies": { argTypes: [STRING], result: BOOL }, + "insp.formatSpecifiesDyn": { argTypes: [DYN], result: BOOL }, "insp.begin": { argTypes: [F64], result: VOID }, - /** util.inspect's breakLength, compact (0 for false) and sorted for the + /** util.inspect's breakLength, compact (-1 for false) and sorted for the * renders that follow; the call site restores 80, 3 and false afterwards. */ "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json new file mode 100644 index 000000000..825fd9882 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7520-util-format-runtime.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json new file mode 100644 index 000000000..0714d5c08 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7521-util-format-runtime.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json new file mode 100644 index 000000000..bcd6b54c6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7522-promisify-untyped.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json new file mode 100644 index 000000000..20e96495a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7523-promisify-untyped.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json new file mode 100644 index 000000000..4f5a3d007 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7524-inspect-custom.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json new file mode 100644 index 000000000..dddad2358 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7525-inspect-custom.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json new file mode 100644 index 000000000..70b202903 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7526-inspect-options-runtime.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json new file mode 100644 index 000000000..dff7b7d6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inspect-custom-fences.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_console_native.c b/packages/runtime/src/scr_console_native.c index 8c7467127..492d57d82 100644 --- a/packages/runtime/src/scr_console_native.c +++ b/packages/runtime/src/scr_console_native.c @@ -523,7 +523,11 @@ ScrStr *scr_console_dir_dyn(ScrDyn *value, double depth) { scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); return scr_str_new("", 0); } - return scr_insp_dyn(value, 0, depth); + /* console.dir is inspect with customInspect: false. */ + bool custom = scr_insp_custom_inspect(false); + ScrStr *out = scr_insp_dyn(value, 0, depth); + scr_insp_custom_inspect(custom); + return out; } /* ── console.table ─────────────────────────────────────────────────── diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 74b710dc9..fdb114535 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -863,6 +863,10 @@ ScrDyn *scr_dyn_open_iterator(const ScrDyn *value, int *kind, bool generators); * for-of protocol reads it. +1, or NULL with the error pending. */ ScrDyn *scr_dyn_iterator_result_get(const ScrDyn *result, const char *key, size_t len); void scr_dyn_install_iterator_symbol(ScrDyn *key); +/* Symbol.for('nodejs.util.inspect.custom'): the symbol unit installs it + * when the registry creates it; NULL (borrowed) until then. */ +void scr_dyn_install_inspect_custom_symbol(ScrDyn *key); +ScrDyn *scr_dyn_inspect_custom_symbol(void); void scr_dyn_install_async_iterator_symbol(ScrDyn *key); void scr_dyn_install_async_dispose_symbol(ScrDyn *key); /* True when key is Symbol.asyncDispose (registered by the symbol unit when diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index a4d124ac3..2c7951103 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -152,6 +152,154 @@ static size_t insp_width(const char *data, size_t len) { return width; } +/* ── colors (stylizeWithColor) ───────────────────────────────────────── */ + +/* ctx.colors for the render in progress: the checked-dynamic walk wraps + * each styled leaf in inspect.colors' SGR codes for its inspect.styles + * style, and the layout measures entries without the codes. Static + * renderings never run with it set. */ +static SCR_TL bool g_colors; + +typedef enum { + INSP_SPECIAL, + INSP_NUMBER, + INSP_BIGINT, + INSP_BOOLEAN, + INSP_UNDEFINED, + INSP_NULL, + INSP_STRING, + INSP_SYMBOL, + INSP_DATE, +} InspStyle; + +/* inspect.styles mapped through inspect.colors: [open, close]. */ +static const unsigned char insp_style_codes[][2] = { + {36, 39}, /* special: cyan */ + {33, 39}, /* number: yellow */ + {33, 39}, /* bigint: yellow */ + {33, 39}, /* boolean: yellow */ + {90, 39}, /* undefined: grey */ + {1, 22}, /* null: bold */ + {32, 39}, /* string: green */ + {32, 39}, /* symbol: green */ + {35, 39}, /* date: magenta */ +}; + +static void insp_style_open(InspBuf *b, InspStyle style) { + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", insp_style_codes[style][0]); + ib_bytes(b, code, (size_t)n); +} + +static void insp_style_close(InspBuf *b, InspStyle style) { + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", insp_style_codes[style][1]); + ib_bytes(b, code, (size_t)n); +} + +/* ctx.stylize(text, style): +1; moves `text` in. */ +static ScrStr *insp_styled(ScrStr *text, InspStyle style) { + if (!g_colors) return text; + InspBuf b = {0}; + insp_style_open(&b, style); + ib_str(&b, text); + insp_style_close(&b, style); + scr_str_release(text); + return ib_take(&b); +} + +static ScrStr *insp_styled_lit(const char *text, size_t len, InspStyle style) { + return insp_styled(scr_str_new(text, len), style); +} + +/* The SC2020 refusal for colored renderings of values whose styles this + * runtime does not produce; answers an empty string. */ +static ScrStr *insp_colors_fence(const char *what) { + InspBuf b = {0}; + ib_cstr(&b, "colored inspect output of "); + ib_cstr(&b, what); + ib_cstr(&b, " has no lowering"); + ScrStr *message = ib_take(&b); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return scr_str_new("", 0); +} + +static ScrStr *insp_colors_fence_values(const char *cls) { + InspBuf b = {0}; + ib_cstr(&b, cls); + ib_cstr(&b, " values"); + ScrStr *what = ib_take(&b); + ScrStr *out = insp_colors_fence(what->data); + scr_str_release(what); + return out; +} + +/* The SGR sequence Node's removeColors strips (\x1b[<1 or 2 digits>m) at + * data[i]: its length, or 0. */ +static size_t insp_sgr_at(const char *data, size_t len, size_t i) { + if (data[i] != '\x1b' || i + 3 >= len || data[i + 1] != '[') return 0; + size_t j = i + 2, digits = 0; + while (j < len && digits < 2 && data[j] >= '0' && data[j] <= '9') j++, digits++; + return digits > 0 && j < len && data[j] == 'm' ? j + 1 - i : 0; +} + +/* A control sequence stripVTControlCharacters removes at data[i] (CSI + * sequences after ESC [ or C1 CSI, and OSC sequences ended by BEL): its + * length, or 0. */ +static size_t insp_vt_at(const char *data, size_t len, size_t i) { + const unsigned char *p = (const unsigned char *)data; + size_t j; + if (p[i] == 0x1b && i + 1 < len && p[i + 1] == '[') j = i + 2; + else if (p[i] == 0xc2 && i + 1 < len && p[i + 1] == 0x9b) j = i + 2; + else if (p[i] == 0x1b && i + 1 < len && p[i + 1] == ']') { + for (j = i + 2; j < len; j++) + if (p[j] == 0x07) return j + 1 - i; + return 0; + } else return 0; + while (j < len && ((p[j] >= '0' && p[j] <= '9') || p[j] == ';' || p[j] == '?')) j++; + if (j >= len) return 0; + unsigned char c = p[j]; + bool final = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'P') || (c >= 'R' && c <= 'T') || + c == 'Z' || c == 'c' || (c >= 'f' && c <= 'n') || c == 'q' || c == 'r' || + c == 't' || c == 'y' || c == '=' || c == '>' || c == '<' || c == '~'; + return final ? j + 1 - i : 0; +} + +/* An entry's display width for the grid (getStringWidth(entry, + * ctx.colors)) and its UTF-16 length for the break-length test (Node's + * removeColors under ctx.colors). */ +static size_t insp_entry_width(const ScrStr *s) { + if (!g_colors) return insp_width(s->data, s->len); + size_t width = 0; + for (size_t i = 0; i < s->len;) { + size_t skip = insp_vt_at(s->data, s->len, i); + if (skip) { + i += skip; + continue; + } + uint32_t cp = insp_cp_at(s->data, s->len, &i); + if (insp_full_width(cp)) width += 2; + else if (!insp_zero_width(cp)) width += 1; + } + return width; +} + +static size_t insp_entry_len(const ScrStr *s) { + if (!g_colors) return insp_utf16_len(s->data, s->len); + size_t units = 0; + for (size_t i = 0; i < s->len;) { + size_t skip = insp_sgr_at(s->data, s->len, i); + if (skip) { + i += skip; + continue; + } + uint32_t cp = insp_cp_at(s->data, s->len, &i); + units += cp >= 0x10000 ? 2 : 1; + } + return units; +} + /* ── number rendering ────────────────────────────────────────────────── */ /* formatNumber: JS ToString EXCEPT -0 renders as '-0'. */ @@ -245,12 +393,12 @@ static SCR_TL size_t g_frames_cap; static SCR_TL size_t g_indent; /* ctx.indentationLvl */ static SCR_TL bool g_no_break; /* breakLength: Infinity (console.table cells) */ static SCR_TL double g_break_length = 80; /* ctx.breakLength */ -static SCR_TL double g_compact = 3; /* ctx.compact: a number, 0 for false */ +static SCR_TL double g_compact = 3; /* ctx.compact: a number, -1 for false */ static SCR_TL bool g_sorted; /* ctx.sorted === true */ static SCR_TL double g_cur_depth; /* ctx.currentDepth (last-entered composite) */ /* util.inspect's breakLength, compact and sorted for the renders that - * follow (compact false is 0); the call site restores 80, 3 and false + * follow (compact false is -1); the call site restores 80, 3 and false * after its render. */ void scr_insp_layout(double break_length, double compact, bool sorted) { g_break_length = break_length; @@ -369,7 +517,7 @@ void scr_insp_seen_push(const void *v) { ScrStr *scr_insp_circular(double id) { char buf[32]; int n = snprintf(buf, sizeof buf, "[Circular *%.0f]", id); - return scr_str_new(buf, (size_t)n); + return insp_styled_lit(buf, (size_t)n, INSP_SPECIAL); } /* Pops the value's seen entry after its frame closed. scr_insp_end already @@ -403,7 +551,7 @@ static void insp_group(InspFrame *f, bool trailing_more) { size_t *data_len = malloc((output_len ? output_len : 1) * sizeof(size_t)); if (!data_len) insp_oom(); for (size_t i = 0; i < output_len; i++) { - size_t w = insp_width(f->items[i]->data, f->items[i]->len); + size_t w = insp_entry_width(f->items[i]); data_len[i] = w; total_length += (double)w + 2; if ((double)w > max_length) max_length = (double)w; @@ -494,7 +642,7 @@ static bool insp_below_break_length(InspFrame *f, size_t start, const ScrStr *ba size_t total = f->len + start; if ((double)(total + f->len) > g_break_length) return false; for (size_t i = 0; i < f->len; i++) { - total += insp_utf16_len(f->items[i]->data, f->items[i]->len); + total += insp_entry_len(f->items[i]); if ((double)total > g_break_length) return false; } return base->len == 0 || !insp_contains_byte(base->data, base->len, '\n'); @@ -520,12 +668,16 @@ ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse, bool int ref_id = f->seen ? insp_circ_id(f->seen) : 0; if (ref_id) { char buf[32]; - int n = snprintf(buf, sizeof buf, base->len ? " " : "", ref_id); - size_t len = (size_t)n + base->len; - ref_base = scr_str_alloc_raw(len, len); - memcpy(ref_base->data, buf, (size_t)n); - memcpy(ref_base->data + n, base->data, base->len); - ref_base->data[len] = '\0'; + int n = snprintf(buf, sizeof buf, "", ref_id); + InspBuf rb = {0}; + if (g_colors) insp_style_open(&rb, INSP_SPECIAL); + ib_bytes(&rb, buf, (size_t)n); + if (g_colors) insp_style_close(&rb, INSP_SPECIAL); + if (base->len) { + ib_char(&rb, ' '); + ib_str(&rb, base); + } + ref_base = ib_take(&rb); base = ref_base; } size_t entries = f->len; @@ -622,6 +774,19 @@ void scr_insp_nested_begin(void) { g_ncirc = 0; } +/* nested_end's restore alone: the caller's context returns. */ +void scr_insp_nested_restore(void) { + InspSaved *sv = &g_saved[--g_nsaved]; + free(g_seen); + g_indent = sv->indent; + g_cur_depth = sv->cur_depth; + g_seen = sv->seen; + g_nseen = sv->nseen; + g_seen_cap = sv->seen_cap; + memcpy(g_circ, sv->circ, sizeof g_circ); + g_ncirc = sv->ncirc; +} + ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner) { InspSaved *sv = &g_saved[--g_nsaved]; free(g_seen); @@ -717,13 +882,17 @@ ScrStr *scr_insp_str(ScrStr *s) { ib_cstr(&out, " +\n"); ib_spaces(&out, g_indent + 2); } + if (g_colors) insp_style_open(&out, INSP_STRING); insp_quote_into(&out, v + chunk_start, end - chunk_start); + if (g_colors) insp_style_close(&out, INSP_STRING); chunk_start = end; first = false; } } } else { + if (g_colors) insp_style_open(&out, INSP_STRING); insp_quote_into(&out, v, vlen); + if (g_colors) insp_style_close(&out, INSP_STRING); } if (trailer[0]) ib_cstr(&out, trailer); free(val.data); @@ -1007,7 +1176,9 @@ static void insp_key_into(InspBuf *b, const char *key, size_t len) { if (bare) { ib_bytes(b, key, len); } else { + if (g_colors) insp_style_open(b, INSP_STRING); insp_quote_into(b, key, len); + if (g_colors) insp_style_close(b, INSP_STRING); } } @@ -1330,7 +1501,7 @@ static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth, ib_char(&out, '['); ib_cstr(&out, label); ib_char(&out, ']'); - return ib_take(&out); + return insp_styled(ib_take(&out), INSP_SPECIAL); } ScrStr *base = ib_take(&head); scr_insp_begin(recurse + 1); @@ -1395,9 +1566,9 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char : scr_str_new("{}", 2); } if (recurse > depth) { - if (cls) return scr_insp_class_tag(cls, NULL); - return d->null_proto ? scr_str_new("[Object: null prototype]", 24) - : scr_str_new("[Object]", 8); + if (cls) return insp_styled(scr_insp_class_tag(cls, NULL), INSP_SPECIAL); + return d->null_proto ? insp_styled_lit("[Object: null prototype]", 24, INSP_SPECIAL) + : insp_styled_lit("[Object]", 8, INSP_SPECIAL); } scr_insp_begin(recurse + 1); for (size_t i = 0; i < d->v.obj.len; i++) { @@ -1412,6 +1583,7 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char ScrStr *entry = ib_take(&eb); scr_insp_entry(entry, false); scr_str_release(entry); + if (scr_exc_pending()) break; } for (size_t i = 0; i < nsymbols; i++) { ScrDynEntry *ent = scr_insp_symbol_entry(d, i); @@ -1455,12 +1627,12 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { if (recurse > depth) { char tag[32]; int n = snprintf(tag, sizeof tag, "[%s]", scr_bytes_elem_name(b->elem)); - return scr_str_new(tag, (size_t)n); + return insp_styled_lit(tag, (size_t)n, INSP_SPECIAL); } scr_insp_begin(recurse + 1); size_t shown = b->len < 100 ? b->len : 100; for (size_t i = 0; i < shown; i++) { - ScrStr *s = scr_insp_f64(scr_bytes_get(b, (double)i)); + ScrStr *s = insp_styled(scr_insp_f64(scr_bytes_get(b, (double)i)), INSP_NUMBER); scr_insp_entry(s, true); scr_str_release(s); } @@ -1480,27 +1652,374 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { return out; } +/* ── custom inspect functions (util.inspect.custom) ────────────────────── + * formatValue's hook: an object whose [util.inspect.custom] member is a + * function (other than util.inspect itself, on an object that is not its + * constructor's prototype) renders as that function's result. It runs + * before the circular and depth checks, with the remaining depth + * (ctx.depth - recurseTimes), the options object getUserOptions builds and + * util.inspect. A string result is re-indented at the current level; the + * object itself renders normally; any other value renders in its place. + * Class instances held as checked-dynamic values have compiled methods + * only, so one whose class defines the method throws SC2020. */ +static SCR_TL bool g_custom_inspect = true; /* ctx.customInspect */ +/* ctx.depth as the options gave it, for the custom functions' depth + * argument and options when the render's depth is unbounded: 0 the number + * g_depth_number (Infinity or NaN), 1 null, 2 undefined. */ +static SCR_TL int g_depth_kind = 1; +static SCR_TL double g_depth_number; +/* The options object that held keys inspect does not read: getUserOptions + * spreads it into the options the custom functions receive (borrowed for + * the render). */ +static SCR_TL const ScrDyn *g_user_options; + +double scr_insp_depth_kind(double kind) { + double previous = g_depth_kind; + g_depth_kind = (int)kind; + g_depth_number = INFINITY; + return previous; +} + +bool scr_insp_custom_inspect(bool enabled) { + bool previous = g_custom_inspect; + g_custom_inspect = enabled; + return previous; +} + +/* The style inspect.styles maps a styleType to, as [open, close] codes: + * the styles above plus regexp (red) and module (underline). false when + * the type has no style. */ +static bool insp_style_of(const ScrStr *type, unsigned *open, unsigned *close) { + static const struct { + const char *name; + unsigned open, close; + } styles[] = { + {"special", 36, 39}, {"number", 33, 39}, {"bigint", 33, 39}, {"boolean", 33, 39}, + {"undefined", 90, 39}, {"null", 1, 22}, {"string", 32, 39}, {"symbol", 32, 39}, + {"date", 35, 39}, {"regexp", 31, 39}, {"module", 4, 24}, + }; + for (size_t i = 0; i < sizeof styles / sizeof *styles; i++) { + if (type->len == strlen(styles[i].name) && memcmp(type->data, styles[i].name, type->len) == 0) { + *open = styles[i].open; + *close = styles[i].close; + return true; + } + } + return false; +} + +/* options.stylize: stylizeWithColor under colors (the text in its style's + * codes, or unchanged), stylizeNoColor otherwise (the argument itself). + * caps[0] holds whether colors apply. */ +static ScrDyn *insp_stylize_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrDyn *colors = scr_box_get_ref(closure->caps[0]); + bool colored = colors && scr_dyn_truthy(colors); + scr_dyn_release(colors); + ScrDyn *text = argc > 0 ? args[0] : (ScrDyn *)scr_dyn_undefined(); + if (!colored) return scr_dyn_retain(text); + ScrStr *str = scr_dyn_string_coerce_js(text); + if (!str) return NULL; + unsigned open, close; + ScrStr *type = argc > 1 && args[1]->kind == SCR_DYN_STR ? args[1]->v.str : NULL; + if (!type || !insp_style_of(type, &open, &close)) { + ScrDyn *out = scr_dyn_new_str(str); + scr_str_release(str); + return out; + } + InspBuf b = {0}; + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", open); + ib_bytes(&b, code, (size_t)n); + ib_str(&b, str); + n = snprintf(code, sizeof code, "\x1b[%um", close); + ib_bytes(&b, code, (size_t)n); + scr_str_release(str); + ScrStr *styled = ib_take(&b); + ScrDyn *out = scr_dyn_new_str(styled); + scr_str_release(styled); + return out; +} + +static ScrDyn *insp_inspect_call(ScrClosure *closure, ScrDyn *const *args, size_t argc); + +/* Whether `value` is the stylize function getUserOptions built for these + * colors. */ +static bool insp_own_stylize(const ScrDyn *value, bool colors) { + if (value->kind != SCR_DYN_FUNC || value->v.fn.thunk != insp_stylize_call) return false; + ScrDyn *flag = scr_box_get_ref(value->v.fn.clo->caps[0]); + bool colored = flag && scr_dyn_truthy(flag); + scr_dyn_release(flag); + return colored == colors; +} + +/* util.inspect as a function value (the custom functions' third + * argument): a fresh inspect context over the call's arguments, the + * caller's restored afterwards. One function per thread, so the argument + * compares equal to util.inspect. +1. */ +static SCR_TL ScrDyn *g_inspect_function; + +static void insp_inspect_function_cleanup(void) { + scr_dyn_release(g_inspect_function); + g_inspect_function = NULL; +} + +static ScrDyn *insp_inspect_value(void) { + if (!g_inspect_function) { + ScrClosure *closure = scr_closure_new(NULL, 0); + g_inspect_function = + scr_dyn_new_func(closure, insp_inspect_call, 2, "native:util.inspect", "inspect"); + scr_atexit(insp_inspect_function_cleanup); + } + return scr_dyn_retain(g_inspect_function); +} + +static void insp_opt_set(ScrDyn *options, const char *key, ScrDyn *value /* moves in */) { + scr_dyn_obj_set(options, key, strlen(key), value); +} + +/* getUserOptions(ctx) for the render in progress at `depth`. +1. */ +static ScrDyn *insp_user_options(double depth) { + ScrDyn *options = scr_dyn_new_obj(); + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(closure->caps[0], scr_dyn_new_bool(g_colors)); + insp_opt_set(options, "stylize", + scr_dyn_new_func(closure, insp_stylize_call, 2, "native:util.stylize", + g_colors ? "stylizeWithColor" : "stylizeNoColor")); + insp_opt_set(options, "showHidden", scr_dyn_new_bool(false)); + insp_opt_set(options, "depth", + isfinite(depth) ? scr_dyn_new_num(depth) + : g_depth_kind == 0 ? scr_dyn_new_num(g_depth_number) + : g_depth_kind == 1 ? scr_dyn_new_null() + : scr_dyn_retain(scr_dyn_undefined())); + insp_opt_set(options, "colors", scr_dyn_new_bool(g_colors)); + insp_opt_set(options, "customInspect", scr_dyn_new_bool(true)); + insp_opt_set(options, "showProxy", scr_dyn_new_bool(false)); + insp_opt_set(options, "maxArrayLength", scr_dyn_new_num(100)); + insp_opt_set(options, "maxStringLength", scr_dyn_new_num(10000)); + insp_opt_set(options, "breakLength", scr_dyn_new_num(g_break_length)); + insp_opt_set(options, "compact", g_compact < 0 ? scr_dyn_new_bool(false) : scr_dyn_new_num(g_compact)); + insp_opt_set(options, "sorted", scr_dyn_new_bool(g_sorted)); + insp_opt_set(options, "getters", scr_dyn_new_bool(false)); + insp_opt_set(options, "numericSeparator", scr_dyn_new_bool(false)); + if (g_user_options && g_user_options->kind == SCR_DYN_OBJ) { + for (size_t i = 0; i < g_user_options->v.obj.len; i++) { + const ScrDynEntry *ent = &g_user_options->v.obj.entries[i]; + if (!ent->enumerable) continue; + ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent->key, ent->key_len); + if (!value) { + scr_dyn_release(options); + return NULL; + } + scr_dyn_obj_set(options, ent->key, ent->key_len, value); + } + } + return options; +} + +/* The custom inspect functions of the program's classes, by the type key + * of their instances' typed refs: compiled functions (self, depth, + * options, inspect) that call the instance's method and answer its + * result as a checked-dynamic value. The program registers them before + * it runs. */ +typedef struct { + char *type_key; + ScrDyn *fn; +} InspCustomClass; + +static SCR_TL InspCustomClass *g_custom_classes; +static SCR_TL size_t g_ncustom_classes; + +static void insp_custom_classes_cleanup(void) { + for (size_t i = 0; i < g_ncustom_classes; i++) { + free(g_custom_classes[i].type_key); + scr_dyn_release(g_custom_classes[i].fn); + } + free(g_custom_classes); + g_custom_classes = NULL; + g_ncustom_classes = 0; +} + +void scr_insp_register_custom(ScrStr *type_key, ScrDyn *fn) { + if (g_ncustom_classes == 0) scr_atexit(insp_custom_classes_cleanup); + InspCustomClass *grown = + realloc(g_custom_classes, (g_ncustom_classes + 1) * sizeof *g_custom_classes); + if (!grown) insp_oom(); + g_custom_classes = grown; + char *key = malloc(type_key->len + 1); + if (!key) insp_oom(); + memcpy(key, type_key->data, type_key->len); + key[type_key->len] = '\0'; + g_custom_classes[g_ncustom_classes].type_key = key; + g_custom_classes[g_ncustom_classes].fn = scr_dyn_retain(fn); + g_ncustom_classes++; +} + +static ScrDyn *insp_custom_class_fn(const ScrDyn *d) { + for (size_t i = 0; i < g_ncustom_classes; i++) + if (strcmp(g_custom_classes[i].type_key, d->v.typed_ref.type_key) == 0) + return g_custom_classes[i].fn; + return NULL; +} + +/* The [util.inspect.custom] result for an object kind, or NULL when the + * value renders normally (*thrown reports a pending exception). +1. */ +static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown) { + *thrown = false; + if (!g_custom_inspect) return NULL; + ScrDyn *fn; + bool compiled = d->kind == SCR_DYN_TYPED_REF; + if (compiled) { + /* A class instance: its class's compiled function, when it has a + * custom inspect method. A class whose method has no compiled call + * (the compiler refuses such classes where it renders them) refuses + * here too. */ + fn = insp_custom_class_fn(d); + if (!fn) { + ScrStr *name = scr_str_new("%symbol:Symbol(nodejs.util.inspect.custom)", 42); + bool declared = scr_dyn_typed_ref_class_has(d, name); + scr_str_release(name); + if (!declared) return NULL; + static const char message[] = + "util.inspect of this class instance's [util.inspect.custom] method has no lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + *thrown = true; + return NULL; + } + fn = scr_dyn_retain(fn); + } else { + ScrDyn *key = scr_dyn_inspect_custom_symbol(); + if (!key) return NULL; + if (d->kind != SCR_DYN_OBJ && d->kind != SCR_DYN_ARR && d->kind != SCR_DYN_FUNC) return NULL; + fn = scr_dyn_reflect_get(d, key, d); + if (!fn) { + *thrown = true; + return NULL; + } + } + if (fn->kind != SCR_DYN_FUNC || fn->v.fn.thunk == insp_inspect_call) { + scr_dyn_release(fn); + return NULL; + } + /* A constructor's prototype object renders normally. */ + ScrDyn *ctor = !compiled && d->kind == SCR_DYN_OBJ ? scr_dyn_obj_read(d, "constructor", 11) : NULL; + if (ctor && ctor->kind == SCR_DYN_FUNC) { + ScrDyn *prototype = scr_dyn_fn_get(ctor, "prototype", 9); + bool own_prototype = prototype == d; + scr_dyn_release(prototype); + if (own_prototype) { + scr_dyn_release(ctor); + scr_dyn_release(fn); + return NULL; + } + } + scr_dyn_release(ctor); + ScrDyn *args[3]; + /* ctx.depth - recurseTimes, or null for a null depth. */ + args[0] = scr_insp_custom_depth(recurse, depth); + args[1] = insp_user_options(depth); + if (!args[1]) { + scr_dyn_release(args[0]); + scr_dyn_release(fn); + *thrown = true; + return NULL; + } + args[2] = insp_inspect_value(); + ScrDyn *ret; + if (compiled) { + ScrDyn *call[4] = {d, args[0], args[1], args[2]}; + ret = scr_dyn_call(fn, call, 4, "[nodejs.util.inspect.custom]"); + } else { + scr_dyn_this_push_dyn(d); + ret = scr_dyn_call(fn, args, 3, "[nodejs.util.inspect.custom]"); + scr_dyn_this_pop(); + } + for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); + scr_dyn_release(fn); + if (!ret) { + *thrown = true; + return NULL; + } + if (ret == d || scr_insp_custom_is_self(ret, d)) { + scr_dyn_release(ret); + return NULL; + } + ScrStr *out; + if (ret->kind == SCR_DYN_STR) out = scr_insp_custom_text(ret->v.str); + else out = scr_insp_dyn(ret, recurse, depth); + scr_dyn_release(ret); + if (scr_exc_pending()) { + scr_str_release(out); + *thrown = true; + return NULL; + } + return out; +} + +bool scr_insp_custom_enabled(void) { return g_custom_inspect; } + +/* ctx.depth - recurseTimes, or null for a null depth (NaN for undefined). */ +ScrDyn *scr_insp_custom_depth(double recurse, double depth) { + if (isfinite(depth)) return scr_dyn_new_num(depth - recurse); + if (g_depth_kind == 0) return scr_dyn_new_num(g_depth_number - recurse); + return g_depth_kind == 1 ? scr_dyn_new_null() : scr_dyn_new_num(NAN); +} + +ScrDyn *scr_insp_custom_options(double depth) { return insp_user_options(depth); } + +ScrDyn *scr_insp_inspect_function(void) { return insp_inspect_value(); } + +bool scr_insp_custom_is_self(ScrDyn *result, ScrDyn *self) { + return result->kind == SCR_DYN_TYPED_REF && self->kind == SCR_DYN_TYPED_REF && + result->v.typed_ref.ptr == self->v.typed_ref.ptr; +} + +ScrStr *scr_insp_custom_result(ScrDyn *result, double recurse, double depth) { + if (result->kind == SCR_DYN_STR) return scr_insp_custom_text(result->v.str); + return scr_insp_dyn(result, recurse, depth); +} + +/* A custom function's string result: each newline followed by the + * current indentation. +1. */ +ScrStr *scr_insp_custom_text(ScrStr *text) { + if (!memchr(text->data, '\n', text->len)) return scr_str_retain(text); + InspBuf out = {0}; + for (size_t i = 0; i < text->len; i++) { + ib_char(&out, text->data[i]); + if (text->data[i] == '\n') ib_spaces(&out, g_indent); + } + return ib_take(&out); +} + ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { if (d->kind == SCR_DYN_PROXY) return scr_insp_dyn(d->v.proxy.target, recurse, depth); + if ((d->kind == SCR_DYN_TYPED_REF ? g_ncustom_classes > 0 : scr_dyn_inspect_custom_symbol() != NULL) && + (d->kind == SCR_DYN_OBJ || d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_FUNC || + d->kind == SCR_DYN_TYPED_REF)) { + bool thrown; + ScrStr *custom = insp_custom(d, recurse, depth, &thrown); + if (custom) return custom; + if (thrown) return scr_str_new("", 0); + } switch (d->kind) { case SCR_DYN_NULL: - return scr_str_new("null", 4); + return insp_styled_lit("null", 4, INSP_NULL); case SCR_DYN_UNDEF: - return scr_str_new("undefined", 9); + return insp_styled_lit("undefined", 9, INSP_UNDEFINED); case SCR_DYN_BOOL: - return d->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); + return d->v.b ? insp_styled_lit("true", 4, INSP_BOOLEAN) : insp_styled_lit("false", 5, INSP_BOOLEAN); case SCR_DYN_BIGINT: - return scr_bigint_inspect(d->v.bigint); + return insp_styled(scr_bigint_inspect(d->v.bigint), INSP_BIGINT); case SCR_DYN_SYMBOL: - return d->v.symbol.render(d->v.symbol.value); + return insp_styled(d->v.symbol.render(d->v.symbol.value), INSP_SYMBOL); case SCR_DYN_NUM: - return scr_insp_f64(d->v.num); + return insp_styled(scr_insp_f64(d->v.num), INSP_NUMBER); case SCR_DYN_STR: return scr_insp_str(d->v.str); case SCR_DYN_ARR: { size_t len = d->v.arr.len; if (len == 0) return scr_str_new("[]", 2); - if (recurse > depth) return scr_str_new("[Array]", 7); + if (recurse > depth) return insp_styled_lit("[Array]", 7, INSP_SPECIAL); scr_insp_begin(recurse + 1); /* Node's maxArrayLength (100) counts output entries; a run of holes * is one `` entry (formatSpecialArray). */ @@ -1512,7 +2031,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { while (next < len && !presence[next]) next++; char hole[48]; int hn = snprintf(hole, sizeof hole, "<%zu empty item%s>", next - i, next - i == 1 ? "" : "s"); - ScrStr *s = scr_str_new(hole, (size_t)hn); + ScrStr *s = insp_styled_lit(hole, (size_t)hn, INSP_UNDEFINED); scr_insp_entry(s, false); scr_str_release(s); i = next; @@ -1522,6 +2041,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { ScrStr *s = scr_insp_dyn(item, recurse + 1, depth); scr_insp_entry(s, item->kind == SCR_DYN_NUM); scr_str_release(s); + if (scr_exc_pending()) break; } bool more = i < len; if (more) { @@ -1542,7 +2062,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { /* An Error carried as a checked-dynamic value (a caught error, an * Error stored in an `unknown` slot): the error's own rendering, * never the encoding's internal marker. */ - if (scr_dyn_obj_get(d, "%error", 6) != NULL) return scr_insp_dyn_error(d, recurse, depth); + if (scr_dyn_obj_get(d, "%error", 6) != NULL) { + if (g_colors) return insp_colors_fence("Error values"); + return scr_insp_dyn_error(d, recurse, depth); + } /* Object.create(null)'s dictionary: Node prefixes the rendering * with "[Object: null prototype]" (formatValue's constructor-less * base), the empty form included, and the beyond-depth answer IS @@ -1562,9 +2085,12 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } else { ib_cstr(&out, "[Function (anonymous)]"); } - return ib_take(&out); + return insp_styled(ib_take(&out), INSP_SPECIAL); } case SCR_DYN_HANDLE: { + if (g_colors && d->v.handle.tag != SCR_DYNH_SET && d->v.handle.tag != SCR_DYNH_MAP && + d->v.handle.tag != SCR_DYNH_DATE) + return insp_colors_fence_values(scr_dyn_handle_cls(d)); if (d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER) { return scr_insp_array_buffer(d->v.handle.ptr, scr_dyn_handle_cls(d), recurse, depth); @@ -1574,7 +2100,8 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { if (d->v.handle.tag == SCR_DYNH_WEAK_MAP || d->v.handle.tag == SCR_DYNH_WEAK_SET) return scr_insp_weak_collection(d->v.handle.tag == SCR_DYNH_WEAK_MAP ? "WeakMap" : "WeakSet", recurse, depth); - if (d->v.handle.tag == SCR_DYNH_DATE) return scr_insp_date(scr_dyn_native_date_value(d)); + if (d->v.handle.tag == SCR_DYNH_DATE) + return insp_styled(scr_insp_date(scr_dyn_native_date_value(d)), INSP_DATE); if (d->v.handle.tag == SCR_DYNH_ITERATOR) { ScrStr *iterator = scr_insp_native_iterator(d, recurse, depth); if (iterator) return iterator; @@ -1623,9 +2150,14 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_TYPED_REF: { + if (g_colors && scr_insp_is_generator(d)) return insp_colors_fence("generator objects"); if (scr_insp_is_generator(d)) return scr_insp_generator(d); const char *cls = NULL; ScrDyn *view = scr_event_ref_hooks ? scr_event_ref_hooks->inspect_view(d, &cls) : NULL; + if (view && g_colors) { + scr_dyn_release(view); + return insp_colors_fence_values(cls); + } if (view) { /* The handle rule above: the class name past the depth limit. */ if (recurse > depth) { @@ -1655,6 +2187,11 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { double time; ScrStr *date_class = materialized->kind == SCR_DYN_OBJ && scr_dyn_date_class_time(d, &time) ? scr_dyn_date_class_name(d) : NULL; + if (date_class && g_colors) { + scr_str_release(date_class); + scr_dyn_release(materialized); + return insp_colors_fence("Date subclass instances"); + } if (date_class) { /* An instance of a class declared `extends Date`: the class name and * the time as Date.prototype.toISOString formats it, then its own @@ -1715,6 +2252,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_BYTES: { ScrBytes *b = d->v.bytes; + if (b->is_data_view && g_colors) return insp_colors_fence("DataView values"); if (b->is_data_view) return scr_insp_data_view(b, recurse, depth); if (d->buffer) { /* Buffer-flavored (a stream chunk): Node's form. */ @@ -1734,6 +2272,535 @@ ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth) { return scr_insp_dyn(d, 0, depth); } +/* ── util.format over checked-dynamic values ───────────────────────────── + * formatWithOptionsInternal over argument values whose types are known + * only at run time: util.format and util.formatWithOptions with a runtime + * format string or spread arguments, console output with a runtime format + * string, and debuglog's lines. The %-substitutions (%s %d %i %f %j %o %O + * %c %%) follow Node's argument cursor, then the remaining arguments join + * with spaces, strings verbatim and everything else inspected under the + * options. With colors, primitives take inspect's default styles; colored + * object renderings have no lowering. These entries call program code + * (toString, valueOf and toJSON), so unlike the rest of this file they + * can throw: NULL with the exception pending. */ +static void insp_fmt_fence(const ScrInspFormatOptions *o, const char *what) { + InspBuf b = {0}; + ib_cstr(&b, o->surface); + ib_char(&b, ' '); + ib_cstr(&b, what); + ScrStr *message = ib_take(&b); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); +} + +static bool insp_fmt_object(const ScrDyn *value) { + switch (value->kind) { + case SCR_DYN_OBJ: + case SCR_DYN_ARR: + case SCR_DYN_FUNC: + case SCR_DYN_BYTES: + case SCR_DYN_HANDLE: + case SCR_DYN_PROMISE: + case SCR_DYN_TYPED_REF: + case SCR_DYN_PROXY: + return true; + default: + return false; + } +} + +void scr_insp_format_defaults(ScrInspFormatOptions *o, const char *surface) { + o->surface = surface; + o->depth = 2; + o->depth_kind = 0; + o->depth_number = 2; + o->colors = false; + o->custom_inspect = true; + o->break_length = 80; + o->compact = 3; + o->sorted = false; + o->user_options = NULL; +} + +/* inspect(value, options) at the given depth, compact and colors, with + * the options' breakLength and sorted; the previous layout returns + * afterwards. +1, or NULL with the exception pending. */ +static ScrStr *insp_fmt_inspect(ScrDyn *value, const ScrInspFormatOptions *o, double depth, + double compact, bool colors) { + double break_length = g_break_length, saved_compact = g_compact; + bool sorted = g_sorted, saved_colors = g_colors, saved_custom = g_custom_inspect; + int saved_depth_kind = g_depth_kind; + double saved_depth_number = g_depth_number; + const ScrDyn *saved_user = g_user_options; + scr_insp_layout(o->break_length, compact, o->sorted); + g_colors = colors; + g_custom_inspect = o->custom_inspect; + g_depth_kind = o->depth_kind; + g_depth_number = o->depth_number; + g_user_options = o->user_options; + ScrStr *text = scr_insp_dyn(value, 0, depth); + g_colors = saved_colors; + g_custom_inspect = saved_custom; + g_depth_kind = saved_depth_kind; + g_depth_number = saved_depth_number; + g_user_options = saved_user; + scr_insp_layout(break_length, saved_compact, sorted); + if (scr_exc_pending()) { + scr_str_release(text); + return NULL; + } + return text; +} + +/* hasBuiltInToString's negative answer: a toString the program defined on + * the object or its prototype chain (1). Symbol-keyed members (where + * Symbol.toPrimitive would also count) are reported as unknown (-1), and a + * class instance whose class defines toString as -2: its methods are + * compiled for the class's own calls, which this runtime cannot make. */ +static int insp_fmt_custom_to_string(const ScrDyn *value) { + if (value->kind == SCR_DYN_TYPED_REF) { + ScrStr *to_string = scr_str_new("toString", 8); + bool has = scr_dyn_typed_ref_class_has(value, to_string); + scr_str_release(to_string); + return has ? -2 : 0; + } + for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) { + if (current->symbol_properties) return -1; + const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8); + if (member) return member->kind == SCR_DYN_FUNC; + } + return 0; +} + +static ScrStr *insp_fmt_s(ScrDyn *value, const ScrInspFormatOptions *o) { + if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str); + if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num); + if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint); + if (value->kind == SCR_DYN_FUNC) { + insp_fmt_fence(o, "%s of a function prints its source text, which a compiled binary does not keep"); + return NULL; + } + if ((value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) || + value->kind == SCR_DYN_TYPED_REF) { + int custom = insp_fmt_custom_to_string(value); + if (custom == -2) { + insp_fmt_fence(o, "%s of a class instance whose class defines toString has no lowering"); + return NULL; + } + if (custom < 0) { + insp_fmt_fence(o, "%s of objects with symbol-keyed members has no lowering"); + return NULL; + } + if (custom) return scr_dyn_string_coerce_js(value); + } + if (!insp_fmt_object(value)) return scr_insp_dyn(value, 0, 0); + /* inspect(arg, { ...inspectOptions, depth: 0, colors: false, compact: 3 }) */ + return insp_fmt_inspect(value, o, 0, 3, false); +} + +static ScrStr *insp_fmt_number(ScrDyn *value, int spec) { + if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint); + if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3); + double number; + if (spec == 'd') { + if (!scr_dyn_number_coerce_js(value, &number)) return NULL; + } else { + ScrStr *text = scr_dyn_string_coerce_js(value); + if (!text) return NULL; + number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); + scr_str_release(text); + } + return scr_insp_f64(number); +} + +/* One formatter's conversions: %s, %j, %d/%i/%f, %O/%o and the remaining + * arguments' inspect (depth -1: the options' depth). The loop references + * only the table it runs, so a program links only the conversions it uses. */ +typedef struct { + ScrStr *(*s)(ScrDyn *value, const ScrInspFormatOptions *o); + ScrStr *(*j)(const ScrDyn *value); + ScrStr *(*number)(ScrDyn *value, int spec); + ScrStr *(*inspect)(ScrDyn *value, const ScrInspFormatOptions *o, double depth); +} InspFmtOps; + +static ScrStr *insp_format_loop(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o, + const InspFmtOps *ops) { + if (argc == 0) return scr_str_new("", 0); + InspBuf out = {0}; + size_t a = 0; + const char *join = ""; + if (args[0]->kind == SCR_DYN_STR) { + const ScrStr *first = args[0]->v.str; + if (argc == 1) return scr_str_retain((ScrStr *)first); + const char *text = first->data; + size_t last = 0; + for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) { + if (text[i] != '%') continue; + int next = (unsigned char)text[++i]; + if (a + 1 != argc) { + ScrStr *piece = NULL; + switch (next) { + case 's': piece = ops->s(args[++a], o); break; + case 'j': piece = ops->j(args[++a]); break; + case 'd': + case 'i': + case 'f': piece = ops->number(args[++a], next); break; + case 'O': piece = ops->inspect(args[++a], o, o->depth); break; + case 'o': + if (insp_fmt_object(args[a + 1])) { + insp_fmt_fence(o, "%o of objects renders hidden properties, which have no lowering; use %O"); + break; + } + piece = ops->inspect(args[++a], o, 4); + break; + case 'c': + a += 1; + piece = scr_str_new("", 0); + break; + case '%': + ib_bytes(&out, text + last, i - last); + last = i + 1; + continue; + default: + continue; + } + if (!piece) { + free(out.data); + return NULL; + } + if (last != i - 1) ib_bytes(&out, text + last, i - 1 - last); + ib_str(&out, piece); + scr_str_release(piece); + last = i + 1; + } else if (next == '%') { + ib_bytes(&out, text + last, i - last); + last = i + 1; + } + } + if (last != 0) { + a++; + join = " "; + if (last < first->len) ib_bytes(&out, text + last, first->len - last); + } + } + for (; a < argc; a++) { + ib_cstr(&out, join); + if (args[a]->kind == SCR_DYN_STR) { + ib_str(&out, args[a]->v.str); + } else { + ScrStr *piece = ops->inspect(args[a], o, o->depth); + if (!piece) { + free(out.data); + return NULL; + } + ib_str(&out, piece); + scr_str_release(piece); + } + join = " "; + } + return ib_take(&out); +} + + +static ScrStr *insp_fmt_inspect_full(ScrDyn *value, const ScrInspFormatOptions *o, double depth) { + return insp_fmt_inspect(value, o, depth, o->compact, o->colors); +} + +static const InspFmtOps insp_fmt_full_ops = {insp_fmt_s, scr_util_format_j, insp_fmt_number, + insp_fmt_inspect_full}; + +ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o) { + return insp_format_loop(args, argc, o, &insp_fmt_full_ops); +} + +/* The formatter over arguments that are all strings, numbers, booleans, + * null or undefined (the compiler proves it from their types): the same + * rules for those kinds, without the checked-dynamic walk the other + * values need. */ +static ScrStr *insp_prim_to_string(const ScrDyn *v) { + switch (v->kind) { + case SCR_DYN_STR: return scr_str_retain(v->v.str); + case SCR_DYN_NUM: { + char buf[40]; + size_t n = scr_f64_to_str(v->v.num, buf); + return scr_str_new(buf, n); + } + case SCR_DYN_BOOL: return v->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); + case SCR_DYN_NULL: return scr_str_new("null", 4); + default: return scr_str_new("undefined", 9); + } +} + +static ScrStr *insp_prim_inspect(ScrDyn *v, const ScrInspFormatOptions *o, double depth) { + (void)o; + (void)depth; + if (v->kind == SCR_DYN_STR) return scr_insp_str(v->v.str); + if (v->kind == SCR_DYN_NUM) return scr_insp_f64(v->v.num); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_s(ScrDyn *v, const ScrInspFormatOptions *o) { + (void)o; + if (v->kind == SCR_DYN_NUM) return scr_insp_f64(v->v.num); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_j(const ScrDyn *v) { + if (v->kind == SCR_DYN_STR) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_put_json_str(&b, v->v.str); + return scr_jb_finish(&b); + } + if (v->kind == SCR_DYN_NUM && !isfinite(v->v.num)) return scr_str_new("null", 4); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_number(ScrDyn *v, int spec) { + double number; + if (spec == 'd') { + number = v->kind == SCR_DYN_NUM ? v->v.num + : v->kind == SCR_DYN_STR ? scr_string_to_number(v->v.str) + : v->kind == SCR_DYN_BOOL ? (v->v.b ? 1 : 0) + : v->kind == SCR_DYN_NULL ? 0 + : NAN; + } else { + ScrStr *text = insp_prim_to_string(v); + number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); + scr_str_release(text); + } + return scr_insp_f64(number); +} + +static const InspFmtOps insp_fmt_primitive_ops = {insp_prim_s, insp_prim_j, insp_prim_number, + insp_prim_inspect}; + +ScrStr *scr_insp_format_primitives(ScrDyn *pack) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.format"); + if (pack->kind != SCR_DYN_ARR) return scr_str_new("", 0); + return insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); +} + +/* The arguments of a dyn array pack (+1 text, or NULL pending). */ +static ScrStr *insp_fmt_pack(ScrDyn *pack, const ScrInspFormatOptions *o) { + if (pack->kind != SCR_DYN_ARR) return scr_insp_format(NULL, 0, o); + return scr_insp_format(pack->v.arr.items, pack->v.arr.len, o); +} + +ScrStr *scr_insp_format_pack(ScrDyn *pack) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.format"); + return insp_fmt_pack(pack, &o); +} + +/* Whether a format string can substitute: a '%' before its last character. + * A string without one formats as its arguments joined with spaces. */ +bool scr_insp_format_specifies(ScrStr *first) { + return first->len > 1 && memchr(first->data, '%', first->len - 1) != NULL; +} + +bool scr_insp_format_specifies_dyn(ScrDyn *first) { + return first->kind == SCR_DYN_STR && scr_insp_format_specifies(first->v.str); +} + +/* One own enumerable option of an inspect options object: +1 value, a + * borrowed undefined when absent, or NULL with the exception pending. */ +static ScrDyn *insp_opt(const ScrDyn *options, const char *key, bool *present) { + size_t len = strlen(key); + *present = options->kind == SCR_DYN_OBJ && scr_dyn_obj_get(options, key, len) != NULL && + scr_dyn_obj_enumerable(options, key, len); + if (!*present) return scr_dyn_retain(scr_dyn_undefined()); + return scr_dyn_obj_read(options, key, len); +} + +/* ctx.depth from an option or inspect's third argument: a number, null + * or undefined (no limit for null, undefined and NaN). */ +static bool insp_depth_option(const ScrDyn *value, ScrInspFormatOptions *o) { + if (value->kind == SCR_DYN_NUM) { + o->depth = isnan(value->v.num) ? INFINITY : value->v.num; + o->depth_kind = 0; + o->depth_number = value->v.num; + } else if (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF) { + o->depth = INFINITY; + o->depth_kind = value->kind == SCR_DYN_NULL ? 1 : 2; + } else { + return false; + } + return true; +} + +/* inspect's option keys (inspectDefaultOptions and stylize) over an + * options object, as `inspect(value, options)` copies them. Options at + * their defaults, depth, colors, compact (a number or false), + * breakLength (a number) and sorted (a boolean) lower; other values of + * the known options throw the SC2020 refusal. */ +bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o) { + ScrDyn *view = NULL; + if (options->kind == SCR_DYN_TYPED_REF) { + view = scr_dyn_typed_ref_materialize(options); + if (scr_exc_pending()) { + scr_dyn_release(view); + return false; + } + options = view; + } + bool ok = false, present; + ScrDyn *value = NULL; +#define INSP_OPT(key) \ + do { \ + scr_dyn_release(value); \ + value = insp_opt(options, key, &present); \ + if (!value) goto done; \ + } while (0) + INSP_OPT("depth"); + if (present) { + if (!insp_depth_option(value, o)) { + insp_fmt_fence(o, "with a depth option that is not a number or null has no lowering"); + goto done; + } + } + INSP_OPT("colors"); + if (present) o->colors = scr_dyn_truthy(value); + INSP_OPT("customInspect"); + if (present) o->custom_inspect = scr_dyn_truthy(value); + INSP_OPT("compact"); + if (present) { + if (value->kind == SCR_DYN_NUM) o->compact = value->v.num < 0 ? 0 : value->v.num; + else if (value->kind == SCR_DYN_BOOL && !value->v.b) o->compact = -1; + else { + insp_fmt_fence(o, "with this compact option has no lowering (compact must be false or a number)"); + goto done; + } + } + INSP_OPT("breakLength"); + if (present) { + if (value->kind != SCR_DYN_NUM) { + insp_fmt_fence(o, "with a breakLength option that is not a number has no lowering"); + goto done; + } + o->break_length = value->v.num; + } + INSP_OPT("sorted"); + if (present) { + if (value->kind != SCR_DYN_BOOL) { + insp_fmt_fence(o, "with a sorted option that is not a boolean has no lowering"); + goto done; + } + o->sorted = value->v.b; + } + /* The options whose non-default values change renderings this runtime + * does not produce. */ + static const struct { + const char *key; + int kind; /* 0: falsy, 1: true, 2: the number 100, 3: the number 10000 */ + } fixed[] = { + {"showHidden", 0}, {"showProxy", 0}, {"maxArrayLength", 2}, {"maxStringLength", 3}, + {"getters", 0}, {"numericSeparator", 0}, {"stylize", 0}, + }; + for (size_t i = 0; i < sizeof fixed / sizeof *fixed; i++) { + INSP_OPT(fixed[i].key); + if (!present) continue; + bool at_default = fixed[i].kind == 0 ? !scr_dyn_truthy(value) + : fixed[i].kind == 1 ? value->kind == SCR_DYN_BOOL && value->v.b + : value->kind == SCR_DYN_NUM && value->v.num == (fixed[i].kind == 2 ? 100 : 10000); + /* The stylize function a custom inspect function received (options + * passed back into util.inspect) is the default for its colors. */ + if (!at_default && strcmp(fixed[i].key, "stylize") == 0 && insp_own_stylize(value, o->colors)) + at_default = true; + /* stylize is replaced by the color stylizer when colors is set. */ + if (!at_default && !(o->colors && strcmp(fixed[i].key, "stylize") == 0)) { + InspBuf b = {0}; + ib_cstr(&b, "with the "); + ib_cstr(&b, fixed[i].key); + ib_cstr(&b, " option has no lowering"); + ScrStr *what = ib_take(&b); + insp_fmt_fence(o, what->data); + scr_str_release(what); + goto done; + } + } +#undef INSP_OPT + /* Keys inspect does not read: the custom functions receive them. */ + if (!view && options->kind == SCR_DYN_OBJ) { + static const char *const known[] = { + "showHidden", "depth", "colors", "customInspect", "showProxy", + "maxArrayLength", "maxStringLength", "breakLength", "compact", "sorted", + "getters", "numericSeparator", "stylize", + }; + for (size_t i = 0; i < options->v.obj.len && !o->user_options; i++) { + const ScrDynEntry *ent = &options->v.obj.entries[i]; + if (!ent->enumerable) continue; + bool read = false; + for (size_t k = 0; k < sizeof known / sizeof *known && !read; k++) + read = ent->key_len == strlen(known[k]) && memcmp(ent->key, known[k], ent->key_len) == 0; + if (!read) o->user_options = options; + } + } + ok = true; +done: + scr_dyn_release(value); + scr_dyn_release(view); + return ok; +} + +/* util.inspect(value[, options]) and inspect(value, showHidden, depth, + * colors) over checked-dynamic arguments; `argc` is the call's argument + * count. A third argument other than undefined sets the depth and a + * fourth the colors; a boolean second argument is showHidden, and an + * object supplies the options, which apply after the legacy arguments. + * +1, or NULL with the exception pending. */ +ScrStr *scr_insp_inspect(ScrDyn *value, ScrDyn *options, ScrDyn *depth, ScrDyn *colors, + double argc) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.inspect"); + if (argc >= 3 && depth->kind != SCR_DYN_UNDEF) { + if (!insp_depth_option(depth, &o)) { + insp_fmt_fence(&o, "with a depth argument that is not a number or null has no lowering"); + return NULL; + } + } + if (argc >= 4 && colors->kind != SCR_DYN_UNDEF) o.colors = scr_dyn_truthy(colors); + if (options->kind == SCR_DYN_BOOL) { + if (options->v.b) { + insp_fmt_fence(&o, "with showHidden has no lowering"); + return NULL; + } + } else if (options->kind == SCR_DYN_OBJ || options->kind == SCR_DYN_TYPED_REF) { + if (!scr_insp_format_read_options(options, &o)) return NULL; + } + return insp_fmt_inspect(value, &o, o.depth, o.compact, o.colors); +} + +static ScrDyn *insp_inspect_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *undefined = scr_dyn_undefined(); + scr_insp_nested_begin(); + ScrStr *text = scr_insp_inspect(argc > 0 ? args[0] : undefined, argc > 1 ? args[1] : undefined, + argc > 2 ? args[2] : undefined, argc > 3 ? args[3] : undefined, + (double)argc); + scr_insp_nested_restore(); + if (!text) return NULL; + ScrDyn *out = scr_dyn_new_str(text); + scr_str_release(text); + return out; +} + +/* util.formatWithOptions(inspectOptions, ...pack): validateObject (arrays + * pass, null and functions do not), then the options as inspect reads + * them. */ +ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack) { + if (options->kind != SCR_DYN_OBJ && options->kind != SCR_DYN_ARR && + options->kind != SCR_DYN_TYPED_REF) { + scr_dyn_arg_type_fail("inspectOptions", "of type object", options); + return NULL; + } + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.formatWithOptions"); + if (!scr_insp_format_read_options(options, &o)) return NULL; + return insp_fmt_pack(pack, &o); +} + /* ── console.table cells ─────────────────────────────────────────────── */ size_t scr_insp_display_width(const char *data, size_t len) { return insp_width(data, len); } diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index 5ac3aa9dd..a95ac1fde 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -47,8 +47,50 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth); ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth); /* format's %s / rest-args twin: dyn strings pass verbatim. */ ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth); +/* util.format over checked-dynamic arguments (formatWithOptionsInternal): + * the options an inspect of each argument takes, and the API name the + * SC2020 refusals name. These entries call program code, so they answer + * NULL with the exception pending when it throws. */ +typedef struct ScrInspFormatOptions { + const char *surface; + double depth; /* Infinity for null, undefined and NaN */ + /* The depth as the options gave it, which custom inspect functions + * receive when the depth is unbounded: 0 a number (depth_number, + * Infinity or NaN), 1 null, 2 undefined. */ + int depth_kind; + double depth_number; + bool colors; + bool custom_inspect; + double break_length; + double compact; /* -1 for false */ + bool sorted; + /* The options object, when it holds keys inspect does not read: custom + * inspect functions receive them (borrowed; NULL otherwise). */ + const ScrDyn *user_options; +} ScrInspFormatOptions; +void scr_insp_format_defaults(ScrInspFormatOptions *o, const char *surface); +/* Reads inspect's options from an options object into o; false with the + * SC2020 refusal pending for values that have no lowering. */ +bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o); +ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o); +/* util.format(...pack) and util.formatWithOptions(options, ...pack) over a + * dyn array of the arguments (+1). */ +ScrStr *scr_insp_format_pack(ScrDyn *pack); +/* util.format(...pack) when every argument is a string, number, boolean, + * null or undefined, without the checked-dynamic walk (+1). */ +ScrStr *scr_insp_format_primitives(ScrDyn *pack); +ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack); +/* util.inspect(value, options, depth, colors) over checked-dynamic + * arguments with Node's legacy-argument rules; argc is the call's + * argument count (+1, or NULL with the exception pending). */ +ScrStr *scr_insp_inspect(ScrDyn *value, ScrDyn *options, ScrDyn *depth, ScrDyn *colors, + double argc); +/* Whether a format string has a '%' before its last character, the only + * strings whose substitutions can change the output. */ +bool scr_insp_format_specifies(ScrStr *first); +bool scr_insp_format_specifies_dyn(ScrDyn *first); void scr_insp_begin(double recurse); -/* util.inspect's breakLength, compact (0 for false) and sorted for the +/* util.inspect's breakLength, compact (-1 for false) and sorted for the * renders that follow; the call site restores the defaults (80, 3, false) * afterwards. */ void scr_insp_layout(double break_length, double compact, bool sorted); @@ -75,6 +117,33 @@ ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse, * caller's level (+1; borrows both). */ void scr_insp_nested_begin(void); ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner); +/* nested_end's restore without its text: the caller's context returns. */ +void scr_insp_nested_restore(void); +/* ctx.customInspect for the renders that follow; answers the previous + * value for the caller to restore. */ +bool scr_insp_custom_inspect(bool enabled); +/* How the unbounded depth of the renders that follow was given, for custom + * inspect functions: 0 Infinity, 1 null, 2 undefined. Answers the + * previous kind for the caller to restore. */ +double scr_insp_depth_kind(double kind); +/* A custom inspect function's string result re-indented at the current + * level (+1; borrows text). */ +ScrStr *scr_insp_custom_text(ScrStr *text); +/* The compiled [util.inspect.custom] calls: ctx.customInspect, the depth + * argument at recursion `recurse` of `depth` and the options object (+1 + * dyn values; options NULL with an exception pending), util.inspect as a + * function value (+1), whether a dyn result is the instance itself, and a + * dyn result's rendering at the instance's level (+1). */ +bool scr_insp_custom_enabled(void); +/* Registers a class's compiled custom inspect function under the type key + * of its instances' typed refs: fn(self, depth, options, inspect) answers + * the method's result (borrows both). */ +void scr_insp_register_custom(ScrStr *type_key, ScrDyn *fn); +ScrDyn *scr_insp_custom_depth(double recurse, double depth); +ScrDyn *scr_insp_custom_options(double depth); +ScrDyn *scr_insp_inspect_function(void); +bool scr_insp_custom_is_self(ScrDyn *result, ScrDyn *self); +ScrStr *scr_insp_custom_result(ScrDyn *result, double recurse, double depth); /* The buf.read* / buf.write* numeric families on u8 bytes, Node-exact: * boundsError's ladder (non-integer offset → 'It must be an integer', a diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index dfa81ace6..3a4e6190d 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -172,6 +172,24 @@ bool scr_dyn_is_async_dispose_symbol(const ScrDyn *key) { key->v.symbol.value == scr_async_dispose_symbol->v.symbol.value; } +/* Symbol.for('nodejs.util.inspect.custom'), installed by the symbol unit + * when the program creates it: the key util.inspect reads custom inspect + * functions under. */ +static SCR_TL ScrDyn *scr_inspect_custom_symbol; + +static void scr_inspect_custom_symbol_cleanup(void) { + scr_dyn_release(scr_inspect_custom_symbol); + scr_inspect_custom_symbol = NULL; +} + +void scr_dyn_install_inspect_custom_symbol(ScrDyn *key) { + if (scr_inspect_custom_symbol) return; + scr_inspect_custom_symbol = scr_dyn_retain(key); + scr_atexit(scr_inspect_custom_symbol_cleanup); +} + +ScrDyn *scr_dyn_inspect_custom_symbol(void) { return scr_inspect_custom_symbol; } + static void scr_to_primitive_symbol_cleanup(void) { scr_dyn_release(scr_to_primitive_symbol); scr_to_primitive_symbol = NULL; diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 304f2544f..e49edb370 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -125,6 +125,11 @@ ScrSym *scr_sym_for(ScrStr *key) { s->reg_key = scr_str_retain(key); s->reg_next = g_sym_registry; g_sym_registry = scr_sym_retain(s); + if (key->len == 26 && !memcmp(key->data, "nodejs.util.inspect.custom", 26)) { + ScrDyn *custom = scr_dyn_new_symbol(s); + scr_dyn_install_inspect_custom_symbol(custom); + scr_dyn_release(custom); + } return s; } diff --git a/packages/runtime/src/scr_util_callable.c b/packages/runtime/src/scr_util_callable.c index c905ea8ae..e3efd0be7 100644 --- a/packages/runtime/src/scr_util_callable.c +++ b/packages/runtime/src/scr_util_callable.c @@ -9,8 +9,8 @@ * and reported on the next tick (scr_warning_emit_deferred), which also * applies process.noDeprecation and process.throwDeprecation. debuglog * matches sections against NODE_DEBUG as the process started and writes - * Node's "SECTION PID: message" lines to stderr. All arguments are - * borrowed. */ + * Node's "SECTION PID: message" lines, formatted by scr_insp_format, to + * stderr. All arguments are borrowed. */ #include "scr_runtime.h" #include @@ -513,163 +513,6 @@ ScrDyn *scr_util_callbackify(ScrDyn *original) { return callbackified; } -/* ── util.format over dyn arguments ──────────────────────────────────── - * formatWithOptionsInternal for debuglog's lines: the %-substitutions - * (%s %d %i %f %j %o %O %c %%) with Node's argument cursor, then the - * remaining arguments space-joined, strings verbatim and everything else - * inspected. With terminal colors, primitives take inspect's default - * styles; colored object renderings have no lowering. */ -static bool uc_primitive(const ScrDyn *value) { - return !uc_object(value); -} - -static ScrStr *uc_inspect(ScrDyn *value, double depth, bool colors) { - if (!colors) return scr_insp_dyn(value, 0, depth); - if (!uc_primitive(value)) { - uc_fence("util.debuglog output of objects on a color terminal has no lowering"); - return NULL; - } - const char *open = "\x1b[33m", *close = "\x1b[39m"; - if (value->kind == SCR_DYN_UNDEF) open = "\x1b[90m"; - else if (value->kind == SCR_DYN_NULL) open = "\x1b[1m", close = "\x1b[22m"; - else if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_SYMBOL) open = "\x1b[32m"; - ScrStr *text = scr_insp_dyn(value, 0, depth); - ScrJsonBuf b; - scr_jb_init(&b); - scr_jb_puts(&b, open); - scr_jb_put_str(&b, text); - scr_jb_puts(&b, close); - scr_str_release(text); - return scr_jb_finish(&b); -} - -/* hasBuiltInToString's negative answer for checked objects: an own or - * inherited toString the program defined. Symbol-keyed members (where - * Symbol.toPrimitive would also count) are reported as unknown. */ -static int uc_custom_to_string(const ScrDyn *value) { - for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) { - if (current->symbol_properties) return -1; - const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8); - if (member) return member->kind == SCR_DYN_FUNC; - } - return 0; -} - -static ScrStr *uc_format_s(ScrDyn *value) { - if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str); - if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num); - if (value->kind == SCR_DYN_FUNC) { - uc_fence("util.debuglog %s of a function prints its source text, which a compiled binary does not keep"); - return NULL; - } - if (value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) { - int custom = uc_custom_to_string(value); - if (custom < 0) { - uc_fence("util.debuglog %s of objects with symbol-keyed members has no lowering"); - return NULL; - } - if (custom) return scr_dyn_string_coerce_js(value); - } - return scr_insp_dyn_s(value, 0); -} - -static ScrStr *uc_format_number(ScrDyn *value, int spec) { - if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint); - if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3); - double number; - if (spec == 'd') { - if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint); - if (!scr_dyn_number_coerce_js(value, &number)) return NULL; - } else { - ScrStr *text = scr_dyn_string_coerce_js(value); - if (!text) return NULL; - number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); - scr_str_release(text); - } - return scr_insp_f64(number); -} - -static ScrStr *uc_format(ScrDyn *const *args, size_t argc, bool colors) { - if (argc == 0) return scr_str_new("", 0); - ScrJsonBuf out; - scr_jb_init(&out); - size_t a = 0; - const char *join = ""; - if (args[0]->kind == SCR_DYN_STR) { - const ScrStr *first = args[0]->v.str; - if (argc == 1) { - scr_str_release(scr_jb_finish(&out)); - return scr_str_retain((ScrStr *)first); - } - const char *text = first->data; - size_t last = 0; - for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) { - if (text[i] != '%') continue; - int next = (unsigned char)text[++i]; - if (a + 1 != argc) { - ScrStr *piece = NULL; - switch (next) { - case 's': piece = uc_format_s(args[++a]); break; - case 'j': piece = scr_util_format_j(args[++a]); break; - case 'd': - case 'i': - case 'f': piece = uc_format_number(args[++a], next); break; - case 'O': piece = uc_inspect(args[++a], 2, colors); break; - case 'o': - if (!uc_primitive(args[a + 1])) { - uc_fence("util.debuglog %o of objects renders hidden properties, which have no lowering; use %O"); - break; - } - piece = uc_inspect(args[++a], 4, colors); - break; - case 'c': - a += 1; - piece = scr_str_new("", 0); - break; - case '%': - scr_jb_write(&out, text + last, i - last); - last = i + 1; - continue; - default: - continue; - } - if (!piece) { - scr_str_release(scr_jb_finish(&out)); - return NULL; - } - if (last != i - 1) scr_jb_write(&out, text + last, i - 1 - last); - scr_jb_put_str(&out, piece); - scr_str_release(piece); - last = i + 1; - } else if (next == '%') { - scr_jb_write(&out, text + last, i - last); - last = i + 1; - } - } - if (last != 0) { - a++; - join = " "; - if (last < first->len) scr_jb_write(&out, text + last, first->len - last); - } - } - for (; a < argc; a++) { - scr_jb_puts(&out, join); - if (args[a]->kind == SCR_DYN_STR) { - scr_jb_put_str(&out, args[a]->v.str); - } else { - ScrStr *piece = uc_inspect(args[a], 2, colors); - if (!piece) { - scr_str_release(scr_jb_finish(&out)); - return NULL; - } - scr_jb_put_str(&out, piece); - scr_str_release(piece); - } - join = " "; - } - return scr_jb_finish(&out); -} - /* ── util.debuglog ───────────────────────────────────────────────────── * The logger defers its NODE_DEBUG test to its first call or `enabled` * read. The first call also selects the section's shared implementation @@ -735,7 +578,10 @@ static bool uc_debug_test(const ScrStr *section) { static ScrDyn *uc_debug_write(ScrClosure *closure, ScrDyn *const *args, size_t argc) { bool colors = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors() : scr_process_is_tty(2) && scr_util_terminal_colors(); - ScrStr *message = uc_format(args, argc, colors); + ScrInspFormatOptions options; + scr_insp_format_defaults(&options, "util.debuglog"); + options.colors = colors; + ScrStr *message = scr_insp_format(args, argc, &options); if (!message) return NULL; ScrDyn *set = uc_cap(closure, 0); char pid[64]; diff --git a/tests/corpus/7520-util-format-runtime.ts b/tests/corpus/7520-util-format-runtime.ts new file mode 100644 index 000000000..d4a7f9ea6 --- /dev/null +++ b/tests/corpus/7520-util-format-runtime.ts @@ -0,0 +1,110 @@ +// util.format, util.formatWithOptions and console output with format +// strings known only at run time, spread arguments and options objects: +// every substitution, the argument cursor, %% and %c, missing and extra +// arguments, the space-joined form of strings without a '%', and Node's +// inspectOptions validation. +import * as util from "node:util"; + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +interface Row { + id: number; + tags: string[]; + nested: { deep: { deeper: { deepest: number } } }; +} + +const formats: string[] = [ + "%s and %s", + "%d|%i|%f", + "%j", + "%O / %o", + "100%% of %s", + "%c%s%c", + "no specifiers", + "trailing %", + "%x %s", + "%s", +]; +const row: Row = { id: 7, tags: ["a", "b"], nested: { deep: { deeper: { deepest: 1 } } } }; + +for (const format of formats) { + console.log(util.format(format, "first", 42, row)); + console.log(util.format(format, 3.5, "12.75")); + console.log(util.format(format)); +} + +function pick(i: number): string { + return ["%s=%d", "%j", "%O", "plain"][i % 4]!; +} +for (let i = 0; i < 4; i++) { + console.log(util.format(pick(i), new Point(1, 2), -0, [1, 2, 3])); + console.log(pick(i), new Map([["k", new Point(3, 4)]]), new Set([1n, 2n])); + console.error(pick(i), "to stderr", null, undefined); +} + +const label: string = process.argv.length > 99 ? "" : "count: %d of %s"; +console.log(label, 5, "five", "extra"); +console.log(util.format(label, 10n, Symbol("sym"))); +console.log(util.format(label, new Point(0, 0), [new Point(5, 6)])); +console.log(util.format(pick(0), { toString: "not a function" }, true)); + +// Arguments that are all strings, numbers, booleans, null or undefined. +const prim: string = process.argv.length > 99 ? "" : "%s|%d|%i|%f|%j|%O|%o|%c|%%"; +console.log(prim, -0, "12.5", -0, true, 'q"uote', "s", 3, "css", "rest", null, undefined); +console.log(prim, NaN, null, undefined, "0x10", Infinity, NaN, false); +console.log(util.format(prim, "a\nb", " 7 ", "-3.9e2", "", -Infinity, 'it\'s', 1e21)); +console.log(util.format(label, 2.5)); + +// A union first argument formats only when it holds a string. +const maybe: string | number = process.argv.length > 99 ? 1 : "%s/%s"; +const notString: string | number = process.argv.length > 99 ? "%s" : 9; +console.log(util.format(maybe, "u", "v")); +console.log(util.format(notString, "u", "v")); +console.log(maybe, "w", "x"); + +// An unknown first argument. +const loose: unknown = process.argv.length > 99 ? 0 : "%s-%i"; +console.log(util.format(loose, "a", "7.9")); +console.log(loose, "b", 3.2); + +// Spread arguments. +const values: unknown[] = ["%s + %s = %d", 1, 2, 3, "tail"]; +console.log(util.format(...values)); +console.log(util.format("%s|%s", ...values)); +console.log(...values); +console.log("prefix", ...new Set(["x", "y"]), "suffix"); +console.error(...[1, "two", [3]]); +console.log(util.format(...([] as string[]))); + +// formatWithOptions with an options object. +const deep = { a: { b: { c: { d: { e: 1 } } } } }; +const depth = process.argv.length > 99 ? 1 : 0; +console.log(util.formatWithOptions({ depth }, "%O", deep)); +console.log(util.formatWithOptions({ depth: null }, deep)); +console.log(util.formatWithOptions({ depth: undefined }, "x", deep)); +console.log(util.formatWithOptions({ depth: 0 }, "%s|%O", deep, deep)); +console.log(util.formatWithOptions({ compact: false }, "%s", "v", { a: 1, b: [1, 2] })); +console.log(util.formatWithOptions({ breakLength: 20 }, { alpha: 1, beta: 2, gamma: 3 })); +console.log(util.formatWithOptions({ sorted: true }, "%O", { b: 1, a: 2, c: { z: 1, y: 2 } })); +console.log(util.formatWithOptions({ colors: true }, "%s %d %O", "s", 2, 3), "|"); +console.log(util.formatWithOptions({ colors: true }, 1, "plain", null, undefined, true, 4n)); +const options: util.InspectOptions = { depth: 1, compact: 1 }; +console.log(util.formatWithOptions(options, "%O", deep)); +console.log(util.formatWithOptions(options, ...values)); +console.log(util.formatWithOptions([] as unknown as util.InspectOptions, "%s", 1)); +const invalid: unknown[] = [null, 1, "x", undefined, () => {}]; +for (const bad of invalid) { + try { + util.formatWithOptions(bad as util.InspectOptions, "a"); + } catch (error) { + const e = error as NodeJS.ErrnoException; + console.log(e.name, e.code, e.message); + } +} diff --git a/tests/corpus/7521-util-format-runtime.cjs b/tests/corpus/7521-util-format-runtime.cjs new file mode 100644 index 000000000..559d09243 --- /dev/null +++ b/tests/corpus/7521-util-format-runtime.cjs @@ -0,0 +1,64 @@ +// util.format, util.formatWithOptions and console output over untyped +// values: format strings held in variables and parameters, spread +// arguments, %s over objects with and without their own toString, %d and +// %i coercions, %j over toJSON, and options objects built at run time. +"use strict"; +const util = require("util"); + +function log(message, ...rest) { + console.log(message, ...rest); +} + +function formatAll(format, values) { + return util.format(format, ...values); +} + +const messages = ["%s: %d items", "%s", "%i%%", "%j", "plain text", 42]; +for (const message of messages) { + log(message, "cart", "3.7", { total: 9 }); + console.log(formatAll(message, ["a", 1n])); +} + +const withToString = { + name: "custom", + toString() { + return `<${this.name}>`; + }, +}; +const inherited = Object.create(withToString); +inherited.name = "inherited"; +const fmt = "%s / %s / %s"; +console.log(util.format(fmt, withToString, inherited, [1, [2, [3]]])); +console.log(fmt, new Date(0), new Error("boom").message, null, Symbol("s")); + +const numeric = "%d %i %f"; +console.log(util.format(numeric, "0x1f", "12.9px", "1e3")); +console.log(util.format(numeric, { valueOf: () => 5 }, true, [])); +console.log(util.format(numeric, 7n, 8n, 9)); + +const json = "%j|%j|%j"; +const circular = { name: "loop" }; +circular.self = circular; +console.log(util.format(json, { toJSON: () => ({ replaced: 1 }) }, circular, undefined)); + +const options = {}; +options.depth = 0; +options.breakLength = 40; +console.log(util.formatWithOptions(options, "%O", { a: { b: 1 } }, ["x", { y: { z: 2 } }])); +options.depth = null; +options.unknownOption = true; +console.log(util.formatWithOptions(options, { a: { b: { c: { d: 1 } } } })); +console.log(util.formatWithOptions({ compact: 1 }, ...[{ k: [1, 2] }, "end"])); +for (const bad of [null, 0, "options", () => {}]) { + try { + util.formatWithOptions(bad, "x"); + } catch (error) { + console.log(error.name, error.code, error.message); + } +} + +const args = ["%s=%s", "key"]; +args.push("value", "rest"); +console.log(...args); +console.warn(...args); +console.log(util.format(...args.slice(1))); diff --git a/tests/corpus/7522-promisify-untyped.cjs b/tests/corpus/7522-promisify-untyped.cjs new file mode 100644 index 000000000..2e1476eac --- /dev/null +++ b/tests/corpus/7522-promisify-untyped.cjs @@ -0,0 +1,64 @@ +// util.promisify over JavaScript functions whose types are not known: +// arrows, function expressions, methods and functions chosen at run time. +// The callback's error rejects and its first value fulfills, a +// synchronous throw rejects, the call's receiver reaches the function, +// the wrapper keeps the original's name and length, and arguments that +// are not functions throw Node's ERR_INVALID_ARG_TYPE. +"use strict"; +const util = require("util"); + +function pick(kind) { + if (kind === "double") return (value, callback) => setImmediate(() => callback(null, value * 2)); + if (kind === "fail") return function failing(callback) { callback(new RangeError("failed")); }; + if (kind === "throw") return () => { throw new TypeError("thrown"); }; + return null; +} + +const store = { + prefix: "item", + read(key, callback) { + setTimeout(() => callback(null, `${this.prefix}:${key}`, "ignored"), 1); + }, +}; + +async function main() { + const double = util.promisify(pick("double")); + console.log("double:", await double(21), double.name, double.length); + try { + await util.promisify(pick("fail"))(); + } catch (error) { + console.log("rejected:", error.name, error.message); + } + try { + await util.promisify(pick("throw"))(); + } catch (error) { + console.log("sync:", error.name, error.message); + } + const read = util.promisify(store.read); + console.log("unbound:", await read.call(store, "a")); + store.readAsync = read; + console.log("method:", await store.readAsync("b")); + const bound = util.promisify(store.read.bind({ prefix: "bound" })); + console.log("bound:", await bound("c")); + const named = util.promisify(function lookup(a, b, callback) { + callback(null, [a, b]); + }); + console.log("named:", named.name, named.length, await named(1, 2)); + const values = []; + for (const n of [1, 2, 3]) values.push(await double(n)); + console.log("each:", values); + for (const value of [pick("none"), 42, "fn", {}, undefined]) { + try { + util.promisify(value); + } catch (error) { + console.log(error.name, error.code, error.message); + } + } + try { + util.promisify(); + } catch (error) { + console.log(error.code, error.message); + } +} + +main(); diff --git a/tests/corpus/7523-promisify-untyped.ts b/tests/corpus/7523-promisify-untyped.ts new file mode 100644 index 000000000..65d1d01c1 --- /dev/null +++ b/tests/corpus/7523-promisify-untyped.ts @@ -0,0 +1,38 @@ +// util.promisify over untyped functions in TypeScript: values typed `any` +// and functions whose parameters are `any` promisify at run time, and +// their promises settle with the callback's value (not the type of a +// builtin target's promisified form). +import { promisify } from "node:util"; + +const loose: any = (value: number, callback: (error: Error | null, result?: number) => void) => + callback(null, value + 1); +const looseAsync = promisify(loose); + +function legacy(a: any, b: any, callback: any): void { + setImmediate(() => (b === 0 ? callback(new RangeError("divide by zero")) : callback(null, a / b))); +} +const divide = promisify(legacy); + +const handlers: Record = { + echo: (text: string, callback: (error: null, value: string) => void) => callback(null, text), + later: (callback: (error: null, value: string[]) => void) => + setTimeout(() => callback(null, ["a", "b"]), 1), +}; + +async function main(): Promise { + console.log("loose:", await looseAsync(1)); + looseAsync(2).then((value: unknown) => console.log("then:", value)); + console.log("divide:", await divide(10, 4)); + try { + await divide(1, 0); + } catch (error) { + console.log("rejected:", (error as Error).message); + } + for (const name of ["echo", "later"]) { + const fn = promisify(handlers[name]); + console.log(`${name}:`, name === "echo" ? await fn("hi") : await fn()); + } + console.log("name:", divide.name, "length:", divide.length); +} + +main(); diff --git a/tests/corpus/7524-inspect-custom.ts b/tests/corpus/7524-inspect-custom.ts new file mode 100644 index 000000000..642ce75a9 --- /dev/null +++ b/tests/corpus/7524-inspect-custom.ts @@ -0,0 +1,91 @@ +// [util.inspect.custom] methods on classes: the method's string replaces +// the instance in util.inspect, console output and util.format, with +// Node's depth argument (negative past the depth limit, null for depth: +// null), options object and util.inspect; a multi-line result indents at +// its position, a returned value renders in the instance's place, and +// console.dir, customInspect: false, String() and JSON skip the method. +import * as util from "node:util"; +import { inspect } from "node:util"; + +class Money { + cents: number; + constructor(cents: number) { + this.cents = cents; + } + [inspect.custom](): string { + return `Money(${(this.cents / 100).toFixed(2)})`; + } +} + +class Probe { + label: string; + constructor(label: string) { + this.label = label; + } + [util.inspect.custom](depth: number | null, options: util.InspectOptionsStylized): string { + const keys = Object.keys(options).join(","); + return `Probe<${this.label} depth=${depth} optionsDepth=${options.depth} compact=${options.compact}> ${keys}`; + } +} + +class Lines { + [inspect.custom](): string { + return "first line\nsecond line"; + } +} + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + [inspect.custom](): { x: number; y: number; sum: number } { + return { x: this.x, y: this.y, sum: this.x + this.y }; + } +} + +class Wrapper { + inner: { values: number[]; nested: { deep: { deeper: number } } }; + constructor() { + this.inner = { values: [1, 2, 3], nested: { deep: { deeper: 4 } } }; + } + [inspect.custom](depth: number, options: util.InspectOptionsStylized, show: typeof inspect): string { + if (depth < 0) return options.stylize("[Wrapper]", "special"); + return `Wrapper ${show(this.inner, { ...options, depth: options.depth == null ? null : depth - 1 })}`; + } +} + +class Base { + name = "base"; + [inspect.custom](): string { + return `Custom(${this.name})`; + } +} +class Derived extends Base { + extra = 1; +} + +console.log(util.inspect(new Money(1234))); +console.log(new Money(5), [new Money(1), new Money(2)]); +console.log(util.inspect({ wallet: { cash: new Money(99) } })); +console.log(util.inspect(new Probe("top"))); +console.log(util.inspect({ a: { b: new Probe("nested") } })); +console.log(util.inspect([new Probe("past depth")], { depth: 0 })); +console.log(util.inspect(new Probe("unbounded"), { depth: null })); +console.log(util.inspect(new Probe("infinite"), { depth: Infinity })); +console.log(util.inspect([new Probe("undefined")], { depth: undefined })); +console.log(util.inspect(new Probe("compact"), { compact: false, breakLength: 40 })); +console.log(util.inspect({ key: new Lines(), list: [new Lines()] })); +console.log(util.inspect(new Map([["k", new Lines()]]))); +console.log(util.inspect(new Point(1, 2)), util.inspect({ p: new Point(3, 4) })); +console.log(util.inspect(new Wrapper()), util.inspect({ w: new Wrapper() }, { depth: 0 })); +console.log(util.inspect(new Derived()), util.inspect([new Base(), new Derived()])); +console.log(util.format("%s | %O", new Money(10), new Money(20))); +console.log("%s and %d", new Money(1), 2); +console.log(util.inspect(new Money(7), { customInspect: false })); +console.dir(new Money(8)); +console.log(String(new Money(9)), JSON.stringify(new Money(10))); +const values: unknown[] = [new Money(11), { money: new Money(12) }]; +console.log(values, util.inspect(values[1]), [new Lines(), values[0]]); diff --git a/tests/corpus/7525-inspect-custom.cjs b/tests/corpus/7525-inspect-custom.cjs new file mode 100644 index 000000000..9c7302fac --- /dev/null +++ b/tests/corpus/7525-inspect-custom.cjs @@ -0,0 +1,75 @@ +// [util.inspect.custom] in JavaScript: methods on classes and object +// literals (also keyed by Symbol.for('nodejs.util.inspect.custom')), the +// CommonJS call form (0, util_1.inspect)(value), results that are the +// object itself or another value, a non-function member under the key, +// and the options and util.inspect a method receives. +"use strict"; +const util_1 = require("util"); + +class Temperature { + constructor(degrees) { + this.degrees = degrees; + } + [util_1.inspect.custom](depth, options, inspect) { + return `Temperature<${this.degrees}> ${typeof options.stylize} ${inspect === util_1.inspect}`; + } +} + +const literal = { + id: 1, + [util_1.inspect.custom](depth) { + return `literal at depth ${depth}`; + }, +}; +const registered = { + [Symbol.for("nodejs.util.inspect.custom")]() { + return { replaced: [1, 2], nested: { x: 1 } }; + }, +}; +const self = { + v: 1, + [util_1.inspect.custom]() { + return this; + }, +}; +const notFunction = { x: 1, [util_1.inspect.custom]: "not a function" }; +const styled = { + [util_1.inspect.custom](depth, options) { + return options.stylize("styled", "special") + options.stylize(5, "number"); + }, +}; +const nesting = { + data: { a: { b: { c: 1 } } }, + [util_1.inspect.custom](depth, options, inspect) { + return `nesting ${inspect(this.data, { ...options, depth: 0 })}`; + }, +}; + +console.log((0, util_1.inspect)(new Temperature(21))); +console.log((0, util_1.inspect)({ t: new Temperature(22), list: [literal] })); +console.log(util_1.inspect([[literal]], { depth: 0 })); +console.log(util_1.inspect(registered), util_1.inspect({ r: registered })); +console.log(util_1.inspect(self), util_1.inspect(notFunction)); +console.log(util_1.inspect(styled), JSON.stringify(util_1.inspect(styled, { colors: true }))); +console.log(util_1.inspect(nesting), util_1.inspect({ n: nesting })); +console.log(literal, [registered]); +console.log((0, util_1.format)("%s / %O", literal, literal)); +console.log(util_1.inspect(literal, { customInspect: false })); +console.dir(literal); +console.log(util_1.inspect.custom === Symbol.for("nodejs.util.inspect.custom"), util_1.inspect.custom); + +const throwing = { + [util_1.inspect.custom]() { + throw new RangeError("from custom"); + }, +}; +try { + console.log([throwing]); +} catch (error) { + console.log("caught", error.name, error.message); +} +try { + util_1.inspect({ t: throwing }); +} catch (error) { + console.log("caught", error.message); +} diff --git a/tests/corpus/7526-inspect-options-runtime.ts b/tests/corpus/7526-inspect-options-runtime.ts new file mode 100644 index 000000000..b9f0c61d7 --- /dev/null +++ b/tests/corpus/7526-inspect-options-runtime.ts @@ -0,0 +1,62 @@ +// util.inspect with colors, option values and options objects known only +// at run time, and the legacy inspect(value, showHidden, depth, colors) +// form: Node's ANSI styles per value kind, color-aware line breaking and +// grouping, runtime depth and layout options, and colored output from +// util.formatWithOptions. +import * as util from "node:util"; + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +const sparse: number[] = [1, 2, 3]; +sparse.length = 5; +const values: unknown[] = [ + 1, + -0, + "text", + "it's", + null, + undefined, + true, + 10n, + Symbol("sym"), + [1, "two", [3, [4, [5]]]], + { a: 1, "b-c": "d", nested: { deep: { deeper: { deepest: 1 } } } }, + new Map([["k", { v: 1 }]]), + new Set(["s", 2]), + new Date(0), + new Uint8Array([1, 2, 3]), + Buffer.from("hi"), + function named() {}, + new Point(1, 2), + sparse, + Object.create(null), + Array.from({ length: 30 }, (_, i) => i * 37), + "a long string with a newline\nand more text than fits on one line of eighty characters", +]; +for (const value of values) { + console.log(JSON.stringify(util.inspect(value, { colors: true }))); +} +const depth = process.argv.length > 99 ? 5 : 1; +const breakLength = process.argv.length > 99 ? 80 : 30; +console.log(util.inspect(values[10], { depth })); +console.log(util.inspect(values[10], { depth: depth - 1, breakLength })); +console.log(util.inspect(values[20], { breakLength, compact: depth })); +console.log(util.inspect({ b: 2, a: 1 }, { sorted: depth > 0 })); +console.log(util.inspect(values[10], { depth: undefined })); +const options: util.InspectOptions = { depth: 0, colors: true }; +console.log(JSON.stringify(util.inspect(values[10], options))); +options.colors = false; +console.log(util.inspect(values[10], options)); +console.log(util.inspect(values[10], false, 0)); +console.log(util.inspect(values[10], false, null, false)); +console.log(JSON.stringify(util.inspect(values[11], false, 1, true))); +console.log(util.inspect(values[10], undefined, depth)); +console.log(JSON.stringify(util.formatWithOptions({ colors: true }, "%s %O", "x", values[12]))); +console.log(JSON.stringify(util.formatWithOptions({ colors: true }, values[10], values[9]))); diff --git a/tests/diagnostics/inspect-custom-fences.ts b/tests/diagnostics/inspect-custom-fences.ts new file mode 100644 index 000000000..f4c7c593d --- /dev/null +++ b/tests/diagnostics/inspect-custom-fences.ts @@ -0,0 +1,44 @@ +// [util.inspect.custom] methods keep their boundaries: a class whose +// subclass declares the method without declaring it itself, an Error +// subclass with the method, a method with more than three parameters, a +// method returning a class instance, and %o over instances with one. +import { format, inspect } from "node:util"; + +class Plain { + a = 1; +} +class Fancy extends Plain { + [inspect.custom](): string { + return "fancy"; + } +} +const items: Plain[] = [new Fancy()]; +console.log(inspect(items)); + +class Failure extends Error { + [inspect.custom](): string { + return "failure"; + } +} +console.log(new Failure("x").message); + +class Wide { + [inspect.custom](a: number, b: unknown, c: unknown, d: unknown): string { + return `${a}${String(b)}${String(c)}${String(d)}`; + } +} +console.log(inspect(new Wide())); + +class Echo { + [inspect.custom](): Echo { + return new Echo(); + } +} +console.log(inspect(new Echo())); + +class Shown { + [inspect.custom](): string { + return "shown"; + } +} +console.log(format("%o", new Shown())); diff --git a/tests/harness/__snapshots__/inspect-custom-fences.ts.txt b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt new file mode 100644 index 000000000..ba7cefefc --- /dev/null +++ b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt @@ -0,0 +1,49 @@ +inspect-custom-fences.ts:16:21 - error SC2020: 'util.inspect of 'Plain[]' values' is part of the standard library types but has no scriptc lowering yet + + 15 | const items: Plain[] = [new Fancy()]; + 16 | console.log(inspect(items)); + | ^~~~~ + 17 | + + hint: classes whose subclasses declare [util.inspect.custom] have no inspect lowering yet (declare the method on the class itself) + +inspect-custom-fences.ts:19:3 - error SC1090: [util.inspect.custom] methods on Error subclasses are not supported yet + + 18 | class Failure extends Error { + 19 | [inspect.custom](): string { + | ^~~~~~~~~~~~~~~~ + 20 | return "failure"; + +inspect-custom-fences.ts:23:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + + 22 | } + 23 | console.log(new Failure("x").message); + | ^~~~~~~~~~~~~~~~ + 24 | + +inspect-custom-fences.ts:30:21 - error SC2020: 'util.inspect of 'Wide' values' is part of the standard library types but has no scriptc lowering yet + + 29 | } + 30 | console.log(inspect(new Wide())); + | ^~~~~~~~~~ + 31 | + + hint: [util.inspect.custom] methods with more than three parameters have no inspect lowering + +inspect-custom-fences.ts:37:21 - error SC2020: 'util.inspect of 'Echo' values' is part of the standard library types but has no scriptc lowering yet + + 36 | } + 37 | console.log(inspect(new Echo())); + | ^~~~~~~~~~ + 38 | + + hint: [util.inspect.custom] methods that return a class instance have no inspect lowering + +inspect-custom-fences.ts:44:26 - error SC2020: 'util.format %o over values with [util.inspect.custom] methods' is part of the standard library types but has no scriptc lowering yet + + 43 | } + 44 | console.log(format("%o", new Shown())); + | ^~~~~~~~~~~ + 45 | + + hint: %o passes showHidden to the methods — use %O \ No newline at end of file diff --git a/tests/harness/corpus-costs/7520-util-format-runtime.ts.json b/tests/harness/corpus-costs/7520-util-format-runtime.ts.json new file mode 100644 index 000000000..9cb750202 --- /dev/null +++ b/tests/harness/corpus-costs/7520-util-format-runtime.ts.json @@ -0,0 +1 @@ +{ "plain": 6414, "san": 9821 } diff --git a/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json b/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json new file mode 100644 index 000000000..05cc5696d --- /dev/null +++ b/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json @@ -0,0 +1 @@ +{ "plain": 4635, "san": 6305 } diff --git a/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json b/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json new file mode 100644 index 000000000..c5358642b --- /dev/null +++ b/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json @@ -0,0 +1 @@ +{ "plain": 3984, "san": 6122 } diff --git a/tests/harness/corpus-costs/7523-promisify-untyped.ts.json b/tests/harness/corpus-costs/7523-promisify-untyped.ts.json new file mode 100644 index 000000000..be46b70d4 --- /dev/null +++ b/tests/harness/corpus-costs/7523-promisify-untyped.ts.json @@ -0,0 +1 @@ +{ "plain": 3595, "san": 36166 } diff --git a/tests/harness/corpus-costs/7524-inspect-custom.ts.json b/tests/harness/corpus-costs/7524-inspect-custom.ts.json new file mode 100644 index 000000000..b9c089238 --- /dev/null +++ b/tests/harness/corpus-costs/7524-inspect-custom.ts.json @@ -0,0 +1 @@ +{ "plain": 5331, "san": 10004 } diff --git a/tests/harness/corpus-costs/7525-inspect-custom.cjs.json b/tests/harness/corpus-costs/7525-inspect-custom.cjs.json new file mode 100644 index 000000000..010f8b482 --- /dev/null +++ b/tests/harness/corpus-costs/7525-inspect-custom.cjs.json @@ -0,0 +1 @@ +{ "plain": 4178, "san": 6619 } diff --git a/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json b/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json new file mode 100644 index 000000000..0599901b9 --- /dev/null +++ b/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json @@ -0,0 +1 @@ +{ "plain": 4855, "san": 7570 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c8b0b51b8..cfa8680ee 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1985,3 +1985,50 @@ console.log("unreachable"); ); }); }); + +// The runtime formatter (format strings known only at run time, spread +// arguments, options objects) throws a coded SC2020 error for renderings +// it has no lowering for. +describe("runtime format refusals", () => { + test("%s of a class instance with toString, %o of objects, colored errors and options", async () => { + const r = await compileAndRun( + "format-runtime-refusals", + `import * as util from "node:util"; +class Named { + name = "n"; + toString(): string { + return this.name; + } +} +const s: string = process.argv.length > 99 ? "" : "%s"; +const o: string = process.argv.length > 99 ? "" : "%o"; +const attempts: Array<() => string> = [ + () => util.format(s, new Named()), + () => util.format(o, { a: 1 }), + () => util.formatWithOptions({ colors: true }, s, "x", new Error("e")), + () => util.formatWithOptions({ maxArrayLength: 1 }, [1, 2]), + () => util.formatWithOptions({ compact: true }, [1, 2]), +]; +for (const attempt of attempts) { + try { + console.log(attempt()); + } catch (error) { + const e = error as Error & { code?: string }; + console.log(e.name, e.code, e.message); + } +} +console.log(util.format(s, { a: 1 }), util.formatWithOptions({ maxArrayLength: 100 }, [1, 2])); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "Error SC2020 util.format %s of a class instance whose class defines toString has no lowering\n" + + "Error SC2020 util.format %o of objects renders hidden properties, which have no lowering; use %O\n" + + "Error SC2020 colored inspect output of Error values has no lowering\n" + + "Error SC2020 util.formatWithOptions with the maxArrayLength option has no lowering\n" + + "Error SC2020 util.formatWithOptions with this compact option has no lowering (compact must be false or a number)\n" + + "{ a: 1 } [ 1, 2 ]\n", + ); + expect(r.stderr).toBe(""); + }); +}); From 6bbabd5fc9c680f5a0c4ae352f525672efd997cc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 20:15:02 -0500 Subject: [PATCH 009/395] Convert dynamic values into nested unions in one pass Converting a dynamic value into a union (`JSON.parse(text) as T`, a cast from `any`) tries the arms in order and builds the first arm whose literal discriminator and whole structure match. The builder of that arm converts its members, and a member typed as another union matched its arm's structure again. Every value was matched once per union around it, so converting a tree of depth d cost about d times as much as a flat one. The arm a union matched now builds as a trusted conversion (scr_dyn_trust_enter). Inside it, a nested union picks an arm without matching the arm's structure, which the enclosing match already checked, when the value's arm is known: a plain object's arm by its literal, when the arms' literals are disjoint and no arm without a literal takes plain objects, or the only arm that takes plain objects (a record) or arrays (an array), as in an optional or nullable member. Other unions, and values that are not plain objects or arrays, match as before. Getters and proxy traps run outside the trusted conversion. Failed conversions throw the same TypeError with the same path. --- packages/compiler/src/backend/llvm/dyn.ts | 103 ++++++++++- .../7160-json-parse-deep-unions.ts.json | 6 + .../7161-dynamic-values-deep-unions.ts.json | 6 + packages/runtime/src/scr_dyn.h | 12 ++ packages/runtime/src/scr_json.c | 22 ++- tests/corpus/7160-json-parse-deep-unions.ts | 164 ++++++++++++++++++ .../corpus/7161-dynamic-values-deep-unions.ts | 73 ++++++++ .../7160-json-parse-deep-unions.ts.json | 1 + .../7161-dynamic-values-deep-unions.ts.json | 1 + tests/harness/dyncheck.test.ts | 98 +++++++++++ 10 files changed, 478 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7160-json-parse-deep-unions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7161-dynamic-values-deep-unions.ts.json create mode 100644 tests/corpus/7160-json-parse-deep-unions.ts create mode 100644 tests/corpus/7161-dynamic-values-deep-unions.ts create mode 100644 tests/harness/corpus-costs/7160-json-parse-deep-unions.ts.json create mode 100644 tests/harness/corpus-costs/7161-dynamic-values-deep-unions.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index bb00e4563..40deb1d30 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -135,6 +135,18 @@ export const DYN_KIND = { SYMBOL: 15, } as const; +/** Union arm kinds whose matchers reject every plain dyn object + * (trustedArmByLiteral). */ +const NON_OBJECT_ARM_KINDS: ReadonlySet = new Set([ + "f64", + "string", + "bool", + "nullT", + "undefinedT", + "bigint", + "symbol", +]); + /** What the dyn helpers need beyond the walker host: interned immortal * unit instances (undefined-armed dynCheck targets build them). */ export interface DynHost extends WalkerHost { @@ -365,8 +377,56 @@ export class LlDyn { B.line(`call void @scr_dyn_build_add(ptr %d, ptr @${name}, ptr ${value})`); } + /** Whether a builder of `def` may pick a plain object's arm by its literal + * discriminator alone inside a trusted build (scr_dyn_trust_enter): the + * arms' literals are disjoint, and every arm without a literal has a kind + * no plain object matches. The value then matched exactly one arm with a + * literal, the arm whose literal it carries. */ + private trustedArmByLiteral(def: IrUnionDef): boolean { + const discriminant = def.discriminant; + if (!discriminant) return false; + const seen = new Set(); + for (const { values } of discriminant.cases) { + for (const value of values) { + const key = `${typeof value}:${String(value)}`; + if (seen.has(key)) return false; + seen.add(key); + } + } + return def.arms.every( + (arm, tag) => + discriminant.cases.some((candidate) => candidate.tag === tag) || + NON_OBJECT_ARM_KINDS.has(arm.kind), + ); + } + + /** Per arm of `def`, the dyn kind of the values a builder may convert into + * that arm without matching its structure inside a trusted build, or null. + * A value there already matched one of the union's arms, so the arm is + * known when it is the only arm that takes values of its kind: the arm + * whose literal a plain object carries (trustedArmByLiteral), or the one + * arm a plain object or an array can match at all. */ + private trustedArmKinds(def: IrUnionDef): (number | null)[] { + const byLiteral = this.trustedArmByLiteral(def); + const objectArms = def.arms.flatMap((arm, tag) => + NON_OBJECT_ARM_KINDS.has(arm.kind) ? [] : [tag], + ); + return def.arms.map((arm, tag) => { + if (def.discriminant?.cases.some((candidate) => candidate.tag === tag)) + return byLiteral ? DYN_KIND.OBJ : null; + if (objectArms.length !== 1 || objectArms[0] !== tag) return null; + if (arm.kind === "array") return DYN_KIND.ARR; + if (arm.kind === "record" && this.host.recordsById.get(arm.shapeId)?.tuple !== true) + return DYN_KIND.OBJ; + return null; + }); + } + /** Branch to a variant only after its literal discriminator and complete - * structure match. Use the same path for predicates and builders. */ + * structure match. Use the same path for predicates and builders. With a + * `trustedKind` (builders, trustedArmKinds), a value of that dyn kind + * inside a trusted build skips the structure match once its literal + * matched. */ private unionArmMatch( B: BlockBuilder, def: IrUnionDef, @@ -374,6 +434,7 @@ export class LlDyn { yes: string, no: string, preserveRefs = false, + trustedKind: number | null = null, ): void { const guard = def.discriminant?.cases.find((candidate) => candidate.tag === tag); if (guard && def.discriminant) { @@ -402,6 +463,19 @@ export class LlDyn { }); B.startBlock(structure); } + if (trustedKind !== null) { + this.host.declare(`declare zeroext i1 @scr_dyn_trusted_build()`); + const kind = this.kindOf(B, "%d"); + const sameKind = B.tmp(); + const trusted = B.tmp(); + const skip = B.tmp(); + B.line(`${sameKind} = icmp eq i32 ${kind}, ${trustedKind}`); + B.line(`${trusted} = call zeroext i1 @scr_dyn_trusted_build()`); + B.line(`${skip} = and i1 ${sameKind}, ${trusted}`); + const full = B.newLabel("du.full"); + B.condBr(skip, yes, full); + B.startBlock(full); + } const matches = B.tmp(); B.line( `${matches} = call zeroext i1 @${this.dynMatchHelper(def.arms[tag]!, preserveRefs)}(ptr %d)`, @@ -2357,11 +2431,22 @@ export class LlDyn { `llvm emitter bug: dynCheck of unknown union ${t.unionId}`, ); // Arms in CANONICAL order, first FULL match wins. The matched - // arm's builder can no longer fail. + // arm's builder can no longer fail, and it runs as a trusted build: + // a nested union inside it picks the arm trustedArmKinds names + // without matching the structure that this match already checked. + const trustedKinds = preserveRefs ? def.arms.map(() => null) : this.trustedArmKinds(def); + const trustedCall = (call: () => void): void => { + host.declare(`declare zeroext i1 @scr_dyn_trust_enter()`); + host.declare(`declare void @scr_dyn_trust_exit(i1 zeroext)`); + const previous = B.tmp(); + B.line(`${previous} = call zeroext i1 @scr_dyn_trust_enter()`); + call(); + B.line(`call void @scr_dyn_trust_exit(i1 zeroext ${previous})`); + }; def.arms.forEach((arm, i) => { const lHit = B.newLabel("dcu.h"); const lNext = B.newLabel("dcu.n"); - this.unionArmMatch(B, def, i, lHit, lNext, preserveRefs); + this.unionArmMatch(B, def, i, lHit, lNext, preserveRefs, trustedKinds[i]!); B.startBlock(lHit); if (arm.kind === "undefinedT" || arm.kind === "nullT") { // A matched unit arm builds nothing: THE interned immortal @@ -2388,14 +2473,22 @@ export class LlDyn { } else if (host.nullableUnions.has(t.unionId)) { // A nullable union is its (+1) reference payload. const x = B.tmp(); - B.line(`${x} = call ptr @${this.dynCheckHelper(arm, preserveRefs)}(ptr %d, ptr %path)`); + trustedCall(() => + B.line( + `${x} = call ptr @${this.dynCheckHelper(arm, preserveRefs)}(ptr %d, ptr %path)`, + ), + ); B.terminate(`ret ptr ${x}`); } else { const rc = vAdapters(host, arm); host.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`); const x = B.tmp(); const u = B.tmp(); - B.line(`${x} = call ptr @${this.dynCheckHelper(arm, preserveRefs)}(ptr %d, ptr %path)`); + trustedCall(() => + B.line( + `${x} = call ptr @${this.dynCheckHelper(arm, preserveRefs)}(ptr %d, ptr %path)`, + ), + ); B.line( `${u} = call ptr @scr_union_new_ref(i32 ${i}, ptr ${x}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host, arm)})`, ); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7160-json-parse-deep-unions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7160-json-parse-deep-unions.ts.json new file mode 100644 index 000000000..fb4ef3c1f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7160-json-parse-deep-unions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7160-json-parse-deep-unions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7161-dynamic-values-deep-unions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7161-dynamic-values-deep-unions.ts.json new file mode 100644 index 000000000..8ad1579da --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7161-dynamic-values-deep-unions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7161-dynamic-values-deep-unions.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 28c2accce..e9b4d33bc 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1442,6 +1442,18 @@ void scr_dyn_build_note(const ScrDyn *d, const void *builder, void *value); void scr_dyn_build_rekey(const ScrDyn *d, const void *builder, void *value); ScrDyn *scr_dyn_visit_view(const ScrDyn *capsule); +/* A union's builder converts the arm whose literal discriminator and + * complete structure matched; every value inside that arm then matched its + * own type too. Between scr_dyn_trust_enter and scr_dyn_trust_exit (which + * restores the value enter returned), scr_dyn_trusted_build answers true, + * and the builder of a nested union whose arms' literals are disjoint picks + * a plain object's arm by its literal alone instead of matching the arm's + * structure again (dyn.ts unionArmMatch). Code a getter or proxy trap runs + * sees false. */ +bool scr_dyn_trust_enter(void); +void scr_dyn_trust_exit(bool previous); +bool scr_dyn_trusted_build(void); + /* ── native handles in the checked-dynamic tree (SCR_DYN_HANDLE) ──────────────────────── * Per-tag dispatch ops, registered by the handle's owning unit * (scr_http_dyn_install / scr_net_dyn_install — emitted main() calls diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 324233028..9fb5efd6d 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -9245,6 +9245,9 @@ static SCR_TL ScrDynVisitLog scr_dyn_viewed_log; /* The member value a matcher or builder read last and holds one * reference to until it calls scr_dyn_release_held. */ static SCR_TL const ScrDyn *scr_dyn_visit_held; +/* Set while a union's builder builds the arm it matched in full + * (scr_dyn_trust_enter). */ +static SCR_TL bool scr_dyn_trusted; /* Tables up to this many slots stay allocated for the next session. */ #define SCR_DYN_VISIT_KEEP 4096 @@ -9356,13 +9359,15 @@ typedef struct { ScrDynVisitLog built_log, viewed_log; bool track; const ScrDyn *held; + bool trusted; } ScrDynVisitState; static void scr_dyn_visit_suspend(ScrDynVisitState *saved) { - *saved = (ScrDynVisitState){scr_dyn_visit_depth, scr_dyn_matching, scr_dyn_built, - scr_dyn_viewed, scr_dyn_built_log, scr_dyn_viewed_log, - scr_dyn_build_track, scr_dyn_visit_held}; + *saved = (ScrDynVisitState){scr_dyn_visit_depth, scr_dyn_matching, scr_dyn_built, + scr_dyn_viewed, scr_dyn_built_log, scr_dyn_viewed_log, + scr_dyn_build_track, scr_dyn_visit_held, scr_dyn_trusted}; scr_dyn_visit_depth = 0; + scr_dyn_trusted = false; scr_dyn_matching = scr_dyn_built = scr_dyn_viewed = (ScrDynVisitSet){NULL, 0, 0}; scr_dyn_built_log = scr_dyn_viewed_log = (ScrDynVisitLog){NULL, 0, 0}; scr_dyn_visit_held = NULL; @@ -9382,6 +9387,7 @@ static void scr_dyn_visit_resume(const ScrDynVisitState *saved) { scr_dyn_viewed_log = saved->viewed_log; scr_dyn_build_track = saved->track; scr_dyn_visit_held = saved->held; + scr_dyn_trusted = saved->trusted; } /* Calls a getter for a member read, outside any visit session. */ @@ -9404,6 +9410,16 @@ static ScrDyn *scr_dyn_visit_proxy_get(const ScrDyn *d, ScrStr *name) { return result; } +bool scr_dyn_trust_enter(void) { + bool previous = scr_dyn_trusted; + scr_dyn_trusted = true; + return previous; +} + +void scr_dyn_trust_exit(bool previous) { scr_dyn_trusted = previous; } + +bool scr_dyn_trusted_build(void) { return scr_dyn_trusted; } + ScrDyn *scr_dyn_obj_read_held(const ScrDyn *d, const char *key, size_t key_len) { ScrDyn *value = scr_dyn_obj_read(d, key, key_len); scr_dyn_visit_held = value; diff --git a/tests/corpus/7160-json-parse-deep-unions.ts b/tests/corpus/7160-json-parse-deep-unions.ts new file mode 100644 index 000000000..bcbf5ea9d --- /dev/null +++ b/tests/corpus/7160-json-parse-deep-unions.ts @@ -0,0 +1,164 @@ +// JSON.parse into recursive discriminated unions: deep and wide trees, +// unions with primitive arms beside the records, unions whose records share +// a literal, and unions with a record arm that has no literal. + +type Expr = + | { kind: "num"; value: number } + | { kind: "str"; text: string } + | { kind: "add"; left: Expr; right: Expr } + | { kind: "neg"; operand: Expr } + | { kind: "call"; callee: string; args: Expr[] } + | { kind: "cond"; test: Expr; then: Expr; otherwise: Expr | null } + | { kind: "label"; inner: Expr; name?: string }; + +type Stmt = + | { kind: "expr"; expr: Expr } + | { kind: "ret"; value: Expr | undefined } + | { kind: "block"; body: Stmt[] }; + +interface Fn { + name: string; + body: Stmt[]; +} + +function evaluate(e: Expr): number { + switch (e.kind) { + case "num": + return e.value; + case "str": + return e.text.length; + case "add": + return evaluate(e.left) + evaluate(e.right); + case "neg": + return -evaluate(e.operand); + case "call": + return e.args.reduce((sum, arg) => sum + evaluate(arg), e.callee.length); + case "cond": + return evaluate(e.test) !== 0 + ? evaluate(e.then) + : e.otherwise === null + ? 0 + : evaluate(e.otherwise); + case "label": + return evaluate(e.inner) + (e.name?.length ?? 0); + } +} + +function depth(e: Expr): number { + switch (e.kind) { + case "num": + case "str": + return 1; + case "add": + return 1 + Math.max(depth(e.left), depth(e.right)); + case "neg": + return 1 + depth(e.operand); + case "call": + return 1 + e.args.reduce((max, arg) => Math.max(max, depth(arg)), 0); + case "cond": + return 1 + Math.max(depth(e.test), depth(e.then), e.otherwise ? depth(e.otherwise) : 0); + case "label": + return 1 + depth(e.inner); + } +} + +function chain(n: number): string { + let text = '{"kind":"num","value":1}'; + for (let i = 0; i < n; i++) { + text = + i % 3 === 0 + ? `{"kind":"neg","operand":${text}}` + : i % 3 === 1 + ? `{"kind":"add","left":${text},"right":{"kind":"str","text":"ab"}}` + : `{"kind":"label","inner":${text}${i % 2 === 0 ? ',"name":"x"' : ""}}`; + } + return text; +} + +function wide(n: number, level: number): string { + if (level === 0) return `{"kind":"num","value":${n}}`; + const args: string[] = []; + for (let i = 0; i < 3; i++) args.push(wide(n + i, level - 1)); + return `{"kind":"call","callee":"f${level}","args":[${args.join(",")}]}`; +} + +for (const n of [0, 1, 5, 60, 400]) { + const e = JSON.parse(chain(n)) as Expr; + console.log("chain", n, e.kind, depth(e), evaluate(e)); +} + +const tree = JSON.parse(wide(1, 6)) as Expr; +console.log("wide", depth(tree), evaluate(tree)); + +const cond = JSON.parse( + '{"kind":"cond","test":{"kind":"num","value":0},"then":{"kind":"num","value":5},"otherwise":null}', +) as Expr; +console.log("cond", evaluate(cond)); + +const fn = JSON.parse( + JSON.stringify({ + name: "main", + body: [ + { kind: "expr", expr: JSON.parse(chain(30)) }, + { kind: "block", body: [{ kind: "ret", value: JSON.parse(wide(2, 3)) }, { kind: "ret" }] }, + ], + }), +) as Fn; +let statements = 0; +const visit = (stmts: Stmt[]): void => { + for (const s of stmts) { + statements++; + if (s.kind === "block") visit(s.body); + else if (s.kind === "expr") console.log("stmt expr", evaluate(s.expr)); + else console.log("stmt ret", s.value === undefined ? "none" : evaluate(s.value)); + } +}; +visit(fn.body); +console.log("statements", statements); + +// Primitive arms beside the records. +type Value = number | string | null | { kind: "list"; items: Value[] } | { kind: "pair"; first: Value; second: Value }; +function show(v: Value): string { + if (v === null || typeof v === "number" || typeof v === "string") return JSON.stringify(v); + return v.kind === "list" ? `[${v.items.map(show).join(" ")}]` : `(${show(v.first)} . ${show(v.second)})`; +} +const values = JSON.parse( + '[1,"a",null,{"kind":"list","items":[2,{"kind":"pair","first":"b","second":{"kind":"list","items":[null,3]}}]}]', +) as Value[]; +console.log("values", values.map(show).join(" | ")); + +// Two records that share a literal: the arm follows the other fields. +type Shared = { kind: "a"; count: number } | { kind: "a"; name: string } | { kind: "b"; next: Shared[] }; +function describe(s: Shared): string { + if (s.kind === "b") return `b(${s.next.map(describe).join(",")})`; + return "count" in s ? `count=${s.count}` : `name=${s.name}`; +} +const shared = JSON.parse( + '{"kind":"b","next":[{"kind":"a","count":1},{"kind":"a","name":"n"},{"kind":"b","next":[{"kind":"a","name":"m"}]}]}', +) as Shared; +console.log("shared", describe(shared)); + +// A record arm without a literal beside records that have one. +type Loose = { kind: "leaf"; value: number } | { kind: "node"; children: Loose[] } | { weight: number; child: Loose }; +function total(l: Loose): number { + if ("weight" in l) return l.weight * total(l.child); + return l.kind === "leaf" ? l.value : l.children.reduce((sum, c) => sum + total(c), 0); +} +const loose = JSON.parse( + '{"kind":"node","children":[{"kind":"leaf","value":2},{"weight":3,"child":{"kind":"node","children":[{"kind":"leaf","value":4},{"weight":2,"child":{"kind":"leaf","value":5}}]}}]}', +) as Loose; +console.log("loose", total(loose)); + +// Optional and nullable members holding records and arrays of records. +type Box = { value: number; child?: Box; rest: Box[] | null }; +function weigh(b: Box): number { + let total = b.value; + if (b.child !== undefined) total += 2 * weigh(b.child); + for (const r of b.rest ?? []) total += weigh(r); + return total; +} +let boxText = '{"value":1,"rest":null}'; +for (let i = 2; i < 120; i++) + boxText = i % 2 === 0 ? `{"value":${i},"child":${boxText},"rest":null}` : `{"value":${i},"rest":[${boxText},{"value":1,"rest":[]}]}`; +const boxes = JSON.parse(boxText) as Box; +console.log("boxes", weigh(boxes) % 1000003); diff --git a/tests/corpus/7161-dynamic-values-deep-unions.ts b/tests/corpus/7161-dynamic-values-deep-unions.ts new file mode 100644 index 000000000..3e85b2689 --- /dev/null +++ b/tests/corpus/7161-dynamic-values-deep-unions.ts @@ -0,0 +1,73 @@ +// Dynamic values cast into recursive discriminated unions: trees built as +// untyped objects, trees that mix typed records into untyped ones, and casts +// of the same values twice. + +type Node = + | { kind: "leaf"; value: number } + | { kind: "pair"; left: Node; right: Node } + | { kind: "wrap"; inner: Node; tag: string } + | { kind: "many"; items: Node[] }; + +function sum(n: Node): number { + switch (n.kind) { + case "leaf": + return n.value; + case "pair": + return sum(n.left) + sum(n.right); + case "wrap": + return sum(n.inner) + n.tag.length; + case "many": + return n.items.reduce((total, item) => total + sum(item), 0); + } +} + +function height(n: Node): number { + switch (n.kind) { + case "leaf": + return 1; + case "pair": + return 1 + Math.max(height(n.left), height(n.right)); + case "wrap": + return 1 + height(n.inner); + case "many": + return 1 + n.items.reduce((max, item) => Math.max(max, height(item)), 0); + } +} + +function untyped(levels: number): any { + let node: any = { kind: "leaf", value: 1 }; + for (let i = 0; i < levels; i++) { + node = + i % 3 === 0 + ? { kind: "wrap", inner: node, tag: "t" } + : i % 3 === 1 + ? { kind: "pair", left: node, right: { kind: "leaf", value: i } } + : { kind: "many", items: [node, { kind: "leaf", value: 2 }] }; + } + return node; +} + +for (const levels of [0, 2, 10, 90, 300]) { + const value: unknown = untyped(levels); + const node = value as Node; + console.log("untyped", levels, node.kind, height(node), sum(node)); +} + +// Typed records inside an untyped tree. +const typedLeaf: Node = { kind: "leaf", value: 7 }; +const typedPair: Node = { kind: "pair", left: typedLeaf, right: { kind: "leaf", value: 3 } }; +const mixed: any = { kind: "many", items: [typedPair, untyped(20), { kind: "wrap", inner: typedLeaf, tag: "xy" }] }; +const fromMixed = mixed as Node; +console.log("mixed", height(fromMixed), sum(fromMixed)); + +// The same untyped value cast twice gives equal trees. +const again: any = untyped(40); +const first = again as Node; +const second = again as Node; +console.log("twice", sum(first) === sum(second), height(first), height(second)); + +// A tree that changes between casts. +const changing: any = { kind: "pair", left: { kind: "leaf", value: 1 }, right: untyped(5) }; +console.log("before", sum(changing as Node)); +changing.left = { kind: "wrap", inner: { kind: "leaf", value: 10 }, tag: "abc" }; +console.log("after", sum(changing as Node)); diff --git a/tests/harness/corpus-costs/7160-json-parse-deep-unions.ts.json b/tests/harness/corpus-costs/7160-json-parse-deep-unions.ts.json new file mode 100644 index 000000000..5d8e97d52 --- /dev/null +++ b/tests/harness/corpus-costs/7160-json-parse-deep-unions.ts.json @@ -0,0 +1 @@ +{ "plain": 11054, "san": 84858 } diff --git a/tests/harness/corpus-costs/7161-dynamic-values-deep-unions.ts.json b/tests/harness/corpus-costs/7161-dynamic-values-deep-unions.ts.json new file mode 100644 index 000000000..bf9a71561 --- /dev/null +++ b/tests/harness/corpus-costs/7161-dynamic-values-deep-unions.ts.json @@ -0,0 +1 @@ +{ "plain": 5843, "san": 57446 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index 6a2d4dc61..fca914792 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -427,6 +427,104 @@ console.log("unreachable", t.label); ); }); + test("nested unions: a failure inside a matched arm names the union that holds it", async () => { + // A union's builder converts the arm that matched; the unions nested in + // it pick their arms by literal or by the kind of value only one arm + // takes. A value that fails anywhere below fails its outermost union, + // whose arms the message names. + const r = await compileAndRun( + "deep-discriminated", + `type Expr = + | { kind: "num"; value: number } + | { kind: "str"; text: string } + | { kind: "add"; left: Expr; right: Expr } + | { kind: "call"; callee: string; args: Expr[] } + | { kind: "neg"; operand: Expr } + | { kind: "opt"; inner: Expr | undefined; label?: string }; +type Stmt = + | { kind: "expr"; expr: Expr } + | { kind: "ret"; value: Expr | null } + | { kind: "block"; body: Stmt[] }; +type Fn = { name: string; body: Stmt[] }; + +function attempt(label: string, text: string, as: "expr" | "stmt" | "fn" | "list"): void { + try { + if (as === "expr") { + const e = JSON.parse(text) as Expr; + console.log(label, "ok", e.kind); + } else if (as === "stmt") { + const s = JSON.parse(text) as Stmt; + console.log(label, "ok", s.kind); + } else if (as === "fn") { + const f = JSON.parse(text) as Fn; + console.log(label, "ok", f.name, f.body.length); + } else { + const l = JSON.parse(text) as Expr[]; + console.log(label, "ok", l.length); + } + } catch (e) { + console.log(label, "error", (e as Error).message); + } +} + +const deep = (leaf: string, depth: number): string => { + let s = leaf; + for (let i = 0; i < depth; i++) s = \`{"kind":"neg","operand":\${s}}\`; + return s; +}; +attempt("valid", deep('{"kind":"num","value":1}', 20), "expr"); +attempt("deep wrong field", deep('{"kind":"num","value":"x"}', 5), "expr"); +attempt("deep unknown kind", deep('{"kind":"nope","value":1}', 5), "expr"); +attempt("deep string node", deep('"text"', 5), "expr"); +attempt("deep missing field", deep('{"kind":"add","left":{"kind":"num","value":1}}', 4), "expr"); +attempt("deep array arg", deep('{"kind":"call","callee":"f","args":[{"kind":"num","value":1},{"kind":"str","text":2}]}', 3), "expr"); +attempt("optional inner", deep('{"kind":"opt","inner":{"kind":"num","value":true}}', 3), "expr"); +attempt("optional label", deep('{"kind":"opt","inner":{"kind":"num","value":1},"label":3}', 3), "expr"); +attempt("stmt deep", '{"kind":"block","body":[{"kind":"expr","expr":' + deep('{"kind":"num","value":null}', 3) + '}]}', "stmt"); +attempt("ret null", '{"kind":"ret","value":null}', "stmt"); +attempt("fn deep", '{"name":"f","body":[{"kind":"ret","value":' + deep('{"kind":"str","text":5}', 4) + '}]}', "fn"); +attempt("list element", '[{"kind":"num","value":1},' + deep('{"kind":"num"}', 2) + "]", "list"); +attempt("kind not string", deep('{"kind":3,"value":1}', 2), "expr"); +attempt("valid list", '[' + deep('{"kind":"num","value":1}', 3) + ',{"kind":"str","text":"t"}]', "list"); + +type Box = { value: number; child?: Box; rest: Box[] | null }; +function box(label: string, text: string): void { + try { + const b = JSON.parse(text) as Box; + console.log(label, "ok", b.value); + } catch (e) { + console.log(label, "error", (e as Error).message); + } +} +box("box valid", '{"value":1,"rest":null,"child":{"value":2,"rest":[{"value":3,"rest":null}]}}'); +box("box deep child", '{"value":1,"rest":null,"child":{"value":2,"rest":null,"child":{"value":"x","rest":null}}}'); +box("box deep rest", '{"value":1,"rest":[{"value":2,"rest":[{"value":3,"rest":[1]}]}]}'); +box("box child array", '{"value":1,"rest":null,"child":[]}'); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout.trimEnd().split("\n")).toEqual([ + "valid ok neg", + "deep wrong field error expected object | object | object | object | object | object at $, got object", + "deep unknown kind error expected object | object | object | object | object | object at $, got object", + "deep string node error expected object | object | object | object | object | object at $, got object", + "deep missing field error expected object | object | object | object | object | object at $, got object", + "deep array arg error expected object | object | object | object | object | object at $, got object", + "optional inner error expected object | object | object | object | object | object at $, got object", + "optional label error expected object | object | object | object | object | object at $, got object", + "stmt deep error expected object | object | object at $, got object", + "ret null ok ret", + "fn deep error expected object | object | object at $.body[0], got object", + "list element error expected object | object | object | object | object | object at $[1], got object", + "kind not string error expected object | object | object | object | object | object at $, got object", + "valid list ok 2", + "box valid ok 1", + "box deep child error expected number at $.child.child.value, got string", + "box deep rest error expected object at $.rest[0].rest[0].rest[0], got number", + "box child array error expected object | undefined at $.child, got array", + ]); + }); + test("array where a record is expected names the mismatch at the root", async () => { const r = await compileAndRun( "root-kind", From b306a4e11e37194c07f8c2718ba348662d0f13ce Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 20:18:02 -0500 Subject: [PATCH 010/395] Leave fresh unknown[] storage out of the view-hole fact Merged with current main, the hole fact for stores through dynamic views fired on the emitter's per-feature state (`states[slot] = create(emitter)` in emitter-state.ts writes past the end of `emitter.moduleStates`, an `unknown[]`). The viewed element types then widened reads of the IR function arrays, and the object spreads of `IrFunction | undefined` in unshare.ts, callback-specialization.ts and constant-globals.ts were refused when the LLVM emitter bootstraps. A receiver that only holds the array its declaration creates (a const binding or a readonly property whose initializer is an array literal, `new Array` or `Array.of`/`Array.from`, possibly through further such bindings) is never a view of a typed array, so stores through it add no hole fact. --- .../compiler/src/frontend/lowering/lowerer.ts | 46 ++++++++++++++++++- .../test/runtime-optional-precision.test.ts | 14 ++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ccb91291c..bd0981601 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6432,10 +6432,54 @@ export class Lowerer { element !== undefined && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 ); }; + // A binding that only ever holds the array its declaration creates (a + // const variable, or a readonly property with an initializer) is never + // a view of a native array, even when its elements are dynamic. + const freshDynamicStorage = (node: ts.Expression, depth: number): boolean => { + const e = peel(node); + if (ts.isArrayLiteralExpression(e)) return true; + if (ts.isNewExpression(e) || ts.isCallExpression(e)) { + const callee = peel(e.expression); + if (ts.isIdentifier(callee)) return this.isStdlibGlobal(callee, "Array"); + return ( + ts.isPropertyAccessExpression(callee) && + (callee.name.text === "of" || callee.name.text === "from") && + this.isStdlibGlobal(callee.expression, "Array") + ); + } + if (depth > 4 || (!ts.isIdentifier(e) && !ts.isPropertyAccessExpression(e))) return false; + // A typed array along the way is the array a view shows. + const type = typeOrNull(e); + if (type === null || !dynamicType(type)) return false; + const symbol = panicSafe( + () => this.checker.getSymbolAtLocation(ts.isIdentifier(e) ? e : e.name), + undefined, + ); + const declaration = symbol?.valueDeclaration; + if (!declaration) return false; + if (ts.isVariableDeclaration(declaration)) { + const list = declaration.parent; + return ( + ts.isVariableDeclarationList(list) && + (list.flags & ts.NodeFlags.Const) !== 0 && + declaration.initializer !== undefined && + freshDynamicStorage(declaration.initializer, depth + 1) + ); + } + return ( + ts.isPropertyDeclaration(declaration) && + declaration.initializer !== undefined && + (declaration.modifiers?.nodes.some( + (modifier) => modifier.kind === ts.SyntaxKind.ReadonlyKeyword, + ) ?? + false) && + freshDynamicStorage(declaration.initializer, depth + 1) + ); + }; const elementStates = new ArrayElementStates({ isDynamicArray: (node) => { const type = typeOrNull(node); - return type === null || dynamicType(type); + return (type === null || dynamicType(type)) && !freshDynamicStorage(node, 0); }, nativeViewKeys: collectNativeViews, elementAdmitsUndefined: (key) => { diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 64f0bd86e..50f1fe138 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -143,6 +143,20 @@ bag["3"] = view; console.log(items.findIndex((item) => weigh(item) > 1)); `); expect(signature(record, "weigh")).toBe("weigh(Item): f64"); + // An unknown[] that only holds the array its declaration creates is never + // a view, so stores past its end leave the viewed arrays alone. + const fresh = await lower(`${ITEM} +class Slots { + readonly states: unknown[] = []; +} +const slots = new Slots(); +const items: Item[] = [new Item(1)]; +const view: unknown = items; +const states = slots.states; +states[3] = view; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(fresh, "weigh")).toBe("weigh(Item): f64"); }); test("holes that only their owning slot can observe stay out of callbacks", async () => { From 4780e7ef22243c28fd0ac36a25e19fdd9e87e08a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 21:01:18 -0500 Subject: [PATCH 011/395] Compare optional string index reads as code units `s[i]` with an index the frontend cannot prove in bounds reads the character or undefined: temps for the string and the index, a range and integer test, and a string-or-undefined union. A lexer that tests `src[pos] === " "` or `c < "a"` on `const c = src[pos]` compared that union with a union-wrapped literal, or boxed it for a dynamic comparison, on every character. Tests of such a read against a literal of one UTF-16 code unit now compare `i === Math.floor(i) ? s.charCodeAt(i) : NaN` instead. Undefined, like NaN, equals no literal and fails every ordered comparison, and charCodeAt reads NaN out of range. A read of plain operands is evaluated in place; others keep the read's temps. An immutable local initialized from such a read, and used only in those tests, in tests for undefined and in a switch over one-unit cases, holds the code unit. Tests for undefined become NaN tests. Copies of the local, such as a switch's discriminant temp, follow it. The code-unit reads are charCodeAt reads, so a loop version can cover them. --- .../compiler/src/ir/char-unit-locals.test.ts | 70 +++ packages/compiler/src/ir/char-unit-locals.ts | 404 ++++++++++++++++-- .../7304-string-optional-char-reads.ts.json | 6 + .../corpus/7304-string-optional-char-reads.ts | 121 ++++++ .../7304-string-optional-char-reads.ts.json | 1 + 5 files changed, 567 insertions(+), 35 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7304-string-optional-char-reads.ts.json create mode 100644 tests/corpus/7304-string-optional-char-reads.ts create mode 100644 tests/harness/corpus-costs/7304-string-optional-char-reads.ts.json diff --git a/packages/compiler/src/ir/char-unit-locals.test.ts b/packages/compiler/src/ir/char-unit-locals.test.ts index ad16ecebd..378a7f0df 100644 --- a/packages/compiler/src/ir/char-unit-locals.test.ts +++ b/packages/compiler/src/ir/char-unit-locals.test.ts @@ -96,3 +96,73 @@ console.log(escapes("a b", 1), rebound(" x", 0), wide("ab", 0), against("a", "b expect(fn(after, name)).toBe(fn(before, name)); } }); + +/** The union and dynamic comparisons left in the function. */ +function unionTests(f: IrFunction): string[] { + const out: string[] = []; + everyStmtList(f.body, { + stmt: () => true, + expr: (e) => { + if (e.kind === "unionEq" || e.kind === "unionIsTag") out.push(e.kind); + if (e.kind === "libCall" && e.fn === "js.compare") out.push(e.fn); + if (e.kind === "strIntrinsic") out.push(e.method); + return true; + }, + }); + return out; +} + +test("optional character reads compare their code unit", async () => { + const mod = holdCharLocalsAsUnits( + await lower(` +function scan(src: string, pos: number): number { + let n = 0; + if (src[pos] === " ") n += 1; + if (src[pos] !== "x") n += 2; + if (src[pos] < "a" || "z" <= src[pos]) n += 4; + if (src[pos + 1] === "=") n += 8; + const c = src[pos]; + if (c === undefined) n += 16; + if (c !== undefined && c > "m") n += 32; + switch (c) { + case "q": + n += 64; + break; + case "r": + n += 128; + } + return n; +} +console.log(scan("a b", 1), scan("q", 0), scan("ab", 5), scan("r=", 0.5)); +`), + ); + const scan = fn(mod, "scan"); + // Only charCodeAt reads remain: no union tests, dynamic compares, charAt + // or length reads. + expect(new Set(unionTests(scan))).toEqual(new Set(["charCodeAt"])); + expect(scan.locals.find((l) => l.name === "c")!.type).toEqual({ kind: "f64" }); +}); + +test("optional character reads with other uses stay reads", async () => { + const before = await lower(` +function kept(src: string, pos: number): string { + const c = src[pos]; + if (c === " ") return "space"; + return c ?? "end"; +} +function longer(src: string, pos: number): boolean { + return src[pos] === "ab"; +} +function twice(src: string, pos: number): boolean { + return src[pos * 2] === undefined; +} +console.log(kept("a b", 1), kept("ab", 0), kept("", 3), longer("ab", 0), twice("ab", 1)); +`); + const after = holdCharLocalsAsUnits(before); + for (const name of ["kept", "longer", "twice"]) { + expect(fn(before, name)).toBeDefined(); + expect(unionTests(fn(after, name))).toEqual(unionTests(fn(before, name))); + } + // The local's own test still reads it as a union. + expect(unionTests(fn(after, "kept"))).toContain("unionEq"); +}); diff --git a/packages/compiler/src/ir/char-unit-locals.ts b/packages/compiler/src/ir/char-unit-locals.ts index 4008f4e1e..9917a148b 100644 --- a/packages/compiler/src/ir/char-unit-locals.ts +++ b/packages/compiler/src/ir/char-unit-locals.ts @@ -11,8 +11,11 @@ import { mapExprChildren, mapStmtChildren } from "./traverse.js"; type CharAt = IrExpr & { kind: "strIntrinsic" }; type UnitUse = IrExpr & { kind: "strEq" | "strCmp" }; +type CompareOp = "<" | "<=" | ">" | ">="; -/** Character locals held as their code unit. +const FLIPPED = { "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as const; + +/** Character reads and locals held as their code unit. * * `const c = s[i]` (or `s.charAt(i)`) followed only by tests such as * `c === " "`, `c >= "a" && c <= "z"` or `switch (c) { case "x": ... }` @@ -28,7 +31,16 @@ type UnitUse = IrExpr & { kind: "strEq" | "strCmp" }; * * Only immutable unboxed string locals qualify, with one declaration whose * initializer is a one-argument charAt, and with no other mention of the - * local. */ + * local. + * + * `s[i]` with an index not proven in bounds reads the character or + * undefined (an optional read, see `optionalCharRead`). Its tests against + * one-unit literals compare `i === Math.floor(i) ? s.charCodeAt(i) : NaN` + * instead: undefined, like NaN, equals no literal and fails every ordered + * comparison, and charCodeAt answers NaN out of range. That holds both for + * the read in place and for an immutable local initialized from it and + * used only in such tests and in tests for undefined, which become NaN + * tests. */ export function holdCharLocalsAsUnits(mod: IrModule): IrModule { let changed = false; const functions = mod.functions.map((fn) => { @@ -39,11 +51,275 @@ export function holdCharLocalsAsUnits(mod: IrModule): IrModule { return changed ? { ...mod, functions } : mod; } +/** The read of `s[i]` for an index not proven in bounds: temps for the + * string and the index, then the string's charAt when the index is an + * integer in [0, length) and undefined otherwise. */ +interface OptionalCharRead { + /** The temps' declarations, and whether both operands are plain (local + * reads and literals), which the unit read may evaluate in place. */ + temps: [IrStmt & { kind: "varDecl" }, IrStmt & { kind: "varDecl" }]; + plain: boolean; + receiver: IrExpr; + index: IrExpr; + unionId: string; + stringTag: number; + undefinedTag: number; +} + +function optionalCharRead(e: IrExpr): OptionalCharRead | null { + if (e.kind !== "seqExpr" || e.stmts.length !== 2 || e.diverges) return null; + const [str, idx] = e.stmts; + if (str?.kind !== "varDecl" || idx?.kind !== "varDecl" || !str.init || !idx.init) return null; + const isStr = (x: IrExpr): boolean => x.kind === "varRef" && x.localId === str.localId; + const isIdx = (x: IrExpr): boolean => x.kind === "varRef" && x.localId === idx.localId; + const r = e.result; + if (r.kind !== "ternary") return null; + const c = r.cond; + if (c.kind !== "logical" || c.op !== "&&" || c.left.kind !== "logical" || c.left.op !== "&&") + return null; + const [low, high, whole] = [c.left.left, c.left.right, c.right]; + if ( + !( + low.kind === "bin" && + low.op === ">=" && + isIdx(low.left) && + low.right.kind === "numLit" && + low.right.value === 0 + ) || + !( + high.kind === "bin" && + high.op === "<" && + isIdx(high.left) && + high.right.kind === "strIntrinsic" && + high.right.method === "length" && + isStr(high.right.receiver) + ) || + !( + whole.kind === "bin" && + whole.op === "===" && + isIdx(whole.left) && + whole.right.kind === "libCall" && + whole.right.fn === "math.floor" && + whole.right.args.length === 1 && + isIdx(whole.right.args[0]!) + ) + ) + return null; + const [present, absent] = [r.then, r.else_]; + if (present.kind !== "unionWrap" || absent.kind !== "unionWrap") return null; + if (present.unionId !== absent.unionId) return null; + const read = present.value; + if ( + read.kind !== "strIntrinsic" || + read.method !== "charAt" || + !isStr(read.receiver) || + read.args.length !== 1 || + !isIdx(read.args[0]!) + ) + return null; + if (absent.value.kind !== "unitLit" || absent.value.unit !== "undefined") return null; + return { + temps: [str, idx], + plain: plainOperand(str.init) && plainOperand(idx.init), + receiver: str.init, + index: idx.init, + unionId: present.unionId, + stringTag: present.tag, + undefinedTag: absent.tag, + }; +} + +function plainOperand(e: IrExpr): boolean { + return e.kind === "varRef" || e.kind === "numLit" || e.kind === "strLit"; +} + +/** `i === Math.floor(i) ? s.charCodeAt(i) : NaN`: the code unit of an + * optional read, or NaN where it reads undefined. Plain operands are read + * in place; others keep the read's temps, so each is evaluated once. */ +function unitRead(read: OptionalCharRead, loc: IrExpr["loc"]): IrExpr { + if (!read.plain) { + const [str, idx] = read.temps; + const temp = (decl: IrStmt & { kind: "varDecl" }, type: IrExpr["type"]): IrExpr => ({ + kind: "varRef", + localId: decl.localId, + type, + loc, + }); + return { + kind: "seqExpr", + stmts: [{ ...str }, { ...idx }], + result: unitOf(temp(str, read.receiver.type), () => temp(idx, F64), loc), + type: F64, + loc, + }; + } + return unitOf({ ...read.receiver }, () => ({ ...read.index }), loc); +} + +function unitOf(receiver: IrExpr, index: () => IrExpr, loc: IrExpr["loc"]): IrExpr { + return { + kind: "ternary", + cond: { + kind: "bin", + op: "===", + left: index(), + right: { kind: "libCall", fn: "math.floor", args: [index()], type: F64, loc }, + type: BOOL, + loc, + }, + then: { + kind: "strIntrinsic", + method: "charCodeAt", + receiver, + args: [index()], + type: F64, + loc, + }, + else_: { kind: "numLit", value: NaN, type: F64, loc }, + type: F64, + loc, + }; +} + +/** A test of an operand that reads one character or undefined, against a + * literal of one code unit or for undefined, and the same test on the + * operand's code unit. `tagsOf` gives the operand's union tags. */ +interface UnitTest { + operand: IrExpr; + /** The test reads the unit twice. */ + twice?: boolean; + test: (unit: () => IrExpr) => IrExpr; +} + +function optionalUnitTest( + e: IrExpr, + tagsOf: (operand: IrExpr) => OptionalCharRead | null, +): UnitTest | null { + const loc = e.loc; + const compare = (op: "===" | "!==" | CompareOp, u: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op, + left: u, + right, + type: BOOL, + loc, + }); + const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); + if (e.kind === "unionEq" && !e.sameValue) { + for (const [operand, other] of [ + [e.left, e.right], + [e.right, e.left], + ] as const) { + const tags = tagsOf(operand); + if ( + tags && + tags.unionId === e.unionId && + other.kind === "unionWrap" && + other.tag === tags.stringTag && + other.value.kind === "strLit" && + other.value.value.length === 1 + ) { + const code = other.value.value.charCodeAt(0); + return { + operand, + test: (unit) => compare(e.negated ? "!==" : "===", unit(), num(code)), + }; + } + // `case undefined:` of a switch: undefined is a NaN unit. + if ( + tags && + tags.unionId === e.unionId && + other.kind === "unionWrap" && + other.tag === tags.undefinedTag && + other.value.kind === "unitLit" && + other.value.unit === "undefined" + ) + return { + operand, + twice: true, + test: (unit) => compare(e.negated ? "===" : "!==", unit(), unit()), + }; + } + return null; + } + if (e.kind === "libCall" && e.fn === "js.compare" && e.args.length === 3) { + const [a, b, opArg] = e.args as [IrExpr, IrExpr, IrExpr]; + if (opArg.kind !== "strLit" || !(opArg.value in FLIPPED)) return null; + const op = opArg.value as CompareOp; + const literalOf = (x: IrExpr): number | null => + x.kind === "libCall" && + x.fn === "js.fromStr" && + x.args.length === 1 && + x.args[0]!.kind === "strLit" && + x.args[0]!.value.length === 1 + ? x.args[0]!.value.charCodeAt(0) + : null; + const operandOf = (x: IrExpr): IrExpr | null => + x.kind === "libCall" && + x.fn === "js.fromDyn" && + x.args.length === 1 && + x.args[0]!.kind === "dynFrom" && + tagsOf(x.args[0]!.value) + ? x.args[0]!.value + : null; + // The comparison with the operand on the left. + const left = operandOf(a); + const right = operandOf(b); + const code = left ? literalOf(b) : right ? literalOf(a) : null; + const operand = left ?? right; + if (operand === null || code === null) return null; + const ordered = left ? op : FLIPPED[op]; + return { operand, test: (unit) => compare(ordered, unit(), num(code)) }; + } + if (e.kind === "unionIsTag") { + const tags = tagsOf(e.value); + if (!tags || tags.unionId !== e.unionId) return null; + if (e.tag !== tags.undefinedTag && e.tag !== tags.stringTag) return null; + // Undefined is a NaN unit; a character is any other. + const present = (e.tag === tags.stringTag) !== e.negated; + return { + operand: e.value, + twice: true, + test: (unit) => compare(present ? "===" : "!==", unit(), unit()), + }; + } + return null; +} + function rewriteFunction(fn: IrFunction): IrFunction { + const locals = new Map(fn.locals.map((l) => [l.id, l])); const candidates = new Set( - fn.locals.filter((l) => !l.mutable && !l.boxed && l.type.kind === "string").map((l) => l.id), + fn.locals + .filter( + (l) => !l.mutable && !l.boxed && (l.type.kind === "string" || l.type.kind === "union"), + ) + .map((l) => l.id), ); - if (candidates.size === 0) return fn; + // The optional reads candidate union locals are declared from, directly + // or as copies of another such local (a switch's discriminant temp). + const reads = new Map(); + const copyOf = new Map(); + const collect = (value: unknown): void => { + if (Array.isArray(value)) for (const item of value) collect(item); + else if (value !== null && typeof value === "object") { + const node = value as Record; + if (node.kind === "varDecl" && candidates.has(node.localId as string) && node.init) { + const id = node.localId as string; + const init = node.init as IrExpr; + const read = optionalCharRead(init); + if (read) reads.set(id, read); + else if (init.kind === "varRef" && reads.has(init.localId)) { + reads.set(id, reads.get(init.localId)!); + copyOf.set(id, init.localId); + } + } + for (const key in node) if (key !== "loc") collect(node[key]); + } + }; + collect(fn.body); + const localTags = (operand: IrExpr): OptionalCharRead | null => + operand.kind === "varRef" ? (reads.get(operand.localId) ?? null) : null; + // Every mention of a candidate, in any field, against the mentions the // rewrite understands. const mentions = new Map(); @@ -59,7 +335,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const item of value) count(item); } else if (value !== null && typeof value === "object") { const node = value as Record; - const id = understoodMention(node); + const id = understoodMention(node, locals, reads, localTags); if (id !== null && candidates.has(id)) { note(understood, id); if (node.kind === "varDecl") note(declarations, id); @@ -68,15 +344,26 @@ function rewriteFunction(fn: IrFunction): IrFunction { } }; count(fn.body); + // A copy is held only with its source, and the source's mention in the + // copy's initializer is understood only while the copy is held. const held = new Set(); for (const id of candidates) - if ( - declarations.get(id) === 1 && - (mentions.get(id) ?? 0) > 0 && - mentions.get(id) === understood.get(id) - ) - held.add(id); - if (held.size === 0) return fn; + if (declarations.get(id) === 1 && (mentions.get(id) ?? 0) > 0) held.add(id); + for (let settled = false; !settled;) { + settled = true; + for (const id of held) { + let copies = 0; + for (const [copy, source] of copyOf) if (source === id && held.has(copy)) copies++; + const source = copyOf.get(id); + if ( + mentions.get(id) !== (understood.get(id) ?? 0) + copies || + (source !== undefined && !held.has(source)) + ) { + held.delete(id); + settled = false; + } + } + } const unitRef = (id: string, loc: IrExpr["loc"]): IrExpr => ({ kind: "varRef", @@ -84,10 +371,19 @@ function rewriteFunction(fn: IrFunction): IrFunction { type: F64, loc, }); + // Operands of optional unit tests: held union locals and reads in place. + const operandTags = (operand: IrExpr): OptionalCharRead | null => + operand.kind === "varRef" + ? held.has(operand.localId) + ? localTags(operand) + : null + : optionalCharRead(operand); + let changed = false; const rewriteExpr = (e: IrExpr): IrExpr => { if (e.kind === "strEq" || e.kind === "strCmp") { const use = unitUse(e); if (use && held.has(use.id)) { + changed = true; const code: IrExpr = { kind: "numLit", value: use.code, type: F64, loc: e.loc }; const c = unitRef(use.id, e.loc); if (e.kind === "strEq") @@ -100,9 +396,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { loc: e.loc, }; // The comparison with the local on the left. - const op = use.left - ? e.op - : ({ "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as const)[e.op]; + const op = use.left ? e.op : FLIPPED[e.op]; if (op === ">" || op === ">=") return { kind: "bin", op, left: c, right: code, type: BOOL, loc: e.loc }; const negation: IrExpr = { @@ -116,21 +410,43 @@ function rewriteFunction(fn: IrFunction): IrFunction { return { kind: "unary", op: "!", operand: negation, type: BOOL, loc: e.loc }; } } + const optional = optionalUnitTest(e, operandTags); + if (optional) { + const operand = optional.operand; + if (operand.kind === "varRef") { + changed = true; + return optional.test(() => unitRef(operand.localId, e.loc)); + } + // A read that keeps its temps declares them once: one unit read. + const read = optionalCharRead(operand)!; + if (read.plain || !optional.twice) { + changed = true; + return optional.test(() => unitRead(read, operand.loc)); + } + } return mapExprChildren(e, rewriteExpr, rewriteStmt); }; const rewriteStmt = (s: IrStmt): IrStmt => { - if (s.kind === "varDecl" && held.has(s.localId) && s.init?.kind === "strIntrinsic") - return { - ...s, - init: { - ...s.init, - method: "charCodeAt", - receiver: rewriteExpr(s.init.receiver), - args: s.init.args.map(rewriteExpr), - type: F64, - }, - }; - if (s.kind === "switch" && s.disc.kind === "varRef" && held.has(s.disc.localId)) + if (s.kind === "varDecl" && held.has(s.localId) && s.init) { + changed = true; + const source = copyOf.get(s.localId); + if (source !== undefined) return { ...s, init: unitRef(source, s.init.loc) }; + const read = reads.get(s.localId); + if (read) return { ...s, init: unitRead(read, s.init.loc) }; + if (s.init.kind === "strIntrinsic") + return { + ...s, + init: { + ...s.init, + method: "charCodeAt", + receiver: rewriteExpr(s.init.receiver), + args: s.init.args.map(rewriteExpr), + type: F64, + }, + }; + } + if (s.kind === "switch" && s.disc.kind === "varRef" && held.has(s.disc.localId)) { + changed = true; return { ...s, disc: unitRef(s.disc.localId, s.disc.loc), @@ -143,11 +459,14 @@ function rewriteFunction(fn: IrFunction): IrFunction { body: c.body.map(rewriteStmt), })), }; + } return mapStmtChildren(s, rewriteExpr, rewriteStmt); }; + const body = fn.body.map(rewriteStmt); + if (!changed) return fn; return { ...fn, - body: fn.body.map(rewriteStmt), + body, locals: fn.locals.map((l) => (held.has(l.id) ? unitLocal(l) : l)), }; } @@ -157,22 +476,37 @@ function unitLocal(local: IrLocal): IrLocal { return { ...local, type: F64 }; } -/** The local a node mentions in a form the rewrite handles: the - * declaration from a one-argument charAt, a one-unit test, or a switch - * over one-unit cases. */ -function understoodMention(node: Record): string | null { +/** The local a node mentions in a form the rewrite handles: a string + * local's declaration from a one-argument charAt, one-unit test, or switch + * over one-unit cases; a union local's declaration from an optional read, + * or its test against a one-unit literal or for undefined. */ +function understoodMention( + node: Record, + locals: ReadonlyMap, + reads: ReadonlyMap, + localTags: (operand: IrExpr) => OptionalCharRead | null, +): string | null { const n = node as unknown as IrExpr | IrStmt; - if (n.kind === "varDecl") return n.init && isCharAt(n.init) ? n.localId : null; + if (n.kind === "varDecl") { + if (!n.init) return null; + const local = locals.get(n.localId); + if (local?.type.kind === "string") return isCharAt(n.init) ? n.localId : null; + const read = reads.get(n.localId); + return read && local?.type.kind === "union" && local.type.unionId === read.unionId + ? n.localId + : null; + } if (n.kind === "strEq" || n.kind === "strCmp") return unitUse(n)?.id ?? null; if (n.kind === "switch") { - if (n.disc.kind !== "varRef") return null; + if (n.disc.kind !== "varRef" || n.disc.type.kind !== "string") return null; if (!n.cases.some((c) => c.test !== null)) return null; const units = n.cases.every( (c) => c.test === null || (c.test.kind === "strLit" && c.test.value.length === 1), ); return units ? n.disc.localId : null; } - return null; + const optional = optionalUnitTest(n as IrExpr, localTags); + return optional?.operand.kind === "varRef" ? optional.operand.localId : null; } function isCharAt(e: IrExpr): e is CharAt { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7304-string-optional-char-reads.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7304-string-optional-char-reads.ts.json new file mode 100644 index 000000000..36ea6f8ec --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7304-string-optional-char-reads.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7304-string-optional-char-reads.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7304-string-optional-char-reads.ts b/tests/corpus/7304-string-optional-char-reads.ts new file mode 100644 index 000000000..13222219d --- /dev/null +++ b/tests/corpus/7304-string-optional-char-reads.ts @@ -0,0 +1,121 @@ +// s[i] with an index not proven in bounds, tested against one-unit +// literals: equality, inequality and ordered comparisons on either side, +// peeks at i - 1 and i + 1, tests for undefined, switches, character +// locals with and without other uses, fractional, negative, -0, NaN and +// infinite indices, reads past the end, a two-unit literal, Object.is, a +// class-based lexer with its position in a field, and ASCII, Latin-1, BMP, +// surrogate pairs, lone surrogates and empty strings. + +class Lexer { + pos = 0; + src: string; + constructor(src: string) { + this.src = src; + } + skipSpaces(): void { + const src = this.src; + let pos = this.pos; + while (pos < src.length && (src[pos] === " " || src[pos] === "\t")) pos++; + this.pos = pos; + } + word(): string { + const src = this.src; + const start = this.pos; + let pos = start; + while (pos < src.length) { + const c = src[pos]; + if ((c < "a" || c > "z") && (c < "0" || c > "9")) break; + pos++; + } + this.pos = pos; + return src.slice(start, pos); + } + operator(): string { + const src = this.src; + const c = src[this.pos]; + switch (c) { + case "=": + this.pos += src[this.pos + 1] === "=" ? 2 : 1; + return src[this.pos - 1] === "=" && src[this.pos - 2] === "=" ? "==" : "="; + case "+": + case "-": + this.pos++; + return c; + case undefined: + return ""; + default: + this.pos++; + return "?"; + } + } + tokens(): string[] { + const out: string[] = []; + while (this.pos < this.src.length) { + this.skipSpaces(); + const w = this.word(); + if (w !== "") out.push(w); + else if (this.pos < this.src.length) out.push(this.operator()); + } + return out; + } +} + +function tests(src: string, pos: number): string { + const bits: number[] = []; + bits.push(src[pos] === " " ? 1 : 0, src[pos] !== "x" ? 1 : 0); + bits.push(src[pos] < "a" ? 1 : 0, src[pos] <= "a" ? 1 : 0); + bits.push(src[pos] > "a" ? 1 : 0, src[pos] >= "a" ? 1 : 0); + bits.push("m" < src[pos] ? 1 : 0, "m" >= src[pos] ? 1 : 0); + bits.push(src[pos + 1] === "b" ? 1 : 0, src[pos - 1] > "\0" ? 1 : 0); + bits.push(src[pos] === undefined ? 1 : 0, src[pos] !== undefined ? 1 : 0); + bits.push(src[pos * 2] === undefined ? 1 : 0, src[pos] === "ab" ? 1 : 0); + bits.push(Object.is(src[pos], "a") ? 1 : 0); + return bits.join(""); +} + +function local(src: string, pos: number): string { + const c = src[pos]; + let n = 0; + if (c === undefined) n += 1; + if (c !== undefined && c >= "a") n += 2; + if ("z" > c) n += 4; + switch (c) { + case "é": + n += 8; + break; + case "b": + n += 16; + break; + default: + n += 32; + } + return String(n); +} + +function kept(src: string, pos: number): string { + const c = src[pos]; + if (c === " ") return "space"; + return c ?? "end"; +} + +const samples = ["abc", "", " ", "a b\tc", "café", "日本 語", "😀x", "\uD800z", "ab=b"]; +const indices = [0, 1, 2, 3, -1, -0, 0.5, 1.5, NaN, Infinity, 10]; +for (const s of samples) { + const row: string[] = []; + for (const i of indices) row.push(`${tests(s, i)}:${local(s, i)}:${kept(s, i)}`); + console.log(JSON.stringify(s), row.join(" ")); +} + +const programs = [ + "let x = a + b - c", + " alpha==beta gamma = delta1 ", + "x=y==z+", + "", + "abc é def", + "\tq\t=\t9", +]; +for (const p of programs) console.log(JSON.stringify(p), JSON.stringify(new Lexer(p).tokens())); + +let text = ""; +for (let k = 0; k < 30; k++) text += (k % 4 === 0 ? "==" : "w" + k) + " "; +console.log(new Lexer(text).tokens().length, new Lexer(text).tokens().slice(0, 6).join(",")); diff --git a/tests/harness/corpus-costs/7304-string-optional-char-reads.ts.json b/tests/harness/corpus-costs/7304-string-optional-char-reads.ts.json new file mode 100644 index 000000000..207668232 --- /dev/null +++ b/tests/harness/corpus-costs/7304-string-optional-char-reads.ts.json @@ -0,0 +1 @@ +{ "plain": 2700, "san": 4200 } From e48b2c343508a831bbef57e569b97705586fa2e9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 21:24:51 -0500 Subject: [PATCH 012/395] Keep held format arguments static and compiling The runtime format branch packed its arguments with a checked-dynamic array literal, a dynamic operation: a console.log with a string first argument put its function in the dynamic tier, and the property-bag analysis read the format calls as possible dynamic stores. The held arguments now go to the formatter as a native list of checked values (insp.formatList and insp.formatPrimitives), which is a tier boundary, and the format and inspect entries join the helpers that never store into a class instance. A first argument whose type may hold a string only through a union, or whose checker type does not map, packed every argument as a checked value and failed to compile when one did not convert (7380's `console.log(top ? [...top] : "none", byName)`). It now takes the held-argument form: the lowered value is tested for a substitution and otherwise joins as before. In TypeScript an argument without a checked-dynamic form is refused at compile time only when the joined form cannot print it either; otherwise the branch that substitutes throws SC2020, as in JavaScript. --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/frontend/lowering/lower-console.ts | 3 +- .../src/frontend/lowering/lower-inspect.ts | 55 +++++++++++++------ packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 3 +- packages/compiler/src/ir/int32-slots.ts | 12 ++++ packages/runtime/src/scr_inspect.c | 23 +++++++- packages/runtime/src/scr_inspect.h | 8 ++- 8 files changed, 81 insertions(+), 25 deletions(-) diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index b02cf44f3..365393326 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1036,6 +1036,7 @@ export const LIB_FN_SYMS: Record = { "insp.dynS": "scr_insp_dyn_s", "insp.format": "scr_insp_format_pack", "insp.formatPrimitives": "scr_insp_format_primitives", + "insp.formatList": "scr_insp_format_list", "insp.formatWithOptions": "scr_insp_format_with_options", "insp.inspect": "scr_insp_inspect", "insp.customEnabled": "scr_insp_custom_enabled", diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index db4d39e70..21540cf47 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -21,6 +21,7 @@ import { lowerInspectValue, lowerRuntimeFormat, runtimeFormatString, + formatStringConverts, } from "./lower-inspect.js"; import { streamSidesOf } from "./lower-stream.js"; @@ -372,7 +373,7 @@ export function lowerConsoleOutput( return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); const runtime = nodes.length > 1 && !isStringLiteralLike(first) ? runtimeFormatString(lowerer, first!) : null; - if (runtime === "other") + if (runtime === "other" && !formatStringConverts(lowerer, first!)) return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); if (runtime !== null) { const held = heldFormatArgs(lowerer, nodes, loc); diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index dd950a2a2..9afe12ed4 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -35,6 +35,7 @@ import { canConvertToDyn, canDynCheckTo, funcOf, + arrayOf, CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, @@ -3205,7 +3206,8 @@ export function lowerFormatCall( if (!firstIsLiteral) { const runtime = argNodes.length > 1 ? runtimeFormatString(lowerer, first) : null; - if (runtime === "other") return lowerRuntimeFormat(lowerer, argNodes, loc); + if (runtime === "other" && !formatStringConverts(lowerer, first)) + return lowerRuntimeFormat(lowerer, argNodes, loc); if (runtime !== null) { // A string that substitutes formats at run time; any other string // joins with the remaining arguments, as below. @@ -3576,6 +3578,14 @@ function runtimeFenceValue( }; } +/** Whether the held-argument form can test a format string whose type + * only may hold a string: a union that converts to a checked-dynamic value, + * or a value whose checker type does not map (its lowered type decides). */ +export function formatStringConverts(lowerer: Lowerer, first: ts.Expression): boolean { + const t = lowerer.mapTypeOf(lowerer.typeOf(first)); + return t === null || t.kind === "dyn" || lowerer.dynConvertible(t); +} + /** How a non-literal format string is known: "string" and "dyn" values * test for a substitution at run time, "other" types (unions with a string * arm) format at run time, and null types never hold a string. */ @@ -3673,14 +3683,10 @@ export function heldFormatArgs( "util.format of a void call result (call it as its own statement, then pass undefined)", ); } - if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) { - // JavaScript keeps the space-joined branch, which printed these - // values before, and refuses only a string that substitutes. - if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { - lowerer.noLowering(unconvertibleSurface(value.type), node, UNCONVERTIBLE_HINT); - } + // The space-joined branch prints these values as before; only a string + // that substitutes refuses them, where it runs. + if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) unconvertible ??= { node, type: value.type }; - } // Unit values have no local: their effects run here and a literal // stands in. const k = value.type.kind; @@ -3694,16 +3700,20 @@ export function heldFormatArgs( return { node, ref: () => varRef(held.id, value.type, loc) }; }); const first = args[0]!.ref(); + // A lowered value that is neither a string nor convertible holds no + // string, so it never substitutes. const specifies: IrExpr = first.type.kind === "string" ? { kind: "libCall", fn: "insp.formatSpecifies", args: [first], type: BOOL, loc } - : { - kind: "libCall", - fn: "insp.formatSpecifiesDyn", - args: [lowerer.coerceToExpected(first, DYN)], - type: BOOL, - loc, - }; + : first.type.kind === "dyn" || lowerer.dynConvertible(first.type) + ? { + kind: "libCall", + fn: "insp.formatSpecifiesDyn", + args: [lowerer.coerceToExpected(first, DYN)], + type: BOOL, + loc, + } + : { kind: "boolLit", value: false, type: BOOL, loc }; const format = (): IrExpr => { if (unconvertible !== null) { const fenced = unconvertible; @@ -3725,14 +3735,18 @@ export function heldFormatArgs( t.kind === "undefinedT" || t.kind === "nullT" || (t.kind === "union" && (lowerer.unions.get(t.unionId)?.arms.every(primitive) ?? false)); + // A native list of the checked values: converting into it is a tier + // boundary, so the statement stays static. return { kind: "libCall", - fn: args.every((arg) => primitive(arg.ref().type)) ? "insp.formatPrimitives" : "insp.format", + fn: args.every((arg) => primitive(arg.ref().type)) + ? "insp.formatPrimitives" + : "insp.formatList", args: [ { - kind: "dynArrLit", + kind: "arrayLit", elems: args.map((arg) => lowerer.coerceToExpected(arg.ref(), DYN)), - type: DYN, + type: arrayOf(DYN), loc, }, ], @@ -3748,6 +3762,11 @@ export function heldFormatArgs( if (t.kind === "bytes") return true; return inspectSupport(lowerer, t, new Set(), { recursive: false }) === null; }); + // Without a joined branch every call formats: TypeScript refuses the + // value at compile time. + const refused = unconvertible as { node: ts.Expression; type: IrType } | null; + if (!joinable && refused !== null && !isJsSourceFile(ts.getSourceFileOfNode(refused.node))) + lowerer.noLowering(unconvertibleSurface(refused.type), refused.node, UNCONVERTIBLE_HINT); return { stmts, args, specifies, format, joinable }; } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index d676b05dd..357992a5a 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -183,6 +183,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // The runtime formatter calls toString, valueOf and toJSON, and refuses // renderings it has no lowering for. "insp.format", + "insp.formatList", "insp.formatWithOptions", "insp.inspect", // The options object reads the caller's options (getters included), and diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 6e07e1467..c127af93c 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4376,7 +4376,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.format": { argTypes: [DYN], result: STRING }, /** insp.format over arguments that are all strings, numbers, booleans, * null or undefined, without the checked-dynamic walk. */ - "insp.formatPrimitives": { argTypes: [DYN], result: STRING }, + "insp.formatPrimitives": { argTypes: [arrayOf(DYN)], result: STRING }, + "insp.formatList": { argTypes: [arrayOf(DYN)], result: STRING }, "insp.formatWithOptions": { argTypes: [DYN, DYN], result: STRING }, /** util.inspect(value, options, depth, colors) with the call's argument * count: the runtime reads the options and renders the checked-dynamic diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index ac0747276..6a30eb92b 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -407,6 +407,18 @@ const DYN_READ_ONLY = new Set([ "fs.utimesSync", "insp.dyn", "insp.dynS", + // util.format and util.inspect over checked values: the inspect walk and + // the format conversions read them; insp.registerCustom keeps a class's + // custom inspect thunk in the runtime's registry. + "insp.customIsSelf", + "insp.customResult", + "insp.format", + "insp.formatList", + "insp.formatPrimitives", + "insp.formatSpecifiesDyn", + "insp.formatWithOptions", + "insp.inspect", + "insp.registerCustom", // The dynamic-tier operators run their dyn twins (scr_js_value.c); the // conversions move a value between dyn and jsval, and js.toF64 is // dynCheck's number check. diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 2c7951103..da49804c1 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -2575,11 +2575,21 @@ static ScrStr *insp_prim_number(ScrDyn *v, int spec) { static const InspFmtOps insp_fmt_primitive_ops = {insp_prim_s, insp_prim_j, insp_prim_number, insp_prim_inspect}; -ScrStr *scr_insp_format_primitives(ScrDyn *pack) { +/* A native list of checked values (the compiled format calls) as the + * runtime's argument pack. +1. */ +static ScrDyn *insp_list_pack(ScrArr *list) { + ScrDyn *pack = scr_dyn_new_arr(); + for (uint32_t i = 0; i < list->len; i++) scr_dyn_arr_push(pack, scr_arr_get_ref(list, i)); + return pack; +} + +ScrStr *scr_insp_format_primitives(ScrArr *list) { ScrInspFormatOptions o; scr_insp_format_defaults(&o, "util.format"); - if (pack->kind != SCR_DYN_ARR) return scr_str_new("", 0); - return insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); + ScrDyn *pack = insp_list_pack(list); + ScrStr *out = insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); + scr_dyn_release(pack); + return out; } /* The arguments of a dyn array pack (+1 text, or NULL pending). */ @@ -2594,6 +2604,13 @@ ScrStr *scr_insp_format_pack(ScrDyn *pack) { return insp_fmt_pack(pack, &o); } +ScrStr *scr_insp_format_list(ScrArr *list) { + ScrDyn *pack = insp_list_pack(list); + ScrStr *out = scr_insp_format_pack(pack); + scr_dyn_release(pack); + return out; +} + /* Whether a format string can substitute: a '%' before its last character. * A string without one formats as its arguments joined with spaces. */ bool scr_insp_format_specifies(ScrStr *first) { diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index a95ac1fde..339fe781b 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -76,9 +76,13 @@ ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOpt /* util.format(...pack) and util.formatWithOptions(options, ...pack) over a * dyn array of the arguments (+1). */ ScrStr *scr_insp_format_pack(ScrDyn *pack); -/* util.format(...pack) when every argument is a string, number, boolean, +/* util.format over a native list of checked values, the compiled calls' + * held arguments (+1). */ +struct ScrArr; +ScrStr *scr_insp_format_list(struct ScrArr *list); +/* scr_insp_format_list when every argument is a string, number, boolean, * null or undefined, without the checked-dynamic walk (+1). */ -ScrStr *scr_insp_format_primitives(ScrDyn *pack); +ScrStr *scr_insp_format_primitives(struct ScrArr *list); ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack); /* util.inspect(value, options, depth, colors) over checked-dynamic * arguments with Node's legacy-argument rules; argc is the call's From 56f70c47b1ae251451c8f1bb42da5fd859c5493e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Thu, 22 Oct 2026 20:06:30 -0700 Subject: [PATCH 013/395] Answer 'in' on native Node handles from their declared members `"closeAllConnections" in server` (the feature test servers use before calling it) was refused for an http.Server and the other native handles. A member Node's declarations give the class is on its prototype, so the test now answers true after evaluating the receiver. Undeclared names keep the refusal, since internal fields can still be present. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/relational.ts | 48 +++++++++++++++++++ .../server/cases/http-in-members/driver.mjs | 7 +++ .../server/cases/http-in-members/main.ts | 18 +++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/fixtures/server/cases/http-in-members/driver.mjs create mode 100644 tests/fixtures/server/cases/http-in-members/main.ts diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 0eb8ae653..edec5ba5f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1762,6 +1762,25 @@ export function lowerInExpression( }; } } + // A native Node handle (an http.Server, a socket, a child process...): + // a member Node's declarations give the class is on its prototype, so + // `"closeAllConnections" in server` answers true. Undeclared names can + // still be real (internal `_`-prefixed fields), so they keep the fence. + if ( + NATIVE_HANDLE_KINDS.has(recv.type.kind) && + nodeDeclaredMember(lowerer, lowerer.typeOf(expr.right), key) + ) { + const answer: IrExpr = { kind: "boolLit", value: true, type: BOOL, loc }; + return isSafeToDiscard(recv) + ? answer + : { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: recv, loc }], + result: answer, + type: BOOL, + loc, + }; + } if (recv.type.kind !== "record") { lowerer.unsupported( "SC1090", @@ -2059,3 +2078,32 @@ function dynInstanceOfRefusal(lowerer: Lowerer, expr: ts.BinaryExpression): stri const line = ts.getLineAndCharacterOfPosition(sf, diag.loc.start).line + 1; return `${diag.message} [${diag.code} at ${diag.loc.file}:${line}]`; } + +/** IR kinds of native Node handles whose members come from Node's own + * declarations. */ +const NATIVE_HANDLE_KINDS: ReadonlySet = new Set([ + "netServer", + "netSocket", + "httpReq", + "httpRes", + "httpClientReq", + "child", + "dgramSocket", + "fsWatcher", + "http2Session", + "http2Stream", +]); + +/** Whether every constituent of `t` declares a required member `key` in + * a declaration file (Node's @types/node). */ +function nodeDeclaredMember(lowerer: Lowerer, t: ts.Type, key: string): boolean { + const parts = t.isUnionType() ? ts.constituentTypes(t) : [t]; + return parts.every((part) => { + const prop = lowerer.checker.getPropertyOfType(part, key); + if (prop === undefined || (prop.flags & ts.SymbolFlags.Optional) !== 0) return false; + return ( + prop.declarations.length > 0 && + prop.declarations.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile) + ); + }); +} diff --git a/tests/fixtures/server/cases/http-in-members/driver.mjs b/tests/fixtures/server/cases/http-in-members/driver.mjs new file mode 100644 index 000000000..9c989bb70 --- /dev/null +++ b/tests/fixtures/server/cases/http-in-members/driver.mjs @@ -0,0 +1,7 @@ +import http from "node:http"; +const port = Number(process.argv[2]); +http.get({ host: "127.0.0.1", port, path: "/" }, (res) => { + let body = ""; + res.on("data", (c) => (body += c)); + res.on("end", () => console.log(res.statusCode, body)); +}); diff --git a/tests/fixtures/server/cases/http-in-members/main.ts b/tests/fixtures/server/cases/http-in-members/main.ts new file mode 100644 index 000000000..80babfc13 --- /dev/null +++ b/tests/fixtures/server/cases/http-in-members/main.ts @@ -0,0 +1,18 @@ +// `in` tests on native Node handles answer from Node's declared members. +import { createServer } from "node:http"; +import type { AddressInfo } from "node:net"; + +const server = createServer((req, res) => { + console.log("request", "method" in req, "setHeader" in res, "writeHead" in res); + res.end("ok"); +}); +console.log("closeAllConnections" in server, "closeIdleConnections" in server, "listen" in server); +server.listen(0, "127.0.0.1", () => { + process.stderr.write(`PORT ${(server.address() as AddressInfo).port}\n`); +}); +server.on("request", () => { + setTimeout(() => { + if ("closeAllConnections" in server) server.closeAllConnections(); + server.close(() => console.log("closed")); + }, 10); +}); From e7bced94fd9082c9007d1af6f4cdefeb97f752f5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 03:20:04 -0500 Subject: [PATCH 014/395] Let LLVM merge the comparisons of a three-way string comparator --- .../compiler/src/backend/llvm/emit-module.ts | 6 +-- .../src/backend/llvm/string-lifetimes.test.ts | 4 +- tests/harness/string-compare-emission.test.ts | 47 +++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 tests/harness/string-compare-emission.test.ts diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 16a1b1601..3c0e91e20 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1601,8 +1601,8 @@ export function helperDefs(emitter: LlEmitter): string[] { // order; other differing words decide by their first differing byte, // and strings shorter than a word by their first byte. Otherwise a // proven-ASCII string on either side orders by bytes (scr_str_cmp), and - // other pairs call scr_str_cmp_u16_bytes, which only reads the two - // strings, so LLVM can merge repeated comparisons of one pair. Callers + // other pairs call scr_str_cmp_u16_bytes. Both only read the two strings, + // so LLVM can merge repeated comparisons of one pair. Callers // only test the sign of the result. if (emitter.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) { const fn = "scr_str_cmp_u16"; @@ -1732,7 +1732,7 @@ export function helperDefs(emitter: LlEmitter): string[] { ` %anyascii = icmp slt i32 %cws, 0`, ` br i1 %anyascii, label %memorder, label %units`, `memorder:`, - ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`, + ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, ` ret i32 %m`, `units:`, ` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts index f85addcf8..07ad31153 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts @@ -478,7 +478,9 @@ test("string comparisons decide identity, lengths and leading bytes before the r expect(units).toContain("%below = icmp ult i8 %bx, %by"); expect(units).toContain("%tailable = and i1 %samelen, %short"); expect(units).toContain("%anyascii = icmp slt i32 %cws, 0"); - expect(units).toContain("call i32 @scr_str_cmp(ptr %a, ptr %b)"); + expect(units).toContain( + "call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)", + ); expect(units).toContain( "call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)", ); diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts new file mode 100644 index 000000000..2654f56ba --- /dev/null +++ b/tests/harness/string-compare-emission.test.ts @@ -0,0 +1,47 @@ +/* String comparison emission: the runtime calls behind a relational string + * operator only read the two strings, so LLVM merges the comparisons of one + * pair. A three-way comparator (`a < b ? -1 : a > b ? 1 : 0`) then compares + * its strings once, as a comparator that tests one operator does. */ +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const repoRoot = join(import.meta.dirname, "../.."); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); + +async function emittedAsm(name: string, source: string): Promise { + const key = createHash("sha256").update(source).digest("hex").slice(0, 16); + const outDir = join(cacheDir, `string-compare-${key}`); + mkdirSync(outDir, { recursive: true }); + const file = join(outDir, `${name}.ts`); + writeFileSync(file, source); + const result = await compile(file, { + outPath: join(outDir, name), + outDir, + backend: "llvm", + outputKind: "asm", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return readFileSync(result.artifact.path, "utf8"); +} + +const program = (body: string): string => `function order(a: string, b: string): number { + return ${body}; +} +const words = process.argv.slice(2); +words.sort(order); +console.log(words.join(",")); +`; + +const compareCalls = (asm: string): number => + asm.match(/\bcall[a-z]*\s+(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g)?.length ?? 0; + +test("a three-way string comparator compares its pair once", async () => { + const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"))); + const both = compareCalls(await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"))); + expect(once).toBeGreaterThan(0); + expect(both).toBe(once); +}); From 03807c0c3574d395c53905aa4b8983ef8ec11d09 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 04:11:54 -0500 Subject: [PATCH 015/395] Run private members and super calls over foreign receivers like Node A JavaScript class's prototype method called with a receiver that is not an instance of its class (`C.prototype.m.call(other)`, or an object that inherits the method) now behaves as in Node when it uses private members or calls super methods, instead of throwing "expected C" for the receiver. - Private fields keep their brand check: reading or writing `this.#x` throws "Cannot read private member #x from an object whose class did not declare it" (or the write message), and `#x in this` answers false. - `this.#m()` brand-checks the receiver through a new `call` use of the private brand helpers and throws "Receiver must be an instance of class C" (V8's wording: the class's own identifier, "anonymous" without one, and "Cannot read properties of undefined (reading 'C')" for a nullish receiver). It also applies to private method calls on other checked-dynamic values. - `super.m(...)` looks the method up on the base class's complete prototype object and calls it with the receiver, as `super` reads [[HomeObject]].[[Prototype]]. Methods that use `super` in other positions, static methods with private names, and classes whose base chain includes a function-local, generic, mixin, Date or runtime-provided class keep the native receiver check. The receiver-independent method lowering and the prototype reflection now mark their frames as `this` boundaries, so lowering one class's members on top of another method's frames (a super call reflecting the base) never reaches the caller's `this`. --- .../frontend/lowering/class-method-values.ts | 50 ++++++- .../src/frontend/lowering/class-prototypes.ts | 3 + .../src/frontend/lowering/lower-calls.ts | 44 ++++++ .../src/frontend/lowering/lower-classes.ts | 14 +- .../src/frontend/lowering/private-brands.ts | 50 ++++++- ...1-class-methods-foreign-receivers.mjs.json | 6 + .../7621-class-methods-foreign-receivers.mjs | 130 ++++++++++++++++++ ...1-class-methods-foreign-receivers.mjs.json | 1 + 8 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7621-class-methods-foreign-receivers.mjs.json create mode 100644 tests/corpus/7621-class-methods-foreign-receivers.mjs create mode 100644 tests/harness/corpus-costs/7621-class-methods-foreign-receivers.mjs.json diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index 6e8256513..af2ef51d5 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -378,9 +378,17 @@ function reflectedMethodValue( !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || info.localClass !== undefined || lowerer.errorHierarchyClassOf(info.def.name); + // `super.m(...)` in a prototype method reads the base class's prototype, + // which lowerSuperMethodCall answers over the call-site receiver when the + // base has a prototype object. Other uses of `super` keep the native + // receiver. + const superCalls = !staticMethod && info.base !== null && prototypeChainViewable(info.base); ts.walkPreorder(member, (node) => { - if (ts.isPrivateIdentifier(node) || node.kind === ts.SyntaxKind.SuperKeyword) + if (node.kind === ts.SyntaxKind.SuperKeyword && !(superCalls && isSuperMethodCallee(node))) needsNativeReceiver = true; + // Private members on a static method's receiver (the class value) keep + // the native receiver; prototype methods brand-check theirs. + if (staticMethod && ts.isPrivateIdentifier(node)) needsNativeReceiver = true; }); if (needsNativeReceiver) return null; // The method reads itself as a value inside its own body: it is the @@ -403,7 +411,11 @@ function reflectedMethodValue( loc: locOf(member), }; } + // The method may be lowered on top of an unrelated caller's frames (a + // `super` call in another class's method reflects this class): its `this` + // is its own receiver, never an enclosing function's. const context = newFnCtx(false, null, null, DYN); + context.thisBoundary = true; const previousClass = lowerer.currentClass; lowerer.currentClass = info; lowerer.fnStack.push(context); @@ -441,6 +453,42 @@ function reflectedMethodValue( } } +/** Whether `base` and every class above it have a complete prototype object + * (reflectClassPrototype): module-level program classes that neither extend + * Date nor a runtime-provided class. */ +function prototypeChainViewable(base: ClassInfo): boolean { + for (let c: ClassInfo | null = base; c; c = c.base) + if ( + c.decl === null || + c.localClass !== undefined || + c.generic !== undefined || + c.genericInstance !== undefined || + c.mixinInstance !== undefined || + c.callableBase !== undefined || + c.extendsDate !== undefined || + c.def.runtime || + c.builtinEmitter || + c.builtinStream || + c.builtinError || + c.builtinEventTarget + ) + return false; + return true; +} + +/** `super` as the receiver of a called member: `super.m(...)`. */ +function isSuperMethodCallee(node: ts.Node): boolean { + const access = node.parent; + return ( + access !== undefined && + ts.isPropertyAccessExpression(access) && + access.expression === node && + access.parent !== undefined && + ts.isCallExpression(access.parent) && + access.parent.expression === access + ); +} + /** Complete adapters after all native class bodies and instantiations exist. */ export function finalizeClassMethodValues(lowerer: Lowerer, functions: IrFunction[]): void { const byName = new Map(functions.map((fn) => [fn.name, fn])); diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index cb2ab2280..116150465 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -400,7 +400,10 @@ export function reflectClassPrototype( writable, ]), ); + // The members lower on top of whatever frames requested the view (a + // `super` call in a subclass's method); their `this` is their own receiver. const context = newFnCtx(false, null, null, DYN); + context.thisBoundary = true; lowerer.fnStack.push(context); try { for (const member of info.decl.members.nodes) { diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 82f5a27c1..d80f342c2 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,3 +1,4 @@ +import { checkedPrivateMethodReceiver, privateNameClass } from "./private-brands.js"; import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; @@ -547,6 +548,17 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ) { return lowerer.lowerSuperMethodCall(expr, expr.expression); } + // `value.#m(...)` on a checked-dynamic value (`this` in a JavaScript + // class's receiver-independent method): the private method of the + // brand-checked receiver. + if ( + ts.isPropertyAccessExpression(expr.expression) && + ts.isPrivateIdentifier(expr.expression.name) && + !expr.expression.questionDotToken + ) { + const privateCall = lowerCheckedPrivateMethodCall(lowerer, expr, expr.expression); + if (privateCall) return privateCall; + } const consoleMember = lowerer.consoleCallMember(expr); const consoleMethod = consoleMember === null ? lowerer.consoleMethodMember(expr) : null; @@ -11286,3 +11298,35 @@ function lowerShimIntrinsicCall( ); return { kind: "libCall", fn: intrinsic.fn, args, type: intrinsic.ret, loc }; } + +/** `value.#m(args)` where `value` lowers to a checked-dynamic value and the + * declaration-level class whose body declares `#m` declares it as a method: + * the method called on the brand-checked receiver + * (checkedPrivateMethodReceiver). Null for any other private member call. */ +function lowerCheckedPrivateMethodCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (!ts.isPrivateIdentifier(access.name)) return null; + const name = access.name.text; + const owner = privateNameClass(lowerer, access.name); + const found = owner ? lowerer.findMethodOn(owner, name) : null; + if (!owner || !found || found.declarer !== owner || found.sig.abstract === true) return null; + const probed = tryLowerExpression(lowerer, access.expression); + if (!probed || !checkedDynamicOperand(probed)) return null; + const value = checkedDynamicOperand(lowerer.lowerExpr(access.expression)); + if (!value) return null; + const loc = locOf(call); + const callee = `%${owner.def.name}.${name}`; + lowerer.noteEdge(callee); + const receiver = checkedPrivateMethodReceiver(lowerer, owner, name, value, loc); + const args = lowerer.completeArgs(call.arguments.nodes, found.sig.params, loc, call); + return { kind: "call", callee, args: [receiver, ...args], type: found.sig.ret, loc }; +} + +/** A checked-dynamic value, or the dynamic value under a checked extraction. */ +function checkedDynamicOperand(expr: IrExpr): IrExpr | null { + if (expr.type.kind === "dyn") return expr; + return expr.kind === "dynCheck" && expr.value.type.kind === "dyn" ? expr.value : null; +} diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 29afbb4fe..db8f35f95 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -181,7 +181,7 @@ import { construction, constructionPrefix } from "./js-class-fields.js"; import { lowerObjectFactoryNew } from "./object-factory-new.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerInstanceConstructorNew } from "./class-instance-constructor.js"; -import { classPrototypeData } from "./class-prototypes.js"; +import { classPrototypeData, reflectClassPrototype } from "./class-prototypes.js"; import { constructionEscape } from "./construction-escapes.js"; import { checkedClassConstruction, checkedClassConstructionPacked } from "./class-construction.js"; import { initializeRuntimeStatics, hasRuntimeStatics } from "./class-runtime-statics.js"; @@ -9384,8 +9384,16 @@ export function lowerSuperMethodCall( if (!thisLocal) lowerer.unsupported("SC1080", access); const loc = locOf(call); const thisRef: IrExpr = { kind: "varRef", localId: thisLocal.id, type: thisLocal.type, loc }; - if (lowerer.prototypeMethodAccesses.has(access.name.text)) { - const prototype = classPrototypeData(lowerer, cls.base, loc); + // A method whose `this` is the call-site receiver (a JavaScript class's + // receiver-independent method value, reflectedClassMethodValue) looks the + // method up on the base class's complete prototype, as `super` reads + // [[HomeObject]].[[Prototype]], and calls it with that receiver. + const dynamicThis = thisRef.type.kind === "dyn"; + if (dynamicThis || lowerer.prototypeMethodAccesses.has(access.name.text)) { + const prototype = + dynamicThis && cls.base.decl + ? reflectClassPrototype(lowerer, cls.base, loc, classValueRef(lowerer, cls.base, access)) + : classPrototypeData(lowerer, cls.base, loc); if (prototype) { const callee: IrExpr = { kind: "dynKeyGet", diff --git a/packages/compiler/src/frontend/lowering/private-brands.ts b/packages/compiler/src/frontend/lowering/private-brands.ts index c7881bce1..92bee4fdd 100644 --- a/packages/compiler/src/frontend/lowering/private-brands.ts +++ b/packages/compiler/src/frontend/lowering/private-brands.ts @@ -17,8 +17,9 @@ import type { Lowerer } from "./lowerer.js"; import type { ClassInfo } from "./lower-classes.js"; /** How a brand helper uses its receiver: `in` answers `#x in v`, `read` and - * `write` return the receiver as the declaring class for a field access. */ -type BrandUse = "in" | "read" | "write"; + * `write` return the receiver as the declaring class for a field access, and + * `call` for a private method call. */ +type BrandUse = "in" | "read" | "write" | "call"; const PREFIX = "%private."; @@ -214,6 +215,35 @@ export function checkedPrivateValue( return { kind: "call", callee: helper, args: [receiver], type: resultType, loc }; } +/** `value.#m(...)` on a checked-dynamic `value` in a declaration-level class + * `info` that declares the private method `#m`: the receiver as an instance + * of `info` when the value is one, else Node's TypeError ("Receiver must be + * an instance of class C"). */ +export function checkedPrivateMethodReceiver( + lowerer: Lowerer, + info: ClassInfo, + name: string, + receiver: IrExpr, + loc: SrcLoc, +): IrExpr { + const className = info.def.name; + const resultType: IrType = { kind: "object", className }; + const helper = `${PREFIX}call:${className}:${name}`; + if (!lowerer.valueHelpers.has(helper)) { + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "value.0", name: "value", type: DYN }], + returnType: resultType, + locals: [{ id: "value.0", name: "value", type: DYN, mutable: false }], + // finalizePrivateBrands fills the body. + body: [], + loc: helperLoc(loc), + }); + lowerer.valueHelpers.set(helper, helper); + } + return { kind: "call", callee: helper, args: [receiver], type: resultType, loc }; +} + /** A plain assignment, a destructuring target, or a for-in/of target writes * the field. Compound assignments and updates read it first, and Node * reports the failed read. */ @@ -358,6 +388,22 @@ export function finalizePrivateBrands(lowerer: Lowerer, functions: IrFunction[]) }, { kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }, ); + } else if (use === "call") { + // V8 names the class by its own identifier (not a name inferred from + // a binding), "anonymous" without one, also for a nullish receiver. + const owner = lowerer.classes.get(className); + const identifier = owner?.decl?.name; + const shown = identifier !== undefined ? identifier.text : "anonymous"; + for (const unit of ["undefined", "null"] as const) { + body.push({ + kind: "if", + cond: { kind: "dynTest", test: unit, value, type: BOOL, loc }, + then: [typeError(strLit(`Cannot read properties of ${unit} (reading '${shown}')`, loc))], + else_: null, + loc, + }); + } + body.push(typeError(strLit(`Receiver must be an instance of class ${shown}`, loc))); } else { const verb = use === "read" ? "reading" : "setting"; const action = use === "read" ? "Cannot read properties" : "Cannot set properties"; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7621-class-methods-foreign-receivers.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7621-class-methods-foreign-receivers.mjs.json new file mode 100644 index 000000000..8f50b09a3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7621-class-methods-foreign-receivers.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7621-class-methods-foreign-receivers.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/7621-class-methods-foreign-receivers.mjs b/tests/corpus/7621-class-methods-foreign-receivers.mjs new file mode 100644 index 000000000..beb046ea4 --- /dev/null +++ b/tests/corpus/7621-class-methods-foreign-receivers.mjs @@ -0,0 +1,130 @@ +// Prototype methods called with a receiver that is not an instance of +// their class (`C.prototype.m.call(other)`): `#private` members throw +// Node's TypeErrors for such a receiver, and `super.m()` looks the method up +// on the base class's prototype and calls it with the receiver. +function report(label, run) { + try { + console.log(label, run()); + } catch (error) { + console.log(label, error.constructor.name, error.message); + } +} + +class Counter { + #count = 0; + #step = 1; + constructor(name) { + this.name = name; + } + read() { + return this.#count; + } + bump() { + this.#count += this.#step; + return this.#count; + } + reset(value) { + this.#count = value; + return this.name; + } + #label() { + return `${this.name}#${this.#count}`; + } + label() { + return this.#label(); + } + owns(value) { + return #count in value; + } + ownsSelf() { + return #count in this; + } +} + +const counter = new Counter("c"); +console.log(counter.bump(), counter.bump(), counter.read(), counter.label()); +console.log(counter.owns(counter), counter.owns({}), counter.ownsSelf(), counter.reset(7), counter.read()); + +const plain = { name: "plain" }; +report("read", () => Counter.prototype.read.call(plain)); +report("bump", () => Counter.prototype.bump.call(plain)); +report("reset", () => Counter.prototype.reset.call(plain, 3)); +report("label", () => Counter.prototype.label.call(plain)); +report("ownsSelf", () => Counter.prototype.ownsSelf.call(plain)); +report("owns primitive", () => Counter.prototype.owns.call(counter, 5)); +report("label undefined", () => Counter.prototype.label.call(undefined)); +report("label null", () => Counter.prototype.label.call(null)); +report("label number", () => Counter.prototype.label.call(4)); +report("read other instance", () => Counter.prototype.read.call(new Counter("d"))); +report("label other instance", () => Counter.prototype.label.call(new Counter("e"))); + +const Anonymous = class { + #m() { + return 1; + } + call() { + return this.#m(); + } +}; +const Named = class Inner { + #m() { + return 2; + } + call() { + return this.#m(); + } +}; +report("anonymous", () => Anonymous.prototype.call.call({})); +report("named", () => Named.prototype.call.call({})); +console.log(new Anonymous().call(), new Named().call()); + +class Shape { + constructor(name) { + this.name = name; + } + describe() { + return `shape ${this.name}`; + } + area() { + return 0; + } +} +class Square extends Shape { + describe() { + return `square: ${super.describe()} area ${this.area()}`; + } + area() { + return this.size * this.size; + } + both() { + return [super.describe(), super.area()].join("|"); + } +} +class Tiny extends Square { + #tag = "t"; + describe() { + return `tiny<${super.describe()}>`; + } + tag() { + return `${this.#tag}:${super.both()}`; + } +} + +const square = new Square("s"); +square.size = 3; +console.log(square.describe(), square.both()); +const tiny = new Tiny("t"); +tiny.size = 1; +console.log(tiny.describe(), tiny.tag()); +const foreign = { + name: "f", + size: 2, + area() { + return 40; + }, +}; +report("square describe", () => Square.prototype.describe.call(foreign)); +report("square both", () => Square.prototype.both.call(foreign)); +report("tiny describe", () => Tiny.prototype.describe.call(foreign)); +report("tiny tag", () => Tiny.prototype.tag.call(foreign)); +report("square describe on tiny", () => Square.prototype.describe.call(tiny)); diff --git a/tests/harness/corpus-costs/7621-class-methods-foreign-receivers.mjs.json b/tests/harness/corpus-costs/7621-class-methods-foreign-receivers.mjs.json new file mode 100644 index 000000000..bb2ade1c2 --- /dev/null +++ b/tests/harness/corpus-costs/7621-class-methods-foreign-receivers.mjs.json @@ -0,0 +1 @@ +{ "plain": 6415, "san": 10107 } From 956292c6274c1aa71b3dcb4ece09cddc920f82d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 06:06:50 -0500 Subject: [PATCH 016/395] Store foreign elements only in number[] and string[] ToNumber and ToString of a foreign element are reached through a table that scr_arr_set_foreign installs, so a program that never stores a foreign element no longer links the dynamic tier through the array runtime's joins, sorts and numeric reads. Only number[] and string[] store a value written through a dynamic view that their element type cannot hold. Other element types keep the checked conversion's TypeError at the write. Counting every viewed element type as possibly undefined made callbacks over records whose arrays reach a package (zod3-infer) take optional elements and refuse their spreads. --- .../lowering/runtime-optional-elements.ts | 14 +++++++--- packages/runtime/src/scr_array.c | 28 ++++++++++++++++--- .../6068-foreign-elements-through-views.ts | 23 ++++----------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 9a85bda1a..4db6ee175 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -62,6 +62,12 @@ const VIEW_HOLES = "%view"; /** The fact name of values stored through dynamic views. */ const VIEW_VALUES = "%view-values"; +/** Element keys whose arrays store a value written through a dynamic view + * that the element type cannot hold, as a foreign element: `number[]` and + * `string[]`. Arrays of other element types reject such a write with the + * checked conversion's TypeError. */ +const FOREIGN_ELEMENT_KEYS: ReadonlySet = new Set(["f64", "string"]); + /** What an array value can contain besides present values. */ interface Contents { undefined: boolean; @@ -373,7 +379,7 @@ export class ArrayElementStates { /** Values of any type stored through a dynamic view can be in arrays of * this key. */ private viewedValues(key: string): boolean { - return this.viewValues && this.isViewed(key); + return this.viewValues && FOREIGN_ELEMENT_KEYS.has(key) && this.isViewed(key); } private isViewed(key: string): boolean { @@ -382,11 +388,11 @@ export class ArrayElementStates { } /** Element keys whose arrays can hold elements outside their element - * type, stored through dynamic views; "*" for every array. Final once - * the fixed point is reached. */ + * type, stored through dynamic views (of FOREIGN_ELEMENT_KEYS). Final + * once the fixed point is reached. */ foreignKeys(): ReadonlySet { if (!this.viewValues) return new Set(); - return (this.viewKeys ??= this.host.nativeViewKeys()); + return new Set([...FOREIGN_ELEMENT_KEYS].filter((key) => this.isViewed(key))); } /** A store through `receiver`, which has no native element key, can diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index e83477142..c7399bcea 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -269,6 +269,25 @@ static uint64_t scr_elem_retain_slot(const ScrArr *a, uint64_t slot) { return scr_elem_retain_with(a->elem, scr_arr_ops(a), slot); } +/* ToNumber and ToString of a foreign element. Both can run user code and + * pull in the dynamic tier, so they are reached through this table, which + * scr_arr_set_foreign installs: every foreign element is stored by it, and + * a program that never stores one links neither conversion. */ +typedef struct ScrArrForeignConversions { + double (*to_number)(const ScrDyn *); + ScrStr *(*to_string)(const ScrDyn *); +} ScrArrForeignConversions; + +static const ScrArrForeignConversions scr_arr_dyn_conversions = { + scr_dyn_number_coerce, + scr_dyn_string_coerce_js, +}; +static const ScrArrForeignConversions *scr_arr_conversions; + +static const ScrArrForeignConversions *scr_arr_foreign_conversions(void) { + return __atomic_load_n(&scr_arr_conversions, __ATOMIC_RELAXED); +} + /* The array may hold SCR_ARR_FOREIGN elements (ScrArrExt.foreign). Such * an array always has states. */ static inline bool scr_arr_may_hold_foreign(const ScrArr *a) { @@ -2056,7 +2075,7 @@ static double *scr_arr_coerced_numbers(const ScrArr *a) { uint64_t slot; uint8_t state = scr_arr_f64_state_at(a, i, &slot); if (state == SCR_ARR_FOREIGN) { - out[i] = scr_dyn_number_coerce(scr_slot_to_ptr(slot)); + out[i] = scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); if (scr_exc_pending()) { free(out); return NULL; @@ -2252,7 +2271,7 @@ double scr_arr_get_number(const ScrArr *a, double i) { } else if (!scr_arr_prop_get_state(a, i, &slot, &state)) { return NAN; } - if (state == SCR_ARR_FOREIGN) return scr_dyn_number_coerce(scr_slot_to_ptr(slot)); + if (state == SCR_ARR_FOREIGN) return scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); return state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } @@ -2368,6 +2387,7 @@ bool scr_arr_set_foreign(ScrArr *a, double i, ScrDyn *v) { * doubles first, and 1-byte and compact slots cannot hold one. */ if (a->elem == SCR_ELEM_I32) scr_arr_widen_i32(a); if (scr_arr_slot_width(a) != sizeof(uint64_t)) return false; + __atomic_store_n(&scr_arr_conversions, &scr_arr_dyn_conversions, __ATOMIC_RELAXED); scr_dyn_retain(v); scr_arr_need_states(a); if (idx >= a->len) a->len = idx + 1; @@ -3411,7 +3431,7 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { if (state != SCR_ARR_VALUE && state != SCR_ARR_FOREIGN) continue; ScrStr *key; if (state == SCR_ARR_FOREIGN) { - key = scr_dyn_string_coerce_js(scr_slot_to_ptr(slot)); + key = scr_arr_foreign_conversions()->to_string(scr_slot_to_ptr(slot)); if (!key) failed = true; } else if (a->elem == SCR_ELEM_STR) { key = scr_str_retain(scr_slot_to_ptr(slot)); @@ -5372,7 +5392,7 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) { /* join converts every element but undefined and null with ToString. */ const ScrDyn *value = scr_slot_to_ptr(slot); if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) continue; - ScrStr *text = scr_dyn_string_coerce_js(value); + ScrStr *text = scr_arr_foreign_conversions()->to_string(value); if (!text) continue; scr_join_append(&out, text->data, text->len); scr_str_release(text); diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 21cfe25a2..e6676eea4 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -1,8 +1,8 @@ -// A typed array seen as `unknown[]` holds the values written through the -// view that its element type cannot hold, as in Node. The view and the typed -// array are the same array: length, the view's reads and iteration, -// printing, JSON, join, searches, copies, Math over a spread, sorting and -// truncation see the written values. +// A number[] or string[] seen as `unknown[]` holds the values written +// through the view that its element type cannot hold, as in Node. The view +// and the typed array are the same array: length, the view's reads and +// iteration, printing, JSON, join, searches, copies, Math over a spread, +// sorting and truncation see the written values. const numbers: number[] = [1, 2]; const numbersView: unknown[] = numbers; @@ -49,19 +49,8 @@ truncated.length = 1; truncated.length = 3; console.log(truncated, truncatedView.length); -// string[] and class arrays hold values of other types the same way. +// string[] holds values of other types the same way. const words: string[] = ["a"]; const wordsView: unknown[] = words; wordsView.push(1, false); console.log(words, words.join("-"), words.length, typeof wordsView[1]); - -class Item { - id: string; - constructor(id: string) { - this.id = id; - } -} -const items: Item[] = [new Item("a")]; -const itemsView: unknown[] = items; -itemsView.push({ id: "plain" }); -console.log(items, items.length, items[0]!.id, itemsView[1] instanceof Item); From d22250f8305a5a21c78e85eca17db51a82da02a5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 08:05:50 -0500 Subject: [PATCH 017/395] Raise slow CI job time limits and prune closed pull request caches --- .github/workflows/ci.yml | 9 +++++--- .github/workflows/prune-caches.yml | 36 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/prune-caches.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f131747fa..fe601ed06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -524,7 +524,9 @@ jobs: if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} name: test (tsgo and tsc-ts give the same IR) runs-on: ubuntu-24.04 - timeout-minutes: 30 + # The two corpus compiles run back to back and take close to 30 minutes on a + # slow hosted runner, so a 30-minute limit cancels otherwise healthy runs. + timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -618,8 +620,9 @@ jobs: # Shard 1 additionally runs package/install and link-parity contracts # before its LLVM differential slice. The slice itself can take ~10 # minutes on a cold hosted runner, so 20 minutes cancels an otherwise - # healthy job before the differential completes. - timeout-minutes: 35 + # healthy job before the differential completes. Slow runners need more + # headroom than that, so the limit is 50 minutes. + timeout-minutes: 50 strategy: fail-fast: false matrix: diff --git a/.github/workflows/prune-caches.yml b/.github/workflows/prune-caches.yml new file mode 100644 index 000000000..008baaa43 --- /dev/null +++ b/.github/workflows/prune-caches.yml @@ -0,0 +1,36 @@ +name: Prune caches + +# Pull request caches are scoped to that pull request, so once it is closed no +# run can use them again. Deleting them keeps the repository under its cache +# size limit, so caches that main and open pull requests use are not evicted. +on: + pull_request: + types: [closed] + schedule: + - cron: "17 6 * * *" + workflow_dispatch: + +permissions: + actions: write + pull-requests: read + +jobs: + prune: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Delete caches of closed pull requests + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + run: | + open=" $(gh api --paginate "repos/$REPO/pulls?state=open&per_page=100" --jq '.[].number' | tr '\n' ' ') " + gh api --paginate "repos/$REPO/actions/caches?per_page=100" \ + --jq '.actions_caches[] | select(.ref | startswith("refs/pull/")) | "\(.id) \(.ref)"' | + while read -r id ref; do + number=$(echo "$ref" | cut -d/ -f3) + case "$open" in + *" $number "*) ;; + *) gh api --method DELETE "repos/$REPO/actions/caches/$id" >/dev/null && echo "deleted cache $id ($ref)" ;; + esac + done From 249773437aeb808798e53f96cc3f6052f9736a01 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 23 Oct 2026 07:21:37 -0700 Subject: [PATCH 018/395] Update the strict-user-js coverage snapshot for main's startup section Main now reports fenced sites in module top-level code under "runs at startup"; the use of 'legacy' on line 22 is one of them. Co-Authored-By: Claude Opus 5.5 --- .../__snapshots__/coverage-node-islands-user-js.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/harness/__snapshots__/coverage-node-islands-user-js.txt b/tests/harness/__snapshots__/coverage-node-islands-user-js.txt index 61cb8bb33..0898e19ba 100644 --- a/tests/harness/__snapshots__/coverage-node-islands-user-js.txt +++ b/tests/harness/__snapshots__/coverage-node-islands-user-js.txt @@ -5,14 +5,16 @@ scriptc coverage tests/coverage-fixtures/node-islands-user-js.cjs in dynamic tier 10 (operate on values typed only at runtime; --explain-dynamic lists them) (1 function not analyzed — signature blocked; their statements count as not static) - deferred to runtime 2 sites (JS statements that throw their fence if executed) - ×1 SC1090 parameterized 'arguments' outside an ES module (sloppy-mode parameter aliases) - at tests/coverage-fixtures/node-islands-user-js.cjs:15:1 - hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) + runs at startup 1 site (JS statements in module top-level code, which runs when the module loads) ×1 SC2004 uses of 'legacy' inherit the blocker on its declaration at tests/coverage-fixtures/node-islands-user-js.cjs:22:57 hint: the declaration of 'legacy' did not compile — fix the diagnostic reported there and these sites clear with it + deferred to runtime 1 site (JS statements that throw their fence if executed) + ×1 SC1090 parameterized 'arguments' outside an ES module (sloppy-mode parameter aliases) + at tests/coverage-fixtures/node-islands-user-js.cjs:15:1 + hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) + blockers: ×1 SC8101 A write of 'Point.prototype.norm' needs a mutable JavaScript object model, which native code does not have at tests/coverage-fixtures/node-islands-user-js.cjs:7:1 From c7c8f5d45ec6b1004a269f33df5688f1f8aec3c6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 09:25:07 -0500 Subject: [PATCH 019/395] Run lone array callbacks as loops with their bodies inlined --- docs/content/docs/contracts.mdx | 2 +- docs/content/limitations/contract-types.mdx | 2 +- .../backend/llvm/appended-array-literals.ts | 35 +- .../src/backend/llvm/emit-statements.ts | 3 + packages/compiler/src/backend/llvm/emitter.ts | 9 +- .../src/backend/llvm/expr-primitives.ts | 57 ++- .../lowering/containers/array-callbacks.ts | 5 +- .../lowering/containers/array-pipelines.ts | 341 ++++++++++++++---- .../src/ir/callback-specialization.ts | 7 +- .../test/array-pipeline-fusion.test.ts | 46 ++- .../test/format-float-arguments.test.ts | 23 +- tests/diagnostics/contract-int32-escapes.ts | 3 +- .../contract-int32-arrays.ts.txt | 6 +- .../contract-int32-escapes.ts.txt | 2 +- 14 files changed, 450 insertions(+), 91 deletions(-) diff --git a/docs/content/docs/contracts.mdx b/docs/content/docs/contracts.mdx index 948fe7960..ad5953fa6 100644 --- a/docs/content/docs/contracts.mdx +++ b/docs/content/docs/contracts.mdx @@ -245,7 +245,7 @@ $ scriptc build scale.ts 1 error. ``` -A copy such as `slice()` or a spread is checked as if it were the same array as its source. The results of callbacks are not followed, so an array built by `map` or `Array.from` with a mapping function cannot be `Int32[]`; build it with a loop that stores `value | 0`. scriptc refuses an `Int32[]` slot whose array can come from code it cannot see, such as `JSON.parse`, dynamic values or the callers of a function used as a value, and an array that can reach one and escapes to such code, such as a call through a function value (SC7003). +A copy such as `slice()` or a spread is checked as if it were the same array as its source. The results of a callback are followed only when scriptc copies it into the loop that runs the array method: a callback written at a `map` or `filter` call whose captured variables are not reassigned, such as `xs.map((x) => (x * 2) | 0)`. Other callback results are not followed, so for example an array built by `Array.from` with a mapping function cannot be `Int32[]`; build it with a loop that stores `value | 0`. scriptc refuses an `Int32[]` slot whose array can come from code it cannot see, such as `JSON.parse`, dynamic values or the callers of a function used as a value, and an array that can reach one and escapes to such code, such as a call through a function value (SC7003). ## Immutable diff --git a/docs/content/limitations/contract-types.mdx b/docs/content/limitations/contract-types.mdx index 35d7a95c0..1fd24a81b 100644 --- a/docs/content/limitations/contract-types.mdx +++ b/docs/content/limitations/contract-types.mdx @@ -1,7 +1,7 @@ [Contract types](/docs/contracts) are checked only where they are written as the annotation of a parameter, variable, class instance field or function result, `Acyclic` only on classes, interfaces and object type aliases, and `ProgramLifetime` only in the `implements` clause of a class declaration; other aliases, interface members and nested type positions are reported. In programs that use worker threads, a surrounding `try`/`catch` does not discharge `NoThrow`. `Int32` refuses parameters of functions used as values and fields of classes whose instances are stored in `any` or `unknown` values. -`Int32[]` refuses arrays that can come from or escape to code scriptc cannot see, does not follow callback results (such as `map` results), and checks a copy made by `slice` or a spread as if it were its source array. +`Int32[]` refuses arrays that can come from or escape to code scriptc cannot see, follows callback results only for callbacks written at a `map` or `filter` call that scriptc runs inline, and checks a copy made by `slice` or a spread as if it were its source array. Element reads that find a value are int32s, but a scalar `Int32` slot still needs `a[i] | 0`, because a read can miss and produce `undefined`. `Immutable` refuses values stored in `any` or `unknown` or that another reference can still change. `ProgramLifetime` applies to root classes of executables only; instances the program drops are never reclaimed unless they belong to a released [lifetime region](/docs/experimental#lifetimeregion). diff --git a/packages/compiler/src/backend/llvm/appended-array-literals.ts b/packages/compiler/src/backend/llvm/appended-array-literals.ts index 3c09904e3..c65aded0c 100644 --- a/packages/compiler/src/backend/llvm/appended-array-literals.ts +++ b/packages/compiler/src/backend/llvm/appended-array-literals.ts @@ -9,7 +9,7 @@ * one directly or through a binding read from it. The choice never changes * behavior. */ -import type { IrExpr, IrModule } from "../../ir/ir.js"; +import type { IrExpr, IrModule, IrStmt } from "../../ir/ir.js"; import { eachNodeOfKinds, type IrNodeIndex, type IrNodeKind } from "../../ir/node-index.js"; const APPENDING_METHODS: ReadonlySet = new Set([ @@ -120,3 +120,36 @@ export function findAppendedArrayLiterals( } return result; } + +/** `const xs = []` directly followed by a capacity hint for `xs` (an + * `arraySetLength` with `reserve`) whose length reads only literals and + * other locals: the literal can be constructed with that capacity, and the + * hint then has nothing left to do. */ +export function reservedArrayLength( + stmts: readonly IrStmt[], + i: number, +): { literal: IrExpr; hint: IrStmt & { kind: "arraySetLength" } } | null { + const decl = stmts[i]; + const next = stmts[i + 1]; + if (decl?.kind !== "varDecl" || decl.init === null || !isEmptyLiteral(decl.init)) return null; + if (next?.kind !== "arraySetLength" || next.reserve !== true) return null; + if (next.arr.kind !== "varRef" || next.arr.localId !== decl.localId) return null; + return plainLength(next.length, decl.localId) ? { literal: decl.init, hint: next } : null; +} + +function plainLength(e: IrExpr, array: string): boolean { + switch (e.kind) { + case "numLit": + return true; + case "varRef": + return e.localId !== array; + case "bin": + return plainLength(e.left, array) && plainLength(e.right, array); + case "ternary": + return ( + plainLength(e.cond, array) && plainLength(e.then, array) && plainLength(e.else_, array) + ); + default: + return false; + } +} diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index c2846a9d4..cbf8fb27d 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -458,6 +458,9 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { emitter.publishedTypes?.containerGuarded(s.arr.type) !== true ) break; + // The literal it follows was constructed with this capacity + // (reservedArrayLength). + if (emitter.consumedReserves.delete(s)) break; const arr = emitBorrowedInput(emitter, s.arr); const length = emitter.emitExpr(s.length); if (s.arr.type.kind !== "array") diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 050c3fb90..b3508b5d0 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -176,7 +176,7 @@ import { ReferenceEffects } from "./reference-effects.js"; import { ReferenceWrites, type Writes } from "./reference-writes.js"; import { LlvmDebugInfo } from "./debug-info.js"; import { StackCallbacks } from "./stack-callbacks.js"; -import { findAppendedArrayLiterals } from "./appended-array-literals.js"; +import { findAppendedArrayLiterals, reservedArrayLength } from "./appended-array-literals.js"; import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js"; import { findLentStringParameters, @@ -1235,6 +1235,11 @@ export class LlEmitter { /** Locals whose array literal emitStmts left for the `await Promise.all` * that reads them (deferredPromiseAllLocal). */ private readonly deferredPromiseAllEntries = new Map>(); + /** Empty array literals that a capacity hint follows directly + * (reservedArrayLength), with that hint. Constructing the literal with + * the hinted capacity consumes the hint, which then emits nothing. */ + readonly reservedArrayLengths = new Map(); + readonly consumedReserves = new Set(); /** The promise a `return` emitted past its finally regions already * produced for its adopting await (borrowed from the return's parked * slot); that await takes it instead of evaluating its operand. */ @@ -5514,6 +5519,8 @@ export class LlEmitter { if (this.B.isTerminated()) return; if (cursor) cursor.index = i; this.stmtCursor = cursor; + const reserve = reservedArrayLength(stmts, i); + if (reserve !== null) this.reservedArrayLengths.set(reserve.literal, reserve.hint); const deferred = this.deferredPromiseAllLocal(stmts, i); if (deferred !== null) { this.deferredPromiseAllEntries.set((stmts[i] as { localId: string }).localId, deferred); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 1eec6a965..9791de8e5 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -870,6 +870,34 @@ export function emitStringExpr( } } +/** scr_arr_reserve_len reserves dense storage only up to this many slots + * (SCR_ARR_DENSE_LIMIT, scr_array.c). */ +const RESERVE_LIMIT = 2 ** 20; + +/** Elements the inline slots of a small array construction + * (scr_arr_new_small, scr_arr_new_ops_small) hold at least + * (SCR_ARR_SMALL_SLOTS, scr_array.c). */ +const SMALL_ARRAY_SLOTS = 3; + +/** The capacity scr_arr_reserve_len would reserve for `length` on an empty + * array: the length truncated when it is at least 1 and at most + * RESERVE_LIMIT, else 0. */ +function reservedCapacity(host: LlvmEmitterContext, length: IrExpr): string { + const B = host.B; + const n = host.emitExpr(length); + const atLeastOne = B.tmp(), + withinLimit = B.tmp(), + reserves = B.tmp(), + slots = B.tmp(), + capacity = B.tmp(); + B.line(`${atLeastOne} = fcmp oge double ${n.name}, ${f64Lit(1)}`); + B.line(`${withinLimit} = fcmp ole double ${n.name}, ${f64Lit(RESERVE_LIMIT)}`); + B.line(`${reserves} = and i1 ${atLeastOne}, ${withinLimit}`); + B.line(`${slots} = select i1 ${reserves}, double ${n.name}, double ${f64Lit(0)}`); + B.line(`${capacity} = fptoui double ${slots} to ${host.sizeType}`); + return capacity; +} + export function emitContainerExpr( host: LlvmEmitterContext, e: ExprOf< @@ -941,7 +969,34 @@ export function emitContainerExpr( ? `${arr} = call ptr @scr_arr_new_small(i32 ${SCR_ELEM_I32})` : `${arr} = call ptr @scr_arr_new(i32 ${SCR_ELEM_I32}, ${host.sizeType} ${capacity})`, ); - } else B.line(`${arr} = ${arrNewCall(host.shapeHost, elem, String(capacity), small)}`); + } else { + const hint = host.reservedArrayLengths.get(e); + if (hint !== undefined) { + host.reservedArrayLengths.delete(e); + host.consumedReserves.add(hint); + const capacity = reservedCapacity(host, hint.length); + // The small construction's inline slots hold at least + // SMALL_ARRAY_SLOTS elements; larger capacities are sized. + const fits = B.tmp(), + smallArr = B.tmp(), + sizedArr = B.tmp(); + const smallLabel = B.newLabel("arr.cap.small"), + sizedLabel = B.newLabel("arr.cap.sized"), + joined = B.newLabel("arr.cap.j"); + B.line(`${fits} = icmp ule ${host.sizeType} ${capacity}, ${SMALL_ARRAY_SLOTS}`); + B.condBr(fits, smallLabel, sizedLabel); + B.startBlock(smallLabel); + B.line(`${smallArr} = ${arrNewCall(host.shapeHost, elem, "0", true)}`); + B.br(joined); + B.startBlock(sizedLabel); + B.line(`${sizedArr} = ${arrNewCall(host.shapeHost, elem, capacity)}`); + B.br(joined); + B.startBlock(joined); + B.line( + `${arr} = phi ptr [ ${smallArr}, %${smallLabel} ], [ ${sizedArr}, %${sizedLabel} ]`, + ); + } else B.line(`${arr} = ${arrNewCall(host.shapeHost, elem, String(capacity), small)}`); + } const out = host.own({ name: arr, type: e.type }); const acc: ArrayAccess = int32 ? "i32" : elemAccess(elem); let pending: LlValue[] = []; diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 86f214bc1..7349566d9 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -827,15 +827,16 @@ function buildArrayHofFn( ); returnType = outT; body = [ + readLen, { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc, }, - readLen, // map's result has the receiver's length, holes where the receiver - // has them. Storage for it is reserved first and the length is set + // has them. Storage for it is reserved first (with the result's + // construction, which directly precedes it) and the length is set // after the loop, so a result without holes is filled by appends and // gets no state bytes. ...(method === "map" diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 2f839a20b..cdcd0d93c 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -1,4 +1,6 @@ import { keyedHelperName } from "../stable-names.js"; +import { writtenLocals } from "../../../ir/callback-specialization.js"; +import { AmbientReceiverReaders } from "../../../ir/receiver-readers.js"; import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js"; import { BOOL, @@ -16,9 +18,12 @@ import { typeKey, } from "../../../ir/ir.js"; import { + everyExpr, everyExprChild, + everyStmt, everyStmtChild, everyStmtList, + transformExpr, transformStmtList, } from "../../../ir/traverse.js"; import { @@ -537,6 +542,65 @@ function matchChain(lowerer: Lowerer, e: IrExpr): Chain | null { }; } +/** The outermost array-HOF call of `e` on its own, over its receiver. */ +function loneChain(lowerer: Lowerer, e: IrExpr): Chain | null { + const top = stageOf(lowerer, e); + if (!top) return null; + const { stage, receiver } = top; + if (receiver.type.kind !== "array") return null; + const stages: Stage[] = []; + let terminal: Terminal; + if (TERMINALS.has(stage.info.method)) { + const init = + stage.info.method === "reduce" && stage.info.hasInit && e.kind === "call" ? e.args[2]! : null; + terminal = { kind: "call", stage, init }; + } else if (PRODUCERS.has(stage.info.method)) { + terminal = { kind: "collect" }; + stages.push(stage); + } else return null; + return { + source: receiver, + sourceType: receiver.type, + slice: null, + stages, + terminal, + type: e.type, + loc: e.loc, + }; +} + +/** The callbacks of `chain` in order, or null when its stages cannot run + * in one loop: element types that do not line up, an array argument past + * the first stage, reduce without a seed, or findIndex over holes. */ +function chainCallbacks(chain: Chain): Stage[] | null { + const { stages, terminal } = chain; + const terminalStage = terminal.kind === "call" ? terminal.stage : null; + const callbacks = terminalStage ? [...stages, terminalStage] : stages; + // Element types must line up stage to stage. + let elem = chain.sourceType.elem; + for (const stage of callbacks) { + if (!typeEquals(stage.info.elem, elem)) return null; + if (stage.info.method === "filter" && !typeEquals(stage.info.outElem, stage.info.elem)) + return null; + if ( + (stage.info.method === "map" || stage.info.method === "flatMap") && + !typeEquals(stage.info.outElem, stage.info.fnRet) + ) + return null; + elem = stage.info.outElem; + } + // Index arguments are reproduced; an array argument only names the source. + for (let k = 0; k < callbacks.length; k++) { + const stage = callbacks[k]!; + const lead = stage.info.method === "reduce" ? 1 : 0; + if (stage.info.arity > lead + 3) return null; + if (stage.info.arity === lead + 3 && (k !== 0 || chain.slice)) return null; + } + if (terminalStage?.info.method === "reduce" && !terminalStage.info.hasInit) return null; + if (terminalStage?.info.method === "findIndex" && holeCarrying(chain)) return null; + return callbacks; +} + /* ── the fusion pass ─────────────────────────────────────────────────── */ /** The values a fused loop hands from one stage to the next: a local of a @@ -550,6 +614,10 @@ interface Plan { writes: Set; /** Some callback may throw or loop; at most one does. */ risky: boolean; + /** How the loop inlines callbacks, when not by their effect summaries. */ + inline?: InlineTarget; + /** Stack frame name of the loop: the inlined callback's, for a lone call. */ + frame?: string; } /** Fuse chains of array-HOF helper calls into one loop. @@ -596,12 +664,22 @@ class PipelineFusion { readonly added: IrFunction[] = []; private readonly effects: CallbackEffects; private readonly helpers = new Map(); + /** Bindings each function, or a closure it creates, writes after their + * declaration. */ + private readonly written: Map>; + /** Bindings of the function being fused that inlined callbacks captured. */ + private inlinedCaptures = new Set(); + /** The helper calls each callback with an undefined receiver; a copied + * body runs under the receiver around the array call instead. */ + private readonly receivers: AmbientReceiverReaders; constructor( private readonly lowerer: Lowerer, functions: readonly IrFunction[], ) { this.effects = new CallbackEffects(functions, lowerer.globalsList); + this.written = writtenLocals({ functions }); + this.receivers = new AmbientReceiverReaders(new Map(functions.map((fn) => [fn.name, fn]))); } run(fn: IrFunction): void { @@ -612,6 +690,7 @@ class PipelineFusion { }); if (!candidate) return; const removed = new Set(); + this.inlinedCaptures = new Set(); let body = this.lists(fn.body, fn, removed); body = transformStmtList(body, { stmt: (s) => s, @@ -619,6 +698,7 @@ class PipelineFusion { }); fn.body = body; if (removed.size > 0) fn.locals = fn.locals.filter((l) => !removed.has(l.id)); + if (this.inlinedCaptures.size > 0) unboxUncaptured(fn, this.inlinedCaptures); } /* ── statement lists: fold single-use chain bindings ── */ @@ -733,40 +813,51 @@ class PipelineFusion { /* ── expressions ── */ private fuse(e: IrExpr, fn: IrFunction): IrExpr | null { - const plan = this.plan(e, fn); + const plan = this.plan(e, fn) ?? this.lonePlan(e, fn); return plan ? this.emit(plan) : null; } + /** One array-HOF call on its own (or the last call of a chain that does + * not fuse), when every callback is a closure literal whose body can be + * copied into the loop. A single stage reorders nothing, so the + * callbacks' effects need no model; their captured bindings must keep + * one value while the loop runs (stableTarget), since the loop reads + * them once, when it starts. The loop keeps the helper's order: the + * length read once, each element read when it is visited, holes + * skipped (and kept by map). */ + private lonePlan(e: IrExpr, fn: IrFunction): Plan | null { + const chain = loneChain(this.lowerer, e); + if (!chain) return null; + const callbacks = chainCallbacks(chain); + if (!callbacks) return null; + const inline = this.stableTarget(fn); + let frame: string | undefined; + for (const stage of callbacks) { + for (const arg of [stage.slow, stage.fast]) { + if (!arg) continue; + const target = inline(arg); + if (!target) return null; + frame ??= target.sourceName; + } + } + return { + chain, + callbacks, + writes: new Set(), + risky: true, + inline, + ...(frame === undefined ? {} : { frame }), + }; + } + /** Decide whether `e` is a fusable chain; null when it is not. */ private plan(e: IrExpr, fn: IrFunction): Plan | null { const chain = matchChain(this.lowerer, e); if (!chain) return null; + const callbacks = chainCallbacks(chain); + if (!callbacks) return null; const { stages, terminal } = chain; const terminalStage = terminal.kind === "call" ? terminal.stage : null; - const callbacks = terminalStage ? [...stages, terminalStage] : stages; - // Element types must line up stage to stage. - let elem = chain.sourceType.elem; - for (const stage of callbacks) { - if (!typeEquals(stage.info.elem, elem)) return null; - if (stage.info.method === "filter" && !typeEquals(stage.info.outElem, stage.info.elem)) - return null; - if ( - (stage.info.method === "map" || stage.info.method === "flatMap") && - !typeEquals(stage.info.outElem, stage.info.fnRet) - ) - return null; - elem = stage.info.outElem; - } - // Index arguments are reproduced; an array argument only names the source. - for (let k = 0; k < callbacks.length; k++) { - const stage = callbacks[k]!; - const lead = stage.info.method === "reduce" ? 1 : 0; - if (stage.info.arity > lead + 3) return null; - if (stage.info.arity === lead + 3 && (k !== 0 || chain.slice)) return null; - } - if (terminalStage?.info.method === "reduce" && !terminalStage.info.hasInit) return null; - const holes = holeCarrying(chain); - if (terminalStage?.info.method === "findIndex" && holes) return null; // Effects of each callback, in chain order. const scope = this.effects.scope(fn, true); const summaries: Effects[] = []; @@ -831,30 +922,62 @@ class PipelineFusion { * are passed in by value, read when the fused call starts; no callback * in the chain assigns a binding another one reads). */ private readonly inlineTarget: InlineTarget = (arg) => { + const fn = this.closureTarget(arg); + if (!fn) return null; + const captures = new Set((fn.captures ?? []).map((c) => c.localId)); + const effects = this.effects.ofFunction(fn); + if (!effects || intersects(effects.writes, captures)) return null; + return inlinableBody(fn, captures) ? fn : null; + }; + + /** inlineTarget for a callback created in `fn` whose captured bindings + * nothing assigns after their declaration: `fn`'s own locals and + * parameters, written neither by `fn` nor by any closure that shares + * them, and not by the callback body. The callback must not observe the + * ambient receiver (AmbientReceiverReaders). */ + private stableTarget(fn: IrFunction): InlineTarget { + const locals = new Map(fn.locals.map((l) => [l.id, l])); + const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); + const written = this.written.get(fn.name); + return (arg) => { + const target = this.closureTarget(arg); + if (!target || !written || arg.kind !== "closure") return null; + if (!this.receivers.ignores(target)) return null; + for (const id of arg.captures) { + const local = locals.get(id); + if (!local || local.tdz || inherited.has(id) || written.has(id)) return null; + } + const captures = new Set((target.captures ?? []).map((c) => c.localId)); + const assigns = !everyStmtList(target.body, { + stmt: (st) => !((st.kind === "assign" || st.kind === "forOf") && captures.has(st.localId)), + expr: (ex) => + !((ex.kind === "assignExpr" || ex.kind === "incDec") && captures.has(ex.localId)), + }); + return !assigns && inlinableBody(target, captures) ? target : null; + }; + } + + /** The function behind a closure-literal callback the loop can inline. */ + private closureTarget(arg: IrExpr): IrFunction | null { if (arg.kind !== "closure" || arg.adapts) return null; const fn = this.effects.fn(arg.fnName); if (!fn || fn.async || fn.generator || fn.classCaptures?.length) return null; - const captures = new Set((fn.captures ?? []).map((c) => c.localId)); - if (captures.size !== arg.captures.length) return null; - const effects = this.effects.ofFunction(fn); - if (!effects || intersects(effects.writes, captures)) return null; - if (fn.locals.some((l) => l.tdz || (l.boxed && !captures.has(l.id)))) return null; - let size = 0; - const small = everyStmtList(fn.body, { - expr: (e) => ++size <= MAX_INLINE_NODES && e.kind !== "closure" && e.kind !== "selfRef", - stmt: (s) => ++size <= MAX_INLINE_NODES && s.kind !== "tryCatch", - }); - return small ? fn : null; - }; + if ((fn.captures ?? []).length !== arg.captures.length) return null; + return fn; + } private emit(plan: Plan): IrExpr | null { - const built = new FusedLoop(this.lowerer, plan.chain, this.inlineTarget).build(); + const built = new FusedLoop(this.lowerer, plan.chain, plan.inline ?? this.inlineTarget).build(); if (!built) return null; + for (const id of built.inlinedCaptures) this.inlinedCaptures.add(id); let name = this.helpers.get(built.key); if (!name) { - name = keyedHelperName(this.lowerer, "%arr.fused", built.key); + name = keyedHelperName(this.lowerer, plan.inline ? "%arr.loop" : "%arr.fused", built.key); this.helpers.set(built.key, name); - this.added.push({ ...built.fn, name }); + const helper: IrFunction = { ...built.fn, name }; + // A lone call's loop stands in the inlined callback's stack frame. + if (plan.frame !== undefined) helper.sourceName = plan.frame; + this.added.push(helper); } return { kind: "call", @@ -866,6 +989,48 @@ class PipelineFusion { } } +/** Whether a statement inside `e` (a sequence expression's) returns. */ +function returns(e: IrExpr): boolean { + return !everyExpr(e, { expr: () => true, stmt: (s) => s.kind !== "return" }); +} + +/** Drops the box of each binding in `inlined` that no closure or class + * created in `fn` captures any more: the callbacks that captured it were + * copied into loops, which receive its value. A dead-zone binding keeps the + * box that represents its uninitialized state, and bindings `fn` itself + * captures keep theirs. */ +function unboxUncaptured(fn: IrFunction, inlined: ReadonlySet): void { + const captured = new Set(); + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "closure" || e.kind === "classRef") + for (const id of e.captures ?? []) captured.add(id); + return true; + }, + stmt: () => true, + }); + const inherited = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); + fn.locals = fn.locals.map((local): IrLocal => { + if (local.boxed !== true || local.tdz === true || !inlined.has(local.id)) return local; + if (captured.has(local.id) || inherited.has(local.id)) return local; + const { boxed: _boxed, ...plain } = local; + return plain; + }); +} + +/** A callback body the loop can copy: small, no nested closures, recursion + * or try statements, and no boxed locals besides its captures. */ +function inlinableBody(fn: IrFunction, captures: ReadonlySet): boolean { + if (fn.locals.some((l) => l.tdz || (l.boxed && !captures.has(l.id)))) return false; + let size = 0; + return everyStmtList(fn.body, { + expr: (e) => ++size <= MAX_INLINE_NODES && e.kind !== "closure" && e.kind !== "selfRef", + stmt: (s) => ++size <= MAX_INLINE_NODES && s.kind !== "tryCatch", + }); +} + function intersects(a: ReadonlySet, b: ReadonlySet): boolean { for (const id of a) if (b.has(id)) return true; return false; @@ -1080,8 +1245,23 @@ class FusedLoop { const id = this.local(l.name, l.type, true); renamed.set(l.id, id); } + const rename = (id: string): string => renamed.get(id) ?? id; + const renameExpr = (e: IrExpr): IrExpr => { + switch (e.kind) { + case "varRef": + case "incDec": + case "assignExpr": + return { ...e, localId: rename(e.localId) }; + default: + return e; + } + }; + const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => + aliased.has(p.localId) + ? [] + : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], + ); const labels = new Map(); - const exit = this.fresh("%fused.exit"); const label = (name: string): string => { let id = labels.get(name); if (!id) { @@ -1090,19 +1270,48 @@ class FusedLoop { } return id; }; + /** Locals and labels of a copied statement; returns are the caller's. */ + const renameStmt = (st: IrStmt): IrStmt => { + switch (st.kind) { + case "varDecl": + case "assign": + return { ...st, localId: rename(st.localId) }; + case "break": + case "continue": + return st.label ? { ...st, label: label(st.label) } : st; + case "while": + case "doWhile": + case "for": + case "block": + return st.labels ? { ...st, labels: st.labels.map(label) } : st; + default: + return st; + } + }; + // A body whose only return is its last statement (`(x) => x * k`, + // `{ const y = x * 2; return y > 4; }`) needs no exit block: its other + // statements run after the bindings, and the call's value is the + // returned expression. + const last = fn.body[fn.body.length - 1]; + const prefix = fn.body.slice(0, -1); + if ( + last !== undefined && + last.kind === "return" && + last.value !== null && + typeEquals(last.value.type, fn.returnType) && + !returns(last.value) && + prefix.every((st) => everyStmt(st, { expr: () => true, stmt: (x) => x.kind !== "return" })) + ) { + const copy = { expr: renameExpr, stmt: renameStmt }; + return { + pre: [...bind, ...transformStmtList(prefix, copy)], + value: transformExpr(last.value, copy), + }; + } + const exit = this.fresh("%fused.exit"); const result = fn.returnType.kind === "void" ? null : this.local("ret", fn.returnType, true); - const rename = (id: string): string => renamed.get(id) ?? id; const body = transformStmtList(fn.body, { - expr: (e) => { - switch (e.kind) { - case "varRef": - case "incDec": - case "assignExpr": - return { ...e, localId: rename(e.localId) }; - default: - return e; - } - }, + expr: renameExpr, stmt: (s): IrStmt => { switch (s.kind) { case "return": { @@ -1122,27 +1331,14 @@ class FusedLoop { loc: s.loc, }; } - case "varDecl": - case "assign": - return { ...s, localId: rename(s.localId) }; case "break": case "continue": return s.label && s.label !== exit ? { ...s, label: label(s.label) } : s; - case "while": - case "doWhile": - case "for": - case "block": - return s.labels ? { ...s, labels: s.labels.map(label) } : s; default: - return s; + return renameStmt(s); } }, }); - const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => - aliased.has(p.localId) - ? [] - : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], - ); const pre: IrStmt[] = []; if (result !== null) pre.push({ kind: "varDecl", localId: result, init: null, loc }); pre.push({ kind: "block", labels: [exit], body: [...bind, ...body], loc }); @@ -1155,7 +1351,13 @@ class FusedLoop { }; } - build(): { fn: Omit; args: IrExpr[]; key: string } | null { + build(): { + fn: Omit; + args: IrExpr[]; + key: string; + /** Bindings the callbacks copied into the loop captured at this site. */ + inlinedCaptures: string[]; + } | null { const { chain, loc } = this; const arrT = chain.sourceType; const a = this.param("a", chain.source, "src"); @@ -1640,7 +1842,10 @@ class FusedLoop { }; foldKnownUnions(fn); scalarizeRecordTemporaries(this.lowerer, fn); - return { fn, args: this.args, key: this.keyParts.join("|") }; + const inlinedCaptures = this.callees.flatMap((c) => + c.inline && c.arg.kind === "closure" ? c.arg.captures : [], + ); + return { fn, args: this.args, key: this.keyParts.join("|"), inlinedCaptures }; } } diff --git a/packages/compiler/src/ir/callback-specialization.ts b/packages/compiler/src/ir/callback-specialization.ts index 6038ccaa5..d67b557c4 100644 --- a/packages/compiler/src/ir/callback-specialization.ts +++ b/packages/compiler/src/ir/callback-specialization.ts @@ -16,7 +16,10 @@ const COPIED_NODE_FRACTION = 0.25; * closure it creates writes through its environment (and transitively the * closures those create). A parameter or capture absent from its function's * set holds the value it entered with for the whole call. */ -export function writtenLocals(mod: IrModule, nodes?: IrNodeIndex): Map> { +export function writtenLocals( + mod: { readonly functions: readonly IrFunction[] }, + nodes?: IrNodeIndex, +): Map> { return rebindings(mod, nodes).written; } @@ -31,7 +34,7 @@ const REBINDING_KINDS: readonly IrNodeKind[] = [ ]; function rebindings( - mod: IrModule, + mod: { readonly functions: readonly IrFunction[] }, nodes?: IrNodeIndex, ): { written: Map>; diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index 55cd0025e..0ff456a2c 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -108,5 +108,49 @@ const kept = rows.filter((r) => r.value > 3); console.log(kept.map((r) => r.id).length, kept.length); ${postlude}`); expect(init(mod).locals.some((l) => l.name === "kept")).toBe(true); - expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.fused"]); + expect(helperCalls(init(mod))).toEqual(["%arr.fused", "%arr.loop"]); +}); + +test("a lone call with a stable closure-literal callback runs as a loop", async () => { + const mod = await lowerSource(`${prelude} +const scale = 3; +const scaled = rows.map((r) => r.value * scale); +let total = 0; +rows.forEach((r) => { total += r.value; }); +const big = rows.filter((r) => r.value > scale); +const sum = scaled.reduce((acc, v) => acc + v, 0); +console.log(scaled.length, total, big.length, sum, rows.some((r) => r.id === scale)); +${postlude}`); + // forEach assigns \`total\`, a binding of \`run\`: it keeps its helper. + expect(helperCalls(init(mod))).toEqual([ + "%arr.forEach", + "%arr.loop", + "%arr.loop", + "%arr.loop", + "%arr.loop", + ]); + const loops = mod.functions.filter((f) => f.name.startsWith("%arr.loop.")); + expect(loops).toHaveLength(4); + // The callback bodies are copied into the loops: no closure value is + // created or called, and the captured \`scale\` is a plain parameter. + for (const fn of loops) { + expect(count(fn, "callValue")).toBe(0); + expect(count(fn, "closure")).toBe(0); + } + expect(count(init(mod), "closure")).toBe(1); + // No closure captures \`scale\` any more, so it needs no box. + expect(init(mod).locals.find((l) => l.name === "scale")?.boxed).toBeUndefined(); +}); + +test("a lone call keeps its helper when a capture can change while it runs", async () => { + const mod = await lowerSource(`${prelude} +let limit = 2; +const bump = (): void => { limit++; }; +const a = rows.filter((r) => { bump(); return r.id < limit; }); +let step = 1; +step = 2; +const b = rows.map((r) => r.id * step); +console.log(a.length, b.length, limit); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.map"]); }); diff --git a/packages/compiler/test/format-float-arguments.test.ts b/packages/compiler/test/format-float-arguments.test.ts index 6ce9c4bc0..795e238ef 100644 --- a/packages/compiler/test/format-float-arguments.test.ts +++ b/packages/compiler/test/format-float-arguments.test.ts @@ -3,6 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { compile, deserializeModule, validateModule } from "../src/index.js"; +import { everyStmtList } from "../src/ir/traverse.js"; test("util.format %f lowers a boolean argument once", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-format-float-")); @@ -22,14 +23,20 @@ test("util.format %f lowers a boolean argument once", async () => { throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); const mod = deserializeModule(await readFile(outPath, "utf8")); expect(validateModule(mod)).toEqual([]); - // One lifted helper per argument: the evaluated-for-effect statement - // reuses the lowered value instead of lowering the argument again. - const helpers = mod.functions - .map((f) => f.name) - .filter((name) => name.startsWith("%arr.")) - .map((name) => name.replace(/\.[0-9a-f]+$/, "")) - .sort(); - expect(helpers).toEqual(["%arr.every", "%arr.some"]); + // One lifted helper call per argument: the evaluated-for-effect + // statement reuses the lowered value instead of lowering the argument + // again. Each call runs its callback as a loop (array-pipelines.ts). + const helpers: string[] = []; + for (const fn of mod.functions) + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "call" && e.callee.startsWith("%arr.")) + helpers.push(e.callee.replace(/\.[0-9a-f]+$/, "")); + return true; + }, + stmt: () => true, + }); + expect(helpers).toEqual(["%arr.loop", "%arr.loop"]); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/tests/diagnostics/contract-int32-escapes.ts b/tests/diagnostics/contract-int32-escapes.ts index fcbbcbfd9..57ed4ce39 100644 --- a/tests/diagnostics/contract-int32-escapes.ts +++ b/tests/diagnostics/contract-int32-escapes.ts @@ -14,4 +14,5 @@ class Box { const box = new Box(); const loose: any = box; loose.size = 0.5; -console.log([1.5, 2].map(double).join(","), box.size); +const pick: (x: number) => number = double; +console.log([1.5, 2].map(pick).join(","), box.size); diff --git a/tests/harness/__snapshots__/contract-int32-arrays.ts.txt b/tests/harness/__snapshots__/contract-int32-arrays.ts.txt index c326adbb5..56db22706 100644 --- a/tests/harness/__snapshots__/contract-int32-arrays.ts.txt +++ b/tests/harness/__snapshots__/contract-int32-arrays.ts.txt @@ -34,14 +34,14 @@ contract-int32-arrays.ts:16:13 - error SC7002: the element stored into 'counts' hint: make the int32 conversion explicit, for example 'Math.PI | 0', so Node stores the same value -contract-int32-arrays.ts:18:26 - error SC7002: the elements 'counts.map((x) => x * 2)' stores are not provably int32s (the array can reach 'doubled', declared Int32[]) +contract-int32-arrays.ts:18:44 - error SC7002: the element stored into 'counts.map((x) => x * 2)' is not provably an int32: 'x * 2' (the array can reach 'doubled', declared Int32[]) 17 | halveInto(counts, ids); 18 | const doubled: Int32[] = counts.map((x) => x * 2); - | ^~~~~~~~~~~~~~~~~~~~~~~~ + | ^~~~~ 19 | const parsed: Int32[] = JSON.parse("[1, 2]"); - hint: callback results are not followed; build the array with a loop that stores 'value | 0' + hint: make the int32 conversion explicit, for example '(x * 2) | 0', so Node stores the same value contract-int32-arrays.ts:19:15 - error SC7003: the Int32[] contract on 'parsed' cannot be checked: the array can come from dynamic (any or unknown) code (contract-int32-arrays.ts:19:25), whose element stores scriptc does not see diff --git a/tests/harness/__snapshots__/contract-int32-escapes.ts.txt b/tests/harness/__snapshots__/contract-int32-escapes.ts.txt index 920f4c4a4..1be04a0cf 100644 --- a/tests/harness/__snapshots__/contract-int32-escapes.ts.txt +++ b/tests/harness/__snapshots__/contract-int32-escapes.ts.txt @@ -21,6 +21,6 @@ contract-int32-escapes.ts:16:1 - error SC7002: the value stored into 'Box.size' 15 | const loose: any = box; 16 | loose.size = 0.5; | ^~~~~~~~~~~~~~~~ - 17 | console.log([1.5, 2].map(double).join(","), box.size); + 17 | const pick: (x: number) => number = double; hint: make the int32 conversion explicit, for example 'loose.size = 0.5 | 0', so Node computes the same value \ No newline at end of file From 116d163e2c96c30bc9c6a9e369ed4ac4476d7d64 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 09:30:37 -0500 Subject: [PATCH 020/395] Take the elements before converting foreign elements Math.max, Math.min and Math.hypot over a spread, and the default sort, convert foreign elements with user code. As in Node, the elements are taken first, each foreign value retained, so a conversion that changes the array changes neither the count nor the values the operation sees, and no slot read before the conversion is released under it. join and the numeric element read hold the value they convert, and join reads the length once and stops at a throw. toSorted of an array with foreign elements now builds a copy of the full length and reads its holes as undefined, as for other arrays. --- packages/runtime/src/scr_array.c | 156 ++++++++++++------ .../6068-foreign-elements-through-views.ts | 35 ++++ 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index c7399bcea..1405589a3 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -2066,33 +2066,47 @@ ScrArr *scr_arr_fill_undefined(ScrArr *a, double start, double end) { * the empty array yields the zero-argument constants. Borrows the array. */ /* ToNumber of every element in order, as Math.max/min/hypot apply it to * their spread arguments before comparing; for arrays that may hold - * foreign elements, whose conversion can call user code. NULL with the - * exception pending when a conversion throws. +1 malloc'd buffer. */ -static double *scr_arr_coerced_numbers(const ScrArr *a) { - double *out = malloc((a->len ? a->len : 1) * sizeof(*out)); - if (!out) scr_arr_oom(); - for (size_t i = 0; i < a->len; i++) { + * foreign elements, whose conversion can call user code. As in the spread, + * the elements are taken first: the foreign values are retained before + * any conversion runs, so a conversion that changes the array changes + * neither the count (*count) nor the values. NULL with the exception + * pending when a conversion throws. +1 malloc'd buffer. */ +static double *scr_arr_coerced_numbers(const ScrArr *a, size_t *count) { + size_t n = a->len; + double *out = malloc((n ? n : 1) * sizeof(*out)); + ScrDyn **held = calloc(n ? n : 1, sizeof(*held)); + if (!out || !held) scr_arr_oom(); + for (size_t i = 0; i < n; i++) { uint64_t slot; uint8_t state = scr_arr_f64_state_at(a, i, &slot); - if (state == SCR_ARR_FOREIGN) { - out[i] = scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); - if (scr_exc_pending()) { - free(out); - return NULL; - } - } else { - out[i] = state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; - } + if (state == SCR_ARR_FOREIGN) held[i] = scr_dyn_retain(scr_slot_to_ptr(slot)); + out[i] = state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } + bool failed = false; + for (size_t i = 0; i < n; i++) { + if (!held[i]) continue; + if (!failed) { + out[i] = scr_arr_foreign_conversions()->to_number(held[i]); + failed = scr_exc_pending(); + } + scr_dyn_release(held[i]); + } + free(held); + if (failed) { + free(out); + return NULL; + } + *count = n; return out; } static double scr_math_extreme_foreign(ScrArr *a, bool max) { - double *values = scr_arr_coerced_numbers(a); + size_t n; + double *values = scr_arr_coerced_numbers(a, &n); if (!values) return NAN; double best = max ? -INFINITY : INFINITY; bool nan = false; - for (size_t i = 0; i < a->len; i++) { + for (size_t i = 0; i < n; i++) { double v = values[i]; if (isnan(v)) nan = true; else if (max ? (v > best || (v == 0.0 && best == 0.0 && !signbit(v))) @@ -2135,8 +2149,9 @@ double scr_math_hypot_arr(ScrArr *a) { double scale = 0.0; bool has_nan = false; double *coerced = NULL; - if (scr_arr_may_hold_foreign(a) && !(coerced = scr_arr_coerced_numbers(a))) return NAN; - for (size_t i = 0; i < a->len; i++) { + size_t n = a->len; + if (scr_arr_may_hold_foreign(a) && !(coerced = scr_arr_coerced_numbers(a, &n))) return NAN; + for (size_t i = 0; i < n; i++) { uint64_t slot; double v; if (coerced) { @@ -2160,7 +2175,7 @@ double scr_math_hypot_arr(ScrArr *a) { } double sum = 0.0; double correction = 0.0; - for (size_t i = 0; i < a->len; i++) { + for (size_t i = 0; i < n; i++) { uint64_t slot = 0; if (!coerced) (void)scr_arr_f64_state_at(a, i, &slot); double v = (coerced ? coerced[i] : scr_slot_to_f64(slot)) / scale; @@ -2271,7 +2286,13 @@ double scr_arr_get_number(const ScrArr *a, double i) { } else if (!scr_arr_prop_get_state(a, i, &slot, &state)) { return NAN; } - if (state == SCR_ARR_FOREIGN) return scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); + if (state == SCR_ARR_FOREIGN) { + /* The conversion can call user code that removes the element. */ + ScrDyn *value = scr_dyn_retain(scr_slot_to_ptr(slot)); + double number = scr_arr_foreign_conversions()->to_number(value); + scr_dyn_release(value); + return number; + } return state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } @@ -3404,20 +3425,21 @@ static void scr_arr_primitive_order(uint64_t *values, size_t count, } /* The default ordering of an array that may hold foreign elements: present - * elements sort by their string conversions (which may call user code, so - * they are computed first, and a throw leaves the array unchanged), then - * present undefined values, then holes. */ + * elements sort by their string conversions, then present undefined + * values, then holes (toSorted reads them as undefined). The conversions + * may call user code, so the elements are taken first, each retained, as + * the sort's element list is; a throw leaves the array unchanged. */ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { - size_t cap = (a->len < a->cap ? a->len : a->cap) + SCR_AX(a)->sparse_len; + size_t length = a->len; + size_t dense = a->len < a->cap ? a->len : a->cap; + size_t cap = dense + SCR_AX(a)->sparse_len; uint64_t *slots = cap ? malloc(cap * sizeof(*slots)) : NULL; uint8_t *states = cap ? malloc(cap) : NULL; ScrStr **keys = cap ? malloc(cap * sizeof(*keys)) : NULL; uint64_t *order_of = cap ? malloc(cap * sizeof(*order_of)) : NULL; if (cap && (!slots || !states || !keys || !order_of)) scr_arr_oom(); size_t count = 0, undefined_count = 0; - bool failed = false; - size_t dense = a->len < a->cap ? a->len : a->cap; - for (size_t at = 0; at < dense + SCR_AX(a)->sparse_len && !failed; at++) { + for (size_t at = 0; at < cap; at++) { uint64_t slot; uint8_t state; if (at < dense) { @@ -3429,10 +3451,17 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { } if (state == SCR_ARR_UNDEFINED) undefined_count++; if (state != SCR_ARR_VALUE && state != SCR_ARR_FOREIGN) continue; + slots[count] = scr_arr_retain_state_slot(a, slot, state); + states[count] = state; + count++; + } + bool failed = false; + size_t keyed = 0; + for (; keyed < count; keyed++) { + uint64_t slot = slots[keyed]; ScrStr *key; - if (state == SCR_ARR_FOREIGN) { + if (states[keyed] == SCR_ARR_FOREIGN) { key = scr_arr_foreign_conversions()->to_string(scr_slot_to_ptr(slot)); - if (!key) failed = true; } else if (a->elem == SCR_ELEM_STR) { key = scr_str_retain(scr_slot_to_ptr(slot)); } else { @@ -3440,37 +3469,48 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { size_t n = scr_f64_to_str(scr_slot_to_f64(slot), text); key = scr_str_new(text, n); } - if (failed) break; - slots[count] = slot; - states[count] = state; - keys[count] = key; - order_of[count] = count; - count++; + if (!key) { + failed = true; + break; + } + keys[keyed] = key; + order_of[keyed] = keyed; } - ScrArr *out = NULL; + ScrArr *out = a; if (!failed) { ScrPrimitiveOrder order = {a->elem, NULL, keys}; scr_arr_primitive_order(order_of, count, &order); - size_t length = a->len; if (copy) { - out = scr_arr_has_ops(a) ? scr_arr_new_ops(scr_arr_ops(a), count + undefined_count) - : scr_arr_new(a->elem, count + undefined_count); + /* The values, then present undefined for every other index. */ + out = scr_arr_has_ops(a) ? scr_arr_new_ops(scr_arr_ops(a), length) + : scr_arr_new(a->elem, length); + for (size_t i = 0; i < length; i++) { + if (i < count) scr_arr_store_state_owned(out, i, slots[order_of[i]], states[order_of[i]]); + else scr_arr_store_state_owned(out, i, 0, SCR_ARR_UNDEFINED); + out->len = (uint32_t)(i + 1); + } } else { - out = a; + /* The values, then the present undefined values; holes after them. + * The taken elements own their references; release the ones the + * array holds now, which a conversion may have changed. */ + size_t now = a->len < a->cap ? a->len : a->cap; + for (size_t i = 0; i < now; i++) + scr_arr_release_state_slot(a, scr_arr_dense_get(a, i), scr_arr_dense_state(a, i)); + for (size_t i = 0; i < SCR_AX(a)->sparse_len; i++) + scr_arr_release_state_slot(a, SCR_AX(a)->sparse[i].slot, SCR_AX(a)->sparse[i].state); ScrArrStorage old = scr_arr_take_storage(a); scr_arr_free_storage(&old); + a->len = (uint32_t)length; + for (size_t i = 0; i < count; i++) + scr_arr_store_state_owned(a, i, slots[order_of[i]], states[order_of[i]]); + for (size_t i = count; i < count + undefined_count; i++) + scr_arr_store_state_owned(a, i, 0, SCR_ARR_UNDEFINED); } - out->len = length; - for (size_t i = 0; i < count; i++) { - size_t k = order_of[i]; - uint64_t slot = copy ? scr_arr_retain_state_slot(a, slots[k], states[k]) : slots[k]; - scr_arr_store_state_owned(out, i, slot, states[k]); - } - for (size_t i = count; i < count + undefined_count; i++) - scr_arr_store_state_owned(out, i, 0, SCR_ARR_UNDEFINED); scr_arr_refill(out); + } else { + for (size_t i = 0; i < count; i++) scr_arr_release_state_slot(a, slots[i], states[i]); } - for (size_t i = 0; i < count; i++) scr_str_release(keys[i]); + for (size_t i = 0; i < keyed; i++) scr_str_release(keys[i]); free(slots); free(states); free(keys); @@ -5384,16 +5424,24 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) { * across its seam; the finished string then needs fusing. */ bool lone = scr_wtf8_any(); bool lead_low = lone && a->len > 1 && scr_wtf8_low_at(sep->data); - for (size_t i = 0; i < a->len; i++) { + /* The length is read once: a conversion that shortens the array leaves + * the indices past its new end empty. */ + size_t length = a->len; + for (size_t i = 0; i < length; i++) { if (i > 0) scr_join_append(&out, sep->data, sep->len); + if (i >= a->len) continue; uint64_t slot; uint8_t state = scr_arr_state_at(a, i, &slot); if (state == SCR_ARR_FOREIGN) { - /* join converts every element but undefined and null with ToString. */ - const ScrDyn *value = scr_slot_to_ptr(slot); + /* join converts every element but undefined and null with ToString, + * which can call user code that removes the element; a throw ends + * the join with the exception pending. */ + ScrDyn *value = scr_slot_to_ptr(slot); if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) continue; + scr_dyn_retain(value); ScrStr *text = scr_arr_foreign_conversions()->to_string(value); - if (!text) continue; + scr_dyn_release(value); + if (!text) break; scr_join_append(&out, text->data, text->len); scr_str_release(text); continue; diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index e6676eea4..76997c624 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -54,3 +54,38 @@ const words: string[] = ["a"]; const wordsView: unknown[] = words; wordsView.push(1, false); console.log(words, words.join("-"), words.length, typeof wordsView[1]); + +// A conversion that changes the array does not change the spread's values, +// and toSorted reads holes as undefined. +const grown: number[] = [3, 1]; +const grownView: unknown[] = grown; +grownView.push({ + valueOf: () => { + for (let i = 0; i < 64; i++) grown.push(i); + grown.length = 1; + return 2; + }, +}); +console.log(Math.max(...grown), Math.min(...grown), grown.length); +const holey: string[] = ["c", "a"]; +const holeyView: unknown[] = holey; +holeyView[4] = 7; +holeyView[6] = undefined; +console.log(holey.toSorted(), holey.length); +holey.sort(); +console.log(holey, holey.length); + +// A conversion that removes the element it converts. +const shrinking: number[] = [1]; +const shrinkingView: unknown[] = shrinking; +shrinkingView.push({ + toString: () => { + shrinking.length = 0; + return "gone"; + }, +}); +console.log(shrinking.join("+"), shrinking.length); +const cut: string[] = ["a", "b"]; +const cutView: unknown[] = cut; +cutView.splice(1, 0, { toString: () => ((cut.length = 0), "x") }); +console.log(JSON.stringify(cut.join("-")), cut.length); From 52792d8c52919b16f703246c305247d7a5ed835f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 09:54:58 -0500 Subject: [PATCH 021/395] Find weak collection entries through hashed indexes WeakMap and WeakSet kept each collection's entries in one list and found a key by walking it, and every collection's entries for a key in 1,024 shared buckets. Filling a collection and reading it back took quadratic time: 40,000 keys took 6.7 s against Node's 0.2 s, and 200,000 keys did not finish. Each thread now indexes its entries twice, in open-addressing tables with linear probing and backward-shift deletion: by collection, key and kind, for get, set, has and delete, and by key pointer, for a key's disposal, which ends the key's entries in every collection. A collection's entries and a key's entries are doubly linked, so an entry leaves both in constant time. The tables grow at half load and are freed when their last entry goes, so an exiting worker leaves none. 200,000 keys now take 0.24 s (Node 0.30 s). Closes #1702 Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com> --- .../7742-weak-collections-many-keys.ts.json | 6 + packages/runtime/src/scr_json.c | 203 +++++++++++++++--- .../corpus/7742-weak-collections-many-keys.ts | 70 ++++++ .../7742-weak-collections-many-keys.ts.json | 1 + 4 files changed, 247 insertions(+), 33 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7742-weak-collections-many-keys.ts.json create mode 100644 tests/corpus/7742-weak-collections-many-keys.ts create mode 100644 tests/harness/corpus-costs/7742-weak-collections-many-keys.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7742-weak-collections-many-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7742-weak-collections-many-keys.ts.json new file mode 100644 index 000000000..e1a75c14e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7742-weak-collections-many-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7742-weak-collections-many-keys.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index da18a1c0b..4841da53d 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -15019,26 +15019,173 @@ typedef struct ScrWeakEntry { unsigned kind; ScrDyn *value; ScrWeakMap *owner; - struct ScrWeakEntry *next, *all_next; + /* The owner's entries, and every collection's entries for the same key + * pointer (whatever their kind): both doubly linked, so an entry leaves + * either list in constant time. */ + struct ScrWeakEntry *next, *prev, *key_next, *key_prev; } ScrWeakEntry; struct ScrWeakMap { size_t rc; ScrWeakEntry *entries; bool is_set; }; /* Weak observers borrow key identities. Values are traced strong edges; - * value-to-key cycles require explicit deletion or container disposal. */ -#define SCR_WEAK_BUCKETS 1024 -static SCR_TL ScrWeakEntry *scr_weak_entries[SCR_WEAK_BUCKETS]; -static size_t scr_weak_bucket(void *key) { - uintptr_t bits = (uintptr_t)key; - return ((bits >> 4) ^ (bits >> 14)) & (SCR_WEAK_BUCKETS - 1); + * value-to-key cycles require explicit deletion or container disposal. + * + * Two open-addressing indexes per thread find entries in expected constant + * time: scr_weak_index by (collection, key, kind), for get/set/has/delete, + * and scr_weak_keys by key pointer, the head of that key's entries, for a + * disposal, which ends the key's entries in every collection. Both use + * linear probing with backward-shift deletion, grow at half load, and are + * freed when their last entry goes. */ +static SCR_TL ScrWeakEntry **scr_weak_index; +static SCR_TL size_t scr_weak_index_mask, scr_weak_index_used; +typedef struct { void *key; ScrWeakEntry *head; } ScrWeakKeySlot; +static SCR_TL ScrWeakKeySlot *scr_weak_keys; +static SCR_TL size_t scr_weak_keys_mask, scr_weak_keys_used; + +static inline size_t scr_weak_mix(uintptr_t bits) { + bits ^= bits >> 33; + bits *= 0xff51afd7ed558ccdull; + bits ^= bits >> 29; + return (size_t)bits; +} +static inline size_t scr_weak_entry_hash(const ScrWeakMap *owner, const void *key, unsigned kind) { + return scr_weak_mix((uintptr_t)key ^ ((uintptr_t)owner * 0x9E3779B97F4A7C15ull) ^ ((uintptr_t)kind << 56)); +} +static inline size_t scr_weak_key_hash(const void *key) { return scr_weak_mix((uintptr_t)key); } + +static void scr_weak_index_put(ScrWeakEntry *entry) { + size_t j = scr_weak_entry_hash(entry->owner, entry->key, entry->kind) & scr_weak_index_mask; + while (scr_weak_index[j]) j = (j + 1) & scr_weak_index_mask; + scr_weak_index[j] = entry; +} +static void scr_weak_index_reserve(void) { + if (scr_weak_index && (scr_weak_index_used + 1) * 2 <= scr_weak_index_mask + 1) return; + ScrWeakEntry **old = scr_weak_index; + size_t n = old ? scr_weak_index_mask + 1 : 0, cap = n ? n * 2 : 64; + scr_weak_index = calloc(cap, sizeof *scr_weak_index); + if (!scr_weak_index) scr_json_oom(); + scr_weak_index_mask = cap - 1; + for (size_t i = 0; i < n; i++) + if (old[i]) scr_weak_index_put(old[i]); + free(old); +} +static ScrWeakEntry *scr_weak_find(ScrWeakMap *map, void *ptr, unsigned kind) { + if (!scr_weak_index) return NULL; + for (size_t j = scr_weak_entry_hash(map, ptr, kind) & scr_weak_index_mask;; j = (j + 1) & scr_weak_index_mask) { + ScrWeakEntry *entry = scr_weak_index[j]; + if (!entry) return NULL; + if (entry->owner == map && entry->key == ptr && entry->kind == kind) return entry; + } +} +static void scr_weak_index_delete(ScrWeakEntry *entry) { + size_t mask = scr_weak_index_mask; + size_t i = scr_weak_entry_hash(entry->owner, entry->key, entry->kind) & mask; + while (scr_weak_index[i] != entry) i = (i + 1) & mask; + if (--scr_weak_index_used == 0) { + /* Empty: give the table back, so a thread that exits leaves none. */ + free(scr_weak_index); + scr_weak_index = NULL; + return; + } + for (size_t k = i;;) { + scr_weak_index[i] = NULL; + for (;;) { + k = (k + 1) & mask; + ScrWeakEntry *e = scr_weak_index[k]; + if (!e) return; + size_t home = scr_weak_entry_hash(e->owner, e->key, e->kind) & mask; + /* e stays unless its home lies cyclically outside (i, k]. */ + if (i <= k ? (i < home && home <= k) : (i < home || home <= k)) continue; + scr_weak_index[i] = e; + i = k; + break; + } + } +} + +static ScrWeakKeySlot *scr_weak_key_slot(const void *key) { + if (!scr_weak_keys) return NULL; + for (size_t j = scr_weak_key_hash(key) & scr_weak_keys_mask;; j = (j + 1) & scr_weak_keys_mask) { + if (scr_weak_keys[j].key == key) return &scr_weak_keys[j]; + if (!scr_weak_keys[j].key) return NULL; + } +} +static ScrWeakKeySlot *scr_weak_key_slot_put(void *key) { + if (!scr_weak_keys || (scr_weak_keys_used + 1) * 2 > scr_weak_keys_mask + 1) { + ScrWeakKeySlot *old = scr_weak_keys; + size_t n = old ? scr_weak_keys_mask + 1 : 0, cap = n ? n * 2 : 64; + scr_weak_keys = calloc(cap, sizeof *scr_weak_keys); + if (!scr_weak_keys) scr_json_oom(); + scr_weak_keys_mask = cap - 1; + for (size_t i = 0; i < n; i++) { + if (!old[i].key) continue; + size_t j = scr_weak_key_hash(old[i].key) & scr_weak_keys_mask; + while (scr_weak_keys[j].key) j = (j + 1) & scr_weak_keys_mask; + scr_weak_keys[j] = old[i]; + } + free(old); + } + size_t j = scr_weak_key_hash(key) & scr_weak_keys_mask; + while (scr_weak_keys[j].key && scr_weak_keys[j].key != key) j = (j + 1) & scr_weak_keys_mask; + if (!scr_weak_keys[j].key) { + scr_weak_keys[j].key = key; + scr_weak_keys[j].head = NULL; + scr_weak_keys_used++; + } + return &scr_weak_keys[j]; +} +static void scr_weak_key_slot_delete(ScrWeakKeySlot *slot) { + size_t mask = scr_weak_keys_mask; + size_t i = (size_t)(slot - scr_weak_keys); + if (--scr_weak_keys_used == 0) { + free(scr_weak_keys); + scr_weak_keys = NULL; + return; + } + for (size_t k = i;;) { + scr_weak_keys[i].key = NULL; + scr_weak_keys[i].head = NULL; + for (;;) { + k = (k + 1) & mask; + if (!scr_weak_keys[k].key) return; + size_t home = scr_weak_key_hash(scr_weak_keys[k].key) & mask; + if (i <= k ? (i < home && home <= k) : (i < home || home <= k)) continue; + scr_weak_keys[i] = scr_weak_keys[k]; + i = k; + break; + } + } +} + +/* Indexes a new entry (its key, kind and owner set) under its key. */ +static void scr_weak_link(ScrWeakEntry *entry) { + scr_weak_index_reserve(); + scr_weak_index_put(entry); + scr_weak_index_used++; + ScrWeakKeySlot *slot = scr_weak_key_slot_put(entry->key); + entry->key_prev = NULL; + entry->key_next = slot->head; + if (slot->head) slot->head->key_prev = entry; + slot->head = entry; +} + +/* Takes an entry out of both indexes (it stays in its owner's list). */ +static void scr_weak_unlink(ScrWeakEntry *entry) { + scr_weak_index_delete(entry); + if (entry->key_next) entry->key_next->key_prev = entry->key_prev; + if (entry->key_prev) entry->key_prev->key_next = entry->key_next; + else { + ScrWeakKeySlot *slot = scr_weak_key_slot(entry->key); + if (entry->key_next) slot->head = entry->key_next; + else scr_weak_key_slot_delete(slot); + } } static void scr_weak_remove_impl(ScrWeakEntry *entry, bool collected) { - ScrWeakEntry **slot = &entry->owner->entries; - while (*slot != entry) slot = &(*slot)->next; - *slot = entry->next; - slot = &scr_weak_entries[scr_weak_bucket(entry->key)]; - while (*slot != entry) slot = &(*slot)->all_next; - *slot = entry->all_next; + ScrWeakMap *owner = entry->owner; + if (entry->next) entry->next->prev = entry->prev; + if (entry->prev) entry->prev->next = entry->next; + else owner->entries = entry->next; + scr_weak_unlink(entry); ScrDyn *value = entry->value; free(entry); /* Unlink before release: releasing a value can dispose another key. */ @@ -15062,10 +15209,9 @@ static void scr_weak_map_gcfree(void *ptr) { static void scr_weak_disposed(void *key) { for (;;) { - ScrWeakEntry *entry = scr_weak_entries[scr_weak_bucket(key)]; - while (entry && entry->key != key) entry = entry->all_next; - if (!entry) return; - scr_weak_remove(entry); + ScrWeakKeySlot *slot = scr_weak_key_slot(key); + if (!slot) return; + scr_weak_remove(slot->head); } } @@ -15122,12 +15268,6 @@ static int scr_weak_key(const ScrDyn *key, void **ptr, unsigned *kind) { } } -static ScrWeakEntry *scr_weak_find(ScrWeakMap *map, void *ptr, unsigned kind) { - for (ScrWeakEntry *entry = map->entries; entry; entry = entry->next) - if (entry->key == ptr && entry->kind == kind) return entry; - return NULL; -} - static bool scr_weak_set(ScrWeakMap *map, ScrDyn *key, ScrDyn *value) { void *ptr = NULL; unsigned kind = 0; @@ -15156,10 +15296,9 @@ static bool scr_weak_set(ScrWeakMap *map, ScrDyn *key, ScrDyn *value) { entry->value = scr_dyn_retain(value); entry->owner = map; entry->next = map->entries; + if (map->entries) map->entries->prev = entry; map->entries = entry; - size_t bucket = scr_weak_bucket(ptr); - entry->all_next = scr_weak_entries[bucket]; - scr_weak_entries[bucket] = entry; + scr_weak_link(entry); return true; } @@ -16371,16 +16510,14 @@ static bool scr_dyn_decl_rekey(ScrDyn *node, void *ptr) { bool moved = false; if (!scr_weak_observing) return false; for (;;) { - ScrWeakEntry **slot = &scr_weak_entries[scr_weak_bucket(node)]; - while (*slot && !((*slot)->key == (void *)node && (*slot)->kind == 0)) slot = &(*slot)->all_next; - ScrWeakEntry *entry = *slot; + ScrWeakKeySlot *slot = scr_weak_key_slot(node); + ScrWeakEntry *entry = slot ? slot->head : NULL; + while (entry && entry->kind != 0) entry = entry->key_next; if (!entry) return moved; - *slot = entry->all_next; + scr_weak_unlink(entry); entry->key = ptr; entry->kind = 32; - ScrWeakEntry **head = &scr_weak_entries[scr_weak_bucket(ptr)]; - entry->all_next = *head; - *head = entry; + scr_weak_link(entry); moved = true; } } diff --git a/tests/corpus/7742-weak-collections-many-keys.ts b/tests/corpus/7742-weak-collections-many-keys.ts new file mode 100644 index 000000000..2cb2b103e --- /dev/null +++ b/tests/corpus/7742-weak-collections-many-keys.ts @@ -0,0 +1,70 @@ +// Weak collections with many keys: entries found among tens of thousands +// in WeakMaps and WeakSets that share keys, deleted and replaced, and gone +// with their keys, so blocks a new key reuses are not found under it. +// Keys in reference cycles die by the cycle collector. +class Key { + readonly id: number; + peer: Key | null = null; + constructor(id: number) { + this.id = id; + } +} + +const n = 40000; +let keys: Key[] = []; +for (let i = 0; i < n; i++) keys.push(new Key(i)); +const values = new WeakMap(); +const labels = new WeakMap(); +const marked = new WeakSet(); +for (const k of keys) { + values.set(k, k.id * 2); + if (k.id % 3 === 0) labels.set(k, "k" + k.id); + if (k.id % 2 === 0) marked.add(k); +} +let sum = 0; +let labelled = 0; +let counted = 0; +for (const k of keys) { + sum += values.get(k) ?? 0; + if (labels.has(k)) labelled++; + if (marked.has(k)) counted++; +} +console.log("filled", sum, labelled, counted); + +// Deleting every fourth key from one collection leaves the others alone. +let deleted = 0; +for (const k of keys) if (k.id % 4 === 0 && values.delete(k)) deleted++; +let remaining = 0; +for (const k of keys) if (values.has(k)) remaining++; +console.log("deleted", deleted, remaining, values.delete(keys[0]!), marked.has(keys[0]!), labels.get(keys[3]!)); + +// Overwriting keeps one entry per key. +for (const k of keys) if (k.id % 5 === 0) values.set(k, -1); +let negative = 0; +for (const k of keys) if (values.get(k) === -1) negative++; +console.log("overwritten", negative); + +// Dropping the keys ends their entries; new keys are not found. +keys = []; +const fresh: Key[] = []; +for (let i = 0; i < n; i++) fresh.push(new Key(n + i)); +let stale = 0; +for (const k of fresh) if (values.has(k) || labels.has(k) || marked.has(k)) stale++; +console.log("fresh", fresh.length, stale); + +// Keys that reach each other die together through the cycle collector. +for (let round = 0; round < 3; round++) { + let ring: Key[] = []; + for (let i = 0; i < 1000; i++) ring.push(new Key(i)); + for (let i = 0; i < ring.length; i++) ring[i]!.peer = ring[(i + 1) % ring.length]!; + for (const k of ring) { + values.set(k, round); + marked.add(k); + } + ring = []; +} +const after: Key[] = []; +for (let i = 0; i < 3000; i++) after.push(new Key(i)); +let found = 0; +for (const k of after) if (values.has(k) || marked.has(k)) found++; +console.log("cycles", found); diff --git a/tests/harness/corpus-costs/7742-weak-collections-many-keys.ts.json b/tests/harness/corpus-costs/7742-weak-collections-many-keys.ts.json new file mode 100644 index 000000000..97e5fc450 --- /dev/null +++ b/tests/harness/corpus-costs/7742-weak-collections-many-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 6000 } From f32d0cd1847c13f1166ae925334c701b41a55fc5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 23 Oct 2026 08:06:38 -0700 Subject: [PATCH 022/395] Make the packaging island fixture a webpack bundle Main now compiles minified npm packages (#1989), so ni-minified no longer refuses and the island report lost its packaging class. The fixture is now ni-bundled, whose build-transform markers still refuse it as shipped. Co-Authored-By: Claude Opus 5.5 --- tests/coverage-fixtures/node-islands/main.ts | 2 +- .../{ni-minified => ni-bundled}/index.d.ts | 0 .../node_modules/ni-bundled/index.js | 15 +++++++++++++ .../{ni-minified => ni-bundled}/package.json | 2 +- .../node_modules/ni-minified/index.js | 1 - .../__snapshots__/coverage-node-islands.txt | 22 +++++++++---------- tests/harness/coverage.test.ts | 10 ++++----- 7 files changed, 33 insertions(+), 19 deletions(-) rename tests/coverage-fixtures/node-islands/node_modules/{ni-minified => ni-bundled}/index.d.ts (100%) create mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.js rename tests/coverage-fixtures/node-islands/node_modules/{ni-minified => ni-bundled}/package.json (65%) delete mode 100644 tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js diff --git a/tests/coverage-fixtures/node-islands/main.ts b/tests/coverage-fixtures/node-islands/main.ts index ac6037d1a..597ba57cf 100644 --- a/tests/coverage-fixtures/node-islands/main.ts +++ b/tests/coverage-fixtures/node-islands/main.ts @@ -5,7 +5,7 @@ import { describe } from "ni-gap"; import { compile } from "ni-eval"; import { Counter } from "ni-ctor"; -import { shout } from "ni-minified"; +import { shout } from "ni-bundled"; import { twice } from "ni-typed"; console.log(describe()); diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts b/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.d.ts similarity index 100% rename from tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.d.ts rename to tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.d.ts diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.js new file mode 100644 index 000000000..2e88085be --- /dev/null +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/index.js @@ -0,0 +1,15 @@ +// A webpack bundle: its build-transform markers keep it out of the native +// subset as shipped (a packaging island). +var __webpack_modules__ = { + 1: function (module) { + module.exports.shout = function shout(s) { + return s.toUpperCase() + "!"; + }; + }, +}; +function __webpack_require__(id) { + var module = { exports: {} }; + __webpack_modules__[id](module); + return module.exports; +} +module.exports = __webpack_require__(1); diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json b/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/package.json similarity index 65% rename from tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json rename to tests/coverage-fixtures/node-islands/node_modules/ni-bundled/package.json index cc819dfc8..fd9bab40d 100644 --- a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/package.json +++ b/tests/coverage-fixtures/node-islands/node_modules/ni-bundled/package.json @@ -1,5 +1,5 @@ { - "name": "ni-minified", + "name": "ni-bundled", "version": "1.0.0", "main": "index.js" } diff --git a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js b/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js deleted file mode 100644 index 25c0e11ac..000000000 --- a/tests/coverage-fixtures/node-islands/node_modules/ni-minified/index.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";exports.shout=function shout(s){return s.toUpperCase()+"!"}; \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-node-islands.txt b/tests/harness/__snapshots__/coverage-node-islands.txt index e8876bb0d..e94ad3560 100644 --- a/tests/harness/__snapshots__/coverage-node-islands.txt +++ b/tests/harness/__snapshots__/coverage-node-islands.txt @@ -5,12 +5,12 @@ scriptc coverage tests/coverage-fixtures/node-islands/main.ts in dynamic tier 17 (operate on values typed only at runtime; --explain-dynamic lists them) npm package compilation: - ni-minified refused (its shipped JS looks minified) - ni-gap static - ni-eval static - ni-ctor static - ni-typed static - ni-dep static + ni-bundled refused (its shipped JS carries build-transform markers (bundled/transpiled dist)) + ni-gap static + ni-eval static + ni-ctor static + ni-typed static + ni-dep static Node islands (would-be, report-only: the build is unchanged; --node-islands=auto) gap ni-gap@1.0.0 @@ -20,8 +20,8 @@ scriptc coverage tests/coverage-fixtures/node-islands/main.ts object-model ni-ctor@1.0.0 tests/coverage-fixtures/node-islands/node_modules/ni-ctor/index.js:9:1: R2.1 constructor functions: a write of 'Counter.prototype.add' +1 more reason in 1 module; carries ni-dep - packaging ni-minified@1.0.0 - SC2013: its shipped JS looks minified + packaging ni-bundled@1.0.0 + SC2013: its shipped JS carries build-transform markers (bundled/transpiled dist) reached from native code and from an island: ni-typed hint: scriptc coverage --explain-island= lists every reason @@ -31,9 +31,9 @@ scriptc coverage tests/coverage-fixtures/node-islands/main.ts hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) blockers: - ×2 SC2013 cannot compile npm package 'ni-minified' statically: its implementation is not part of the compiled module graph + ×2 SC2013 cannot compile npm package 'ni-bundled' statically: its implementation is not part of the compiled module graph at tests/coverage-fixtures/node-islands/main.ts:8:1, tests/coverage-fixtures/node-islands/main.ts:16:13 hint: use a dependency with supported source, or replace the unsupported operation; scriptc has no interpreter fallback - ×1 SC2013 cannot compile npm package 'ni-minified' statically: its shipped JS looks minified + ×1 SC2013 cannot compile npm package 'ni-bundled' statically: its shipped JS carries build-transform markers (bundled/transpiled dist) at tests/coverage-fixtures/node-islands/main.ts:8:1 - hint: to compile it anyway, name it with --npm-static=ni-minified (or --npm-static=ni-minified... to include its dependencies) \ No newline at end of file + hint: to compile it anyway, name it with --npm-static=ni-bundled (or --npm-static=ni-bundled... to include its dependencies) \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 17c76034a..2c34a69e1 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -832,7 +832,7 @@ test("a cycle whose top level calls a builtin with a builtin function value is a test("Node islands (report-only): one package per class, gap islands first", async () => { // ni-gap (a fence inside the native subset), ni-eval (new Function), - // ni-ctor (a constructor function, carrying ni-dep), ni-minified (not + // ni-ctor (a constructor function, carrying ni-dep), ni-bundled (not // compilable as shipped), ni-typed (native, also reached from ni-ctor). const entry = fixture("node-islands/main.ts"); await expect(report(entry)).toMatchFileSnapshot("__snapshots__/coverage-node-islands.txt"); @@ -842,7 +842,7 @@ test("Node islands (report-only): one package per class, gap islands first", asy ["ni-gap", "gap"], ["ni-eval", "runtime"], ["ni-ctor", "object-model"], - ["ni-minified", "packaging"], + ["ni-bundled", "packaging"], ]); expect(islands.packages.map((p) => [p.package, p.status, p.islands])).toEqual([ ["ni-dep", "inside", ["ni-ctor"]], @@ -861,10 +861,10 @@ test("Node islands (report-only): one package per class, gap islands first", asy ["ni-gap", "gap", 7], ["ni-eval", "runtime", 4], ["ni-ctor", "object-model", 9], - ["ni-minified", "packaging", undefined], + ["ni-bundled", "packaging", undefined], ]); // The gates: the blocker gate is today's build (which refuses - // ni-minified with SC2013, and nothing else); the island gates fail on + // ni-bundled with SC2013, and nothing else); the island gates fail on // the would-be islands. expect(coverage.diagnostics.every((d) => d.code === "SC2013")).toBe(true); expect(coveragePasses(coverage, "island-gaps")).toBe(false); @@ -879,7 +879,7 @@ test("Node island policy from the command line: native, forced, off", () => { expect(forced.islands.map((i) => [i.package, i.class, i.decision])).toEqual([ ["ni-gap", "gap", "auto"], ["ni-ctor", "object-model", "auto"], - ["ni-minified", "packaging", "auto"], + ["ni-bundled", "packaging", "auto"], ["ni-typed", "marked", "cli"], ]); expect(forced.violations.map((v) => v.code)).toEqual(["SC8003"]); From e1358fb5635b9fe65a24c1854170366773437dd9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 10:16:30 -0500 Subject: [PATCH 023/395] Read Math, JSON and Reflect as real objects in JavaScript In JavaScript sources, Math, JSON or Reflect read as a value lowered to an identity token, the string "[builtin Math]": typeof, String, `in`, descriptors and member calls through an alias all had string semantics. Each of these namespaces read as a value is now one object per program, built on first use, with Node 24's own properties in Node's order: function members are writable, configurable and non-enumerable with Node's `length`, the Math constants are read-only, and Symbol.toStringTag names the namespace. Members hold the same function values the spelled member read gives; a member without one throws its fence when called. Global reads, globalThis, destructuring and shorthand properties all produce the same object, and an object with a string Symbol.toStringTag coerces to "[object ]". Direct member calls keep their static lowerings, so replacing or deleting a function member would not reach them: such changes throw SC1090. New properties and attribute-only redefinitions behave as in Node. --- .../compiler/src/backend/llvm/expr-dynamic.ts | 4 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../frontend/lowering/builtin-namespaces.ts | 285 ++++++++++++++++++ .../src/frontend/lowering/builtins/json.ts | 12 +- .../lowering/expressions/object-literals.ts | 7 +- .../lowering/expressions/property-access.ts | 12 + .../src/frontend/lowering/lower-stdlib.ts | 65 ++-- .../src/frontend/lowering/lower-stmts.ts | 4 +- .../compiler/src/frontend/lowering/lowerer.ts | 8 + .../src/frontend/lowering/surfaces.ts | 3 + .../compiler/src/ir/builtin-signatures.ts | 2 + packages/compiler/src/ir/ir.ts | 12 +- packages/compiler/src/ir/runtime-features.ts | 2 + .../test/namespace-member-fences.test.ts | 67 ++++ .../7186-namespace-object-values.js.json | 6 + .../7187-namespace-object-reflection.js.json | 6 + .../7188-namespace-object-extensions.js.json | 6 + packages/runtime/src/scr_dyn.h | 22 ++ packages/runtime/src/scr_dyn_invoke.c | 29 ++ packages/runtime/src/scr_js_object.c | 4 +- packages/runtime/src/scr_json.c | 90 +++++- packages/runtime/src/scr_symbol.c | 5 + tests/corpus/7186-namespace-object-values.js | 22 ++ .../7187-namespace-object-reflection.js | 23 ++ .../7188-namespace-object-extensions.js | 16 + .../7186-namespace-object-values.js.json | 1 + .../7187-namespace-object-reflection.js.json | 1 + .../7188-namespace-object-extensions.js.json | 1 + 28 files changed, 678 insertions(+), 39 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/builtin-namespaces.ts create mode 100644 packages/compiler/test/namespace-member-fences.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7186-namespace-object-values.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7187-namespace-object-reflection.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7188-namespace-object-extensions.js.json create mode 100644 tests/corpus/7186-namespace-object-values.js create mode 100644 tests/corpus/7187-namespace-object-reflection.js create mode 100644 tests/corpus/7188-namespace-object-extensions.js create mode 100644 tests/harness/corpus-costs/7186-namespace-object-values.js.json create mode 100644 tests/harness/corpus-costs/7187-namespace-object-reflection.js.json create mode 100644 tests/harness/corpus-costs/7188-namespace-object-extensions.js.json diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 07a538184..fac708b82 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -115,6 +115,10 @@ export function emitDynamicExpr( const box = host.dyn.dynFuncBoxHelper(v.type); const t = B.tmp(); B.line(`${t} = call ptr @${box}(ptr ${v.name}, ptr ${name})`); + if (e.fnLength !== undefined) { + host.declare(`declare void @scr_dyn_function_set_length(ptr, i32)`); + B.line(`call void @scr_dyn_function_set_length(ptr ${t}, i32 ${e.fnLength})`); + } return host.own({ name: t, type: e.type }); } const conv = host.dyn.toDynHelper(v.type); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index af32c6936..44db20d03 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1027,6 +1027,8 @@ export const LIB_FN_SYMS: Record = { "dyn.nativeDateHandleIs": "scr_dyn_native_date_is", "dyn.globalConstructor": "scr_dyn_global_constructor", "dyn.builtinFunction": "scr_dyn_builtin_function", + "dyn.namespaceNew": "scr_dyn_namespace_new", + "dyn.namespaceDefine": "scr_dyn_namespace_define", "date.nativeNew": "scr_dyn_native_date_new", "date.nativeNewFields": "scr_dyn_native_date_new_fields", "date.nativeNewValue": "scr_dyn_native_date_new_value", diff --git a/packages/compiler/src/frontend/lowering/builtin-namespaces.ts b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts new file mode 100644 index 000000000..bfe3c5e39 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts @@ -0,0 +1,285 @@ +/** The standard namespace objects (`Math`, `JSON`, `Reflect`) read as + * JavaScript values. Each is one object per program, built on first use, + * with Node's own properties in Node's order: the functions are writable, + * configurable and non-enumerable, the Math constants are read-only and + * non-configurable, and `Symbol.toStringTag` names the namespace. Function + * members hold the same function values the spelled `Math.abs` read gives; + * members without one hold a function that throws the named fence when + * called. Direct member calls (`Math.abs(x)`) keep their static lowerings. */ +import { + BOOL, + DYN, + F64, + STRING, + SYMBOL_T, + VOID, + type IrExpr, + type IrStmt, + type SrcLoc, + funcOf, +} from "../../ir/ir.js"; +import { helperLoc, varRef } from "../../ir/build.js"; +import { unsupportedDiag } from "../../diagnostics/diagnostic.js"; +import type { Lowerer } from "./lowerer.js"; +import { InternalCompilerError } from "../../errors.js"; +import { mathMemberValue } from "./lower-stdlib.js"; +import { jsonMemberValue } from "./builtins/json.js"; + +/** A function member with its `length`, or a constant (`length` -1). */ +interface NamespaceMember { + readonly key: string; + readonly length: number; +} + +/** Node 24's own string-keyed properties of the namespace `name`, in + * order, or null when `name` is not a namespace. */ +function namespaceMembers(name: string): readonly NamespaceMember[] | null { + switch (name) { + case "Math": + return [ + { key: "abs", length: 1 }, + { key: "acos", length: 1 }, + { key: "acosh", length: 1 }, + { key: "asin", length: 1 }, + { key: "asinh", length: 1 }, + { key: "atan", length: 1 }, + { key: "atanh", length: 1 }, + { key: "atan2", length: 2 }, + { key: "ceil", length: 1 }, + { key: "cbrt", length: 1 }, + { key: "expm1", length: 1 }, + { key: "clz32", length: 1 }, + { key: "cos", length: 1 }, + { key: "cosh", length: 1 }, + { key: "exp", length: 1 }, + { key: "floor", length: 1 }, + { key: "fround", length: 1 }, + { key: "hypot", length: 2 }, + { key: "imul", length: 2 }, + { key: "log", length: 1 }, + { key: "log1p", length: 1 }, + { key: "log2", length: 1 }, + { key: "log10", length: 1 }, + { key: "max", length: 2 }, + { key: "min", length: 2 }, + { key: "pow", length: 2 }, + { key: "random", length: 0 }, + { key: "round", length: 1 }, + { key: "sign", length: 1 }, + { key: "sin", length: 1 }, + { key: "sinh", length: 1 }, + { key: "sqrt", length: 1 }, + { key: "tan", length: 1 }, + { key: "tanh", length: 1 }, + { key: "trunc", length: 1 }, + { key: "E", length: -1 }, + { key: "LN10", length: -1 }, + { key: "LN2", length: -1 }, + { key: "LOG10E", length: -1 }, + { key: "LOG2E", length: -1 }, + { key: "PI", length: -1 }, + { key: "SQRT1_2", length: -1 }, + { key: "SQRT2", length: -1 }, + { key: "f16round", length: 1 }, + ]; + case "JSON": + return [ + { key: "parse", length: 2 }, + { key: "stringify", length: 3 }, + { key: "rawJSON", length: 1 }, + { key: "isRawJSON", length: 1 }, + ]; + case "Reflect": + return [ + { key: "defineProperty", length: 3 }, + { key: "deleteProperty", length: 2 }, + { key: "apply", length: 3 }, + { key: "construct", length: 2 }, + { key: "get", length: 2 }, + { key: "getOwnPropertyDescriptor", length: 2 }, + { key: "getPrototypeOf", length: 1 }, + { key: "has", length: 2 }, + { key: "isExtensible", length: 1 }, + { key: "ownKeys", length: 1 }, + { key: "preventExtensions", length: 1 }, + { key: "set", length: 3 }, + { key: "setPrototypeOf", length: 2 }, + ]; + default: + return null; + } +} + +/** Property attribute bits for dyn.namespaceDefine (the members are never + * enumerable). */ +const WRITABLE = 1; +const CONFIGURABLE = 4; + +/** The namespace object the stdlib global `canonical` reads as in + * JavaScript, or null when it is not a namespace. */ +export function builtinNamespaceValue( + lowerer: Lowerer, + canonical: string, + loc: SrcLoc, +): IrExpr | null { + const members = namespaceMembers(canonical); + if (members === null) return null; + const name = `%builtin.namespace.${canonical}`; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) + defineNamespaceBuilder(lowerer, canonical, members, name, helperLoc(loc)); + lowerer.noteEdge(name); + return { kind: "call", callee: name, args: [], type: DYN, loc }; +} + +/** The lifted function that builds the namespace object once and answers + * the same object afterwards. */ +function defineNamespaceBuilder( + lowerer: Lowerer, + canonical: string, + members: readonly NamespaceMember[], + name: string, + loc: SrcLoc, +): void { + const storage = `%g.${name}`; + const ready = `${storage}%ready`; + lowerer.globalsList.push( + { id: storage, name: `${canonical} namespace`, type: DYN, mutable: true }, + { id: ready, name: "%ready", type: BOOL, mutable: true }, + ); + const object = varRef("namespace.0", DYN, loc); + const body: IrStmt[] = [ + { + kind: "if", + cond: varRef(ready, BOOL, loc), + then: [{ kind: "return", value: varRef(storage, DYN, loc), loc }], + else_: null, + loc, + }, + { + kind: "varDecl", + localId: "namespace.0", + init: { + kind: "libCall", + fn: "dyn.namespaceNew", + args: [ + { kind: "strLit", value: canonical, type: STRING, loc }, + { + kind: "dynFrom", + value: { + kind: "libCall", + fn: "sym.wellKnown", + args: [{ kind: "strLit", value: "toStringTag", type: STRING, loc }], + type: SYMBOL_T, + loc, + }, + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }, + loc, + }, + ]; + for (const { key, length } of members) { + const value = + length < 0 + ? constantValue(lowerer, canonical, key, loc) + : functionValue(lowerer, canonical, key, loc); + const attributes = length < 0 ? 0 : WRITABLE | CONFIGURABLE; + body.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.namespaceDefine", + args: [ + object, + { kind: "strLit", value: key, type: STRING, loc }, + value, + { kind: "numLit", value: attributes, type: F64, loc }, + { kind: "numLit", value: length, type: F64, loc }, + ], + type: VOID, + loc, + }, + loc, + }); + } + body.push( + { kind: "assign", localId: storage, value: object, loc }, + { + kind: "assign", + localId: ready, + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, + { kind: "return", value: varRef(storage, DYN, loc), loc }, + ); + lowerer.liftedFns.push({ + name, + params: [], + locals: [{ id: "namespace.0", name: "namespace", type: DYN, mutable: false }], + returnType: DYN, + body, + loc, + }); +} + +/** A Math constant, boxed. */ +function constantValue(lowerer: Lowerer, canonical: string, key: string, loc: SrcLoc): IrExpr { + const value = canonical === "Math" ? mathMemberValue(lowerer, key, loc) : null; + if (value === null) + throw new InternalCompilerError( + `builtin namespace '${canonical}.${key}' has no constant value`, + ); + return { kind: "dynFrom", value, type: DYN, loc }; +} + +/** A function member: its function value under its own name, or a function + * that throws the named fence when called. */ +function functionValue(lowerer: Lowerer, canonical: string, key: string, loc: SrcLoc): IrExpr { + const value = + canonical === "Math" + ? mathMemberValue(lowerer, key, loc) + : canonical === "JSON" + ? jsonMemberValue(lowerer, key, loc) + : null; + if (value !== null) + return value.type.kind === "dyn" + ? value + : { kind: "dynFrom", value, fnName: key, type: DYN, loc }; + return fencedMember(lowerer, `${canonical}.${key}`, key, loc); +} + +/** One function per member without a function value: calling it throws the + * SC1090 fence, while `typeof`, identity and the property's presence stay + * as in Node. */ +function fencedMember(lowerer: Lowerer, display: string, key: string, loc: SrcLoc): IrExpr { + const name = `%builtin.fence.${display}`; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const diag = unsupportedDiag( + "SC1090", + loc, + `'${display}' as a function value (call it directly)`, + ); + lowerer.runtimeFences.push(diag); + lowerer.liftedFns.push({ + name, + params: [], + locals: [], + returnType: DYN, + body: [ + { kind: "runtimeFence", code: diag.code, message: `${diag.message} [${diag.code}]`, loc }, + ], + loc, + }); + } + return { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([], DYN), loc }, + fnName: key, + type: DYN, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index 64f829865..c1cdac334 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -606,8 +606,15 @@ export function lowerJsonProperty( ): IrExpr | null { const member = lowerer.stdlibGlobalMember(expr, "JSON"); if (member === null) return null; + const value = jsonMemberValue(lowerer, member, locOf(expr)); + if (value !== null) return value; + lowerer.unsupported("SC1090", expr, `JSON methods as values (call '${member}' directly)`); +} + +/** The function value of the JSON member `member` (stringify and parse), + * or null for members without one. */ +export function jsonMemberValue(lowerer: Lowerer, member: string, loc: SrcLoc): IrExpr | null { if (member === "stringify") { - const loc = locOf(expr); const name = "%builtin.JSON.stringify"; if (!lowerer.builtinCallableValueFns.has(name)) { lowerer.builtinCallableValueFns.set(name, name); @@ -654,7 +661,6 @@ export function lowerJsonProperty( if (member === "parse") { // JSON.parse as a value: ToString of the text, then the reviver form // (a reviver that is not a function is ignored, as in Node). - const loc = locOf(expr); const name = "%builtin.JSON.parse"; if (!lowerer.builtinCallableValueFns.has(name)) { lowerer.builtinCallableValueFns.set(name, name); @@ -703,5 +709,5 @@ export function lowerJsonProperty( loc, }; } - lowerer.unsupported("SC1090", expr, `JSON methods as values (call '${member}' directly)`); + return null; } diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 4cc0bee1a..1d6c887c1 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -38,6 +38,7 @@ import { planUnionRetag } from "../union-retag.js"; import { jsHoldsDynObject, runtimeOptionalStorageOperand } from "../lower-exprs.js"; import { builtinGlobalValue } from "./property-access.js"; import { holdsEvolvingArray } from "../lower-stmts.js"; +import { builtinNamespaceValue } from "../builtin-namespaces.js"; /** `{ a: 1, b: "x" }` → recordLit. The record type comes from the * contextual type when tsc has one (annotated declarations, arguments, @@ -3262,7 +3263,11 @@ function shorthandBuiltinValue( const symbol = lowerer.checker.getShorthandAssignmentValueSymbol(prop); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; const js = isJsSourceFile(ts.getSourceFileOfNode(prop)); - return builtinGlobalValue(ts.symbolName(symbol), locOf(prop.name), js); + const name = ts.symbolName(symbol); + return ( + (js ? builtinNamespaceValue(lowerer, name, locOf(prop.name)) : null) ?? + builtinGlobalValue(name, locOf(prop.name), js) + ); } export function lowerShorthandValue( diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 7b12297a8..5ec81f5ca 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,5 +1,6 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ import { keyedHelperName } from "../stable-names.js"; +import { builtinNamespaceValue } from "../builtin-namespaces.js"; import { staticallySelected } from "../static-typeof.js"; import * as ts from "../../ts7/adapter.js"; import { lowerHistogramProperty } from "../builtins/performance.js"; @@ -1105,11 +1106,18 @@ export function lowerPropertyAccessExpr( ) ) { const fnName = jsFuncNameOf(expr.expression); + // A builtin function value whose length is not its adapter's + // parameter count (`Math.max.length` is 2). + const fnLength = + probed.kind === "closure" + ? lowerer.builtinCallableValueLengths.get(probed.fnName) + : undefined; const boxed: IrExpr = { kind: "dynFrom", value: probed, type: DYN, ...(fnName !== null ? { fnName } : {}), + ...(fnLength !== undefined ? { fnLength } : {}), loc, }; const key: IrExpr = { @@ -4218,6 +4226,10 @@ export function builtinGlobalValueAt( loc: SrcLoc, js: boolean, ): IrExpr | null { + // A namespace object models its own properties, so member reads and + // reflection read it like any object. + const namespace = js ? builtinNamespaceValue(lowerer, canonical, loc) : null; + if (namespace !== null) return namespace; if (readsBuiltinGlobalMembers(expr)) return null; const value = builtinGlobalValue(canonical, loc, js); if (value === null) return null; diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index e671a0a08..097ccadbe 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -2553,35 +2553,8 @@ export function lowerMathProperty( const member = lowerer.stdlibGlobalMember(expr, "Math"); if (member === null) return null; const loc = locOf(expr); - const staticValue = own(STATIC_MATH_PROPS, member); - if (staticValue !== undefined) { - return { kind: "numLit", value: staticValue, type: F64, loc }; - } - const native = own(STATIC_MATH_FNS, member); - if (native && (member === "min" || member === "max" || member === "hypot")) - return lowerer.lowerNativeCallableValue( - { - fn: member === "min" ? "math.minArr" : member === "max" ? "math.maxArr" : "math.hypotArr", - params: [arrayOf(F64)], - result: F64, - valueParams: [{ mode: "rest", type: F64 }], - }, - `Math.${member}`, - loc, - ); - if (native && member !== "min" && member !== "max" && member !== "hypot") { - const params = Array.from({ length: native.arity }, () => F64); - return lowerer.lowerNativeCallableValue( - { - fn: native.fn, - params, - result: F64, - valueParams: params.map((type) => ({ mode: "required", type })), - }, - `Math.${member}`, - loc, - ); - } + const value = mathMemberValue(lowerer, member, loc); + if (value !== null) return value; const propType = own(STDLIB_SURFACE.math.props, member); if (propType !== undefined) { lowerer.refuseUnsupportedApi(`'Math.${member}'`, expr); @@ -2595,6 +2568,40 @@ export function lowerMathProperty( return null; // declared-but-untabled members fall through generically } +/** The value of the Math member `member`: a constant, or the interned + * native function value of a Math function. Null for other members. */ +export function mathMemberValue(lowerer: Lowerer, member: string, loc: SrcLoc): IrExpr | null { + const staticValue = own(STATIC_MATH_PROPS, member); + if (staticValue !== undefined) { + return { kind: "numLit", value: staticValue, type: F64, loc }; + } + const native = own(STATIC_MATH_FNS, member); + if (!native) return null; + if (member === "min" || member === "max" || member === "hypot") + return lowerer.lowerNativeCallableValue( + { + fn: member === "min" ? "math.minArr" : member === "max" ? "math.maxArr" : "math.hypotArr", + params: [arrayOf(F64)], + result: F64, + valueParams: [{ mode: "rest", type: F64 }], + valueLength: 2, + }, + `Math.${member}`, + loc, + ); + const params = Array.from({ length: native.arity }, () => F64); + return lowerer.lowerNativeCallableValue( + { + fn: native.fn, + params, + result: F64, + valueParams: params.map((type) => ({ mode: "required", type })), + }, + `Math.${member}`, + loc, + ); +} + export function npmPackageOf(lowerer: Lowerer, type: ts.Type): string | null { const pkg = lowerer.npmPackageOfSymbol(type.getAliasSymbol() ?? type.getSymbol()); if (pkg) return pkg; diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 66ecea602..b83333397 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -26,6 +26,7 @@ import { InternalCompilerError } from "../../errors.js"; * explicit resource management, and blocked-binding poisoning. */ import * as ts from "../ts7/adapter.js"; import { lowerOptionalIndex } from "./undefined-index.js"; +import { builtinNamespaceValue } from "./builtin-namespaces.js"; import { processDataMemberValue, programReadsExitCode } from "./builtins/process.js"; import { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; @@ -4248,7 +4249,8 @@ function stdlibGlobalTokenDestructure( value = canonical === "crypto" ? { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc: locOf(el) } - : builtinGlobalValue(canonical, locOf(el), true); + : (builtinNamespaceValue(lowerer, canonical, locOf(el)) ?? + builtinGlobalValue(canonical, locOf(el), true)); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 3c74cfbad..6c95f00a9 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2969,6 +2969,9 @@ export class Lowerer { readonly builtinCallableValueFns = new Map(); /** Each builtin function-value adapter's Node name, for boxed values. */ readonly builtinCallableValueNames = new Map(); + /** Builtin function values whose `length` differs from their adapter's + * parameter count (BuiltinModuleFn.valueLength), by adapter name. */ + readonly builtinCallableValueLengths = new Map(); /** Optional-chain lowering state. While a chain body lowers, the guarded * receiver NODE reads as a chainRecv (typed by the narrowed arm) instead * of re-lowering, its checker type reads non-nullish (typeOf), and the @@ -9719,6 +9722,8 @@ export class Lowerer { // name Node's function carries (`[Function: randomUUID]`). const builtinName = expr.kind === "closure" ? this.builtinCallableValueNames.get(expr.fnName) : undefined; + const builtinLength = + expr.kind === "closure" ? this.builtinCallableValueLengths.get(expr.fnName) : undefined; return { kind: "dynFrom", value: expr, @@ -9726,6 +9731,7 @@ export class Lowerer { loc: expr.loc, ...(liveRef ? { liveRef: true as const } : {}), ...(builtinName !== undefined ? { fnName: builtinName } : {}), + ...(builtinLength !== undefined ? { fnLength: builtinLength } : {}), }; } return expr; @@ -13901,6 +13907,8 @@ export class Lowerer { fnName = keyedHelperName(this, `%builtin.value.${fn.valueFn ?? fn.fn}`, key); this.builtinCallableValueFns.set(key, fnName); this.builtinCallableValueNames.set(fnName, display.slice(display.lastIndexOf(".") + 1)); + if (fn.valueLength !== undefined) + this.builtinCallableValueLengths.set(fnName, fn.valueLength); const params = funcType.params.map((type, index) => ({ localId: `arg.${index}`, name: `arg${index}`, diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 44564f2f1..a77ad2990 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -805,6 +805,9 @@ export interface BuiltinModuleFn { * direct-call row's `fn` (a direct call picks an arity-specific entry * point; the value accepts every arity through one). */ valueFn?: IrLibFn; + /** The function value's `length` when it is not the count of its + * required value parameters (`Math.max`'s 2 over its rest pack). */ + valueLength?: number; } export type BuiltinValueParam = diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1be0f95cd..cd5b4eb20 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -289,6 +289,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * timer APIs (it pulls in the event loop). The name is a compiler-chosen * literal. */ "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, + "dyn.namespaceNew": { argTypes: [STRING, DYN], result: DYN }, + "dyn.namespaceDefine": { argTypes: [DYN, STRING, DYN, F64, F64], result: VOID }, "date.nativeNew": { argTypes: [DYN], result: DYN }, /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's * first seven values pass through ToNumber in order (later ones are diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 878088d65..ec95926ac 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -3325,7 +3325,17 @@ export type IrExpr = * from a union) whose Web API contract exposes the reference again, such * as stream chunks and abort reasons; ordinary JSON-shaped values retain * the documented deep-copy boundary. */ - | { kind: "dynFrom"; value: IrExpr; fnName?: string; liveRef?: true; type: IrType; loc: SrcLoc } + | { + kind: "dynFrom"; + value: IrExpr; + fnName?: string; + /** A boxed function's `length` when it differs from its parameter + * count (builtin function values). */ + fnLength?: number; + liveRef?: true; + type: IrType; + loc: SrcLoc; + } /** CALLING a dyn value — `fn(a, b)` where fn is checked-dynamic (an * implicit-any JS binding, a dyn record member, a dynKeyGet result). * Arguments are ALREADY dyn-typed (typed values box through dynFrom at diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 4ad3dcc5c..1495d2e2b 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -309,6 +309,8 @@ const DYN_INVOKE_LIB_FNS: ReadonlySet = new Set([ "dyn.definePrototypeProps", "dyn.functionApply", "dyn.globalConstructor", + "dyn.namespaceDefine", + "dyn.namespaceNew", "dyn.objCreateWithProperties", "dyn.objectTag", "stream.dynNative", diff --git a/packages/compiler/test/namespace-member-fences.test.ts b/packages/compiler/test/namespace-member-fences.test.ts new file mode 100644 index 000000000..c040f35c7 --- /dev/null +++ b/packages/compiler/test/namespace-member-fences.test.ts @@ -0,0 +1,67 @@ +import { spawnSync } from "node:child_process"; +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"; + +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +// Static member calls (`Math.abs(x)`) do not read the namespace objects, so +// a change to a function member's value fences (SC1090) instead of leaving +// those calls with the original. New properties and attribute-only changes +// behave as in Node (corpus 7188). +test("replacing or deleting a namespace member fences", async () => { + const dir = mkdtempSync(join(tmpdir(), "scriptc-namespace-fences-")); + try { + const entry = join(dir, "main.js"); + writeFileSync( + entry, + ` +const m = /** @type {any} */ (Math); +const j = /** @type {any} */ (JSON); +function attempt(label, run) { + try { console.log(label, run()); } catch (error) { console.log(label, error.code); } +} +attempt("alias", () => { m.abs = () => 0; return "replaced"; }); +attempt("direct", () => { Math.max = () => 0; return "replaced"; }); +attempt("define", () => Object.defineProperty(m, "min", { value: () => 0 }) === m); +attempt("same", () => Object.defineProperty(m, "min", { value: Math.min, enumerable: true }) === m); +attempt("reflect", () => Reflect.set(j, "parse", () => 0)); +attempt("assign", () => Object.assign(m, { floor: 1 }) === m); +attempt("delete", () => delete m.ceil); +attempt("added", () => { m.extra = 5; return m.extra; }); +console.log(Math.abs(-1), Math.max(1, 2), Math.min(1, 2), JSON.parse("[3]")[0], Math.floor(1.5), Math.ceil(1.5)); +console.log(Object.keys(Math).join(",")); +`, + ); + const result = await compile(entry, { + backend: "llvm", + sanitize, + outDir: dir, + outPath: join(dir, "program"), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + if (!result.ok) return; + const child = spawnSync(result.binaryPath, [], { encoding: "utf8" }); + expect(child.stderr).toBe(""); + expect(child.status).toBe(0); + expect(child.stdout).toBe( + [ + "alias SC1090", + "direct SC1090", + "define SC1090", + "same true", + "reflect SC1090", + "assign SC1090", + "delete SC1090", + "added 5", + "1 2 1 3 1 2", + "min,extra", + "", + ].join("\n"), + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7186-namespace-object-values.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7186-namespace-object-values.js.json new file mode 100644 index 000000000..a130b4af6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7186-namespace-object-values.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7186-namespace-object-values.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7187-namespace-object-reflection.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7187-namespace-object-reflection.js.json new file mode 100644 index 000000000..b2d9bc751 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7187-namespace-object-reflection.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7187-namespace-object-reflection.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7188-namespace-object-extensions.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7188-namespace-object-extensions.js.json new file mode 100644 index 000000000..96cbad8c2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7188-namespace-object-extensions.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7188-namespace-object-extensions.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 669f4aedb..a9f4dc2c6 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -319,6 +319,9 @@ typedef struct { * must not inspect the value: an earlier cycle member may already be freed. * Ordinary mutations need not maintain this collector-only decision. */ bool value_traced : 1; + /* A namespace object's function member (scr_dyn_namespace_define), which + * static member calls bypass: a change to its value fences instead. */ + bool fenced : 1; /* Ordinary object insertion sets all three attributes true. */ bool writable; bool enumerable; @@ -870,6 +873,19 @@ ScrDyn *scr_dyn_error_constructor(const char *name); /* A builtin global function (setTimeout, setImmediate, process.nextTick, ...) * as an interned dynamic function value: +1. */ ScrDyn *scr_dyn_builtin_function(const ScrStr *name); +/* A standard namespace object (Math, JSON, Reflect read as a JavaScript + * value): an ordinary object whose Symbol.toStringTag is `tag` (+1). + * `tag_key` is the Symbol.toStringTag key (borrowed), which the compiler + * passes so this unit needs no symbol entry points. */ +ScrDyn *scr_dyn_namespace_new(const ScrStr *tag, ScrDyn *tag_key); +/* Defines the own data property `key` of a namespace object. `attributes` + * has bits 1 writable, 2 enumerable, 4 configurable; a function value + * takes `length` as its length when it is not negative. */ +void scr_dyn_namespace_define(ScrDyn *object, const ScrStr *key, ScrDyn *value, double attributes, + double length); +/* Marks the own property `key` of `object` as a fenced namespace member + * (ScrDynEntry.fenced). */ +void scr_dyn_fence_member(ScrDyn *object, const ScrStr *key); /* A constant-key read of an Object.prototype member name: takes the * ordinary read's +1 result and answers the inherited member in place of * an undefined one (scr_dyn_invoke.c). */ @@ -905,6 +921,9 @@ ScrDyn *scr_dyn_iterator_result_get(const ScrDyn *result, const char *key, size_ void scr_dyn_install_iterator_symbol(ScrDyn *key); void scr_dyn_install_has_instance_symbol(ScrDyn *key); void scr_dyn_install_async_iterator_symbol(ScrDyn *key); +/* Registered by the symbol unit when Symbol.toStringTag is created; the + * core's Object.prototype.toString reads it. */ +void scr_dyn_install_to_string_tag_symbol(ScrDyn *key); void scr_dyn_install_async_dispose_symbol(ScrDyn *key); /* True when key is Symbol.asyncDispose (registered by the symbol unit when * the program names it); handle symbol_get ops outside that unit use it. */ @@ -1380,6 +1399,9 @@ ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, con /* Gives a function box made with thunk its typed message entry (an * EventEmitter listener registered from it keeps the entry). */ void scr_dyn_func_set_message(ScrDyn *fn, ScrDynThunk thunk, ScrDynMessageListener listener); +/* Sets a freshly boxed function's `length` (a builtin function value whose + * length is not its adapter's parameter count). */ +void scr_dyn_function_set_length(ScrDyn *fn, uint32_t length); ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key); /* EventEmitter and the stream constructors as untyped constructor values, * and instanceof over untyped constructor functions diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 364017e05..a55f748e5 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -2236,6 +2236,35 @@ ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { return scr_dyn_retain(value); } +/* An own data property of a namespace object. Attribute bits: 1 writable, + * 2 enumerable, 4 configurable. */ +static void dyn_namespace_put(ScrDyn *object, ScrDyn *key, ScrDyn *value, unsigned attributes) { + ScrDyn *descriptor = scr_dyn_new_obj(); + scr_dyn_obj_set(descriptor, "value", 5, scr_dyn_retain(value)); + scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool((attributes & 1) != 0)); + scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool((attributes & 2) != 0)); + scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool((attributes & 4) != 0)); + scr_dyn_release(scr_dyn_define_property(object, key, descriptor)); + scr_dyn_release(descriptor); +} + +ScrDyn *scr_dyn_namespace_new(const ScrStr *tag, ScrDyn *tag_key) { + ScrDyn *object = scr_dyn_new_obj(); + ScrDyn *value = scr_dyn_new_str((ScrStr *)tag); + dyn_namespace_put(object, tag_key, value, 4); + scr_dyn_release(value); + return object; +} + +void scr_dyn_namespace_define(ScrDyn *object, const ScrStr *key, ScrDyn *value, double attributes, + double length) { + if (length >= 0 && value->kind == SCR_DYN_FUNC) value->v.fn.arity = (uint32_t)length; + ScrDyn *name = scr_dyn_new_str((ScrStr *)key); + dyn_namespace_put(object, name, value, (unsigned)attributes); + scr_dyn_release(name); + if (length >= 0) scr_dyn_fence_member(object, key); +} + ScrDyn *scr_dyn_array_prototype(void) { static SCR_TL bool initialized; ScrDyn *prototype = scr_dyn_array_prototype_base(); diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index 63929ab57..29849fea1 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -268,7 +268,7 @@ static bool scr_js_error_synced_key(const ScrStr *key) { static bool scr_js_replace(ScrDyn *receiver, size_t slot, ScrDyn *value) { ScrDynEntry *found = &receiver->v.obj.entries[slot]; - if (found->accessor || !found->writable) return false; + if (found->accessor || !found->writable || found->fenced) return false; scr_dyn_region_store(receiver, value); ScrDyn *old = found->value; found->value = scr_dyn_retain(value); @@ -313,7 +313,7 @@ bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJ size_t slot = scr_js_shape_find(shape, key->data, key->len); if (slot != SIZE_MAX) { const ScrDynEntry *found = &receiver->v.obj.entries[slot]; - if (found->accessor || !found->writable) return false; + if (found->accessor || !found->writable || found->fenced) return false; scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, shape, NULL, NULL, slot}); return scr_js_replace(receiver, slot, value); } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index da18a1c0b..a67740f40 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -52,6 +52,14 @@ static bool scr_dyn_error_prototype_fence(const ScrDyn *value) { SCR_TL const ScrHandleStreamHooks *scr_handle_stream_hooks = NULL; +/* Static member calls (`Math.abs(x)`) do not read the namespace objects, + * so replacing or deleting one of their function members would not reach + * those calls: the change throws instead. */ +static void scr_dyn_namespace_member_fence(void) { + static const char message[] = "Replacing or deleting a Math, JSON or Reflect member is not supported"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC1090"); +} + /* Constructor capsules expose identity and name/length, but do not model * per-class static property tables yet. Never report a fabricated empty view. */ static bool scr_dyn_class_reflection_fence(const ScrDyn *value) { @@ -87,6 +95,7 @@ static void scr_json_oom(void) { static SCR_TL ScrDyn *scr_iterator_symbol; static SCR_TL ScrDyn *scr_async_iterator_symbol; static SCR_TL ScrDyn *scr_to_primitive_symbol; +static SCR_TL ScrDyn *scr_to_string_tag_symbol; static bool scr_dyn_generator(const ScrDyn *value) { return value && value->kind == SCR_DYN_TYPED_REF && (value->v.typed_ref.type_key[0] == 'g' || value->v.typed_ref.type_key[0] == 'a') && @@ -183,6 +192,17 @@ void scr_dyn_install_to_primitive_symbol(ScrDyn *key) { scr_atexit(scr_to_primitive_symbol_cleanup); } +static void scr_to_string_tag_symbol_cleanup(void) { + scr_dyn_release(scr_to_string_tag_symbol); + scr_to_string_tag_symbol = NULL; +} + +void scr_dyn_install_to_string_tag_symbol(ScrDyn *key) { + if (scr_to_string_tag_symbol) return; + scr_to_string_tag_symbol = scr_dyn_retain(key); + scr_atexit(scr_to_string_tag_symbol_cleanup); +} + void scr_dyn_install_async_iterator_symbol(ScrDyn *key) { if (scr_async_iterator_symbol) return; scr_async_iterator_symbol = scr_dyn_retain(key); @@ -1728,6 +1748,11 @@ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len) : scr_dyn_find_linear_entry(obj, key, key_len); if (existing) { + if (existing->fenced) { + scr_dyn_release(value); + scr_dyn_namespace_member_fence(); + return; + } if (existing->accessor || !existing->writable) { scr_dyn_release(value); static const char message[] = "Cannot assign to read only property"; @@ -1769,6 +1794,7 @@ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn e->getter = NULL; e->setter = NULL; e->accessor = false; + e->fenced = false; e->writable = true; e->enumerable = true; e->configurable = true; @@ -4426,6 +4452,15 @@ ScrDyn *scr_dyn_new_chunk(const ScrBytes *b) { /* A boxed static function value (the compiler's static→dyn converters). * Ownership of the closure MOVES in; sig/name are static literals. */ +void scr_dyn_function_set_length(ScrDyn *fn, uint32_t length) { + if (fn->kind == SCR_DYN_FUNC) fn->v.fn.arity = length; +} + +void scr_dyn_fence_member(ScrDyn *object, const ScrStr *key) { + ScrDynEntry *entry = scr_dyn_entry(object, key); + if (entry) entry->fenced = true; +} + ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, const char *sig, const char *name) { if (clo->function_kind & 8) { ScrDyn *original = scr_box_get_ref(clo->caps[0]); @@ -5296,6 +5331,11 @@ void scr_dyn_obj_set_accessor(ScrDyn *obj, const char *key, size_t key_len, ScrD void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) { ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len) : scr_dyn_find_linear_entry(obj, key, key_len); + if (existing && existing->fenced) { + scr_dyn_release(value); + scr_dyn_namespace_member_fence(); + return; + } if (existing && !existing->accessor) { scr_dyn_region_store(obj, value); ScrDyn *old = existing->value; @@ -7294,6 +7334,13 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); return NULL; } + if (prior && prior->fenced && + (accessor || (value && !scr_dyn_property_same_value(prior->value, value)))) { + scr_dyn_descriptor_fields_drop(fields); + scr_str_release(name); + scr_dyn_namespace_member_fence(); + return NULL; + } if (prior) { if (accessor != prior->accessor) { ScrDyn *old_value = prior->value, *old_getter = prior->getter, *old_setter = prior->setter; @@ -7497,7 +7544,37 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_trace_v, scr_dyn_is_object(d)); } +/* Object.prototype.toString's tag from a string-valued Symbol.toStringTag + * data property of an object or its prototypes ("[object Math]"), or NULL + * when there is none. */ +static ScrStr *scr_dyn_own_string_tag(const ScrDyn *d) { + /* The symbol unit installs the key when the program creates it; without + * it no object can carry the property. */ + const ScrDyn *key = scr_to_string_tag_symbol; + if (!key) return NULL; + ScrStr *out = NULL; + for (const ScrDyn *p = d; p; p = p->prototype) { + if (!p->symbol_properties) continue; + ScrDynEntry *entry = scr_dyn_own_symbol_entry(p, key); + if (!entry) continue; + if (!entry->accessor && entry->value && entry->value->kind == SCR_DYN_STR) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "[object "); + scr_jb_write(&b, entry->value->v.str->data, entry->value->v.str->len); + scr_jb_putc(&b, ']'); + out = scr_jb_finish(&b); + } + break; + } + return out; +} + ScrStr *scr_dyn_object_tag(const ScrDyn *d) { + if (d->kind == SCR_DYN_OBJ) { + ScrStr *tag = scr_dyn_own_string_tag(d); + if (tag) return tag; + } if (d->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Symbol.toStringTag lookup"); return NULL; @@ -8739,7 +8816,9 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) { scr_error_release(error); return text; } - return scr_str_new("[object Object]", 15); + // Object.prototype.toString: "[object Object]", or the object's + // Symbol.toStringTag ("[object Math]"). + return scr_dyn_object_tag(d); } if (!m || m->kind != SCR_DYN_FUNC) { scr_dyn_release(m); continue; } /* OrdinaryToPrimitive performs a method call, not a bare function @@ -10477,6 +10556,7 @@ static void scr_dyn_obj_append_shared(ScrDyn *obj, char *key, size_t key_len, Sc e->getter = NULL; e->setter = NULL; e->accessor = false; + e->fenced = false; e->writable = true; e->enumerable = true; e->configurable = true; @@ -11271,6 +11351,10 @@ static void scr_json_delete_member(ScrDyn *object, const ScrStr *key) { for (size_t i = 0; i < object->v.obj.len; i++) { ScrDynEntry *entry = &object->v.obj.entries[i]; if (entry->key_len != key->len || memcmp(entry->key, key->data, key->len) != 0) continue; + if (entry->fenced) { + scr_dyn_namespace_member_fence(); + return; + } ScrDynEntry removed = *entry; memmove(entry, entry + 1, (object->v.obj.len - i - 1) * sizeof *entry); object->v.obj.len--; @@ -12866,7 +12950,9 @@ bool scr_dyn_reflect_define(ScrDyn *receiver, ScrDyn *raw_key, ScrDyn *value) { ScrDynEntry *entry = key->kind == SCR_DYN_SYMBOL ? (symbols ? scr_dyn_symbol_entry(symbols, key, true) : NULL) : receiver->kind == SCR_DYN_OBJ ? scr_dyn_entry(receiver, key->v.str) : receiver->kind == SCR_DYN_ARR && receiver->v.arr.properties ? scr_dyn_entry(receiver->v.arr.properties, key->v.str) : NULL; - if (!(entry && (entry->accessor || !entry->writable)) && !(receiver->non_extensible && !scr_dyn_has_own_computed(receiver, key))) { + if (entry && entry->fenced) { + scr_dyn_namespace_member_fence(); + } else if (!(entry && (entry->accessor || !entry->writable)) && !(receiver->non_extensible && !scr_dyn_has_own_computed(receiver, key))) { if (entry) { ScrDyn *old = entry->value; entry->value = scr_dyn_retain(value); diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 647c31dc3..1f7f867dc 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -105,6 +105,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_dyn_install_to_primitive_symbol(key); scr_dyn_release(key); } + if (name->len == 11 && !memcmp(name->data, "toStringTag", 11)) { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_to_string_tag_symbol(key); + scr_dyn_release(key); + } return symbol; } diff --git a/tests/corpus/7186-namespace-object-values.js b/tests/corpus/7186-namespace-object-values.js new file mode 100644 index 000000000..c61d96e11 --- /dev/null +++ b/tests/corpus/7186-namespace-object-values.js @@ -0,0 +1,22 @@ +// Math, JSON and Reflect read as values are objects with Node's own +// properties: typeof, String, `in`, aliasing and member calls through +// the value behave as in Node, and every spelling is the same object. +function describe(value) { + return [typeof value, String(value), Object.prototype.toString.call(value)].join(" "); +} +console.log(describe(Math), "|", describe(JSON), "|", describe(Reflect)); + +const m = Math; +console.log(m.abs(-2), m.max(1, 5, 3), m.PI.toFixed(5), "trunc" in m, m === Math); +console.log(m === globalThis.Math, JSON === globalThis.JSON); +const { Math: fromGlobal } = globalThis; +console.log(fromGlobal === Math, fromGlobal.sqrt(81)); + +const known = new Set([Math, JSON, Reflect]); +console.log(known.has(Math), known.has(globalThis.JSON), known.size); + +const parse = JSON.parse; +console.log(parse("[1,2]").length, JSON.stringify({ a: [1] })); + +console.log(typeof Math === "object" && typeof Reflect === "object"); +console.log(Math.max.length, Math.min.length, Math.hypot.length, Math.abs.length); diff --git a/tests/corpus/7187-namespace-object-reflection.js b/tests/corpus/7187-namespace-object-reflection.js new file mode 100644 index 000000000..d62699dea --- /dev/null +++ b/tests/corpus/7187-namespace-object-reflection.js @@ -0,0 +1,23 @@ +// Reflection over the namespace objects answers Node's property +// attributes: members are own, non-enumerable properties, the Math +// constants are read-only, and Symbol.toStringTag names the namespace. +function show(object, key) { + const d = Object.getOwnPropertyDescriptor(object, key); + if (d === undefined) return `${String(key)}: none`; + const value = typeof d.value === "function" ? `fn/${d.value.length}/${d.value.name}` : String(d.value); + return `${String(key)}: ${value} ${d.writable} ${d.enumerable} ${d.configurable}`; +} +function inspect(object) { + return [ + Object.hasOwn(object, "parse"), + Object.hasOwn(object, "abs"), + Object.keys(object).length, + Object.getOwnPropertyNames(object).length, + ].join(" "); +} +console.log(show(Math, "abs"), "|", show(Math, "PI"), "|", show(Math, "max")); +console.log(show(Math, Symbol.toStringTag), "|", show(JSON, "stringify"), "|", show(Reflect, "apply")); +console.log(inspect(Math), "|", inspect(JSON), "|", inspect(Reflect)); +console.log(Object.getOwnPropertyNames(JSON).join(",")); +console.log(Object.getOwnPropertyNames(Math).slice(0, 5).join(","), Object.getOwnPropertyNames(Math).at(-1)); +console.log(Object.prototype.hasOwnProperty.call(Math, "random"), Object.keys(Reflect).length); diff --git a/tests/corpus/7188-namespace-object-extensions.js b/tests/corpus/7188-namespace-object-extensions.js new file mode 100644 index 000000000..0a33f7155 --- /dev/null +++ b/tests/corpus/7188-namespace-object-extensions.js @@ -0,0 +1,16 @@ +// Namespace objects take new properties and attribute-only redefinitions +// as in Node, and their members keep the values static calls use. +const m = Math; +m.tau = 2 * Math.PI; +console.log(m.tau.toFixed(4), Math.tau === m.tau, "tau" in Math, Object.keys(Math).join(",")); +Object.defineProperty(Math, "hypot", { enumerable: true }); +Object.defineProperty(Math, "trunc", { value: Math.trunc, writable: false }); +const d = Object.getOwnPropertyDescriptor(Math, "trunc"); +console.log(Object.keys(Math).join(","), d.writable, d.enumerable, d.configurable, Math.trunc(-1.5)); +console.log(Math.hypot(3, 4), m.hypot === Math.hypot, Object.getOwnPropertyDescriptor(m, "hypot").enumerable); +const j = JSON; +j.label = "json"; +console.log(j.label, Object.keys(JSON).join(","), Reflect.ownKeys(j).length); +const r = Reflect; +r.extra = [1, 2]; +console.log(Reflect.extra.length, Object.getOwnPropertyNames(Reflect).at(-1), "extra" in r); diff --git a/tests/harness/corpus-costs/7186-namespace-object-values.js.json b/tests/harness/corpus-costs/7186-namespace-object-values.js.json new file mode 100644 index 000000000..b9c097972 --- /dev/null +++ b/tests/harness/corpus-costs/7186-namespace-object-values.js.json @@ -0,0 +1 @@ +{ "plain": 3233, "san": 3781 } diff --git a/tests/harness/corpus-costs/7187-namespace-object-reflection.js.json b/tests/harness/corpus-costs/7187-namespace-object-reflection.js.json new file mode 100644 index 000000000..82f5d5ebf --- /dev/null +++ b/tests/harness/corpus-costs/7187-namespace-object-reflection.js.json @@ -0,0 +1 @@ +{ "plain": 3183, "san": 3882 } diff --git a/tests/harness/corpus-costs/7188-namespace-object-extensions.js.json b/tests/harness/corpus-costs/7188-namespace-object-extensions.js.json new file mode 100644 index 000000000..8d90b4234 --- /dev/null +++ b/tests/harness/corpus-costs/7188-namespace-object-extensions.js.json @@ -0,0 +1 @@ +{ "plain": 3075, "san": 3749 } From bbaa7d7ba40427eb7462551f0dc065280af1c68e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 10:28:44 -0500 Subject: [PATCH 024/395] Pass callbacks through bindings in tests of the array-helper and closure paths --- .../test/callback-pending-emission.test.ts | 19 ++++++++++++------- .../test/runtime-optional-precision.test.ts | 4 +++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/compiler/test/callback-pending-emission.test.ts b/packages/compiler/test/callback-pending-emission.test.ts index 9dcbbf0a1..3b2ed3823 100644 --- a/packages/compiler/test/callback-pending-emission.test.ts +++ b/packages/compiler/test/callback-pending-emission.test.ts @@ -9,7 +9,9 @@ import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; // A specialized array helper calls its known callback directly after // checking the closure's entry. A callback that cannot throw skips the // pending test on that path, and the fallback through the closure's own -// entry keeps it; a callback that can throw keeps it on both paths. +// entry keeps it; a callback that can throw keeps it on both paths. The +// callbacks are passed through bindings: a closure literal at the call +// would run as a loop with its body copied in (array-pipelines.ts). test("known callbacks outside the may-throw set test the pending flag only on the fallback", async () => { const dir = mkdtempSync(join(tmpdir(), "scriptc-callback-pending-")); try { @@ -18,13 +20,16 @@ test("known callbacks outside the may-throw set test the pending flag only on th writeFileSync( entry, `const values = [1, 2, 3, 4]; -console.log(values.every((v) => v > 0)); -console.log(values.map((v) => v * 2).join(",")); +const positive = (v: number): boolean => v > 0; +const double = (v: number): number => v * 2; +const stopAtThree = (v: number): boolean => { + if (v > 2) throw new Error("stop at " + v); + return true; +}; +console.log(values.every(positive)); +console.log(values.map(double).join(",")); try { - console.log(values.every((v) => { - if (v > 2) throw new Error("stop at " + v); - return true; - })); + console.log(values.every(stopAtThree)); } catch (error) { console.log("caught", (error as Error).message); } diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 7840b0c62..b47cb828d 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -158,7 +158,9 @@ test("an unchanged captured parameter is borrowed and boxed only where a closure const module = await lower(`${ITEM} function heavier(item: Item, limits: number[]): boolean { if (limits.length === 0) return false; - return limits.some((limit) => weigh(item) > limit); + // A closure value: a closure literal at the call would run as a loop. + const over = (limit: number): boolean => weigh(item) > limit; + return limits.some(over); } function replaced(item: Item, other: Item): number { const read = () => item.weight; From 81e2e35e6446973e35a9040e747241c4fe967e37 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 11:09:31 -0500 Subject: [PATCH 025/395] Hold array and record elements whose serialization can call user code JSON.stringify walked a dense native array through its borrowed storage and wrote record fields borrowed from the record. When an element's own serialization can run user code (it reaches an array that may hold foreign elements, whose getters and toJSON run, or a class instance or checked-dynamic value), that code could grow the array, reallocating the storage the walk still read, or replace the field it was writing. In programs that store foreign elements, such arrays now take the general walk, which reads each index afresh and holds its element, and such record fields are held across their write. Programs without foreign elements compile as before. --- packages/compiler/src/backend/llvm/walkers.ts | 65 +++++++++++++++---- .../6068-foreign-elements-through-views.ts | 25 +++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/backend/llvm/walkers.ts b/packages/compiler/src/backend/llvm/walkers.ts index 77643ca87..ab8c2430c 100644 --- a/packages/compiler/src/backend/llvm/walkers.ts +++ b/packages/compiler/src/backend/llvm/walkers.ts @@ -26,6 +26,7 @@ import { llFieldType, loadStructField, releaseSym, + retainSym, traceAdapter, type ShapeHost, } from "./shapes.js"; @@ -323,9 +324,7 @@ export class LlWalkers { if (i > 0) this.putc(B, "%b", "44"); // ',' if (edgeable(f.type)) this.jbEdgeIdx(B, String(i)); const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name)!, f.type); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; [${llvmCommentText(f.name)}]`, - ); + this.writeField(B, v, f.type, `[${llvmCommentText(f.name)}]`); }); this.putc(B, "%b", "93"); // ']' if (cyclic) this.jbLeave(B); @@ -357,9 +356,7 @@ export class LlWalkers { this.puts(B, "%b", `${i > 0 ? "," : ""}${jsonObjectKeyLabel(f.name)}`); if (edgeable(f.type)) this.jbEdgeProp(B, f.name); const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name)!, f.type); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(f.name)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(f.name)}`); }); } else { const first = B.slot(); @@ -394,9 +391,7 @@ export class LlWalkers { comma(); this.puts(B, "%b", jsonObjectKeyLabel(f.name)); if (edgeable(f.type)) this.jbEdgeProp(B, f.name); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(f.name)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(f.name)}`); if (skip !== null) { B.br(skip); B.startBlock(skip); @@ -518,9 +513,7 @@ export class LlWalkers { B.startBlock(lKey); this.puts(B, "%b", jsonObjectKeyLabel(fieldName)); if (cyclic && traceAdapter(this.host, f.type) !== null) this.jbEdgeProp(B, fieldName); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(fieldName)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(fieldName)}`); B.terminate("ret i1 true"); }); B.startBlock(lSkip); @@ -676,6 +669,47 @@ export class LlWalkers { B.line(`call void @scr_arr_release(ptr ${ks})`); } + /** Serializing a value of `t` can call user code that changes the + * arrays or records holding it: it reaches an array that can hold + * foreign elements (their toJSON and getters), or a value whose own + * serialization may (a class instance, a checked-dynamic value), in a + * program that has foreign elements at all. */ + private serializationMayCallUserCode(t: IrType, seen = new Set()): boolean { + const host = this.host; + if (!host.mayHoldForeign({ kind: "f64" }) && !host.mayHoldForeign({ kind: "string" })) + return false; + const key = typeKey(t); + if (seen.has(key)) return false; + seen.add(key); + switch (t.kind) { + case "dyn": + case "object": + return true; + case "array": + return host.mayHoldForeign(t.elem) || this.serializationMayCallUserCode(t.elem, seen); + case "record": + return (host.recordsById.get(t.shapeId)?.fields ?? []).some((field) => + this.serializationMayCallUserCode(field.type, seen), + ); + case "union": + return (host.unionsById.get(t.unionId)?.arms ?? []).some((arm) => + this.serializationMayCallUserCode(arm, seen), + ); + default: + return false; + } + } + + /** Write a field value loaded borrowed from its record. When its + * serialization can call user code that replaces the field, the value + * is held across the write. */ + private writeField(B: BlockBuilder, v: string, t: IrType, comment: string): void { + const held = isRefCounted(t) && this.serializationMayCallUserCode(t); + if (held) B.line(`call ptr ${retainSym(this.host, t)}(ptr ${v})`); + B.line(`call void @${this.jsonWriteHelper(t)}(ptr %b, ${this.valTy(t)} ${v}) ; ${comment}`); + if (held) B.line(`call void ${releaseSym(this.host, t)}(ptr ${v})`); + } + private emitArrayWriter(B: BlockBuilder, elem: IrType, cyclic: boolean): void { const host = this.host; const w = this.jsonWriteHelper(elem); @@ -685,7 +719,12 @@ export class LlWalkers { if (cyclic) this.jbEnter(B, true); this.putc(B, "%b", "91"); // '[' const finish = B.newLabel("jwa.end"); - if (elem.kind !== "bool") this.emitDenseArrayElements(B, elem, w, cyclic, finish); + // The dense walk borrows the storage and each element. When an + // element's serialization can call user code, which can grow or + // shrink the array or replace the element, the general walk below + // reads each index afresh and holds its element instead. + if (elem.kind !== "bool" && !this.serializationMayCallUserCode(elem)) + this.emitDenseArrayElements(B, elem, w, cyclic, finish); const len = B.tmp(); B.line(`${len} = call double @scr_arr_len(ptr %v)`); B.countedLoop(len, (i) => { diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 76997c624..6b1715e01 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -89,3 +89,28 @@ const cut: string[] = ["a", "b"]; const cutView: unknown[] = cut; cutView.splice(1, 0, { toString: () => ((cut.length = 0), "x") }); console.log(JSON.stringify(cut.join("-")), cut.length); + +// JSON of elements whose getters change the array or record holding them. +const fillers: number[][] = []; +const rows: number[][] = [[1], [2]]; +const rowView: unknown[] = rows[0]!; +rowView.push({ + get grown() { + for (let i = 0; i < 64; i++) rows.push([i]); + rows.length = 3; + // Reuse the memory the old storage held. + for (let i = 0; i < 64; i++) fillers.push([i, i]); + return true; + }, +}); +console.log(JSON.stringify(rows), rows.length); +const holder = { values: [5] as number[], name: "h" }; +const holderView: unknown[] = holder.values; +holderView.push({ + get replaced() { + holder.values = [9]; + for (let i = 0; i < 64; i++) fillers.push([i]); + return true; + }, +}); +console.log(JSON.stringify(holder), JSON.stringify(holder)); From 53115c7e50cc89220cd223daf18638e6f2a4ae21 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 11:15:27 -0500 Subject: [PATCH 026/395] Merge main and fix the Node island report --- .../compiler/src/diagnostics/diagnostic.ts | 15 ++++ .../compiler/src/node-islands/report.test.ts | 78 ++++++++++++++++ packages/compiler/src/node-islands/report.ts | 89 +++++++++++++------ packages/compiler/src/project-config.test.ts | 9 ++ packages/compiler/src/project-config.ts | 4 +- 5 files changed, 167 insertions(+), 28 deletions(-) diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index bc5cc4918..bb5efba31 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -249,6 +249,21 @@ export function nodeIslandsOffDiag(pkg: string, why: string, loc: SrcLoc): ScrDi }; } +/** SC8002 — a user module is marked to run in a Node island while + * Node islands are off. */ +export function nodeIslandsOffModuleDiag( + file: string, + source: "config" | "cli", + loc: SrcLoc, +): ScrDiagnostic { + return { + code: "SC8002", + message: `user module '${file}' is marked to run in Node (${source === "cli" ? "--node-island" : "scriptc.nodeIslands.modules"}) and Node islands are off`, + loc, + hint: "allow Node islands (--node-islands=auto or explicit), or remove the marking", + }; +} + /** SC8003 — a package marked "native" is outside the native subset. */ export function nativePackageDiag(pkg: string, why: string, loc: SrcLoc): ScrDiagnostic { return { diff --git a/packages/compiler/src/node-islands/report.test.ts b/packages/compiler/src/node-islands/report.test.ts index a11d9fb8b..d4221d31f 100644 --- a/packages/compiler/src/node-islands/report.test.ts +++ b/packages/compiler/src/node-islands/report.test.ts @@ -199,3 +199,81 @@ test("globMatch", () => { expect(globMatch("/a/?.js", "/a/x.js")).toBe(true); expect(globMatch("/a/x.js", "/a/x.js")).toBe(true); }); + +describe("reachability", () => { + const userEdge = (fromFile: string, to: string): PackageEdge => ({ + ...edge(null, to), + fromFile, + loc: { file: fromFile, start: 0, end: 1 }, + }); + const codeFromString = (pkg: string) => + mod(pkg, `${NM}/${pkg}/index.js`, [ + marker(`${NM}/${pkg}/index.js`, "R1.1", "code-from-string"), + ]); + const build = (scan: Omit, options: NodeIslandOptions = {}) => + buildNodeIslandReport({ + entry: "/app/main.ts", + scan: { ...scan, refusals: [] }, + npm: [], + diagnostics: [], + fences: [], + policy: nodeIslandPolicy(emptyProjectConfig(), options), + }); + + test("a marked user module owns the packages only it imports", () => { + const r = build( + { + modules: [ + mod(null, "/app/main.ts"), + mod(null, "/app/plugin.ts"), + mod("x", `${NM}/x/index.js`), + mod("y", `${NM}/y/index.js`), + ], + edges: [userEdge("/app/plugin.ts", "x"), userEdge("/app/plugin.ts", "y"), edge(null, "y")], + }, + { islands: ["/app/plugin.ts"] }, + ); + expect(r.packages.map((p) => [p.package, p.status, p.islands])).toEqual([ + ["x", "inside", ["/app/plugin.ts"]], + ["y", "shared", ["/app/plugin.ts"]], + ]); + }); + + test("an island imported by a native package lists that import site", () => { + const r = build({ + modules: [mod(null, "/app/main.ts"), mod("n", `${NM}/n/index.js`), codeFromString("i")], + edges: [edge(null, "n"), edge("n", "i")], + }); + expect(r.islands.map((i) => [i.package, i.importedFrom.map((l) => l.file)])).toEqual([ + ["i", [`${NM}/n/index.js`]], + ]); + }); + + test("the dependencies of a shared package are shared with the island", () => { + const r = build({ + modules: [ + mod(null, "/app/main.ts"), + codeFromString("a"), + mod("s", `${NM}/s/index.js`), + mod("d", `${NM}/d/index.js`), + ], + edges: [edge(null, "a"), edge(null, "s"), edge("a", "s"), edge("s", "d")], + }); + expect(r.packages.map((p) => [p.package, p.status, p.islands])).toEqual([ + ["d", "shared", ["a"]], + ["s", "shared", ["a"]], + ]); + }); + + test("with islands off, a marked user module is SC8002 and keeps the user JavaScript rules", () => { + const file = "/app/legacy.js"; + const r = build( + { modules: [mod(null, file, [marker(file, "R2.1", "constructor-function")])], edges: [] }, + { mode: "off", islands: [file], strictUserJs: true }, + ); + expect(r.userModules).toEqual([]); + expect(r.violations.map((v) => v.code)).toEqual(["SC8002"]); + expect(r.violations[0]!.message).toContain(`user module '${file}'`); + expect(r.userJs.map((d) => d.code)).toEqual(["SC8101"]); + }); +}); diff --git a/packages/compiler/src/node-islands/report.ts b/packages/compiler/src/node-islands/report.ts index a6898e855..c82d0f664 100644 --- a/packages/compiler/src/node-islands/report.ts +++ b/packages/compiler/src/node-islands/report.ts @@ -14,6 +14,7 @@ import { nativePackageDiag, nodeIslandsOffDiag, + nodeIslandsOffModuleDiag, userJsObjectModelDiag, type ScrDiagnostic, } from "../diagnostics/diagnostic.js"; @@ -67,7 +68,9 @@ interface PackageState { dir: string | null; reasons: NodeIslandReason[]; importers: Set; - nativeSites: SrcLoc[]; + /** Every import site, with its importer ("" native user code, + * "user:" a marked user module, else a package key). */ + sites: { from: string; loc: SrcLoc }[]; island: boolean; decision: IslandDecision; carriedBy: string | null; @@ -88,7 +91,7 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR dir, reasons: [], importers: new Set(), - nativeSites: [], + sites: [], island: false, decision: "auto", carriedBy: null, @@ -113,16 +116,22 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR const s = state(m.packageName, m.packageDir); for (const marker of m.markers) addReason(s, classifyMarker(marker)); } - // Import edges. + // Import edges. A marked user module is an island owner of its own + // (unless islands are off, where it is a violation and stays native). + const USER = "user:"; + const userIsland = (file: string): boolean => + policy.mode !== "off" && isMarkedUserModule(policy, file) !== null; + const imports = new Map>(); for (const e of input.scan.edges) { if (e.shim || ignored(e.to)) continue; const s = state(e.to, e.toDir); - if (e.fromDir === null) { - s.importers.add(""); - s.nativeSites.push(e.loc); - } else if (e.fromDir !== s.key) { - s.importers.add(e.fromDir); - } + const from = e.fromDir ?? (userIsland(e.fromFile) ? USER + e.fromFile : ""); + if (from === s.key) continue; + s.importers.add(from); + s.sites.push({ from, loc: e.loc }); + let out = imports.get(from); + if (out === undefined) imports.set(from, (out = new Set())); + out.add(s.key); } // Lowering refusals and fences inside npm modules. const addDiagnostics = (diags: readonly ScrDiagnostic[], source: NodeIslandReasonSource) => { @@ -161,7 +170,7 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR const forcedNode = policy.forcedNode.get(s.name); const forcedNative = policy.forcedNative.get(s.name); const outside = s.reasons.length > 0; - const loc = s.nativeSites[0] ?? fallbackLoc; + const loc = (s.sites.find((x) => x.from === "") ?? s.sites[0])?.loc ?? fallbackLoc; if (forcedNative !== undefined) { if (outside) { s.violation = true; @@ -197,6 +206,16 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR s.decision = "auto"; } + // The island that owns an importer: a marked user module, an island + // package, or the island a carried package runs inside; null for native. + const ownerOf = (from: string): string | null => { + if (from.startsWith(USER)) return from; + const importer = states.get(from); + return importer === undefined ? null : importer.island ? importer.key : importer.carriedBy; + }; + const ownerName = (owner: string): string => + owner.startsWith(USER) ? owner.slice(USER.length) : states.get(owner)!.name; + // Closure: packages imported only by islands (or by packages inside // them) run inside the island. for (let changed = true; changed;) { @@ -207,9 +226,7 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR let carrier: string | null = null; let all = true; for (const from of s.importers) { - const importer = states.get(from); - const owner = - importer === undefined ? null : importer.island ? importer.key : importer.carriedBy; + const owner = ownerOf(from); if (owner === null) { all = false; break; @@ -223,6 +240,31 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR } } + // Transitive reach: every non-island package an island loads, through + // carried and shared packages alike (an island package is a boundary). + const reach = new Map>(); + const owners = [...imports.keys()].filter( + (k) => k.startsWith(USER) || states.get(k)?.island === true, + ); + for (const owner of owners) { + const name = ownerName(owner); + const queue = [...(imports.get(owner) ?? new Set())]; + const seen = new Set(queue); + while (queue.length > 0) { + const key = queue.pop()!; + if (states.get(key)?.island === true) continue; + let names = reach.get(key); + if (names === undefined) reach.set(key, (names = new Set())); + names.add(name); + for (const next of imports.get(key) ?? new Set()) { + if (!seen.has(next)) { + seen.add(next); + queue.push(next); + } + } + } + } + // Entries. const islands: NodeIslandEntry[] = []; const packages: NativePackage[] = []; @@ -248,7 +290,7 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR reasons: s.reasons, modules: modules.size, carries, - importedFrom: s.nativeSites, + importedFrom: s.sites.filter((x) => ownerOf(x.from) === null).map((x) => x.loc), evaluation: "eager", }); continue; @@ -257,23 +299,13 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR // A package that resolves to no installed JavaScript never loads (an // optional dependency that is absent): nothing to compile or island. if (s.dir === null && s.reasons.length === 0) continue; - const reachingIslands: string[] = []; - for (const from of s.importers) { - const importer = states.get(from); - if (importer === undefined) continue; - const owner = importer.island ? importer.key : importer.carriedBy; - if (owner !== null) { - const name = states.get(owner)!.name; - if (!reachingIslands.includes(name)) reachingIslands.push(name); - } - } - reachingIslands.sort(); + const reachingIslands = [...(reach.get(s.key) ?? new Set())].sort(); packages.push({ package: s.name, version, path: s.dir, status: s.carriedBy !== null ? "inside" : reachingIslands.length > 0 ? "shared" : "native", - islands: s.carriedBy !== null ? [states.get(s.carriedBy)!.name] : reachingIslands, + islands: s.carriedBy !== null ? [ownerName(s.carriedBy)] : reachingIslands, }); } islands.sort( @@ -292,10 +324,13 @@ export function buildNodeIslandReport(input: NodeIslandReportInput): NodeIslandR for (const m of input.scan.modules) { if (m.origin !== "user") continue; const marked = isMarkedUserModule(policy, m.file); - if (marked !== null) { + if (marked !== null && policy.mode !== "off") { userModules.push({ file: m.file, source: marked }); continue; } + if (marked !== null) { + violations.push(nodeIslandsOffModuleDiag(m.file, marked, { file: m.file, start: 0, end: 0 })); + } if (!policy.strictUserJs) continue; for (const marker of m.markers) { const code = ruleInfo(marker.rule).userJsCode; diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index b9750f24e..ce475a4ae 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -124,3 +124,12 @@ test("a non-object packages or nodeIslands value is SC8004", () => { // Other keys of the scriptc object belong to other readers. expect(config({ somethingElse: 1 }).diagnostics).toEqual([]); }); + +test("package setting keys named like Object.prototype members are unknown settings", () => { + const c = config({ packages: { debug: { constructor: "node", toString: "node" } } }); + expect(c.diagnostics.map((d) => [d.code, d.message])).toEqual([ + ["SC8004", "package.json 'scriptc.packages.debug.constructor' is not a known package setting"], + ["SC8004", "package.json 'scriptc.packages.debug.toString' is not a known package setting"], + ]); + expect(c.packages.has("debug")).toBe(false); +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index cc13a868d..bc68e6476 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -201,7 +201,9 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi } let valid = true; for (const key of Object.keys(value)) { - const allowed = PACKAGE_OBJECT_KEYS[key]; + const allowed = Object.hasOwn(PACKAGE_OBJECT_KEYS, key) + ? PACKAGE_OBJECT_KEYS[key] + : undefined; if (allowed === undefined) { problem( ["packages", name, key], From d10ddff54807cee1b8baef43c7fc4c4004c88a3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 11:36:23 -0500 Subject: [PATCH 027/395] Compile createRequire probes for optional addons as deferred refusals Packages such as pi-tui look for their prebuilt Node-API addons at run time: a createRequire require call with a computed path, and the loader's resolve passed as a value, each inside a try block whose catch takes a fallback. Preflight refused these packages. Preflight now classifies each use of a createRequire loader. Inside the try block of a try statement in the same function, a call with a computed specifier or another argument shape, and resolve or resolve.paths read as a value, no longer refuse the package. Outside a try block they keep it refused, with a reason that names the computed specifier. The loader itself as a value, writes and optional calls stay refused. In JavaScript source, such a call evaluates its arguments and then throws a runtime fence, and the resolve value is a function that throws one when called, so `(options.resolve ?? require.resolve)(id)` still calls the option. Both are listed in coverage as deferred to runtime. TypeScript source keeps the compile-time refusals. Refs #1777 --- docs/content/limitations/native-addons.mdx | 6 ++ .../lowering/builtins/module-resolution.ts | 89 +++++++++++++++++-- .../lowering/expressions/property-access.ts | 22 +++++ packages/compiler/src/frontend/program.ts | 76 ++++++++++++---- tests/fixtures/npm-static/addon-probe-cli.ts | 5 ++ .../node_modules/addon-probe/index.d.ts | 2 + .../node_modules/addon-probe/index.js | 28 ++++++ .../node_modules/addon-probe/package.json | 7 ++ .../__snapshots__/create-require.ts.txt | 4 +- tests/harness/npm-static.test.ts | 59 ++++++++++++ 10 files changed, 275 insertions(+), 23 deletions(-) create mode 100644 tests/fixtures/npm-static/addon-probe-cli.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.js create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/package.json diff --git a/docs/content/limitations/native-addons.mdx b/docs/content/limitations/native-addons.mdx index 469d1cdd2..5fff3d7e8 100644 --- a/docs/content/limitations/native-addons.mdx +++ b/docs/content/limitations/native-addons.mdx @@ -2,5 +2,11 @@ Node-API (N-API) and V8 `.node` addons depend on Node.js itself, so compiled pro Direct `createRequire` calls to local addons are rejected at compile time. A package that requires an addon needs a JavaScript implementation that compiles, or a replacement through native FFI. +A package that looks for an optional addon at run time compiles with `--npm-static` when the lookup is inside a `try` block. +There, a `createRequire` require call with a computed specifier throws an error where it runs, and its `require.resolve` read as a value is a function that throws when called. +Coverage lists both as deferred to runtime. +The package's `catch` clause then runs as it does in Node.js when the addon is missing. +Outside a `try` block, the same uses keep the package refused. + Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI function linked from an object or static archive. The guide includes a replacement for a small Node-API helper. diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index 6b2e9e589..42ecfdd50 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -2,7 +2,7 @@ import { nodeThrowExpr, boolLit, strLit, varRef } from "../../../ir/build.js"; import { dirname, isAbsolute, resolve } from "node:path"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, PoisonError } from "../lowerer.js"; -import { canonicalBuiltinModule, isNodeEsmFile, locOf } from "../../program.js"; +import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf } from "../../program.js"; import { isRelativeSpecifier } from "../../workspace-registry.js"; import { probeNodeRequireRefusal } from "../../npm.js"; import { isNpmStaticPackage } from "../../npm-static.js"; @@ -17,6 +17,7 @@ import { import { invalidJsonModuleDiag, nativeAddonDiag, + noLoweringDiag, unsupportedPackageImportDiag, } from "../../../diagnostics/diagnostic.js"; import { NODE_BUILTIN_MODULES_V24 } from "../surfaces.js"; @@ -24,12 +25,14 @@ import { BOOL, DYN, type IrExpr, + type IrStmt, type IrType, NULL_T, STRING, type SrcLoc, UNDEFINED_T, arrayOf, + funcOf, } from "../../../ir/ir.js"; import { lowerFfiMemoryModule } from "../native-ffi.js"; import { @@ -38,6 +41,7 @@ import { createRequireCalleeFileOf, } from "./module-bindings.js"; import { stripTypeCasts } from "./arguments.js"; +import { keyedHelperName } from "../stable-names.js"; /** node:module's two compiler-only calls. isBuiltin compares one evaluated * string against the pinned Node 24 list (bare builtins also accept their @@ -134,6 +138,61 @@ export function lowerNodeModuleCall( }; } +/** The fence a createRequire use with no build-time lowering throws in + * JavaScript source, where preflight admits it inside a try block + * (createRequireUse7 in program.ts). The diagnostic joins the runtime-fence + * ledger, so coverage lists the use as deferred to runtime. */ +function createRequireFence( + lowerer: Lowerer, + surface: string, + node: ts.Node, + hint: string, +): IrStmt & { kind: "runtimeFence" } { + const loc = locOf(node); + const d = noLoweringDiag(surface, loc, hint); + lowerer.runtimeFences.push(d); + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; + return { + kind: "runtimeFence", + code: d.code, + message: `${d.message} [${d.code} at ${loc.file}:${line}]`, + loc, + }; +} + +/** A createRequire loader's `resolve` (or `resolve.paths`) read as a value + * in JavaScript source: a function that throws the fence when called, so + * `(options.resolve ?? require.resolve)(id)` still calls the option when + * one is given. */ +export function createRequireResolveFenceValue( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + loc: SrcLoc, +): IrExpr { + const surface = "createRequire's require.resolve as a value"; + const hint = + 'call it with a string literal (require.resolve("")), which resolves at build time'; + if (!isJsSourceFile(ts.getSourceFileOfNode(access))) lowerer.noLowering(surface, access, hint); + const name = keyedHelperName(lowerer, "%createRequire.fence", `${loc.file}:${loc.start}`); + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + lowerer.liftedFns.push({ + name, + params: [], + locals: [], + returnType: DYN, + body: [createRequireFence(lowerer, surface, access, hint)], + loc, + }); + } + return { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([], DYN), loc }, + fnName: access.name.text, + type: DYN, + loc, + }; +} + export function lowerCreateRequireCall( lowerer: Lowerer, call: ts.CallExpression, @@ -142,11 +201,31 @@ export function lowerCreateRequireCall( const cr = createRequireSpecOf(lowerer, call); if (cr === null) return null; if (cr.spec === null) { - lowerer.noLowering( - "createRequire's require with this argument shape", - call, - 'the compiled module graph is fixed at build time — the one lowered form is require("")', + const surface = "createRequire's require with a computed specifier or this argument shape"; + const hint = + 'the compiled module graph is fixed at build time — the one lowered form is require(""); native addons are not loaded'; + if (!isJsSourceFile(ts.getSourceFileOfNode(call))) lowerer.noLowering(surface, call, hint); + // Node evaluates the arguments, then the require throws. + const stmts: IrStmt[] = call.arguments.nodes.map((arg) => ({ + kind: "exprStmt", + expr: lowerer.lowerExpr(arg), + loc: locOf(arg), + })); + const type = lowerer.irTypeOf(call); + const slot = lowerer.declareHiddenLocal("%never", type); + slot.mutable = true; + stmts.push( + { kind: "varDecl", localId: slot.id, init: null, loc }, + createRequireFence(lowerer, surface, call, hint), ); + return { + kind: "seqExpr", + stmts, + result: varRef(slot.id, type, loc), + diverges: true, + type, + loc, + }; } const spec = cr.spec; if (spec === "node:ffi") { diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 7b12297a8..539f27f80 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -144,6 +144,8 @@ import { nullishGenericBindingUnitOf } from "../binding-analysis.js"; import { blockedBindingUseDiag } from "../../../diagnostics/diagnostic.js"; import { npmStaticPackageOfPath } from "../../npm-static.js"; import { importMetaField, isImportMeta } from "../import-meta.js"; +import { createRequireCalleeFileOf } from "../builtins/module-bindings.js"; +import { createRequireResolveFenceValue } from "../builtins/module-resolution.js"; import { builtinPrototypeMethod, lowerArrayIsArrayValue, @@ -283,6 +285,21 @@ const DGRAM_METHOD_VALUES = new Set([ "unref", ]); +function isCreateRequireResolveValue(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { + let resolve: ts.Expression = expr; + if ( + expr.name.text === "paths" && + ts.isPropertyAccessExpression(expr.expression) && + !expr.expression.questionDotToken + ) + resolve = expr.expression; + return ( + ts.isPropertyAccessExpression(resolve) && + resolve.name.text === "resolve" && + createRequireCalleeFileOf(lowerer, resolve.expression) !== null + ); +} + export function lowerPropertyAccessExpr( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -1266,6 +1283,11 @@ export function lowerPropertyAccessExpr( return declared && declared.kind !== "void" ? lowerer.coerceToExpected(read, declared) : read; } } + // A createRequire loader's resolve read as a value (passed on, or + // picked by `??`): resolution happens at build time, so only calls + // with a literal specifier lower. + if (isCreateRequireResolveValue(lowerer, expr)) + return createRequireResolveFenceValue(lowerer, expr, loc); // The lib fence's PROPERTY chokepoint: a stdlib-declared member that // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all, // re.exec as a value, ...) reports SC2020 here. diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 6d8ffe4c0..54dd83714 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -2320,12 +2320,27 @@ export function isCreateRequireBinding7(program: ts.Program, callee: ts.Identifi ); } -/** Only literal calls and literal resolution queries can erase the loader - * value. In particular, assignments, exported aliases, shorthand properties, - * optional calls, and passing the loader to user code cannot pass this test. */ -function staticCreateRequireUse7(expr: ts.Expression): boolean { +/** How a use of a createRequire loader lowers: + * - "static": a literal call or a literal resolution query, which erases + * the loader value. + * - "deferred": a call with a computed specifier or another argument shape, + * or `resolve` / `resolve.paths` read as a value, inside the try block + * of a try statement in the same function. The call throws a runtime + * fence, and the resolve value is a function that throws one when called + * (module-resolution.ts), so the statement's catch clause handles them + * (packages that look for optional native addons). + * - "computed": the same shapes outside a try block. + * - "escaping": the loader itself as a value, writes, optional calls, and + * other members. */ +const COMPUTED_CREATE_REQUIRE_REASON = + "its createRequire result is used with a computed specifier, or its resolve as a value, outside a try block (static requires need literal specifiers)"; + +type CreateRequireUse7 = "static" | "deferred" | "computed" | "escaping"; + +function createRequireUse7(expr: ts.Expression): CreateRequireUse7 { let use: ts.Node = expr; while (ts.isParenthesizedExpression(use.parent)) use = use.parent; + let member = false; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && @@ -2333,6 +2348,7 @@ function staticCreateRequireUse7(expr: ts.Expression): boolean { !use.parent.questionDotToken ) { use = use.parent; + member = true; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && @@ -2342,15 +2358,42 @@ function staticCreateRequireUse7(expr: ts.Expression): boolean { use = use.parent; } const call = use.parent; + if (ts.isCallExpression(call) && call.expression === use) { + if (call.questionDotToken) return "escaping"; + if (call.arguments.nodes.length === 1 && ts.isStringLiteralLike(call.arguments.nodes[0]!)) + return "static"; + } else if (!member || isAssignmentTarget7(use)) { + return "escaping"; + } + return insideTryBlock7(use) ? "deferred" : "computed"; +} + +function isAssignmentTarget7(node: ts.Node): boolean { + const parent = node.parent; return ( - ts.isCallExpression(call) && - call.expression === use && - !call.questionDotToken && - call.arguments.nodes.length === 1 && - ts.isStringLiteralLike(call.arguments.nodes[0]!) + ts.isBinaryExpression(parent) && + parent.left === node && + parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment ); } +/** True when `node` is inside the try block of a try statement in the same + * function (a catch clause or finally block does not count). A class field + * initializer runs when the class is instantiated, so it is a boundary too. */ +function insideTryBlock7(node: ts.Node): boolean { + for (let child = node, parent = node.parent; parent; child = parent, parent = parent.parent) { + if (ts.isTryStatement(parent) && parent.tryBlock === child) return true; + if ( + ts.isFunctionLike(parent) || + ts.isClassStaticBlockDeclaration(parent) || + ts.isPropertyDeclaration(parent) + ) + return false; + } + return false; +} + function stableCreateRequireBindingReason7( program: ts.Program, decl: ts.VariableDeclaration, @@ -2383,9 +2426,10 @@ function stableCreateRequireBindingReason7( : checker.getSymbolAtLocation(ref); if (target && (target.flags & ts.SymbolFlags.Alias) !== 0) target = checker.getAliasedSymbol(target); - if (target === symbol && !staticCreateRequireUse7(ref)) { - return "its createRequire binding is reassigned, escapes, or uses a computed specifier"; - } + if (target !== symbol) continue; + const use = createRequireUse7(ref); + if (use === "computed") return COMPUTED_CREATE_REQUIRE_REASON; + if (use === "escaping") return "its createRequire binding is reassigned or escapes"; } const preceding = stmt.declarationList.declarations.nodes.slice( 0, @@ -2448,10 +2492,10 @@ function npmStaticModuleImportReason7( checker.prefetchSymbolNodesExact(identifierOccurrences7(sf, specifier.name.text)); let reason: string | null = null; const checkUse = (expr: ts.Expression): void => { - if (!staticCreateRequireUse7(expr)) { - reason = - "its createRequire result escapes or is used with a computed specifier (static requires need literal specifiers)"; - } + const use = createRequireUse7(expr); + if (use === "computed") reason = COMPUTED_CREATE_REQUIRE_REASON; + else if (use === "escaping") + reason = "its createRequire result escapes (static requires need literal specifiers)"; }; ts.walkPreorder(sf, (node) => { if ( diff --git a/tests/fixtures/npm-static/addon-probe-cli.ts b/tests/fixtures/npm-static/addon-probe-cli.ts new file mode 100644 index 000000000..4515fb5bf --- /dev/null +++ b/tests/fixtures/npm-static/addon-probe-cli.ts @@ -0,0 +1,5 @@ +import { probe, resolveHelper } from "addon-probe"; +import { join } from "node:path"; + +console.log(join("a", "b"), [probe("clipboard"), probe("keys"), resolveHelper("addon-probe-helper")].join(",")); +console.log(resolveHelper("other", (id) => `custom:${id}`)); diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts new file mode 100644 index 000000000..cfacc4764 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts @@ -0,0 +1,2 @@ +export declare function probe(name: string): string; +export declare function resolveHelper(name: string, resolvePackage?: (id: string) => string): string; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.js b/tests/fixtures/npm-static/node_modules/addon-probe/index.js new file mode 100644 index 000000000..e65b1ad0e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.js @@ -0,0 +1,28 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const load = createRequire(import.meta.url); +const here = dirname(fileURLToPath(import.meta.url)); + +// Looks for a prebuilt addon in each packaging location, the way +// packages with optional native helpers do. +export function probe(name) { + for (const dir of ["prebuilds", "native"]) { + try { + const addon = load(join(here, dir, `${name}.node`)); + if (typeof addon?.run === "function") return "loaded"; + } catch { + // Try the next location. + } + } + return "absent"; +} + +export function resolveHelper(name, resolvePackage) { + try { + return (resolvePackage ?? load.resolve)(name); + } catch { + return "unresolved"; + } +} diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/package.json b/tests/fixtures/npm-static/node_modules/addon-probe/package.json new file mode 100644 index 000000000..a2f7406f5 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/package.json @@ -0,0 +1,7 @@ +{ + "name": "addon-probe", + "version": "1.0.0", + "type": "module", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/__snapshots__/create-require.ts.txt b/tests/harness/__snapshots__/create-require.ts.txt index 219af92a4..a3c56a033 100644 --- a/tests/harness/__snapshots__/create-require.ts.txt +++ b/tests/harness/__snapshots__/create-require.ts.txt @@ -7,14 +7,14 @@ create-require.ts:13:13 - error SC2020: 'module.createRequire' is part of the st hint: the lowered shape is a const binding (or a proven stable top-level let/var) over createRequire(import.meta.url) (or __filename) whose require calls take STATIC string literals — builtins, project modules (including #imports), relative .json documents and natively compiled npm packages resolve at build time; dynamic specifiers cannot exist in a compiled binary's fixed module graph -create-require.ts:20:1 - error SC2020: 'createRequire's require with this argument shape' is part of the standard library types but has no scriptc lowering yet +create-require.ts:20:1 - error SC2020: 'createRequire's require with a computed specifier or this argument shape' is part of the standard library types but has no scriptc lowering yet 19 | const which = "left" + "-pad"; 20 | require(which); | ^~~~~~~~~~~~~~ 21 | - hint: the compiled module graph is fixed at build time — the one lowered form is require("") + hint: the compiled module graph is fixed at build time — the one lowered form is require(""); native addons are not loaded create-require.ts:23:1 - error SC2020: 'createRequire's require of './helper.js'' is part of the standard library types but has no scriptc lowering yet diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 388da2138..026da8f4e 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -423,6 +423,40 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { }, ); + test.each(["llvm"] as const)( + "a package probing for optional addons through createRequire compiles with deferred refusals (%s)", + async (backend) => { + const entry = join(pilotRoot, "addon-probe-cli.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toEqual([{ package: "addon-probe", status: "static" }]); + expect(coverage.diagnostics).toHaveLength(0); + expect( + (coverage.runtimeFences ?? []).filter((f) => f.code === "SC2020").map((f) => f.message), + ).toEqual([ + expect.stringContaining("'createRequire's require with a computed specifier"), + expect.stringContaining("'createRequire's require.resolve as a value'"), + ]); + const outDir = join(cacheDir, "addon-probe", sanitize ? "san" : "plain", backend); + const result = await compile(entry, { + backend, + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary(process.execPath, [entry]), + runBinary(result.binaryPath, []), + ]); + expect(nativeRes.stdout.toString()).toBe("a/b absent,absent,unresolved\ncustom:other\n"); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, + ); + test.each([ [ "computed specifier", @@ -430,6 +464,31 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { "computed specifier", ], ["escaped require", 'const alias = load; const path = alias("node:path");', "escapes"], + [ + "computed specifier in a catch clause", + 'try { load("node:path"); } catch { load(String(1)); } const path = load("node:path");', + "outside a try block", + ], + [ + "computed specifier in a function inside a try block", + 'try { [1].map((n) => load(String(n))); } catch {} const path = load("node:path");', + "outside a try block", + ], + [ + "computed specifier in a class field inside a try block", + 'try { new (class { addon = load(String(1)); })(); } catch {} const path = load("node:path");', + "outside a try block", + ], + [ + "resolve value outside a try block", + 'const find = load.resolve; const path = load("node:path");', + "outside a try block", + ], + [ + "escaped require in a try block", + 'try { const alias = load; alias("node:path"); } catch {} const path = load("node:path");', + "escapes", + ], [ "reassigned require", 'let load = createLoader(import.meta.url); load = () => ({}); const path = load("node:path");', From ccd45c748f91d060ec0631c53df93f34bac383dc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 12:21:50 -0500 Subject: [PATCH 028/395] Address the Node island report review findings --- packages/compiler/src/cli/command.ts | 15 +++- packages/compiler/src/diagnostics/envelope.ts | 5 +- packages/compiler/src/diagnostics/render.ts | 27 ++++++- packages/compiler/src/executable/prepare.ts | 6 +- packages/compiler/src/frontend/analysis.ts | 4 +- .../compiler/src/frontend/pipeline-native.ts | 4 +- packages/compiler/src/frontend/pipeline.ts | 78 ++++++++++++------- packages/compiler/src/index.ts | 9 ++- packages/compiler/src/native/compiler.ts | 3 +- .../node-islands-config/main.ts.json | 6 -- .../node-islands-config/main.ts | 4 + .../node-islands-config/package.json | 0 tests/diagnostics/node-islands-config/main.ts | 3 - .../node-islands-config/main.ts.txt | 35 --------- tests/harness/coverage.test.ts | 28 +++++++ 15 files changed, 141 insertions(+), 86 deletions(-) delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json create mode 100644 tests/coverage-fixtures/node-islands-config/main.ts rename tests/{diagnostics => coverage-fixtures}/node-islands-config/package.json (100%) delete mode 100644 tests/diagnostics/node-islands-config/main.ts delete mode 100644 tests/harness/__snapshots__/node-islands-config/main.ts.txt diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 2d15cbbb3..a660d339a 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -19,7 +19,7 @@ import { } from "../node-islands/policy.js"; import { TIER_MODES, type TierMode } from "../frontend/tier-analysis.js"; import { buildEnvelope } from "../diagnostics/envelope.js"; -import { renderWarnings } from "../diagnostics/render.js"; +import { renderConfigWarnings, renderWarnings } from "../diagnostics/render.js"; import { isPerformanceCode, renderPerformanceWarnings } from "../diagnostics/dynamic-tier.js"; import { isNativeOptimization } from "../backend/optimization.js"; import { setProvenanceSources } from "../frontend/provenance-registry.js"; @@ -609,14 +609,21 @@ async function main(args: string[], host: CliHost): Promise { // warning names likely-hot functions that use untyped values. `run` // keeps stderr for the program's own output. const color = process.stderr.isTTY ?? false; - const divergences = warnings.filter((d) => !isPerformanceCode(d.code)); + const config = warnings.filter((d) => d.code === "SC8004"); + const divergences = warnings.filter((d) => !isPerformanceCode(d.code) && d.code !== "SC8004"); const performance = warnings.filter((d) => isPerformanceCode(d.code)); - if (divergences.length > 0) + if (config.length > 0) + process.stderr.write( + renderConfigWarnings(config, result.sourceTexts ?? new Map(), { color }) + "\n", + ); + if (divergences.length > 0) { + if (config.length > 0) process.stderr.write("\n"); process.stderr.write( renderWarnings(divergences, result.sourceTexts ?? new Map(), { color }) + "\n", ); + } if (performance.length > 0) { - if (divergences.length > 0) process.stderr.write("\n"); + if (divergences.length > 0 || config.length > 0) process.stderr.write("\n"); process.stderr.write(renderPerformanceWarnings(performance) + "\n"); } } diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index ff08c3968..ad2e31507 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -334,7 +334,10 @@ export function locateDiagnostic( return { code: diag.code, category: categoryOf(diag), - severity: isDivergenceCode(diag.code) || isPerformanceCode(diag.code) ? "warning" : "error", + severity: + isDivergenceCode(diag.code) || isPerformanceCode(diag.code) || diag.code === "SC8004" + ? "warning" + : "error", scope, file: diag.loc.file, line: start.line, diff --git a/packages/compiler/src/diagnostics/render.ts b/packages/compiler/src/diagnostics/render.ts index e5a75c6bc..16fe3f0bf 100644 --- a/packages/compiler/src/diagnostics/render.ts +++ b/packages/compiler/src/diagnostics/render.ts @@ -102,14 +102,12 @@ export function renderDiagnostic( frame.push( c(DIM, ` ${" ".repeat(gutterWidth)} | `) + " ".repeat(caretCol) + - c(/^SC6\d{3}$/.test(diag.code) ? YELLOW : RED, "^" + "~".repeat(spanOnLine - 1)), + c(isWarningCode(diag.code) ? YELLOW : RED, "^" + "~".repeat(spanOnLine - 1)), ); emitLine(lineNum + 1); } - // The divergence band (SC6xxx) is the one warning severity: the program - // compiles, and the site can behave differently from Node. - const warning = /^SC6\d{3}$/.test(diag.code); + const warning = isWarningCode(diag.code); out.push( `${c(BOLD, `${diag.loc.file}:${lineNum}:${colNum}`)} - ${warning ? c(YELLOW, "warning") : c(RED, "error")} ${c(BOLD, diag.code)}: ${diag.message}`, ); @@ -154,6 +152,27 @@ export function renderDiagnostics( /** Divergence warnings after a successful build: the rendered sites plus a * one-line count, on stderr. */ +/** The warning severities: the divergence band (SC6xxx), where the program + * compiles and the site can behave differently from Node, and unknown + * settings in the project's scriptc configuration (SC8004), which builds do + * not use. */ +export function isWarningCode(code: string): boolean { + return /^SC6\d{3}$/.test(code) || code === "SC8004"; +} + +/** A build's SC8004 warnings: the unknown scriptc settings it ignored. */ +export function renderConfigWarnings( + diags: readonly ScrDiagnostic[], + sourceTextByFile: Map, + opts: RenderOptions = {}, +): string { + const n = diags.length; + return ( + renderDiagnostics([...diags], sourceTextByFile, opts) + + `\n\n${n} warning${n === 1 ? "" : "s"}: the build does not use ${n === 1 ? "this setting" : "these settings"}; scriptc coverage reports Node islands once they are valid.` + ); +} + export function renderWarnings( diags: readonly ScrDiagnostic[], sourceTextByFile: Map, diff --git a/packages/compiler/src/executable/prepare.ts b/packages/compiler/src/executable/prepare.ts index 1771f98f5..027bd6ca6 100644 --- a/packages/compiler/src/executable/prepare.ts +++ b/packages/compiler/src/executable/prepare.ts @@ -122,6 +122,10 @@ export function prepareExecutableModule( mod: lowered.module!, sourceTexts, stats: lowered.stats, - warnings: [...(lowered.divergences ?? []), ...(lowered.performance ?? [])], + warnings: [ + ...(fe.warnings ?? []), + ...(lowered.divergences ?? []), + ...(lowered.performance ?? []), + ], }; } diff --git a/packages/compiler/src/frontend/analysis.ts b/packages/compiler/src/frontend/analysis.ts index 403cb70b7..848306b66 100644 --- a/packages/compiler/src/frontend/analysis.ts +++ b/packages/compiler/src/frontend/analysis.ts @@ -38,7 +38,9 @@ export function analyzeWithFrontend( } ffi = loaded.profile; } - const fe = createFrontend(entryPath, opts.npmStatic, opts.externalTypes); + const fe = createFrontend(entryPath, opts.npmStatic, opts.externalTypes, undefined, { + nodeIslands: true, + }); try { const emptyStats = { statementsTotal: 0, diff --git a/packages/compiler/src/frontend/pipeline-native.ts b/packages/compiler/src/frontend/pipeline-native.ts index 511fd62f2..f2df52608 100644 --- a/packages/compiler/src/frontend/pipeline-native.ts +++ b/packages/compiler/src/frontend/pipeline-native.ts @@ -1,4 +1,4 @@ -import { runFrontend, type Frontend } from "./pipeline.js"; +import { runFrontend, type Frontend, type FrontendOptions } from "./pipeline.js"; import { loadProgram } from "./program.js"; import { FrontendServices, type ComptimeEvaluator } from "./services.js"; import { createNativeTs7Api } from "./ts7/native-api.js"; @@ -13,6 +13,7 @@ export function runNativeFrontend( externalTypes?: Readonly>, evaluateComptime?: ComptimeEvaluator, libraryNpmStatic: readonly string[] = [], + options: FrontendOptions = {}, ): Frontend { const services = new FrontendServices( (options) => createNativeTs7Api({ ...options, executable }), @@ -31,6 +32,7 @@ export function runNativeFrontend( npmStatic, externalTypes, libraryNpmStatic, + options, ); return { ...frontend, diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 1021202cc..4e6a1c0f5 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -115,18 +115,30 @@ export interface Frontend { /** The Node island detector's input (report-only): the program's * modules with their pre-scan markers, package import edges, and how * each refused package was refused. Call before dispose. */ - nodeIslandScan?: (opts: { userJs: boolean }) => NodeIslandScan; + nodeIslandScan?: ((opts: { userJs: boolean }) => NodeIslandScan) | undefined; + /** Diagnostics that do not fail a build: unknown settings in the + * project's scriptc configuration (SC8004), which builds do not use. */ + warnings?: ScrDiagnostic[]; /** Releases the frontend's resources (the spawned tsgo server). Call * exactly once, after the last lower(). */ dispose: () => void; } /** Hosts own transport lifetimes; shared compiler stages own the frontend. */ +/** What a frontend computes beyond the build's own needs. */ +export interface FrontendOptions { + /** The Node island report (coverage): the project's scriptc settings and + * the detector's scan of the program and its npm packages. Builds do not + * read either. */ + nodeIslands?: boolean; +} + export type FrontendFactory = ( entryPath: string, npmStatic?: readonly string[] | "auto" | "lib", externalTypes?: Readonly>, libraryNpmStatic?: readonly string[], + options?: FrontendOptions, ) => Frontend; /** Adds the program modules a literal `import()` reaches to `scanned`, @@ -426,6 +438,7 @@ export function runFrontend( npmStatic?: readonly string[] | "auto" | "lib", externalTypes?: Readonly>, libraryNpmStatic: readonly string[] = [], + options: FrontendOptions = {}, ): Frontend { // A preflight or package probe may throw after its host has opened. Track // only live loads, releasing each immediately on normal fallback; this @@ -465,6 +478,7 @@ export function runFrontend( externalTypes, [...new Set(names)], transitive, + options, ); } catch (error) { for (const load of active) { @@ -486,6 +500,7 @@ function loadFrontend( externalTypes?: Readonly>, libraryNpmStatic: readonly string[] = [], transitive: Set = new Set(), + options: FrontendOptions = {}, ): Frontend { // Resolver package/workspace metadata is intentionally shared across the // several load attempts of ONE auto-detection fixpoint, but never across @@ -632,7 +647,7 @@ function loadFrontend( } const dropping = [...reasons.keys()].filter((p) => effective.has(p)); if (dropping.length === 0) break; - scanDroppedPackages(load, dropping, droppedScan); + if (options.nodeIslands === true) scanDroppedPackages(load, dropping, droppedScan); for (const p of dropping) { effective.delete(p); statuses.push({ package: p, status: "refused", detail: reasons.get(p)! }); @@ -684,13 +699,15 @@ function loadFrontend( probe.dispose(); if (probeDiags.some((d) => d.code === "SC0001")) dropWithNote(p); } - scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); + if (options.nodeIslands === true) + scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); load.dispose(); load = loadProgram(entryPath, { npmStatic: [...effective], externalTypes }); preflight = checkPreflight(load); if (preflight.some((d) => d.code === "SC0001") && effective.size > 0) { for (const p of [...effective]) dropWithNote(p); - scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); + if (options.nodeIslands === true) + scanDroppedPackages(load, lastResortDropped.splice(0), droppedScan); load.dispose(); load = loadProgram(entryPath, { npmStatic: [...effective], externalTypes }); preflight = checkPreflight(load); @@ -711,12 +728,16 @@ function loadFrontend( } const finalLoad = load; - // An unknown key or value in the project's scriptc configuration fails - // the build before anything else (SC8004). + // An unknown key or value in the project's scriptc configuration makes + // the Node island report unavailable (SC8004). The settings configure + // only that report: a build warns about them and goes on. const projectConfig = readProjectConfig(entryPath); const configDiagnostics = projectConfig.diagnostics; + const report = options.nodeIslands === true; return { - preflight: configDiagnostics.length > 0 ? [...configDiagnostics, ...preflight] : preflight, + preflight: + report && configDiagnostics.length > 0 ? [...configDiagnostics, ...preflight] : preflight, + ...(!report && configDiagnostics.length > 0 ? { warnings: configDiagnostics } : {}), entryText: () => finalLoad.entry.text, entryExports: () => entryFunctionExports(finalLoad.entry), // The contract scans the PROGRAM's source files, not the runtime @@ -779,26 +800,29 @@ function loadFrontend( }, npmStatic: statuses, npmImportSites: npmSites, - nodeIslandScan: ({ userJs }) => { - const files = new Set([finalLoad.entry, ...finalLoad.moduleOrder]); - addDynamicImportGraph(finalLoad, files); - // Package edges matter only when the program has npm packages: one - // that loads, one that was refused, or one judged and dropped. - const npm = - statuses.length > 0 || - droppedScan.modules.length > 0 || - [...files].some((sf) => sf.fileName.includes("/node_modules/")); - const scanned = scanFiles(finalLoad.program, [...files], { - userJs, - dropped: false, - edges: npm, - }); - return { - modules: [...droppedScan.modules, ...scanned.modules], - edges: [...droppedScan.edges, ...scanned.edges], - refusals, - }; - }, + nodeIslandScan: + options.nodeIslands !== true + ? undefined + : ({ userJs }) => { + const files = new Set([finalLoad.entry, ...finalLoad.moduleOrder]); + addDynamicImportGraph(finalLoad, files); + // Package edges matter only when the program has npm packages: one + // that loads, one that was refused, or one judged and dropped. + const npm = + statuses.length > 0 || + droppedScan.modules.length > 0 || + [...files].some((sf) => sf.fileName.includes("/node_modules/")); + const scanned = scanFiles(finalLoad.program, [...files], { + userJs, + dropped: false, + edges: npm, + }); + return { + modules: [...droppedScan.modules, ...scanned.modules], + edges: [...droppedScan.edges, ...scanned.edges], + refusals, + }; + }, dispose: finalLoad.dispose, }; } diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 41f5cd619..14bdebf3c 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -412,8 +412,13 @@ export async function analyzeCached( return result; } -const nodeFrontend: FrontendFactory = (entry, npmStatic, externalTypes, libraryNpmStatic) => - runFrontend(entry, loadProgram, npmStatic, externalTypes, libraryNpmStatic); +const nodeFrontend: FrontendFactory = ( + entry, + npmStatic, + externalTypes, + libraryNpmStatic, + options, +) => runFrontend(entry, loadProgram, npmStatic, externalTypes, libraryNpmStatic, options); /** The checker's part of the executable cache key (`checkerCacheIdentity`), as the CLI's startup route computes it. */ function checkerField(): { checker?: string } { diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index a37d76f80..8f88356e9 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -108,7 +108,7 @@ export class NativeCompiler { private readonly cache: NativeCache | null = null, ) { const evaluator = toolchain.comptimeExecutable; - this.frontend = (entry, npmStatic, externalTypes, libraryNpmStatic) => + this.frontend = (entry, npmStatic, externalTypes, libraryNpmStatic, options) => runNativeFrontend( entry, toolchain.ts7Executable, @@ -119,6 +119,7 @@ export class NativeCompiler { : (source, timeout) => evaluateNativeComptime(source, timeout, toolchain.ts7Executable, evaluator), libraryNpmStatic, + options, ); } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json deleted file mode 100644 index 6b86e7f9a..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/node-islands-config/main.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/node-islands-config/main.ts" - ], - "diags": [] -} diff --git a/tests/coverage-fixtures/node-islands-config/main.ts b/tests/coverage-fixtures/node-islands-config/main.ts new file mode 100644 index 000000000..5bacea88a --- /dev/null +++ b/tests/coverage-fixtures/node-islands-config/main.ts @@ -0,0 +1,4 @@ +// Unknown values in scriptc.packages or scriptc.nodeIslands are SC8004: a +// build warns about them and goes on (it does not use the settings), and +// coverage reports no Node islands until they are valid. +console.log("configured"); diff --git a/tests/diagnostics/node-islands-config/package.json b/tests/coverage-fixtures/node-islands-config/package.json similarity index 100% rename from tests/diagnostics/node-islands-config/package.json rename to tests/coverage-fixtures/node-islands-config/package.json diff --git a/tests/diagnostics/node-islands-config/main.ts b/tests/diagnostics/node-islands-config/main.ts deleted file mode 100644 index cc7b3735f..000000000 --- a/tests/diagnostics/node-islands-config/main.ts +++ /dev/null @@ -1,3 +0,0 @@ -// An unknown value in scriptc.packages or scriptc.nodeIslands is SC8004: -// the configuration the project asked for is never silently ignored. -console.log("configured"); diff --git a/tests/harness/__snapshots__/node-islands-config/main.ts.txt b/tests/harness/__snapshots__/node-islands-config/main.ts.txt deleted file mode 100644 index 03cc714aa..000000000 --- a/tests/harness/__snapshots__/node-islands-config/main.ts.txt +++ /dev/null @@ -1,35 +0,0 @@ -node-islands-config/package.json:6:7 - error SC8004: package.json 'scriptc.packages.encoding' has the unknown value "unloded" - - 5 | "packages": { - 6 | "encoding": "unloded", - | ^ - 7 | "debug": { "run": "island" } - - hint: use one of "unloaded", "node", "native", "auto", or an object with run, evaluate, share, marshal, schedule - -node-islands-config/package.json:7:18 - error SC8004: package.json 'scriptc.packages.debug.run' has the unknown value "island" - - 6 | "encoding": "unloded", - 7 | "debug": { "run": "island" } - | ^ - 8 | }, - - hint: use one of "native", "node", "auto" - -node-islands-config/package.json:10:7 - error SC8004: package.json 'scriptc.nodeIslands.auto' must be true or false, not "yes" - - 9 | "nodeIslands": { - 10 | "auto": "yes", - | ^ - 11 | "mode": "auto" - - hint: use a boolean - -node-islands-config/package.json:11:7 - error SC8004: package.json 'scriptc.nodeIslands.mode' is not a known Node island setting - - 10 | "auto": "yes", - 11 | "mode": "auto" - | ^ - 12 | } - - hint: the settings are auto, modules, node, nodeVersion, packaging, speculativeStart \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 2c34a69e1..8dd7173ad 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -5,6 +5,7 @@ import { join } from "node:path"; import { expect, test } from "vitest"; import { analyze, + compile, coverageEnvelope, coveragePasses, renderCoverage, @@ -871,6 +872,33 @@ test("Node islands (report-only): one package per class, gap islands first", asy expect(coveragePasses(coverage, "node-islands")).toBe(false); }); +test("unknown scriptc settings warn in a build and make the island report unavailable", async () => { + const entry = fixture("node-islands-config/main.ts"); + const outDir = mkdtempSync(join(tmpdir(), "scriptc-island-config-")); + try { + const built = await compile(entry, { + outputKind: "ir", + outPath: join(outDir, "main.ir.json"), + outDir, + }); + expect(built.ok).toBe(true); + if (!built.ok) return; + expect((built.warnings ?? []).map((d) => [d.code, d.message])).toEqual([ + ["SC8004", `package.json 'scriptc.packages.encoding' has the unknown value "unloded"`], + ["SC8004", `package.json 'scriptc.packages.debug.run' has the unknown value "island"`], + ["SC8004", `package.json 'scriptc.nodeIslands.auto' must be true or false, not "yes"`], + ["SC8004", "package.json 'scriptc.nodeIslands.mode' is not a known Node island setting"], + ]); + } finally { + rmSync(outDir, { recursive: true, force: true }); + } + const { coverage } = analyze(entry); + expect(coverage.nodeIslands).toBeUndefined(); + expect(renderCoverage({ ...coverage, file: "main.ts" }, { root: repoRoot })).toContain( + "not analyzable: the project's scriptc configuration has 4 unknown settings", + ); +}); + test("Node island policy from the command line: native, forced, off", () => { const entry = fixture("node-islands/main.ts"); const forced = analyze(entry, { From 8afec299b7816138e187c8f18e6d2c1a1f46e771 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 17:52:58 -0500 Subject: [PATCH 029/395] Kill corpus 6611's children after they start, and run the second module after the first close The children 6611 spawned ran `process.stdout.write('ok')` and were killed right after spawn. When a child exited before the SIGTERM took effect, Node reported `close 0` for it and closed it after the second child, so the expected output depended on process start-up timing. - The program re-runs itself as the child (`main.cjs child`), which writes `ready` and then waits. Each parent kills its child on the first stdout data, so both closes report `null` and `SIGTERM`. - definer.cjs runs on the first child's `close` instead of its `exit`, so the first close always prints before the second module's output. --- .../definer.cjs | 2 +- .../main.cjs | 33 +++++++++++-------- .../spawner.cjs | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/tests/corpus/6611-child-properties-across-modules/definer.cjs b/tests/corpus/6611-child-properties-across-modules/definer.cjs index 8b1c8f1a3..ea58bc046 100644 --- a/tests/corpus/6611-child-properties-across-modules/definer.cjs +++ b/tests/corpus/6611-child-properties-across-modules/definer.cjs @@ -21,5 +21,5 @@ module.exports = function run() { console.log(e instanceof TypeError, e.message.length, e.message.slice(0, 30), e.message.slice(-40)); } child.on("close", (code) => console.log("close", code, child.killedBy)); - child.kill("SIGTERM"); + child.stdout.once("data", () => child.kill("SIGTERM")); }; diff --git a/tests/corpus/6611-child-properties-across-modules/main.cjs b/tests/corpus/6611-child-properties-across-modules/main.cjs index a3f422c64..3dfb6756a 100644 --- a/tests/corpus/6611-child-properties-across-modules/main.cjs +++ b/tests/corpus/6611-child-properties-across-modules/main.cjs @@ -5,17 +5,24 @@ // and writes a getter-only property with a long name. const patched = require("./patcher.cjs"); const definer = require("./definer.cjs"); -const child = patched(); -console.log(child.label); -child.label = "x"; -console.log(child.stored, child.same); -try { - child.same = 1; -} catch (e) { - console.log(e instanceof TypeError, e.message); +if (process.argv.at(2) === "child") { + // The child reports that it started, then waits for the parent's SIGTERM. + // It exits after 30 seconds without one. + process.stdout.write("ready"); + setTimeout(() => {}, 30000); +} else { + const child = patched(); + console.log(child.label); + child.label = "x"; + console.log(child.stored, child.same); + try { + child.same = 1; + } catch (e) { + console.log(e instanceof TypeError, e.message); + } + child.on("close", (code) => { + console.log("close", code, child.killedBy); + }); + child.stdout.once("data", () => child.kill("SIGTERM")); + child.on("close", () => definer()); } -child.on("close", (code) => { - console.log("close", code, child.killedBy); -}); -child.kill("SIGTERM"); -child.on("exit", () => definer()); diff --git a/tests/corpus/6611-child-properties-across-modules/spawner.cjs b/tests/corpus/6611-child-properties-across-modules/spawner.cjs index d0c52d9b7..5701145a8 100644 --- a/tests/corpus/6611-child-properties-across-modules/spawner.cjs +++ b/tests/corpus/6611-child-properties-across-modules/spawner.cjs @@ -1,5 +1,5 @@ "use strict"; const { spawn } = require("child_process"); module.exports = function start() { - return spawn(process.execPath, ["-e", "process.stdout.write('ok')"]); + return spawn(process.execPath, [process.argv[1], "child"]); }; From 072543f5b98e5757b73a23b00127e8c7e2186417 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 18:01:19 -0500 Subject: [PATCH 030/395] Support X509Certificate fields and methods, TLS certificates and tls.Server contexts --- docs/content/docs/limitations.mdx | 8 + .../limitations/builtin-apis/crypto.mdx | 4 + docs/content/limitations/tls-certificates.mdx | 11 + docs/content/limitations/tls-sessions.mdx | 3 + .../src/generated/node-v24-compatibility.json | 126 +- .../generated/node-v24-internal.json | 232 ++- internal/compatibility/static-support.json | 8 + .../ambient/scriptc-node-fallback.d.ts | 108 +- .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/library-pack.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 22 + packages/compiler/src/backend/llvm/lib-tls.ts | 25 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 4 + .../compiler/src/backend/native/executable.ts | 3 + .../compiler/src/backend/native/library.ts | 3 +- .../src/backend/runtime-pack-targets.test.ts | 1 + .../compiler/src/backend/runtime-pack.test.ts | 1 + packages/compiler/src/backend/warm-cache.ts | 1 + .../src/executable/executable-cache.test.ts | 1 + .../src/executable/executable-cache.ts | 2 + .../src/frontend/lowering/builtins/x509.ts | 487 +++++ .../frontend/lowering/expressions/binary.ts | 2 + .../lowering/expressions/object-literals.ts | 8 + .../lowering/expressions/property-access.ts | 6 + .../src/frontend/lowering/lower-calls.ts | 3 + .../src/frontend/lowering/lower-classes.ts | 286 ++- .../src/frontend/lowering/lower-server.ts | 125 +- .../src/frontend/lowering/lower-stmts.ts | 2 + .../compiler/src/frontend/lowering/lowerer.ts | 3 + packages/compiler/src/frontend/type-mapper.ts | 74 +- packages/compiler/src/index.ts | 3 + packages/compiler/src/ir/builtin-effects.ts | 15 + .../compiler/src/ir/builtin-signatures.ts | 46 + packages/compiler/src/ir/runtime-features.ts | 32 + packages/compiler/src/library/ir-policy.ts | 3 + .../src/library/library-cache.test.ts | 13 + .../compiler/src/library/library-cache.ts | 3 + packages/compiler/src/native/cli.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../compiler/src/native/sanitizer.test.ts | 1 + .../6936-x509-certificate-fields.ts.json | 6 + .../corpus/7090-x509-check-names.ts.json | 6 + .../corpus/7091-x509-certificate-keys.ts.json | 6 + .../corpus/7092-x509-legacy-object.ts.json | 6 + .../corpus/7093-x509-methods-js.mjs.json | 6 + .../corpus/7095-tls-peer-certificates.ts.json | 6 + .../7096-tls-peer-certificates.mjs.json | 6 + .../7097-tls-client-certificates.ts.json | 6 + ...7098-tls-client-certificates-paths.ts.json | 6 + .../7099-tls-client-certificates.mjs.json | 6 + .../7440-tls-server-add-context.ts.json | 6 + .../corpus/7441-tls-client-error.ts.json | 6 + .../corpus/7442-tls-server-contexts.mjs.json | 6 + .../corpus/7443-tls-final-flight.ts.json | 6 + .../corpus/7444-tls-final-flight.mjs.json | 6 + .../corpus/7454-tls-peer-embedded-pem.ts.json | 6 + .../corpus/7455-tls-als-contexts.mjs.json | 6 + .../tests/diagnostics/x509-fences.ts.json | 6 + .../tls-client-certificates.ts.json | 6 + .../node-types/tls-peer-certificates.ts.json | 6 + .../node-types/tls-server-contexts.ts.json | 6 + .../fixtures/node-types/x509-methods.ts.json | 6 + .../runtime-pack-common/library-matrix.mjs | 1 + .../runtime-pack-matrix.mjs | 3 + .../runtime-wasm32-wasi/scripts/build.mjs | 1 + packages/runtime/src/scr_crypto.h | 31 + packages/runtime/src/scr_lib.c | 21 +- packages/runtime/src/scr_net.c | 67 +- packages/runtime/src/scr_net.h | 18 + packages/runtime/src/scr_pki.c | 217 ++ packages/runtime/src/scr_tls.c | 966 ++++++++- packages/runtime/src/scr_tls.h | 19 + packages/runtime/src/scr_tls_x509.c | 119 ++ packages/runtime/src/scr_x509.c | 1820 +++++++++++++++++ tests/corpus/6936-x509-certificate-fields.ts | 189 ++ tests/corpus/7090-x509-check-names.ts | 141 ++ tests/corpus/7091-x509-certificate-keys.ts | 332 +++ tests/corpus/7092-x509-legacy-object.ts | 158 ++ tests/corpus/7093-x509-methods-js.mjs | 158 ++ tests/corpus/7095-tls-peer-certificates.ts | 188 ++ tests/corpus/7096-tls-peer-certificates.mjs | 35 + tests/corpus/7097-tls-client-certificates.ts | 84 + .../7098-tls-client-certificates-paths.ts | 161 ++ tests/corpus/7099-tls-client-certificates.mjs | 34 + tests/corpus/7440-tls-server-add-context.ts | 119 ++ tests/corpus/7441-tls-client-error.ts | 93 + tests/corpus/7442-tls-server-contexts.mjs | 51 + tests/corpus/7443-tls-final-flight.ts | 114 ++ tests/corpus/7444-tls-final-flight.mjs | 28 + tests/corpus/7454-tls-peer-embedded-pem.ts | 46 + tests/corpus/7455-tls-als-contexts.mjs | 48 + tests/diagnostics/x509-fences.ts | 21 + .../node-types/tls-client-certificates.ts | 32 + .../node-types/tls-peer-certificates.ts | 37 + .../node-types/tls-server-contexts.ts | 38 + tests/fixtures/node-types/x509-methods.ts | 20 + tests/fixtures/server/certs/README.md | 12 + tests/fixtures/server/certs/client-key.pem | 5 + tests/fixtures/server/certs/client.cnf | 7 + tests/fixtures/server/certs/client.pem | 11 + tests/fixtures/server/certs/embedded-key.pem | 5 + tests/fixtures/server/certs/embedded.cnf | 9 + tests/fixtures/server/certs/embedded.pem | 26 + tests/fixtures/tls/empty-ca.ts | 36 + .../tls-sni-callback-fences.ts.txt | 2 +- .../harness/__snapshots__/x509-fences.ts.txt | 48 + .../6936-x509-certificate-fields.ts.json | 1 + .../7090-x509-check-names.ts.json | 1 + .../7091-x509-certificate-keys.ts.json | 1 + .../7092-x509-legacy-object.ts.json | 1 + .../7093-x509-methods-js.mjs.json | 1 + .../7095-tls-peer-certificates.ts.json | 1 + .../7096-tls-peer-certificates.mjs.json | 1 + .../7097-tls-client-certificates.ts.json | 1 + ...7098-tls-client-certificates-paths.ts.json | 1 + .../7099-tls-client-certificates.mjs.json | 1 + .../7440-tls-server-add-context.ts.json | 1 + .../7441-tls-client-error.ts.json | 1 + .../7442-tls-server-contexts.mjs.json | 1 + .../7443-tls-final-flight.ts.json | 1 + .../7444-tls-final-flight.mjs.json | 1 + .../7454-tls-peer-embedded-pem.ts.json | 1 + .../7455-tls-als-contexts.mjs.json | 1 + tests/harness/errors.test.ts | 157 +- tests/harness/library-asyncfree.test.ts | 9 + tests/harness/library-mode.test.ts | 15 + tests/harness/project-config.test.ts | 7 + .../self-hosting-native-toolchain.test.ts | 1 + .../harness/self-hosting-runtime-pack.test.ts | 1 + tests/harness/tls-socket-leaks.test.ts | 10 +- tests/harness/tls-trust-store.test.ts | 42 + tests/library-mode/x509/lib.ts | 41 + tests/library-mode/x509/probe.c | 23 + tests/library-mode/x509/profile.json | 14 + 135 files changed, 7408 insertions(+), 314 deletions(-) create mode 100644 docs/content/limitations/tls-certificates.mdx create mode 100644 docs/content/limitations/tls-sessions.mdx create mode 100644 packages/compiler/src/frontend/lowering/builtins/x509.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-x509-certificate-fields.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7090-x509-check-names.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7091-x509-certificate-keys.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7092-x509-legacy-object.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7093-x509-methods-js.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7095-tls-peer-certificates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7096-tls-peer-certificates.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7097-tls-client-certificates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7098-tls-client-certificates-paths.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7099-tls-client-certificates.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7440-tls-server-add-context.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-tls-client-error.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7442-tls-server-contexts.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7443-tls-final-flight.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7444-tls-final-flight.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7454-tls-peer-embedded-pem.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7455-tls-als-contexts.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/x509-fences.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-client-certificates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-peer-certificates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-server-contexts.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/x509-methods.ts.json create mode 100644 packages/runtime/src/scr_tls_x509.c create mode 100644 packages/runtime/src/scr_x509.c create mode 100644 tests/corpus/6936-x509-certificate-fields.ts create mode 100644 tests/corpus/7090-x509-check-names.ts create mode 100644 tests/corpus/7091-x509-certificate-keys.ts create mode 100644 tests/corpus/7092-x509-legacy-object.ts create mode 100644 tests/corpus/7093-x509-methods-js.mjs create mode 100644 tests/corpus/7095-tls-peer-certificates.ts create mode 100644 tests/corpus/7096-tls-peer-certificates.mjs create mode 100644 tests/corpus/7097-tls-client-certificates.ts create mode 100644 tests/corpus/7098-tls-client-certificates-paths.ts create mode 100644 tests/corpus/7099-tls-client-certificates.mjs create mode 100644 tests/corpus/7440-tls-server-add-context.ts create mode 100644 tests/corpus/7441-tls-client-error.ts create mode 100644 tests/corpus/7442-tls-server-contexts.mjs create mode 100644 tests/corpus/7443-tls-final-flight.ts create mode 100644 tests/corpus/7444-tls-final-flight.mjs create mode 100644 tests/corpus/7454-tls-peer-embedded-pem.ts create mode 100644 tests/corpus/7455-tls-als-contexts.mjs create mode 100644 tests/diagnostics/x509-fences.ts create mode 100644 tests/fixtures/node-types/tls-client-certificates.ts create mode 100644 tests/fixtures/node-types/tls-peer-certificates.ts create mode 100644 tests/fixtures/node-types/tls-server-contexts.ts create mode 100644 tests/fixtures/node-types/x509-methods.ts create mode 100644 tests/fixtures/server/certs/client-key.pem create mode 100644 tests/fixtures/server/certs/client.cnf create mode 100644 tests/fixtures/server/certs/client.pem create mode 100644 tests/fixtures/server/certs/embedded-key.pem create mode 100644 tests/fixtures/server/certs/embedded.cnf create mode 100644 tests/fixtures/server/certs/embedded.pem create mode 100644 tests/fixtures/tls/empty-ca.ts create mode 100644 tests/harness/__snapshots__/x509-fences.ts.txt create mode 100644 tests/harness/corpus-costs/6936-x509-certificate-fields.ts.json create mode 100644 tests/harness/corpus-costs/7090-x509-check-names.ts.json create mode 100644 tests/harness/corpus-costs/7091-x509-certificate-keys.ts.json create mode 100644 tests/harness/corpus-costs/7092-x509-legacy-object.ts.json create mode 100644 tests/harness/corpus-costs/7093-x509-methods-js.mjs.json create mode 100644 tests/harness/corpus-costs/7095-tls-peer-certificates.ts.json create mode 100644 tests/harness/corpus-costs/7096-tls-peer-certificates.mjs.json create mode 100644 tests/harness/corpus-costs/7097-tls-client-certificates.ts.json create mode 100644 tests/harness/corpus-costs/7098-tls-client-certificates-paths.ts.json create mode 100644 tests/harness/corpus-costs/7099-tls-client-certificates.mjs.json create mode 100644 tests/harness/corpus-costs/7440-tls-server-add-context.ts.json create mode 100644 tests/harness/corpus-costs/7441-tls-client-error.ts.json create mode 100644 tests/harness/corpus-costs/7442-tls-server-contexts.mjs.json create mode 100644 tests/harness/corpus-costs/7443-tls-final-flight.ts.json create mode 100644 tests/harness/corpus-costs/7444-tls-final-flight.mjs.json create mode 100644 tests/harness/corpus-costs/7454-tls-peer-embedded-pem.ts.json create mode 100644 tests/harness/corpus-costs/7455-tls-als-contexts.mjs.json create mode 100644 tests/harness/tls-trust-store.test.ts create mode 100644 tests/library-mode/x509/lib.ts create mode 100644 tests/library-mode/x509/probe.c create mode 100644 tests/library-mode/x509/profile.json diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index a86ed217e..cb6b93d0d 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -220,6 +220,14 @@ title: "Limitations" ../limitations/tls-ciphers.mdx +### TLS certificates + +../limitations/tls-certificates.mdx + +### TLS sessions + +../limitations/tls-sessions.mdx + ### Console and process streams ../limitations/console-and-process-streams.mdx diff --git a/docs/content/limitations/builtin-apis/crypto.mdx b/docs/content/limitations/builtin-apis/crypto.mdx index 329aa6809..cba219788 100644 --- a/docs/content/limitations/builtin-apis/crypto.mdx +++ b/docs/content/limitations/builtin-apis/crypto.mdx @@ -41,6 +41,10 @@ They support `type`, `asymmetricKeyType`, `asymmetricKeyDetails`, `export()`, `equals()`, and `instanceof KeyObject`. `crypto.sign`, `crypto.verify`, `createSign`, and `createVerify` support RSA PKCS#1 v1.5 and PSS padding and ECDSA with DER and IEEE P1363 encodings over SHA-1 and SHA-2 digests, and Ed25519 through `crypto.sign` and `crypto.verify` with a `null` or `undefined` algorithm. Ed25519 signing, verification, and private keys link when a string literal in the program names `ed25519` (in any case); without one they throw a coded SC2020 error, while Ed25519 public keys still import and export. + X509Certificate supports `publicKey`, `verify()`, `checkIssued()`, `checkPrivateKey()`, `checkHost()`, `checkEmail()`, `checkIP()`, `toLegacyObject()`, `toString()`, and `toJSON()`, and `String()`, template literals, and `+` give its PEM text as `toString()` does. + `publicKey`, `verify()`, and `checkPrivateKey()` are refused in wasm32-wasi builds, and an Ed25519 certificate's `verify()` needs the `ed25519` literal like the other Ed25519 operations. + Own-key walks over a certificate (`Object.keys()`, `Object.entries()`, spread, `for...in`, and `hasOwnProperty`) are refused, since Node keeps its fields as prototype getters. + `JSON.stringify()` and `console.log()` of a certificate, a certificate stored in an `unknown` or `any` slot, and its methods on a union-typed value are also refused. Sign and Verify values accumulate `update()` data and are not writable streams. Encrypted private keys (`passphrase`, `cipher`), the other key types (`rsa-pss`, `dsa`, `ed448`, `x448`, and the rest), and wasm32-wasi builds throw a coded SC2020 error or are refused. Comparing keys of different types with `equals()` answers false; Node also leaves an OpenSSL error queued, which a later key operation can report. diff --git a/docs/content/limitations/tls-certificates.mdx b/docs/content/limitations/tls-certificates.mdx new file mode 100644 index 000000000..bbe4ceaf9 --- /dev/null +++ b/docs/content/limitations/tls-certificates.mdx @@ -0,0 +1,11 @@ +`tlsSocket.getPeerCertificate()`, `getCertificate()`, `getPeerX509Certificate()`, and `getX509Certificate()` follow Node, including the `detailed` issuer chain, on sockets whose type is known. +On a TLS socket held in an `unknown` or `any` value, or in an untyped JavaScript parameter, they throw an error that names the call as not supported. +Client certificates follow Node: `tls.createServer`, `https.createServer`, and `new tls.TLSSocket` take `requestCert`, `rejectUnauthorized`, and `ca`, and `tls.connect` and `https.request` take `cert` and `key`. +A server's `authorized` and `authorizationError` report the client certificate's verification as Node does, and a server, client, or `tls.createSecureContext` given a `ca` also sends the chain it completes, as OpenSSL's automatic chain does. +`requestCert` beside a TLSSocket's `secureContext` or an `SNICallback`, client certificates on http2 secure servers, and a `cert` without a `key` (or the reverse) are refused. +`server.addContext()` and `server.setSecureContext()` follow Node on tls and https servers, including wildcard names, which match one label without regard to case. +A name with regular-expression groups or alternation, or one outside ASCII, is refused, as are both methods on a server that is not a tls or https server. +The `'tlsClientError'` event reports Node's error for a failed handshake: OpenSSL's code and reason, or `socket hang up` for a client that leaves. +OpenSSL's messages leave out the thread identifier Node prints before `error:` and the source location after the reason. +A failure whose OpenSSL reason the runtime does not know, such as a malformed client hello, is reported as an error with code `SC2020` that says so. +The X509Certificate those accessors return has the same refusals as any other certificate (see Crypto), so in JavaScript narrow it, for example with `if (cert)`, before reading its fields. diff --git a/docs/content/limitations/tls-sessions.mdx b/docs/content/limitations/tls-sessions.mdx new file mode 100644 index 000000000..8d34573a8 --- /dev/null +++ b/docs/content/limitations/tls-sessions.mdx @@ -0,0 +1,3 @@ +TLS servers send no session tickets, so a connection to them cannot be resumed. +A client's `'session'` event fires once per connection, when its handshake finishes and before `secureConnect`. +Node fires it for each ticket the server sends, after `secureConnect`, and OpenSSL servers send two. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 1039bc3d8..e463b872d 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -13822,9 +13822,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13845,9 +13845,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13868,9 +13868,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13891,9 +13891,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13914,9 +13914,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13937,9 +13937,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13960,9 +13960,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -13983,9 +13983,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -14006,9 +14006,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -14029,9 +14029,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -14236,9 +14236,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -23790,9 +23790,9 @@ "anchor": "err_tls_required_server_name", "nodeStability": null, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -67484,9 +67484,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -67599,9 +67599,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -67783,9 +67783,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -67921,9 +67921,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -68013,9 +68013,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -68059,9 +68059,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -68174,9 +68174,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -68312,9 +68312,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented by a dedicated static compiler/runtime path.", + "verification": "test-backed" } }, { @@ -68335,9 +68335,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index b3d91e453..1ee5cacd6 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -19394,6 +19394,8 @@ "tests": [ "tests/corpus/1358-crypto-random.ts", "tests/corpus/1534-crypto-hash-chain.ts", + "tests/corpus/1568-x509-fingerprint.ts", + "tests/corpus/1572-x509-validity-date-gettime.ts", "tests/corpus/2556-crypto-introspection.ts", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/5250-cipher-aes-gcm.ts", @@ -19436,6 +19438,7 @@ "tests/corpus/6709-dh-generate.ts", "tests/corpus/6710-dh-encodings.cjs", "tests/corpus/6711-dh-handles.ts", + "tests/corpus/6936-x509-certificate-fields.ts", "tests/corpus/7055-cipher-streams.ts", "tests/corpus/7056-cipher-stream-errors.ts", "tests/corpus/7057-cipher-streams.mjs", @@ -19443,6 +19446,10 @@ "tests/corpus/7082-crypto-key-objects.ts", "tests/corpus/7083-crypto-sign-verify.ts", "tests/corpus/7084-crypto-key-pairs.cjs", + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", "tests/corpus/7122-webcrypto-node-crypto.ts", "tests/corpus/7123-webcrypto-node-crypto.cjs", "tests/corpus/7150-cipher-legacy-modes.ts", @@ -19461,6 +19468,7 @@ "tests/fixtures/node-types/dh.ts", "tests/fixtures/node-types/ecdh.ts", "tests/fixtures/node-types/keys.ts", + "tests/fixtures/node-types/x509-methods.ts", "tests/harness/npm-static.test.ts" ] }, @@ -23784,8 +23792,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:X509Certificate", + "tests": [ + "tests/corpus/1568-x509-fingerprint.ts", + "tests/corpus/1572-x509-validity-date-gettime.ts", + "tests/corpus/6936-x509-certificate-fields.ts" + ] }, "anchorSource": "exact" }, @@ -23807,8 +23820,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.checkEmail", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23830,8 +23850,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.checkHost", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23853,8 +23880,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.checkIP", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23876,8 +23910,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.checkIssued", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23899,8 +23940,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.checkPrivateKey", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23922,8 +23970,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.toJSON", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23945,8 +24000,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.toLegacyObject", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23968,8 +24030,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.toString", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -23991,8 +24060,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-dedicated:x509.verify", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "exact" }, @@ -24198,8 +24274,15 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:crypto" + "status": "partial", + "evidence": "compiler-feature:crypto.publicKey", + "tests": [ + "tests/corpus/7090-x509-check-names.ts", + "tests/corpus/7091-x509-certificate-keys.ts", + "tests/corpus/7092-x509-legacy-object.ts", + "tests/corpus/7093-x509-methods-js.mjs", + "tests/fixtures/node-types/x509-methods.ts" + ] }, "anchorSource": "ancestor" }, @@ -32679,6 +32762,7 @@ "tests": [ "tests/corpus/6702-ecdh-errors.js", "tests/corpus/6708-dh-errors.js", + "tests/corpus/7440-tls-server-add-context.ts", "tests/corpus/caught-system-error-fields.ts", "tests/corpus/error-stack-frames.ts", "tests/corpus/error-stack-limit.ts", @@ -39008,8 +39092,11 @@ "anchor": "err_tls_required_server_name", "nodeStability": null, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:errors" + "status": "partial", + "evidence": "compiler-dedicated:ERR_TLS_REQUIRED_SERVER_NAME", + "tests": [ + "tests/corpus/7440-tls-server-add-context.ts" + ] }, "anchorSource": "exact" }, @@ -95873,6 +95960,17 @@ "tests/corpus/6722-tls-min-max-version.ts", "tests/corpus/7087-tls-socket-wrap.ts", "tests/corpus/7088-tls-socket-connect-tunnel.ts", + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7096-tls-peer-certificates.mjs", + "tests/corpus/7097-tls-client-certificates.ts", + "tests/corpus/7098-tls-client-certificates-paths.ts", + "tests/corpus/7099-tls-client-certificates.mjs", + "tests/corpus/7440-tls-server-add-context.ts", + "tests/corpus/7441-tls-client-error.ts", + "tests/corpus/7442-tls-server-contexts.mjs", + "tests/fixtures/node-types/tls-client-certificates.ts", + "tests/fixtures/node-types/tls-peer-certificates.ts", + "tests/fixtures/node-types/tls-server-contexts.ts", "tests/fixtures/node-types/tls-socket-wrap.ts" ] }, @@ -96241,8 +96339,14 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:server.addContext", + "tests": [ + "tests/corpus/7440-tls-server-add-context.ts", + "tests/corpus/7441-tls-client-error.ts", + "tests/corpus/7442-tls-server-contexts.mjs", + "tests/fixtures/node-types/tls-server-contexts.ts" + ] }, "anchorSource": "exact" }, @@ -96368,8 +96472,14 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:server.setSecureContext", + "tests": [ + "tests/corpus/7440-tls-server-add-context.ts", + "tests/corpus/7441-tls-client-error.ts", + "tests/corpus/7442-tls-server-contexts.mjs", + "tests/fixtures/node-types/tls-server-contexts.ts" + ] }, "anchorSource": "exact" }, @@ -96552,8 +96662,14 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:'tlsClientError'", + "tests": [ + "tests/corpus/7440-tls-server-add-context.ts", + "tests/corpus/7441-tls-client-error.ts", + "tests/corpus/7442-tls-server-contexts.mjs", + "tests/fixtures/node-types/tls-server-contexts.ts" + ] }, "anchorSource": "exact" }, @@ -96695,8 +96811,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:tlsSocket.getCertificate", + "tests": [ + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7096-tls-peer-certificates.mjs", + "tests/fixtures/node-types/tls-peer-certificates.ts" + ] }, "anchorSource": "exact" }, @@ -96787,8 +96908,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:tlsSocket.getPeerCertificate", + "tests": [ + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7096-tls-peer-certificates.mjs", + "tests/fixtures/node-types/tls-peer-certificates.ts" + ] }, "anchorSource": "exact" }, @@ -96856,8 +96982,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:tlsSocket.getPeerX509Certificate", + "tests": [ + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7096-tls-peer-certificates.mjs", + "tests/fixtures/node-types/tls-peer-certificates.ts" + ] }, "anchorSource": "exact" }, @@ -96974,8 +97105,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:tlsSocket.getX509Certificate", + "tests": [ + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7096-tls-peer-certificates.mjs", + "tests/fixtures/node-types/tls-peer-certificates.ts" + ] }, "anchorSource": "exact" }, @@ -97115,8 +97251,14 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-dedicated:tlsSocket.authorizationError", + "tests": [ + "tests/corpus/7097-tls-client-certificates.ts", + "tests/corpus/7098-tls-client-certificates-paths.ts", + "tests/corpus/7099-tls-client-certificates.mjs", + "tests/fixtures/node-types/tls-client-certificates.ts" + ] }, "anchorSource": "exact" }, @@ -97138,8 +97280,14 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:tls" + "status": "partial", + "evidence": "compiler-feature:tls.authorized", + "tests": [ + "tests/corpus/7097-tls-client-certificates.ts", + "tests/corpus/7098-tls-client-certificates-paths.ts", + "tests/corpus/7099-tls-client-certificates.mjs", + "tests/fixtures/node-types/tls-client-certificates.ts" + ] }, "anchorSource": "ancestor" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 92124f4f5..a098390f2 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -202,6 +202,8 @@ "stdlib.abort": ["tests/harness/fetch-conformance.test.ts"] }, "features": [ + { "chapter": "tls", "parent": "Class: `tls.TLSSocket`", "signatures": ["`authorized` Type: {boolean}"], "status": "partial", "evidence": ["tests/corpus/7097-tls-client-certificates.ts", "tests/corpus/7098-tls-client-certificates-paths.ts", "tests/corpus/7099-tls-client-certificates.mjs", "tests/fixtures/node-types/tls-client-certificates.ts"] }, + { "chapter": "crypto", "parent": "Class: `X509Certificate`", "signatures": ["`publicKey` Type: {KeyObject}"], "status": "partial", "evidence": ["tests/corpus/7090-x509-check-names.ts", "tests/corpus/7091-x509-certificate-keys.ts", "tests/corpus/7092-x509-legacy-object.ts", "tests/corpus/7093-x509-methods-js.mjs", "tests/fixtures/node-types/x509-methods.ts"] }, { "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.generateKey", "subtle.exportKey", "subtle.encrypt", "subtle.decrypt", "subtle.wrapKey", "subtle.unwrapKey", "subtle.deriveBits", "subtle.deriveKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/7130-webcrypto-wrap-key.ts", "tests/corpus/7131-webcrypto-wrap-key-errors.mjs", "tests/corpus/7132-jose-pbes2/main.mjs", "tests/corpus/7128-webcrypto-derive.ts", "tests/corpus/7129-webcrypto-derive-errors.mjs", "tests/corpus/7126-webcrypto-aes.ts", "tests/corpus/7127-webcrypto-aes-errors.mjs", "tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/7125-webcrypto-key-errors.mjs", "tests/fixtures/node-types/webcrypto-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts", "tests/corpus/7081-webcrypto-ed25519.mjs", "tests/corpus/7085-webcrypto-ecdsa-rsa.ts", "tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs"] }, { "chapter": "webcrypto", "signatures": ["Class: `SubtleCrypto`", "`subtle` Type: {SubtleCrypto}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, { "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, @@ -427,6 +429,11 @@ { "symbols": ["assert", "assert.ok", "assert.equal", "assert.notEqual", "assert.strictEqual", "assert.notStrictEqual", "assert.deepStrictEqual", "assert.notDeepStrictEqual", "assert.fail", "assert.match", "assert.doesNotMatch", "assert.throws", "assert.rejects", "assert.ifError"], "status": "partial", "evidence": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1604-assert-deep-structures.ts", "tests/corpus/1609-assert-async.ts", "tests/corpus/1721-assert-throws-regex-class.ts", "tests/corpus/2936-static-loose-equality.ts", "tests/corpus/5515-assert-deep-strict-typed-messages.ts"] }, { "chapter": "crypto", "symbols": ["Cipheriv", "cipher.update", "cipher.final", "cipher.setAAD", "cipher.getAuthTag", "cipher.setAutoPadding", "Decipheriv", "decipher.update", "decipher.final", "decipher.setAAD", "decipher.setAuthTag", "decipher.setAutoPadding"], "status": "partial", "evidence": ["tests/corpus/5250-cipher-aes-gcm.ts", "tests/corpus/5251-cipher-aes-cbc.ts", "tests/corpus/5252-cipher-aes-ctr.ts", "tests/corpus/5253-cipher-chacha20-poly1305.ts", "tests/corpus/5254-cipher-errors.js", "tests/corpus/5255-cipher-encodings.cjs", "tests/corpus/5256-cipher-deprecation.cjs", "tests/corpus/5257-cipher-vectors.ts", "tests/corpus/5258-cipher-handles.js", "tests/corpus/5259-cipher-chunked.ts", "tests/corpus/7055-cipher-streams.ts", "tests/corpus/7056-cipher-stream-errors.ts", "tests/corpus/7057-cipher-streams.mjs", "tests/fixtures/node-types/cipher.ts"] }, { "chapter": "crypto", "symbols": ["crypto.webcrypto", "subtle"], "status": "partial", "evidence": ["tests/corpus/7122-webcrypto-node-crypto.ts", "tests/corpus/7123-webcrypto-node-crypto.cjs"] }, + { "chapter": "tls", "symbols": ["server.addContext", "server.setSecureContext", "'tlsClientError'"], "status": "partial", "evidence": ["tests/corpus/7440-tls-server-add-context.ts", "tests/corpus/7441-tls-client-error.ts", "tests/corpus/7442-tls-server-contexts.mjs", "tests/fixtures/node-types/tls-server-contexts.ts"] }, + { "chapter": "errors", "symbols": ["ERR_TLS_REQUIRED_SERVER_NAME"], "status": "partial", "evidence": ["tests/corpus/7440-tls-server-add-context.ts"] }, + { "chapter": "tls", "symbols": ["tlsSocket.authorizationError"], "status": "partial", "evidence": ["tests/corpus/7097-tls-client-certificates.ts", "tests/corpus/7098-tls-client-certificates-paths.ts", "tests/corpus/7099-tls-client-certificates.mjs", "tests/fixtures/node-types/tls-client-certificates.ts"] }, + { "chapter": "tls", "symbols": ["tlsSocket.getPeerCertificate", "tlsSocket.getCertificate", "tlsSocket.getPeerX509Certificate", "tlsSocket.getX509Certificate"], "status": "partial", "evidence": ["tests/corpus/7095-tls-peer-certificates.ts", "tests/corpus/7096-tls-peer-certificates.mjs", "tests/fixtures/node-types/tls-peer-certificates.ts"] }, + { "chapter": "crypto", "symbols": ["x509.checkEmail", "x509.checkHost", "x509.checkIP", "x509.checkIssued", "x509.checkPrivateKey", "x509.toJSON", "x509.toLegacyObject", "x509.toString", "x509.verify"], "status": "partial", "evidence": ["tests/corpus/7090-x509-check-names.ts", "tests/corpus/7091-x509-certificate-keys.ts", "tests/corpus/7092-x509-legacy-object.ts", "tests/corpus/7093-x509-methods-js.mjs", "tests/fixtures/node-types/x509-methods.ts"] }, { "chapter": "crypto", "symbols": ["ECDH", "ecdh.computeSecret", "ecdh.generateKeys", "ecdh.getPrivateKey", "ecdh.getPublicKey", "ecdh.setPrivateKey", "ecdh.setPublicKey"], "status": "partial", "evidence": ["tests/corpus/6700-ecdh-shared-secret.ts", "tests/corpus/6701-ecdh-formats.ts", "tests/corpus/6702-ecdh-errors.js", "tests/corpus/6703-ecdh-private-keys.ts", "tests/corpus/6704-ecdh-set-public-key.cjs", "tests/corpus/6705-ecdh-handles.ts", "tests/fixtures/node-types/ecdh.ts"] }, { "chapter": "crypto", "symbols": ["DiffieHellman", "DiffieHellmanGroup", "diffieHellman.computeSecret", "diffieHellman.generateKeys", "diffieHellman.getGenerator", "diffieHellman.getPrime", "diffieHellman.getPrivateKey", "diffieHellman.getPublicKey", "diffieHellman.setPrivateKey", "diffieHellman.setPublicKey", "diffieHellman.verifyError"], "status": "partial", "evidence": ["tests/corpus/6706-dh-groups.ts", "tests/corpus/6707-dh-explicit-params.ts", "tests/corpus/6708-dh-errors.js", "tests/corpus/6709-dh-generate.ts", "tests/corpus/6710-dh-encodings.cjs", "tests/corpus/6711-dh-handles.ts", "tests/fixtures/node-types/dh.ts"] }, { "chapter": "errors", "symbols": ["ERR_CRYPTO_UNKNOWN_DH_GROUP", "ERR_CRYPTO_INVALID_KEYLEN", "ERR_CRYPTO_INVALID_STATE"], "status": "partial", "evidence": ["tests/corpus/6708-dh-errors.js"] }, @@ -437,6 +444,7 @@ { "symbols": ["crypto.createPrivateKey", "crypto.createPublicKey", "crypto.generateKeyPair", "crypto.generateKeyPairSync", "crypto.sign", "crypto.verify", "crypto.createSign", "crypto.createVerify", "Sign", "sign.update", "sign.sign", "Verify", "verify.update", "verify.verify", "asymmetricKeyType", "asymmetricKeyDetails"], "status": "partial", "evidence": ["tests/corpus/7080-crypto-ed25519.mjs", "tests/corpus/7082-crypto-key-objects.ts", "tests/corpus/7083-crypto-sign-verify.ts", "tests/corpus/7084-crypto-key-pairs.cjs", "tests/fixtures/node-types/keys.ts"] }, { "symbols": ["crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] }, { "symbols": ["crypto.timingSafeEqual"], "status": "supported", "evidence": ["tests/corpus/5444-crypto-timing-safe-equal.js", "tests/corpus/2890-crypto-static-utility.ts"] }, + { "symbols": ["X509Certificate"], "status": "partial", "evidence": ["tests/corpus/1568-x509-fingerprint.ts", "tests/corpus/1572-x509-validity-date-gettime.ts", "tests/corpus/6936-x509-certificate-fields.ts"] }, { "symbols": ["crypto.randomInt"], "status": "partial", "evidence": ["tests/corpus/5442-crypto-random-values.ts", "tests/corpus/2890-crypto-static-utility.ts"] }, { "symbols": ["crypto.hkdf", "crypto.hkdfSync", "crypto.scrypt", "crypto.scryptSync"], "status": "partial", "evidence": ["tests/corpus/node24-crypto-kdf.ts", "tests/corpus/node24-crypto-kdf-errors.ts", "tests/corpus/node24-crypto-kdf-options.ts", "tests/corpus/5438-crypto-kdf-digests.ts"] }, { "symbols": ["EventEmitter", "emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.off", "emitter.removeListener", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.listeners", "emitter.rawListeners", "emitter.eventNames", "emitter.setMaxListeners", "emitter.getMaxListeners", "events.once"], "status": "partial", "evidence": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 0be5bdca2..9cd0abdd3 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3533,17 +3533,45 @@ declare module "crypto" { keylen: number, callback: (error: Error | null, derivedKey: ArrayBuffer) => void, ): void; - /* The lowered X509Certificate surface is the data-record slice: - * fingerprint (the SHA-1 of the DER, uppercase colon-separated) and - * the validFrom/validTo validity window (Node's ASN1_TIME_print - * strings — "Jul 1 00:00:00 2026 GMT"; the cert-expiry idiom composes - * them with new Date(...).getTime()). All compute at construction; - * unparseable input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ + /* The lowered X509Certificate surface is the data-record slice: the + * fields below, all computed at construction (raw decodes the input, + * and the others read raw). validFrom/validTo are Node's + * ASN1_TIME_print strings ("Jul 1 00:00:00 2026 GMT"). Unparseable + * input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ export class X509Certificate { constructor(buffer: Uint8Array | string); + readonly ca: boolean; readonly fingerprint: string; + readonly fingerprint256: string; + readonly fingerprint512: string; + readonly subject: string; + readonly subjectAltName: string | undefined; + readonly infoAccess: string | undefined; + readonly issuer: string; + readonly raw: Buffer; + readonly serialNumber: string; + readonly signatureAlgorithm: string | undefined; + readonly signatureAlgorithmOid: string; readonly validFrom: string; + readonly validFromDate: Date; readonly validTo: string; + readonly validToDate: Date; + /* the certificate's public key; every read answers the same KeyObject */ + readonly publicKey: KeyObject; + verify(publicKey: KeyObject): boolean; + checkPrivateKey(privateKey: KeyObject): boolean; + checkIssued(otherCert: X509Certificate): boolean; + checkHost(name: string, options?: X509CheckOptions): string | undefined; + checkEmail(email: string, options?: X509CheckOptions): string | undefined; + checkIP(ip: string): string | undefined; + toLegacyObject(): import("tls").PeerCertificate; + } + export interface X509CheckOptions { + subject?: "always" | "default" | "never"; + wildcards?: boolean; + partialWildcards?: boolean; + multiLabelWildcards?: boolean; + singleLabelSubdomains?: boolean; } /* The crypto introspection statics: getFips() answers 0 (a compiled * binary is never a FIPS build), the three name lists bake as fresh @@ -5439,6 +5467,11 @@ declare module "net" { /* The demux route: hand an accepted socket to another server. */ emit(event: "connection", socket: Socket): boolean; on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + on( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; on(event: "close" | "listening", listener: () => void): void; on(event: "drop", listener: (data?: DropArgument) => void): void; on(event: "timeout", listener: (socket: Socket) => void): void; @@ -5458,6 +5491,11 @@ declare module "net" { ): void; /* addListener IS on (Node aliases them) — the suite spells both. */ addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + addListener( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; addListener(event: "close" | "listening", listener: () => void): void; addListener(event: "drop", listener: (data?: DropArgument) => void): void; addListener(event: "timeout", listener: (socket: Socket) => void): void; @@ -5471,6 +5509,11 @@ declare module "net" { listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, ): void; once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + once( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; once(event: "close" | "listening", listener: () => void): void; once(event: "drop", listener: (data?: DropArgument) => void): void; once(event: "timeout", listener: (socket: Socket) => void): void; @@ -5924,8 +5967,15 @@ declare module "node:http" { * are PEM strings or Buffers. Handshake failures tear the socket down * without emitting an event, Node's default for 'tlsClientError' with no listener. */ declare module "tls" { - import { Server, Socket } from "net"; - export { Server }; + import { Server as NetServer, Socket } from "net"; + /** tls.Server: a net server whose connections handshake first. */ + export interface Server extends NetServer { + /* serve `context` to clients whose SNI name matches `hostname` ('*' + * matches within one label); the newest match wins */ + addContext(hostname: string, context: TlsOptions | SecureContext): void; + /* new connections serve a context made from `options` */ + setSecureContext(options: TlsOptions): void; + } export interface TlsOptions { /* Optional in the DECLARATION only (real Node programs build these * literals with every TLS knob): the lowering still requires both — @@ -5959,7 +6009,47 @@ declare module "tls" { getCipher(): CipherNameAndProtocol; /* whether the handshake resumed a cached session */ isSessionReused(): boolean; + /* the peer's certificate: {} before the handshake or without one, + * null once the socket is destroyed; detailed adds issuerCertificate */ + getPeerCertificate(detailed: true): DetailedPeerCertificate; + getPeerCertificate(detailed?: false): PeerCertificate; + getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate; + /* the certificate this socket serves, the same way */ + getCertificate(): PeerCertificate | object | null; + /* a new X509Certificate on each call, or undefined without one */ + getPeerX509Certificate(): import("crypto").X509Certificate | undefined; + getX509Certificate(): import("crypto").X509Certificate | undefined; } + /* A certificate as getPeerCertificate() and X509Certificate's + * toLegacyObject() answer it: subject and issuer map attribute short + * names to values, infoAccess its " - " keys to values. */ + export interface PeerCertificate { + subject: { [attribute: string]: string | string[] }; + issuer: { [attribute: string]: string | string[] }; + subjectaltname?: string; + infoAccess?: { [key: string]: string[] }; + ca: boolean; + modulus?: string; + exponent?: string; + pubkey?: Buffer; + bits?: number; + valid_from: string; + valid_to: string; + fingerprint: string; + fingerprint256: string; + fingerprint512: string; + ext_key_usage?: string[]; + serialNumber: string; + raw: Buffer; + asn1Curve?: string; + nistCurve?: string; + } + /* getPeerCertificate(true): each certificate links to its issuer; a + * self-issued root is its own issuerCertificate */ + export interface DetailedPeerCertificate extends PeerCertificate { + issuerCertificate: DetailedPeerCertificate; + } + /** new tls.TLSSocket(socket, options): the server-mode wrap of an open * socket (a server connection or an http CONNECT socket). The options * record reads at runtime; isServer, cert, key, secureContext, @@ -6014,6 +6104,8 @@ declare module "tls" { /** An opaque parsed cert/key pair — what an SNI callback answers with * (the value is a runtime handle; the members are runtime-internal). */ export interface SecureContext {} + /* @types/node's name for createSecureContext's options */ + export type SecureContextOptions = TlsOptions; export function createSecureContext(options: TlsOptions): SecureContext; /** The CA-store introspection surface (scr_tls_ca.c): per-type cached * PEM string arrays. The host bundle stands in for Node's compiled-in diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index a9076af22..48be5b564 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -61,6 +61,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { dh: features.dh, pki: features.pki, ed25519: features.ed25519, + x509: features.x509, webcrypto: features.webcrypto, perfLoop: features.perfLoop, }; diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 79be316db..e7fa7a782 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -64,6 +64,7 @@ export async function compilePackedLibrary( dh: false, pki: false, ed25519: false, + x509: options.x509 ?? false, webcrypto: false, perfLoop: false, tlsCa: false, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 9df33e63f..5383ca034 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1082,6 +1082,28 @@ export const LIB_FN_SYMS: Record = { "crypto.verifyOneshot": "scr_crypto_verify_oneshot", "crypto.createSign": "scr_crypto_create_sign", "crypto.createVerify": "scr_crypto_create_verify", + "crypto.x509Raw": "scr_crypto_x509_raw", + "crypto.x509RawDer": "scr_crypto_x509_raw_der", + "crypto.x509RawStr": "scr_crypto_x509_raw_str", + "crypto.x509Subject": "scr_crypto_x509_subject", + "crypto.x509Issuer": "scr_crypto_x509_issuer", + "crypto.x509SubjectAltName": "scr_crypto_x509_subject_alt_name", + "crypto.x509InfoAccess": "scr_crypto_x509_info_access", + "crypto.x509SerialNumber": "scr_crypto_x509_serial_number", + "crypto.x509Fingerprint256": "scr_crypto_x509_fingerprint256", + "crypto.x509Fingerprint512": "scr_crypto_x509_fingerprint512", + "crypto.x509Ca": "scr_crypto_x509_ca", + "crypto.x509SignatureAlgorithm": "scr_crypto_x509_signature_algorithm", + "crypto.x509SignatureAlgorithmOid": "scr_crypto_x509_signature_algorithm_oid", + "crypto.x509CheckHost": "scr_crypto_x509_check_host", + "crypto.x509CheckEmail": "scr_crypto_x509_check_email", + "crypto.x509CheckIP": "scr_crypto_x509_check_ip", + "crypto.x509CheckIssued": "scr_crypto_x509_check_issued", + "crypto.x509LegacyObject": "scr_crypto_x509_legacy_object", + "crypto.x509Pem": "scr_crypto_x509_pem", + "crypto.x509PublicKey": "scr_crypto_x509_public_key", + "crypto.x509Verify": "scr_crypto_x509_verify", + "crypto.x509CheckPrivateKey": "scr_crypto_x509_check_private_key", "fs.writeFileModeSync": "scr_fs_write_file_mode", "fs.chownSync": "scr_fs_chown", "fs.lchownSync": "scr_fs_lchown", diff --git a/packages/compiler/src/backend/llvm/lib-tls.ts b/packages/compiler/src/backend/llvm/lib-tls.ts index 1d1cf659c..307aeb3dd 100644 --- a/packages/compiler/src/backend/llvm/lib-tls.ts +++ b/packages/compiler/src/backend/llvm/lib-tls.ts @@ -110,6 +110,31 @@ export function emitTlsLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu return finishRuntimeCall(host, e, "scr_tls_sock_get_cipher", a); case "tls.sockAlpnProtocol": return finishRuntimeCall(host, e, "scr_tls_sock_alpn_protocol", a); + case "tls.serverAddContext": + return finishRuntimeCall(host, e, "scr_tls_server_add_context", [...a, ptr()]); + case "tls.serverAddContextCtx": + return finishRuntimeCall(host, e, "scr_tls_server_add_context", [a[0]!, a[1]!, ptr(), a[2]!]); + case "tls.serverSetSecureContext": + return finishRuntimeCall(host, e, "scr_tls_server_set_secure_context", a); + case "tls.serverOnClientError": { + // (err, tlsSocket) listeners by arity: the adapter retains what the + // listener takes. + const cb = args[1]!; + const thunk = `scr_net_client_error_thunk${callbackType(cb).params.length}`; + return finishRuntimeCall(host, e, "scr_net_server_on_client_error", [ + a[0]!, + ...callbackAdapter(host, cb, thunk, ["ptr", "ptr", "ptr"]), + a[2]!, + ]); + } + case "tls.sockGetPeerCertificate": + return finishRuntimeCall(host, e, "scr_tls_sock_get_peer_certificate", a); + case "tls.sockGetCertificate": + return finishRuntimeCall(host, e, "scr_tls_sock_get_certificate", a); + case "tls.sockPeerCertDer": + return finishRuntimeCall(host, e, "scr_tls_sock_peer_cert_der", a); + case "tls.sockOwnCertDer": + return finishRuntimeCall(host, e, "scr_tls_sock_own_cert_der", a); case "tls.sockAuthError": { const result = callRuntime(host, "scr_tls_sock_auth_error", "ptr", a); return nullableReference(host, e.type, result, "string", "nullT"); diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index c073bb9a6..1235a3f3e 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -104,6 +104,9 @@ export interface NativeLinkFeatures { /** Ed25519 keys and signatures with pki: scr_ed25519.c (the vendored * libsodium Ed25519). */ ed25519: boolean; + /** X509Certificate fields beyond the validity window and fingerprint: + * scr_x509.c. */ + x509: boolean; /** perf_hooks event-loop metrics: scr_perf_loop.c. */ perfLoop: boolean; tlsCa: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 53e160984..ef84f4d10 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -269,6 +269,8 @@ export interface CcOptions { /** Ed25519 with pki: compiles scr_ed25519.c (the vendored libsodium * Ed25519). */ ed25519?: boolean; + /** X509Certificate fields: compiles scr_x509.c. */ + x509?: boolean; /** crypto.subtle importKey/sign/verify/digest: compiles scr_webcrypto.c. */ webcrypto?: boolean; /** perf_hooks event-loop metrics: compiles scr_perf_loop.c. */ @@ -354,6 +356,8 @@ export interface LibArchiveOptions { searchParams?: boolean; emitter?: boolean; zlib?: boolean; + /** X509Certificate fields: scr_x509.c. */ + x509?: boolean; copying?: boolean; textDecoderLegacy?: boolean; } diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 1578a0c80..59957f664 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -309,6 +309,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.webcrypto, "scr_webcrypto.c"), unit((u) => u.opts.webcrypto === true && u.opts.pki === true, "scr_webcrypto_pki.c"), unit((u) => u.opts.pki === true && u.opts.ed25519 === true, "scr_ed25519.c"), + unit((u) => u.opts.x509 === true, "scr_x509.c"), + // The TLS socket certificate accessors read scr_tls.c and build with scr_x509.c. + unit((u) => u.tls && u.opts.x509 === true, "scr_tls_x509.c"), // The crypto global's object extends scr_async_dyn.c's. unit((u) => u.opts.webcrypto && u.opts.dynAsync, "scr_webcrypto_object.c"), // The CA-store unit rides its own gate OR the tls one: scr_tls.c diff --git a/packages/compiler/src/backend/native/library.ts b/packages/compiler/src/backend/native/library.ts index f04e9926c..cca706896 100644 --- a/packages/compiler/src/backend/native/library.ts +++ b/packages/compiler/src/backend/native/library.ts @@ -73,7 +73,7 @@ import { CacheInputsChangedError } from "./session.js"; * async_free refusal already guarantees nothing references them — and * scr_library.c (sink, arena, reset registry, library funnel) joins. The gated * units a library may reach are the pure-data ones (regex + the vendored matcher, assert, - * inspect, symbol, searchParams, emitter+dyn_handle, zlib); every + * inspect, symbol, searchParams, emitter+dyn_handle, zlib, x509); every * loop-hooked or ambient unit was refused at SC4005 before emission. * External-symbol contract: undefined references only to the target's C/math * runtime and system APIs. Windows embedders additionally link advapi32, @@ -115,6 +115,7 @@ export async function compileLibArchive(opts: LibArchiveOptions): Promise ...(opts.dynInvoke && opts.dynBuffer ? ["scr_bytes_dyn.c"] : []), ...(opts.dynTier === true ? ["scr_js_value.c", "scr_js_object.c"] : []), ...(opts.zlib ? ["scr_zlib.c"] : []), + ...(opts.x509 ? ["scr_x509.c"] : []), ...(opts.copying ? ["scr_copying.c"] : []), ]; const cflags = [ diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 9846e8d89..9312ecfd0 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -129,6 +129,7 @@ const features = { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, }; diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index e2b24f8ca..13f74d4e1 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -65,6 +65,7 @@ const BASE: NativeLinkFeatures = { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, }; diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 02acb5462..c42ceb79e 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -71,6 +71,7 @@ export async function warmNativeCaches( dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, tlsCa: false, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index e066c0412..4aaa86e0d 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -69,6 +69,7 @@ const native: EarlyExecutableNativeFeatures = { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, }; diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 96922ca13..10fcae7c5 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -102,6 +102,7 @@ export interface EarlyExecutableNativeFeatures { dh: boolean; pki: boolean; ed25519: boolean; + x509: boolean; perfLoop: boolean; tlsCa: boolean; webcrypto: boolean; @@ -247,6 +248,7 @@ const BOOLEAN_NATIVE_KEYS = [ "dh", "pki", "ed25519", + "x509", "webcrypto", "perfLoop", ] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[]; diff --git a/packages/compiler/src/frontend/lowering/builtins/x509.ts b/packages/compiler/src/frontend/lowering/builtins/x509.ts new file mode 100644 index 000000000..bc0092369 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/x509.ts @@ -0,0 +1,487 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { locOf } from "../../program.js"; +import { + BOOL, + BYTES_U8, + DYN, + F64, + STRING, + UNDEFINED_T, + type IrExpr, + type IrFunction, + type IrLibFn, + type IrType, + type SrcLoc, +} from "../../../ir/ir.js"; +import { everyStmtList } from "../../../ir/traverse.js"; +import { unsupportedDiag } from "../../../diagnostics/diagnostic.js"; +import { X509_PUBLIC_KEY_FIELD } from "../../type-mapper.js"; +import { keyedHelperName } from "../stable-names.js"; +import { x509RecordCall } from "../lower-classes.js"; + +/* node:crypto's X509Certificate members beyond its fields: the methods + * and the publicKey getter. A certificate is the Dirent-style record + * (type-mapper.ts), and each member reads its raw DER. checkHost, + * checkEmail, checkIP, checkIssued, toLegacyObject and the PEM of + * toString/toJSON run in scr_x509.c; + * publicKey, verify and checkPrivateKey read keys in scr_pki.c. The first + * publicKey read stores the KeyObject in the record's hidden slot, so + * every read answers the same object, as in Node. */ + +/** Whether `t` is the X509Certificate record (its hidden publicKey slot + * marks the shape; no program can name that field). */ +export function isX509Type(lowerer: Lowerer, t: IrType | null | undefined): boolean { + return ( + t?.kind === "record" && + lowerer.shapes.get(t.shapeId)?.fields.some((f) => f.name === X509_PUBLIC_KEY_FIELD) === true + ); +} + +/** Whether `node` is an X509Certificate record. */ +function isX509Record(lowerer: Lowerer, node: ts.Expression): boolean { + const type = lowerer.typeOf(node); + const sym = type.getSymbol(); + if (!sym || ts.symbolName(sym) !== "X509Certificate" || !lowerer.isStdlibSymbol(sym)) + return false; + return isX509Type(lowerer, lowerer.mapTypeOf(type)); +} + +/** A certificate in an `unknown` or `any` slot would cross as a plain + * object of its fields, which Node's certificate is not: its fields are + * prototype getters, and toString() and toJSON() give its PEM. Every such + * conversion in the lowered program, including a certificate inside an + * array or record that converts, is refused where it happens. */ +export function refuseX509DynValues(lowerer: Lowerer, functions: readonly IrFunction[]): void { + // Certificates exist only where `new X509Certificate` built one. + if ( + !lowerer.valueHelpers.has("x509.record:bytes") && + !lowerer.valueHelpers.has("x509.record:str") && + !lowerer.valueHelpers.has("x509.record:der") + ) + return; + const seen = new Map(); + const holds = (t: IrType): boolean => { + switch (t.kind) { + case "record": { + const known = seen.get(t.shapeId); + if (known !== undefined) return known; + seen.set(t.shapeId, false); + const shape = lowerer.shapes.get(t.shapeId); + const result = + isX509Type(lowerer, t) || + (shape !== undefined && + (shape.fields.some((f) => holds(f.type)) || + (shape.indexValue !== undefined && holds(shape.indexValue)))); + seen.set(t.shapeId, result); + return result; + } + case "array": + case "set": + return holds(t.elem); + case "map": + return holds(t.key) || holds(t.value); + case "promise": + return holds(t.inner); + case "union": + return lowerer.unions.get(t.unionId)?.arms.some(holds) === true; + default: + return false; + } + }; + const visitor = { + expr: (e: IrExpr): boolean => { + if (e.kind === "dynFrom" && holds(e.value.type)) { + lowerer.pushDiag( + unsupportedDiag( + "SC1090", + e.loc, + "X509Certificate values in 'unknown' or 'any' slots (Node keeps a certificate's fields as prototype getters, and its toString() and toJSON() give the PEM; keep the X509Certificate type)", + ), + ); + } + return true; + }, + stmt: (): boolean => true, + }; + for (const fn of functions) everyStmtList(fn.body, visitor); +} + +/** String(x509), `${x509}` and x509 + "": the certificate's toString(), + * its PEM. */ +export function x509ToString(record: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "crypto.x509Pem", args: [rawOf(record, loc)], type: STRING, loc }; +} + +/** Node keeps a certificate's fields as prototype getters, so own-key + * walks (Object.keys/values/entries, for-in, spread, hasOwnProperty) see + * none of them. Those walks over a certificate fence instead of listing + * the fields. */ +export function fenceX509KeyWalk( + lowerer: Lowerer, + t: IrType | null | undefined, + node: ts.Node, + what: string, +): void { + if (!isX509Type(lowerer, t)) return; + lowerer.unsupported( + "SC1090", + node, + `${what} over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields)`, + ); +} + +function rawOf(record: IrExpr, loc: SrcLoc): IrExpr { + if (record.type.kind !== "record") return record; + return { + kind: "recordGet", + obj: record, + shapeId: record.type.shapeId, + field: "raw", + type: BYTES_U8, + loc, + }; +} + +const dynUndefined = (loc: SrcLoc): IrExpr => ({ + kind: "dynFrom", + value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type: DYN, + loc, +}); + +/** The method's string answer as `string | undefined`: "" is undefined. + * A lifted helper takes the text once. */ +function optionalString(lowerer: Lowerer, text: IrExpr, type: IrType, loc: SrcLoc): IrExpr { + if (type.kind !== "union") return text; + const def = lowerer.unions.get(type.unionId); + const stringTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1; + const undefinedTag = def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1; + if (stringTag < 0 || undefinedTag < 0) return text; + const key = `x509.optional:${type.unionId}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%x509.optional", key); + lowerer.valueHelpers.set(key, helper); + const s: IrExpr = { kind: "varRef", localId: "s.0", type: STRING, loc }; + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "s.0", name: "s", type: STRING }], + returnType: type, + locals: [{ id: "s.0", name: "s", type: STRING, mutable: false }], + body: [ + { + kind: "return", + value: { + kind: "ternary", + cond: { + kind: "strEq", + negated: false, + left: s, + right: { kind: "strLit", value: "", type: STRING, loc }, + type: BOOL, + loc, + }, + then: { + kind: "unionWrap", + unionId: type.unionId, + tag: undefinedTag, + value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + loc, + }, + else_: { + kind: "unionWrap", + unionId: type.unionId, + tag: stringTag, + value: s, + type, + loc, + }, + type, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [text], type, loc }; +} + +/** `x509.publicKey`: the cached KeyObject (a lifted helper fills the + * hidden slot on the first read). */ +export function lowerX509PublicKey( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.name.text !== "publicKey" || access.questionDotToken) return null; + if (!isX509Record(lowerer, access.expression)) return null; + const loc = locOf(access); + const record = lowerer.lowerExpr(access.expression); + if (record.type.kind !== "record") return null; + const shapeId = record.type.shapeId; + const key = `x509.publicKey:${shapeId}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%x509.publicKey", key); + lowerer.valueHelpers.set(key, helper); + const c: IrExpr = { kind: "varRef", localId: "c.0", type: record.type, loc }; + const slot: IrExpr = { + kind: "recordGet", + obj: c, + shapeId, + field: X509_PUBLIC_KEY_FIELD, + type: DYN, + loc, + }; + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "c.0", name: "c", type: record.type }], + returnType: DYN, + locals: [{ id: "c.0", name: "c", type: record.type, mutable: false }], + body: [ + { + kind: "if", + cond: { kind: "dynTest", test: "undefined", value: slot, type: BOOL, loc }, + then: [ + { + kind: "recordSet", + obj: c, + shapeId, + field: X509_PUBLIC_KEY_FIELD, + value: { + kind: "libCall", + fn: "crypto.x509PublicKey", + args: [rawOf(c, loc)], + type: DYN, + loc, + }, + loc, + }, + ], + else_: null, + loc, + }, + { kind: "return", value: slot, loc }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [record], type: DYN, loc }; +} + +type X509Method = { fn: IrLibFn; min: number; max: number; form: string }; + +const METHODS: ReadonlyMap = new Map([ + ["verify", { fn: "crypto.x509Verify", min: 1, max: 1, form: "verify(publicKey)" }], + [ + "checkPrivateKey", + { fn: "crypto.x509CheckPrivateKey", min: 1, max: 1, form: "checkPrivateKey(privateKey)" }, + ], + ["checkIssued", { fn: "crypto.x509CheckIssued", min: 1, max: 1, form: "checkIssued(otherCert)" }], + ["checkHost", { fn: "crypto.x509CheckHost", min: 1, max: 2, form: "checkHost(name[, options])" }], + [ + "checkEmail", + { fn: "crypto.x509CheckEmail", min: 1, max: 2, form: "checkEmail(email[, options])" }, + ], + ["checkIP", { fn: "crypto.x509CheckIP", min: 1, max: 2, form: "checkIP(ip[, options])" }], + ["toLegacyObject", { fn: "crypto.x509LegacyObject", min: 0, max: 0, form: "toLegacyObject()" }], + ["toString", { fn: "crypto.x509Pem", min: 0, max: 0, form: "toString()" }], + ["toJSON", { fn: "crypto.x509Pem", min: 0, max: 0, form: "toJSON()" }], +]); + +/** The X509Certificate methods: verify, checkPrivateKey, checkIssued, + * checkHost, checkEmail, checkIP, toLegacyObject, toString and toJSON. */ +export function lowerX509Call(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + if (call.questionDotToken || !ts.isPropertyAccessExpression(call.expression)) return null; + const access = call.expression; + if (access.questionDotToken) return null; + const method = METHODS.get(access.name.text); + if (!method) return null; + if (!isX509Record(lowerer, access.expression)) return null; + const loc = locOf(call); + const args = call.arguments.nodes; + if (args.length < method.min || args.length > method.max || args.some(ts.isSpreadElement)) { + lowerer.noLowering( + `X509Certificate.${access.name.text} with ${args.length} argument(s)`, + call, + `the supported form is ${method.form}`, + ); + } + const raw = rawOf(lowerer.lowerExpr(access.expression), loc); + if (method.fn === "crypto.x509LegacyObject") { + return { kind: "libCall", fn: method.fn, args: [raw], type: DYN, loc }; + } + if (method.fn === "crypto.x509Pem") + return { kind: "libCall", fn: method.fn, args: [raw], type: STRING, loc }; + if (method.fn === "crypto.x509CheckIssued") { + if (!isX509Record(lowerer, args[0]!)) { + // Node's validation of otherCert, after both operands evaluate. + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: raw, loc }], + result: { + kind: "libCall", + fn: "error.argTypeThrow", + args: [ + { kind: "strLit", value: "otherCert", type: STRING, loc }, + { kind: "strLit", value: "an instance of X509Certificate", type: STRING, loc }, + lowerer.lowerExprExpecting(args[0]!, DYN), + ], + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + } + const other = rawOf(lowerer.lowerExpr(args[0]!), loc); + return { kind: "libCall", fn: method.fn, args: [raw, other], type: BOOL, loc }; + } + const first = lowerer.lowerExprExpecting(args[0]!, DYN); + if (method.fn === "crypto.x509Verify" || method.fn === "crypto.x509CheckPrivateKey") { + return { kind: "libCall", fn: method.fn, args: [raw, first], type: BOOL, loc }; + } + const options = args.length > 1 ? lowerer.lowerExprExpecting(args[1]!, DYN) : dynUndefined(loc); + const text: IrExpr = { + kind: "libCall", + fn: method.fn, + args: [raw, first, options], + type: STRING, + loc, + }; + return optionalString(lowerer, text, lowerer.mapTypeOf(lowerer.typeOf(call)) ?? STRING, loc); +} + +/** The TLS socket certificate accessors. getPeerCertificate([detailed]) + * and getCertificate() answer Node's legacy object (scr_tls_x509.c): + * Node reads `detailed` only when it is true. getPeerX509Certificate() and + * getX509Certificate() build a new X509Certificate from the certificate's + * DER on each call, or answer undefined without one. Null for other + * members. */ +export function lowerTlsCertificateCall( + lowerer: Lowerer, + call: ts.CallExpression, + name: string, + receiver: () => IrExpr, +): IrExpr | null { + const loc = locOf(call); + const args = call.arguments.nodes; + if (name === "getPeerCertificate" || name === "getCertificate") { + const max = name === "getPeerCertificate" ? 1 : 0; + if (args.length > max || args.some(ts.isSpreadElement)) { + lowerer.noLowering( + `${name} with ${args.length} argument(s)`, + call, + name === "getPeerCertificate" + ? "the form is getPeerCertificate(detailed?)" + : "getCertificate() takes no arguments", + ); + } + const socket = receiver(); + const value: IrExpr = + name === "getCertificate" + ? { kind: "libCall", fn: "tls.sockGetCertificate", args: [socket], type: DYN, loc } + : { + kind: "libCall", + fn: "tls.sockGetPeerCertificate", + args: [ + socket, + args.length > 0 ? lowerer.lowerExprExpecting(args[0]!, DYN) : dynUndefined(loc), + ], + type: DYN, + loc, + }; + const declared = lowerer.mapTypeOf(lowerer.typeOf(call)); + return declared === null || declared.kind === "dyn" + ? value + : lowerer.coerceInto(call, value, declared); + } + if (name !== "getPeerX509Certificate" && name !== "getX509Certificate") return null; + if (args.length > 0) { + lowerer.noLowering( + `${name} with ${args.length} argument(s)`, + call, + `${name}() takes no arguments`, + ); + } + const type = lowerer.mapTypeOf(lowerer.typeOf(call)); + const def = type?.kind === "union" ? lowerer.unions.get(type.unionId) : undefined; + const recordTag = def ? def.arms.findIndex((arm) => isX509Type(lowerer, arm)) : -1; + const undefinedTag = def ? def.arms.findIndex((arm) => arm.kind === "undefinedT") : -1; + if (type?.kind !== "union" || !def || recordTag < 0 || undefinedTag < 0) { + lowerer.noLowering( + `tlsSocket.${name} with this result type`, + call, + "its type is X509Certificate | undefined", + ); + } + const record = def.arms[recordTag] as IrType & { kind: "record" }; + const der: IrExpr = { + kind: "libCall", + fn: name === "getPeerX509Certificate" ? "tls.sockPeerCertDer" : "tls.sockOwnCertDer", + args: [receiver()], + type: BYTES_U8, + loc, + }; + const key = `tls.x509:${type.unionId}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%tls.x509", key); + lowerer.valueHelpers.set(key, helper); + const d: IrExpr = { kind: "varRef", localId: "d.0", type: BYTES_U8, loc }; + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "d.0", name: "der", type: BYTES_U8 }], + returnType: type, + locals: [{ id: "d.0", name: "der", type: BYTES_U8, mutable: false }], + body: [ + { + kind: "return", + value: { + kind: "ternary", + cond: { + kind: "bin", + op: "===", + left: { + kind: "bytesIntrinsic", + method: "length", + receiver: d, + args: [], + type: F64, + loc, + }, + right: { kind: "numLit", value: 0, type: F64, loc }, + type: BOOL, + loc, + }, + then: { + kind: "unionWrap", + unionId: type.unionId, + tag: undefinedTag, + value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + loc, + }, + else_: { + kind: "unionWrap", + unionId: type.unionId, + tag: recordTag, + // The handshake's DER, read without PEM detection. + value: x509RecordCall(lowerer, d, record, loc, true), + type, + loc, + }, + type, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [der], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index b26a3dd9f..ab5ddeab6 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -58,6 +58,7 @@ import { fenceNodeModuleMutation, lowerRequireCacheHas } from "../lower-node-mod import { errorToStringCall } from "../error-methods.js"; import { classToStringConversion, classUnionToString } from "../class-to-primitive.js"; import { isPerfEntryType, perfEntryTag } from "../builtins/performance.js"; +import { isX509Type, x509ToString } from "../builtins/x509.js"; import { guardNullProto, nullProtoRecordShape } from "../null-prototype.js"; import { numLit, varRef } from "../../../ir/build.js"; import { lowerUrlAssignment } from "../builtins/url.js"; @@ -798,6 +799,7 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr // other record is Object.prototype.toString's constant. const shape = lowerer.shapes.get(e.type.shapeId); if (isPerfEntryType(lowerer, e.type)) return perfEntryTag(lowerer, e, e.type, e.loc); + if (isX509Type(lowerer, e.type)) return x509ToString(e, e.loc); if (shape && !shape.tuple && !shape.fields.some((f) => f.name === "toString")) { // A null-prototype record (Object.groupBy) has no toString or // valueOf: ToPrimitive throws Node's TypeError. diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 4cc0bee1a..d6f413dc1 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -28,6 +28,7 @@ import type { IndexMergeContributor } from "../containers/indexed-objects.js"; import type { KeyOrderSegment } from "../key-order-witness.js"; import { PERF_ENTRY_MARKER, + X509_PUBLIC_KEY_FIELD, isGenericCallableMemberType, jsOpenObjectType, } from "../../type-mapper.js"; @@ -501,6 +502,13 @@ function fenceAccessorSpreadSource( "object spread over performance entries (Node keeps their fields as prototype getters, so own-key walks see none; read the fields or call toJSON())", ); } + if (srcShape && srcShape.fields.some((f) => f.name === X509_PUBLIC_KEY_FIELD)) { + lowerer.unsupported( + "SC1090", + prop, + "object spread over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields)", + ); + } if (srcShape && shapeHasAccessorSlots(srcShape)) { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 8148e85f5..b542fa303 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -58,6 +58,7 @@ import { BYTES_ELEMENT_SIZE, } from "../../../ir/ir.js"; import { lowerNavigatorMember } from "../builtins/navigator.js"; +import { lowerX509PublicKey } from "../builtins/x509.js"; import { NODE_COMPAT_VERSION } from "../builtins/process.js"; import { inlineNamespaceRequireOf, @@ -347,6 +348,11 @@ export function lowerPropertyAccessExpr( const reading = lowerHistogramProperty(lowerer, expr); if (reading) return reading; } + // An X509Certificate's publicKey KeyObject. + { + const publicKey = lowerX509PublicKey(lowerer, expr); + if (publicKey) return publicKey; + } // `.length` of a forwarded fixed tuple (a typed `arguments`, a tuple // rest parameter, an alias of either): the signature's arity. if (expr.name.text === "length" && !expr.questionDotToken) { diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 8ac97fd32..87102377c 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -167,6 +167,7 @@ import { lowerPerfHooksCall, perfEntryTag, } from "./builtins/performance.js"; +import { fenceX509KeyWalk } from "./builtins/x509.js"; import { lowerProcStreamMethodCall, lowerProcessBuiltinModuleNamespace, @@ -2957,6 +2958,7 @@ function lowerObjectOwnCall( } else if (type.kind === "record") { const shape = lowerer.shapes.get(type.shapeId); fencePerfEntryKeyWalk(lowerer, type, receiverNode ?? call, `Object.prototype.${method}`); + fenceX509KeyWalk(lowerer, type, receiverNode ?? call, `Object.prototype.${method}`); if (!shape || shape.tuple || shapeHasAccessorSlots(shape)) { lowerer.noLowering( "Object.prototype." + method + " over this record shape", @@ -8921,6 +8923,7 @@ function lowerObjectStaticCall( // RESULTS, invoked in key order. The static field walk models neither // (accessor slots live outside declaredOrder), so the surface fences. fencePerfEntryKeyWalk(lowerer, argIr, call, `Object.${member}`); + fenceX509KeyWalk(lowerer, argIr, call, `Object.${member}`); if (shapeHasAccessorSlots(shape)) { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 3e49304d2..2283c7dfb 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -43,6 +43,7 @@ import type { FnCtx, Lowerer } from "./lowerer.js"; import { inlineNamespaceRequireOf, lowerWithInlineRequireInit } from "./cjs-inline-require.js"; import { BOOL, + BYTES_U8, canDynCheckTo, DATE_T, DYN, @@ -99,6 +100,7 @@ import { jsOpenObjectType, typeKey, withUnitArm, + X509_PUBLIC_KEY_FIELD, } from "../type-mapper.js"; import { canonicalBuiltinModule, @@ -10108,6 +10110,52 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const agent = lowerHttpAgentNew(lowerer, expr); if (agent) return agent; } + // `new crypto.X509Certificate(data)` — the Dirent-style data record: + // the certificate's lowered members (fingerprint — the SHA-1 of the + // DER, uppercase colon-separated — plus the validFrom/validTo + // validity window in Node's ASN1_TIME_print shape) compute AT + // CONSTRUCTION, when Node parses too, so unparseable input throws + // Node's exact PEM error here (ERR_OSSL_PEM_NO_START_LINE) and the + // handle never exists. Both import forms (`crypto.X509Certificate` + // through the namespace, named `X509Certificate`); Buffer input only + // — the readFileSync idiom. mapType interns the matching record, so + // locals and the composed member reads all flow. The data argument + // feeds THREE field computations, so construction goes through an + // interned helper whose parameter evaluates it exactly once. + { + const callee = expr.expression; + const isX509 = + (ts.isPropertyAccessExpression(callee) && + callee.name.text === "X509Certificate" && + lowerer.builtinNamespaceModuleOf(callee.expression) === "crypto") || + (ts.isIdentifier(callee) && + (() => { + const bi = lowerer.builtinImportOf(callee); + return bi?.module === "crypto" && bi.member === "X509Certificate"; + })()); + if (isX509) { + const args: readonly ts.Expression[] = expr.arguments?.nodes ?? []; + if (args.length !== 1) { + lowerer.noLowering( + "X509Certificate with this argument shape", + expr, + "the supported form is new X509Certificate(readFileSync(path))", + ); + } + const data = lowerer.lowerExpr(args[0]!); + const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; + if (!isBytes && data.type.kind !== "string") { + lowerer.noLowering( + `X509Certificate over '${lowerer.fmt(data.type)}' data`, + args[0]!, + "pass the certificate Buffer or PEM string (an fs.readFileSync result)", + ); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (t?.kind !== "record") lowerer.badType(expr, lowerer.typeOf(expr)); + return x509RecordCall(lowerer, data, t, loc); + } + } if ( ts.isIdentifier(expr.expression) || (ts.isPropertyAccessExpression(expr.expression) && @@ -10888,99 +10936,6 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { ); } } - // `new crypto.X509Certificate(data)` — the Dirent-style data record: - // the certificate's lowered members (fingerprint — the SHA-1 of the - // DER, uppercase colon-separated — plus the validFrom/validTo - // validity window in Node's ASN1_TIME_print shape) compute AT - // CONSTRUCTION, when Node parses too, so unparseable input throws - // Node's exact PEM error here (ERR_OSSL_PEM_NO_START_LINE) and the - // handle never exists. Both import forms (`crypto.X509Certificate` - // through the namespace, named `X509Certificate`); Buffer input only - // — the readFileSync idiom. mapType interns the matching record, so - // locals and the composed member reads all flow. The data argument - // feeds THREE field computations, so construction goes through an - // interned helper whose parameter evaluates it exactly once. - { - const callee = expr.expression; - const isX509 = - (ts.isPropertyAccessExpression(callee) && - callee.name.text === "X509Certificate" && - lowerer.builtinNamespaceModuleOf(callee.expression) === "crypto") || - (ts.isIdentifier(callee) && - (() => { - const bi = lowerer.builtinImportOf(callee); - return bi?.module === "crypto" && bi.member === "X509Certificate"; - })()); - if (isX509) { - const args: readonly ts.Expression[] = expr.arguments?.nodes ?? []; - if (args.length !== 1) { - lowerer.noLowering( - "X509Certificate with this argument shape", - expr, - "the supported form is new X509Certificate(readFileSync(path))", - ); - } - const data = lowerer.lowerExpr(args[0]!); - const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; - if (!isBytes && data.type.kind !== "string") { - lowerer.noLowering( - `X509Certificate over '${lowerer.fmt(data.type)}' data`, - args[0]!, - "pass the certificate Buffer or PEM string (an fs.readFileSync result)", - ); - } - const t = lowerer.mapTypeOf(lowerer.typeOf(expr)); - if (t?.kind !== "record") lowerer.badType(expr, lowerer.typeOf(expr)); - const key = `x509.record:${isBytes ? "bytes" : "str"}`; - let helper = lowerer.valueHelpers.get(key); - if (!helper) { - helper = keyedHelperName(lowerer, "%x509.record", key); - lowerer.valueHelpers.set(key, helper); - const dataT = data.type; - const dRef: IrExpr = { kind: "varRef", localId: "d.0", type: dataT, loc }; - const field = ( - name: string, - fn: - | "crypto.x509Fingerprint" - | "crypto.x509FingerprintStr" - | "crypto.x509ValidFrom" - | "crypto.x509ValidFromStr" - | "crypto.x509ValidTo" - | "crypto.x509ValidToStr", - ): { name: string; value: IrExpr } => ({ - name, - value: { kind: "libCall", fn, args: [dRef], type: STRING, loc }, - }); - lowerer.liftedFns.push({ - name: helper, - params: [{ localId: "d.0", name: "d", type: dataT }], - returnType: t, - locals: [{ id: "d.0", name: "d", type: dataT, mutable: false }], - body: [ - { - kind: "return", - value: { - kind: "recordLit", - fields: [ - field( - "fingerprint", - isBytes ? "crypto.x509Fingerprint" : "crypto.x509FingerprintStr", - ), - field("validFrom", isBytes ? "crypto.x509ValidFrom" : "crypto.x509ValidFromStr"), - field("validTo", isBytes ? "crypto.x509ValidTo" : "crypto.x509ValidToStr"), - ], - type: t, - loc, - }, - loc, - }, - ], - loc, - }); - } - return { kind: "call", callee: helper, args: [data], type: t, loc }; - } - } // `new X(...)` through a class VALUE (a classval-typed binding, array // element, map read, param): the newValue dispatch through the class // object's construct thunk. Arguments complete against the STATIC @@ -11254,6 +11209,151 @@ function genericFieldFnNodeOf( return null; } +/** The lifted `new crypto.X509Certificate(d)` constructor: raw decodes the + * input once (Node's PEM error throws here), and every other field reads + * raw. subjectAltName, infoAccess and signatureAlgorithm hold undefined + * where the runtime answers "". */ +/** The X509Certificate record `t` built from `data`, a certificate Buffer + * (PEM or DER) or PEM string, through the interned constructor helper. + * `der` marks a Buffer known to hold a certificate's DER (a TLS socket's), + * which is read without looking for PEM armor inside it. */ +export function x509RecordCall( + lowerer: Lowerer, + data: IrExpr, + t: IrType & { kind: "record" }, + loc: SrcLoc, + der = false, +): IrExpr { + const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; + const key = `x509.record:${der ? "der" : isBytes ? "bytes" : "str"}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%x509.record", key); + lowerer.valueHelpers.set(key, helper); + lowerer.liftedFns.push(x509RecordHelper(lowerer, helper, data.type, t, isBytes, der, loc)); + } + return { kind: "call", callee: helper, args: [data], type: t, loc }; +} + +function x509RecordHelper( + lowerer: Lowerer, + name: string, + dataT: IrType, + t: IrType & { kind: "record" }, + isBytes: boolean, + der: boolean, + loc: SrcLoc, +): IrFunction { + const shape = lowerer.shapes.get(t.shapeId); + if (!shape) throw new InternalCompilerError("lowerer bug: X509Certificate shape unregistered"); + const raw: IrExpr = { kind: "varRef", localId: "raw.0", type: BYTES_U8, loc }; + const call = (fn: IrLibFn, type: IrType): IrExpr => ({ + kind: "libCall", + fn, + args: [raw], + type, + loc, + }); + const locals: IrLocal[] = [ + { id: "d.0", name: "d", type: dataT, mutable: false }, + { id: "raw.0", name: "raw", type: BYTES_U8, mutable: false }, + ]; + const decls: IrStmt[] = [ + { + kind: "varDecl", + localId: "raw.0", + init: { + kind: "libCall", + fn: der ? "crypto.x509RawDer" : isBytes ? "crypto.x509Raw" : "crypto.x509RawStr", + args: [{ kind: "varRef", localId: "d.0", type: dataT, loc }], + type: BYTES_U8, + loc, + }, + loc, + }, + ]; + const validTime = (fn: IrLibFn): IrExpr => ({ + kind: "libCall", + fn: "date.newString", + args: [call(fn, STRING)], + type: DATE_T, + loc, + }); + const values: Record IrExpr> = { + ca: () => call("crypto.x509Ca", BOOL), + fingerprint: () => call("crypto.x509Fingerprint", STRING), + fingerprint256: () => call("crypto.x509Fingerprint256", STRING), + fingerprint512: () => call("crypto.x509Fingerprint512", STRING), + subject: () => call("crypto.x509Subject", STRING), + subjectAltName: (type) => optionalText("san", "crypto.x509SubjectAltName", type), + infoAccess: (type) => optionalText("access", "crypto.x509InfoAccess", type), + issuer: () => call("crypto.x509Issuer", STRING), + raw: () => raw, + serialNumber: () => call("crypto.x509SerialNumber", STRING), + signatureAlgorithm: (type) => optionalText("sigalg", "crypto.x509SignatureAlgorithm", type), + signatureAlgorithmOid: () => call("crypto.x509SignatureAlgorithmOid", STRING), + validFrom: () => call("crypto.x509ValidFrom", STRING), + validFromDate: () => validTime("crypto.x509ValidFrom"), + validTo: () => call("crypto.x509ValidTo", STRING), + validToDate: () => validTime("crypto.x509ValidTo"), + [X509_PUBLIC_KEY_FIELD]: () => ({ + kind: "dynFrom", + value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type: DYN, + loc, + }), + }; + /** A string | undefined field: the runtime's text, or undefined for "". */ + function optionalText(local: string, fn: IrLibFn, type: IrType): IrExpr { + if (type.kind !== "union") + throw new InternalCompilerError("lowerer bug: X509Certificate optional field type"); + const def = lowerer.unions.get(type.unionId); + const stringTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1; + const undefinedTag = def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1; + if (stringTag < 0 || undefinedTag < 0) + throw new InternalCompilerError("lowerer bug: X509Certificate optional field arms"); + const id = `${local}.0`; + locals.push({ id, name: local, type: STRING, mutable: false }); + decls.push({ kind: "varDecl", localId: id, init: call(fn, STRING), loc }); + const text: IrExpr = { kind: "varRef", localId: id, type: STRING, loc }; + return { + kind: "ternary", + cond: { + kind: "strEq", + negated: false, + left: text, + right: { kind: "strLit", value: "", type: STRING, loc }, + type: BOOL, + loc, + }, + then: { + kind: "unionWrap", + unionId: type.unionId, + tag: undefinedTag, + value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + loc, + }, + else_: { kind: "unionWrap", unionId: type.unionId, tag: stringTag, value: text, type, loc }, + type, + loc, + }; + } + const fields = shape.fields.map((f) => { + const value = values[f.name]; + if (!value) throw new InternalCompilerError(`lowerer bug: X509Certificate field ${f.name}`); + return { name: f.name, value: value(f.type) }; + }); + return { + name, + params: [{ localId: "d.0", name: "d", type: dataT }], + returnType: t, + locals, + body: [...decls, { kind: "return", value: { kind: "recordLit", fields, type: t, loc }, loc }], + loc, + }; +} + /** Whether a JavaScript class's own binding is the target of * `Object.defineProperty`/`defineProperties` in its file * (`Object.defineProperty(Definition, "name", { value })`): its constructor diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index cb79accea..4121d13b4 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -1,4 +1,5 @@ import { keyedHelperName } from "./stable-names.js"; +import { lowerTlsCertificateCall } from "./builtins/x509.js"; import { dynUndefinedExpr, nodeThrowExpr, @@ -1765,12 +1766,82 @@ function lowerNetServerMethodCall( ); return { kind: "libCall", fn: "net.serverCloseCb", args: [receiver, cb], type: VOID, loc }; } + if (name === "addContext" || name === "setSecureContext") { + // tls.Server's SNI table and default context (scr_tls.c): addContext + // takes a SecureContext or createSecureContext's options; the runtime + // throws Node's errors and the fence on a non-TLS server. + requireStatementPosition(lowerer, call, `server.${name}(...)`); + const want = name === "addContext" ? 2 : 1; + if (args.length !== want || args.some(ts.isSpreadElement)) { + lowerer.noLowering( + `${name} with ${args.length} arguments`, + call, + name === "addContext" + ? "the form is addContext(servername, context)" + : "the form is setSecureContext(options)", + ); + } + const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); + if (name === "setSecureContext") { + const options = lowerer.lowerExprExpecting(args[0]!, DYN); + return { + kind: "libCall", + fn: "tls.serverSetSecureContext", + args: [receiver, options], + type: VOID, + loc, + }; + } + const servername = lowerer.lowerExprExpecting(args[0]!, DYN); + const context = lowerer.lowerExpr(args[1]!); + if (context.type.kind === "secureCtx") { + return { + kind: "libCall", + fn: "tls.serverAddContextCtx", + args: [receiver, servername, context], + type: VOID, + loc, + }; + } + return { + kind: "libCall", + fn: "tls.serverAddContext", + args: [receiver, servername, lowerer.coerceInto(args[1]!, context, DYN)], + type: VOID, + loc, + }; + } if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) { requireStatementPosition(lowerer, call, `server.${name}(...)`); const once = boolLit(name === "once", loc); const evT = lowerer.typeOf(args[0]!); const event = evT.isStringLiteralType() ? evT.value : null; const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); + if (event === "tlsClientError") { + // A failed handshake on a tls or https server: (err, tlsSocket). + const listener = args[1]!; + const { cb } = lowerCallbackArgShared( + lowerer, + listener, + "tlsClientError listeners", + 2, + (p, i) => + i === 0 ? p.kind === "object" && p.className === "%Error" : p.kind === "netSocket", + "use (err, tlsSocket), (err) or ()", + { + dynTuple: [ERROR_T, NETSOCKET_T], + checkAllParams: true, + adapt: (c) => voidizedCallback(lowerer, c, locOf(listener)), + }, + ); + return { + kind: "libCall", + fn: "tls.serverOnClientError", + args: [receiver, cb, once], + type: VOID, + loc, + }; + } if (event === "connection" || event === "secureConnection") { // 'secureConnection' rides the same list: a TLS server's // 'connection' listeners are DEFERRED to handshake completion @@ -2632,6 +2703,12 @@ function lowerNetSocketMethodCall( ? value : lowerer.coerceInto(call, value, declared); } + { + const certificate = lowerTlsCertificateCall(lowerer, call, name, () => + coerceToHandle(lowerer, access.expression, NETSOCKET_T), + ); + if (certificate) return certificate; + } if (name === "isSessionReused" && args.length === 0) { // TLSSocket.isSessionReused(): the client never offers a cached // session (no session resumption, a documented TLS divergence), so @@ -4941,19 +5018,12 @@ function lowerTlsServerOptions( } if (k !== "cert" && k !== "key") { const init = ts.isPropertyAssignment(prop) ? prop.initializer : null; - if ( - (k === "ca" || k === "rejectUnauthorized") && - (init === null || sideEffectFreeOptionValue(init)) - ) { - // Both are inert without requestCert (which fences): the server's - // `ca` verifies CLIENT certificates and rejectUnauthorized only - // gates that verification — Node-observably droppable, matching + if (k === "rejectUnauthorized" && (init === null || sideEffectFreeOptionValue(init))) { + // Without requestCert (a literal with requestCert or ca rides the + // runtime walk) rejectUnauthorized gates nothing — droppable, like // the runtime walk's split (effectful values keep the fence). continue; } - if (k === "requestCert" && (init === null || init.kind === ts.SyntaxKind.FalseKeyword)) { - continue; // Node's false default, spelled out - } fenceOrDropOptionKey( lowerer, prop, @@ -4964,8 +5034,6 @@ function lowerTlsServerOptions( { SNICallback: "SNICallback lowers on tls.createServer and http2.createSecureServer({ allowHTTP1: true, ... }) — serve one cert/key pair here", - requestCert: - "client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert: false is the lowered value", }, ); continue; // an undocumented key, dropped like Node drops it @@ -5063,12 +5131,19 @@ function tlsLiteralNeedsRuntimeWalk( ); } +/** The client-certificate options (requestCert, and ca, which also + * completes the server's own chain): the runtime walk implements them for + * tls.createServer and https.createServer, and the literal cert/key + * lowering does not. */ +const TLS_CLIENT_AUTH_OPTIONS: ReadonlySet = new Set(["requestCert", "ca"]); + /** tls.createServer options the runtime walk implements and the literal * cert/key lowering does not. */ const TLS_SERVER_RUNTIME_OPTIONS: ReadonlySet = new Set([ "ALPNProtocols", "minVersion", "maxVersion", + ...TLS_CLIENT_AUTH_OPTIONS, ]); function lowerTlsServerOptionsOrDyn( @@ -5092,7 +5167,7 @@ function lowerTlsServerOptionsOrDyn( lowerer.noLowering( `${what} with SNICallback and options the runtime walk reads`, sniOption, - "SNICallback lowers with cert and key; ALPNProtocols, minVersion, maxVersion and the validated options are not combined with it yet", + "SNICallback lowers with cert and key; ALPNProtocols, minVersion, maxVersion, requestCert, ca and the validated options are not combined with it yet", ); } } @@ -5124,14 +5199,14 @@ function lowerTlsServerOptionsOrDyn( * connect(options[, cb]) and connect(port[, host][, options][, cb]) with * a RUNTIME options record (the checked-dynamic JS lane's shape — a dyn * value, or a record that converts): the runtime walk implements - * port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout and throws the catchable + * port/host/socket/rejectUnauthorized/ca/cert/key/servername/ALPNProtocols/minVersion/maxVersion/timeout and throws the catchable * fence for other documented members (the divergence-66 stance). The * callback (and 'secureConnect'/'connect' listeners) fires * post-handshake — Node's secureConnect timing. */ function lowerTlsConnectCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { const args = expr.arguments.nodes; const FORMS_HINT = - "the supported forms are connect(options[, cb]) and connect(port[, host][, options][, cb]) — options implement port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout"; + "the supported forms are connect(options[, cb]) and connect(port[, host][, options][, cb]) — options implement port/host/socket/rejectUnauthorized/ca/cert/key/servername/ALPNProtocols/minVersion/maxVersion/timeout"; if (args.length < 1 || args.length > 4 || args.some(ts.isSpreadElement)) { lowerer.noLowering(`tls.connect with ${args.length} arguments`, expr, FORMS_HINT); } @@ -5368,7 +5443,13 @@ function lowerTlsModuleCall( "the supported form is createSecureContext({ cert, key })", ); } - const opts = lowerTlsServerOptionsOrDyn(lowerer, args[0]!, "tls.createSecureContext"); + const opts = lowerTlsServerOptionsOrDyn( + lowerer, + args[0]!, + "tls.createSecureContext", + false, + TLS_CLIENT_AUTH_OPTIONS, + ); if (opts.dyn !== undefined) { return { kind: "libCall", @@ -5625,7 +5706,13 @@ function lowerHttpsModuleCall( "the supported form is createServer({ cert, key }, (req, res) => ...)", ); } - const opts = lowerTlsServerOptionsOrDyn(lowerer, args[0]!, "https.createServer", true); + const opts = lowerTlsServerOptionsOrDyn( + lowerer, + args[0]!, + "https.createServer", + true, + TLS_CLIENT_AUTH_OPTIONS, + ); if (args.length === 1) { // The handler-less form: 'request' listeners arrive later via // server.on("request", ...) — the createSecureServer emission's @@ -6281,7 +6368,9 @@ function runtimeRequestOptions( return ( (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) && - prop.name.text === "socketPath" + (prop.name.text === "socketPath" || + // a client certificate: the runtime request walk reads cert and key + (secureish && (prop.name.text === "cert" || prop.name.text === "key"))) ); }); if (!needsRuntime) return null; diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 82c0dbb3e..a19061fde 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -248,6 +248,7 @@ import { constituentTypes } from "../ts7/checker.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; import { fenceNodeModuleMutation } from "./lower-node-module.js"; import { fencePerfEntryKeyWalk } from "./builtins/performance.js"; +import { fenceX509KeyWalk } from "./builtins/x509.js"; import { classGeneratorIterable } from "./class-iterables.js"; import { iterableUnionOf, lowerForOfIterable } from "./iterable-values.js"; @@ -11935,6 +11936,7 @@ function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { // them (accessor slots live outside declaredOrder), so the loop // fences rather than skip keys. fencePerfEntryKeyWalk(lowerer, receiver.type, stmt.expression, "for-in"); + fenceX509KeyWalk(lowerer, receiver.type, stmt.expression, "for-in"); if (shapeHasAccessorSlots(rShape)) { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 35080e8f8..2dd44bf3e 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -63,6 +63,7 @@ import { discoverWasmModules, type WasmModuleReport } from "../../wasm/discover. import { encodeBase64 } from "../../wasm/base64.js"; import { resolveRequireRuntime } from "../runtime-resolve.js"; import { lowerKeyObjectCall } from "./builtins/key-object.js"; +import { lowerX509Call, refuseX509DynValues } from "./builtins/x509.js"; import { isEcdhType, lowerEcdhCall } from "./builtins/ecdh.js"; import { isDiffieHellmanType, lowerDiffieHellmanCall } from "./builtins/dh.js"; import { fenceWebCryptoMember, lowerWebCryptoCall } from "./builtins/webcrypto.js"; @@ -7825,6 +7826,7 @@ export class Lowerer { // every producer and consumer agrees. Programs with no unregistered // reference are untouched — byte-stability holds. checkRequireBindingAccesses(this, functions); + refuseX509DynValues(this, functions); const module: IrModule | null = this.diags.length > 0 ? null @@ -13237,6 +13239,7 @@ export class Lowerer { lowerStaticBlobCall(this, expr) ?? lowerWebCryptoCall(this, expr) ?? lowerKeyObjectCall(this, expr) ?? + lowerX509Call(this, expr) ?? lowerEcdhCall(this, expr) ?? lowerDiffieHellmanCall(this, expr) ?? lowerNetAddressCall(this, expr) ?? diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index fc159116f..8a9b59c9a 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -169,9 +169,10 @@ export const WEBCRYPTO_DYN_TYPES: ReadonlySet = new Set([ "AesDerivedKeyParams", ]); -/** node:crypto's key and signature option interfaces: the key functions, - * KeyObject.export and sign/verify take them as checked-dynamic arguments - * (scr_lib.c, scr_pki.c). */ +/** node:crypto's key, signature and X509Certificate check option + * interfaces: the key functions, KeyObject.export, sign/verify and the + * X509Certificate checks take them as checked-dynamic arguments + * (scr_lib.c, scr_pki.c, scr_x509.c). */ export const CRYPTO_KEY_OPTION_DYN_TYPES: ReadonlySet = new Set([ "AsymmetricKeyExportOptions", "ECKeyPairKeyObjectOptions", @@ -190,6 +191,7 @@ export const CRYPTO_KEY_OPTION_DYN_TYPES: ReadonlySet = new Set([ "SigningOptions", "VerifyKeyObjectInput", "VerifyPublicKeyInput", + "X509CheckOptions", ]); export const FETCH_AMBIENT_TYPES = [ @@ -4457,13 +4459,25 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ), }; } - // crypto.X509Certificate — the Dirent-style data record: THREE visible - // fields (fingerprint and the validFrom/validTo validity window, all - // computed at construction by the new-expression lowering). Module- - // checked like SecureContext; @types/node declares the class inside - // `declare module "crypto"`, the shipped fallback mirrors it. - // JSON.stringify of one shows the three fields where Node's - // internal-slot object shows {} — the Dirent %dtype edge's cousin. + // X509Certificate.toLegacyObject()'s PeerCertificate (and tls's detailed + // form) is a plain checked-dynamic object. + if ( + psym !== undefined && + (ts.symbolName(psym) === "PeerCertificate" || + ts.symbolName(psym) === "DetailedPeerCertificate") && + psym.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ) + ) { + return DYN; + } + // crypto.X509Certificate — the Dirent-style data record: its fields are + // the certificate's lowered members, all computed at construction by the + // new-expression lowering (raw decodes the input once, and the others + // read raw). Module-checked like SecureContext; @types/node declares the + // class inside `declare module "crypto"`, the shipped fallback mirrors + // it. JSON.stringify of one shows the fields where Node's internal-slot + // object shows {} — the Dirent %dtype edge's cousin. if ( psym !== undefined && ts.symbolName(psym) === "X509Certificate" && @@ -4474,17 +4488,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isDeclaredInAmbientModule(d, "crypto"), ) ) { + const optionalString = withUndefinedArm(STRING, ctx.unions); + if (!optionalString) return null; + const fields = X509_FIELDS.map(([name, type]) => ({ + name, + type: type === "string?" ? optionalString : type, + })); return { kind: "record", shapeId: ctx.shapes.intern( - [ - { name: "fingerprint", type: STRING }, - { name: "validFrom", type: STRING }, - { name: "validTo", type: STRING }, - ], + [...fields].sort((a, b) => (a.name < b.name ? -1 : 1)), false, undefined, - ["fingerprint", "validFrom", "validTo"], + fields.map((f) => f.name), ), }; } @@ -6693,6 +6709,32 @@ function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): * result is exactly the nullable-callback shape mapType's union branch * admits, `(() => void) | undefined`) so the interned union is IDENTICAL * to what mapping the checker's own `T | undefined` produces. */ +/** The X509Certificate record's hidden slot for its publicKey KeyObject, + * filled on the first read (builtins/x509.ts). */ +export const X509_PUBLIC_KEY_FIELD = "%publicKey"; + +/** The X509Certificate record's fields in @types/node's declaration order + * ("string?" is string | undefined), then the hidden publicKey slot. */ +export const X509_FIELDS: readonly (readonly [string, IrType | "string?"])[] = [ + ["ca", BOOL], + ["fingerprint", STRING], + ["fingerprint256", STRING], + ["fingerprint512", STRING], + ["subject", STRING], + ["subjectAltName", "string?"], + ["infoAccess", "string?"], + ["issuer", STRING], + ["raw", { kind: "bytes", elem: "u8" }], + ["serialNumber", STRING], + ["signatureAlgorithm", "string?"], + ["signatureAlgorithmOid", STRING], + ["validFrom", STRING], + ["validFromDate", DATE_T], + ["validTo", STRING], + ["validToDate", DATE_T], + [X509_PUBLIC_KEY_FIELD, DYN], +]; + export function withUndefinedArm(t: IrType, unions: UnionRegistry): IrType | null { return withUnitArm(t, "undefinedT", unions); } diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 58dc39c7b..8b88ead00 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -638,6 +638,7 @@ async function compileExecutableNative( dh: features.dh, pki: features.pki, ed25519: features.ed25519, + x509: features.x509, webcrypto: features.webcrypto, perfLoop: features.perfLoop, ...(onArtifactReady === undefined ? {} : { onArtifactReady }), @@ -1869,6 +1870,7 @@ function libraryNativeFeatures(mod: IrModule, backend: "llvm"): EarlyLibraryNati searchParams: features.searchParams, emitter: features.emitter, zlib: features.zlib, + x509: features.x509, copying: features.copying, textDecoderLegacy: features.legacyTextDecoder, dynInvoke: features.dynInvoke, @@ -1964,6 +1966,7 @@ async function compileLibraryNative( searchParams: features.searchParams, emitter: features.emitter, zlib: features.zlib, + x509: features.x509, copying: features.copying, textDecoderLegacy: features.textDecoderLegacy, dynInvoke: features.dynInvoke, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index c8e7536ae..7682fe148 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -247,6 +247,11 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "tls.connectCb", "tls.wrapSocket", "tls.wrapSocketCtx", + // addContext's ERR_TLS_REQUIRED_SERVER_NAME and the context's errors, + // setSecureContext's validateObject. + "tls.serverAddContext", + "tls.serverAddContextCtx", + "tls.serverSetSecureContext", // The CA-store surface: an unknown getCACertificates type throws // Node's ERR_INVALID_ARG_VALUE TypeError; setDefaultCACertificates // throws Node's ERR_CRYPTO_OPERATION_FAILED on a certificate-free @@ -1037,6 +1042,16 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "crypto.diffieHellman", "crypto.createSign", "crypto.createVerify", + "crypto.x509Raw", + "crypto.x509RawDer", + "crypto.x509RawStr", + // Node's argument errors (name, options, pkey) and the key parse. + "crypto.x509CheckHost", + "crypto.x509CheckEmail", + "crypto.x509CheckIP", + "crypto.x509PublicKey", + "crypto.x509Verify", + "crypto.x509CheckPrivateKey", ]); /** libCall members that always throw. Their result type is the replaced diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index d563f2951..982550d72 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1216,6 +1216,38 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "crypto.x509ValidFromStr": { argTypes: [STRING], result: STRING }, "crypto.x509ValidTo": { argTypes: [BYTES_U8], result: STRING }, "crypto.x509ValidToStr": { argTypes: [STRING], result: STRING }, + /** The certificate's DER as a fresh Buffer (the raw field), decoded + * once from the constructor's input; the same PEM error (may-throw). */ + "crypto.x509Raw": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawDer": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawStr": { argTypes: [STRING], result: BYTES_U8 }, + /** The remaining X509Certificate fields over that DER, in Node's + * renderings (scr_lib.c). subjectAltName, infoAccess and + * signatureAlgorithm answer "" where Node answers undefined. Borrowed; + * never throw. */ + "crypto.x509Subject": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Issuer": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SubjectAltName": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509InfoAccess": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SerialNumber": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint256": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint512": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Ca": { argTypes: [BYTES_U8], result: BOOL }, + "crypto.x509SignatureAlgorithm": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SignatureAlgorithmOid": { argTypes: [BYTES_U8], result: STRING }, + /** X509Certificate methods over its raw DER. checkHost/checkEmail/checkIP + * answer the matching name or "" (undefined), checkIssued compares two + * certificates, x509Pem is toString's PEM (scr_x509.c); publicKey, + * verify and checkPrivateKey read keys (scr_pki.c). */ + "crypto.x509CheckHost": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckEmail": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIP": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIssued": { argTypes: [BYTES_U8, BYTES_U8], result: BOOL }, + "crypto.x509LegacyObject": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Pem": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509PublicKey": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Verify": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "crypto.x509CheckPrivateKey": { argTypes: [BYTES_U8, DYN], result: BOOL }, "fs.closeSync": { argTypes: [F64], result: VOID }, "stats.isFile": { argTypes: [STATS_T], result: BOOL }, "stats.isDirectory": { argTypes: [STATS_T], result: BOOL }, @@ -2218,6 +2250,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "tls.sockGetProtocol": { argTypes: [NETSOCKET_T], result: DYN }, "tls.sockGetCipher": { argTypes: [NETSOCKET_T], result: DYN }, "tls.sockAlpnProtocol": { argTypes: [NETSOCKET_T], result: DYN }, + /** getPeerCertificate(detailed) and getCertificate() (the legacy object, + * {} or null; scr_tls_x509.c), and the DER behind getPeerX509Certificate() + * and getX509Certificate() (a Buffer, empty without a certificate). */ + "tls.sockGetPeerCertificate": { argTypes: [NETSOCKET_T, DYN], result: DYN }, + "tls.sockGetCertificate": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockPeerCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, + "tls.sockOwnCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, "tls.sockAuthError": { argTypes: [NETSOCKET_T], result: VOID }, "tls.sockOnSecureConnect": { argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], @@ -2249,6 +2288,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * walk — a validated { cert, key } bag builds the real context. * May-throw. */ "tls.createSecureContextDyn": { argTypes: [DYN], result: SECURECTX_T }, + /** server.addContext(servername, options | SecureContext), + * server.setSecureContext(options) and the 'tlsClientError' listener of + * a tls or https server (scr_tls.c). */ + "tls.serverAddContext": { argTypes: [NETSERVER_T, DYN, DYN], result: VOID }, + "tls.serverAddContextCtx": { argTypes: [NETSERVER_T, DYN, SECURECTX_T], result: VOID }, + "tls.serverSetSecureContext": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "tls.serverOnClientError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, /** tls.getCACertificates(type): validateString + the documented name * set, then the trailing compiler-rendered fence — ALWAYS THROWS (the * error.nodeThrow polymorphic-result carve-out). May-throw seed. */ diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 2ae19375f..cf1f30a4c 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -738,6 +738,9 @@ export const PKI_LIB_FNS: ReadonlySet = new Set([ "crypto.generateKeyPairSync", "crypto.signOneshot", "crypto.verifyOneshot", + "crypto.x509PublicKey", + "crypto.x509Verify", + "crypto.x509CheckPrivateKey", "crypto.diffieHellman", ]); @@ -1120,6 +1123,34 @@ const RUNTIME_FEATURE_RULES = { /** crypto.subtle importKey/sign/verify/digest and CryptoKey: * scr_webcrypto.c. */ webcrypto: rule({ prefixes: ["webcrypto."] }), + /** scr_x509.c: the X509Certificate fields beyond the validity window and + * fingerprint, which stay in scr_lib.c. */ + x509: rule({ + fns: [ + "crypto.x509Ca", + "crypto.x509Fingerprint256", + "crypto.x509Fingerprint512", + "crypto.x509InfoAccess", + "crypto.x509Issuer", + "crypto.x509Raw", + "crypto.x509RawDer", + "crypto.x509RawStr", + "crypto.x509SerialNumber", + "crypto.x509SignatureAlgorithm", + "crypto.x509SignatureAlgorithmOid", + "crypto.x509Subject", + "crypto.x509SubjectAltName", + "crypto.x509CheckHost", + "crypto.x509CheckEmail", + "crypto.x509CheckIP", + "crypto.x509CheckIssued", + "crypto.x509LegacyObject", + "crypto.x509Pem", + // getPeerCertificate and getCertificate build with scr_x509.c too. + "tls.sockGetPeerCertificate", + "tls.sockGetCertificate", + ], + }), zlib: rule({ prefixes: ["zlib."] }), }; @@ -1151,6 +1182,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dynInvoke: false, dynTier: false, ecdh: false, + x509: false, dh: false, emitter: false, eventTarget: false, diff --git a/packages/compiler/src/library/ir-policy.ts b/packages/compiler/src/library/ir-policy.ts index ac5d5e47b..fde82f2b6 100644 --- a/packages/compiler/src/library/ir-policy.ts +++ b/packages/compiler/src/library/ir-policy.ts @@ -63,6 +63,9 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [ ["crypto.generateKeyPair", "asymmetric KeyObjects, generateKeyPair, sign and verify"], ["crypto.signOneshot", "asymmetric KeyObjects, generateKeyPair, sign and verify"], ["crypto.verifyOneshot", "asymmetric KeyObjects, generateKeyPair, sign and verify"], + ["crypto.x509PublicKey", "X509Certificate publicKey, verify and checkPrivateKey"], + ["crypto.x509Verify", "X509Certificate publicKey, verify and checkPrivateKey"], + ["crypto.x509CheckPrivateKey", "X509Certificate publicKey, verify and checkPrivateKey"], ["crypto.diffieHellman", "crypto.diffieHellman"], ["webcrypto.", "WebCrypto (crypto.subtle)"], ["fs.watch", "fs.watch"], diff --git a/packages/compiler/src/library/library-cache.test.ts b/packages/compiler/src/library/library-cache.test.ts index 846d90272..c19275733 100644 --- a/packages/compiler/src/library/library-cache.test.ts +++ b/packages/compiler/src/library/library-cache.test.ts @@ -85,6 +85,7 @@ test("early library cache restores generated artifacts and metadata", async () = searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -138,6 +139,7 @@ test("early library cache publishes after creating a fresh output directory", as searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -168,6 +170,7 @@ test("early library cache hits refresh every payload's LRU time", async () => { searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -213,6 +216,7 @@ test("early library cache misses on source edits and newly-resolved candidates", searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -242,6 +246,7 @@ test("early library cache misses on source edits and newly-resolved candidates", searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -291,6 +296,7 @@ test("semantic library cache restores and rebases IR after a comment-only edit", searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -354,6 +360,7 @@ test("semantic library cache refuses token and directive edits", async () => { searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -402,6 +409,7 @@ test("semantic LLVM cache accepts comment edits that shift source lines", async searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -440,6 +448,7 @@ test("semantic LLVM cache accepts line separator normalization", async () => { searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -501,6 +510,7 @@ test("semantic LLVM cache rebases comment edits in multi-source graphs", async ( searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -540,6 +550,7 @@ test("early library cache is separated by the host Node version", async () => { searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -571,6 +582,7 @@ test("early library cache rejects corrupted artifacts and metadata", async () => searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, @@ -605,6 +617,7 @@ test("disabled early library cache performs no reads or writes", async () => { searchParams: false, emitter: false, zlib: false, + x509: false, copying: false, textDecoderLegacy: false, dynInvoke: false, diff --git a/packages/compiler/src/library/library-cache.ts b/packages/compiler/src/library/library-cache.ts index 3e77ccced..7e61e95b8 100644 --- a/packages/compiler/src/library/library-cache.ts +++ b/packages/compiler/src/library/library-cache.ts @@ -54,6 +54,7 @@ interface EarlyLibraryCacheStamp { searchParams: boolean; emitter: boolean; zlib: boolean; + x509: boolean; copying: boolean; textDecoderLegacy: boolean; }; @@ -74,6 +75,7 @@ export interface EarlyLibraryNativeFeatures { searchParams: boolean; emitter: boolean; zlib: boolean; + x509: boolean; copying: boolean; textDecoderLegacy: boolean; /** Volatile exact-source build identity emitted from the tiny identity TU. */ @@ -132,6 +134,7 @@ const BOOLEAN_NATIVE_KEYS = [ "searchParams", "emitter", "zlib", + "x509", "copying", "textDecoderLegacy", ] as const satisfies readonly (keyof EarlyLibraryNativeFeatures)[]; diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index aa46944f5..b729b2557 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -147,6 +147,7 @@ async function main(): Promise { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, tlsCa: false, diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index a238029dc..1530e8759 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -250,6 +250,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "dh", "pki", "ed25519", + "x509", "webcrypto", "perfLoop", ].every((name) => typeof features[name] === "boolean"); diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 33a4c19b9..e039d6404 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -125,6 +125,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, nativeFetch: false, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-x509-certificate-fields.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-x509-certificate-fields.ts.json new file mode 100644 index 000000000..2f7224882 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6936-x509-certificate-fields.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6936-x509-certificate-fields.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7090-x509-check-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7090-x509-check-names.ts.json new file mode 100644 index 000000000..e375f8d72 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7090-x509-check-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7090-x509-check-names.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7091-x509-certificate-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7091-x509-certificate-keys.ts.json new file mode 100644 index 000000000..d4e9c4a6d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7091-x509-certificate-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7091-x509-certificate-keys.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7092-x509-legacy-object.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7092-x509-legacy-object.ts.json new file mode 100644 index 000000000..fd0b42f36 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7092-x509-legacy-object.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7092-x509-legacy-object.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7093-x509-methods-js.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7093-x509-methods-js.mjs.json new file mode 100644 index 000000000..deeeb0047 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7093-x509-methods-js.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7093-x509-methods-js.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7095-tls-peer-certificates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7095-tls-peer-certificates.ts.json new file mode 100644 index 000000000..b6ebaa3e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7095-tls-peer-certificates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7095-tls-peer-certificates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7096-tls-peer-certificates.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7096-tls-peer-certificates.mjs.json new file mode 100644 index 000000000..2b85b584a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7096-tls-peer-certificates.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7096-tls-peer-certificates.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7097-tls-client-certificates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7097-tls-client-certificates.ts.json new file mode 100644 index 000000000..ecd6a35d7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7097-tls-client-certificates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7097-tls-client-certificates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7098-tls-client-certificates-paths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7098-tls-client-certificates-paths.ts.json new file mode 100644 index 000000000..8be352a07 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7098-tls-client-certificates-paths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7098-tls-client-certificates-paths.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7099-tls-client-certificates.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7099-tls-client-certificates.mjs.json new file mode 100644 index 000000000..9dc6fbc9f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7099-tls-client-certificates.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7099-tls-client-certificates.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7440-tls-server-add-context.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7440-tls-server-add-context.ts.json new file mode 100644 index 000000000..8acc4aade --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7440-tls-server-add-context.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7440-tls-server-add-context.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-tls-client-error.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-tls-client-error.ts.json new file mode 100644 index 000000000..ee5e78748 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-tls-client-error.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7441-tls-client-error.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7442-tls-server-contexts.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7442-tls-server-contexts.mjs.json new file mode 100644 index 000000000..22eda3ac8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7442-tls-server-contexts.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7442-tls-server-contexts.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7443-tls-final-flight.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7443-tls-final-flight.ts.json new file mode 100644 index 000000000..2a8d134cc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7443-tls-final-flight.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7443-tls-final-flight.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7444-tls-final-flight.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7444-tls-final-flight.mjs.json new file mode 100644 index 000000000..98f68721a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7444-tls-final-flight.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7444-tls-final-flight.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7454-tls-peer-embedded-pem.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7454-tls-peer-embedded-pem.ts.json new file mode 100644 index 000000000..e4300cd15 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7454-tls-peer-embedded-pem.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7454-tls-peer-embedded-pem.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7455-tls-als-contexts.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7455-tls-als-contexts.mjs.json new file mode 100644 index 000000000..cd99090a6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7455-tls-als-contexts.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7455-tls-als-contexts.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/x509-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/x509-fences.ts.json new file mode 100644 index 000000000..e2b83a7c2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/x509-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/x509-fences.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-client-certificates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-client-certificates.ts.json new file mode 100644 index 000000000..449e0f3ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-client-certificates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/tls-client-certificates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-peer-certificates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-peer-certificates.ts.json new file mode 100644 index 000000000..c5f8ed40a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-peer-certificates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/tls-peer-certificates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-server-contexts.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-server-contexts.ts.json new file mode 100644 index 000000000..73e294ea2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-server-contexts.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/tls-server-contexts.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/x509-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/x509-methods.ts.json new file mode 100644 index 000000000..c42f8f9cc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/x509-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/x509-methods.ts" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/library-matrix.mjs b/packages/runtime-pack-common/library-matrix.mjs index d9878ec7c..1a621a375 100644 --- a/packages/runtime-pack-common/library-matrix.mjs +++ b/packages/runtime-pack-common/library-matrix.mjs @@ -13,6 +13,7 @@ export function withLibraryRuntimeFlavors(matrix) { "scr_events_emitter.c", "scr_dyn_handle.c", "scr_zlib.c", + "scr_x509.c", "scr_copying.c", "scr_dyn_invoke.c", "scr_bytes_dyn.c", diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 0ab467236..2c02b55c3 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -163,6 +163,9 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_webcrypto.c", "webcrypto"], ["scr_webcrypto_pki.c", all("webcrypto", "pki")], ["scr_ed25519.c", all("pki", "ed25519")], + ["scr_x509.c", "x509"], + // The TLS socket certificate accessors read scr_tls.c and build with scr_x509.c. + ["scr_tls_x509.c", all("tlsEffective", "x509")], // WebCrypto's AES ciphers run over the cipher feature's mbedTLS archive. ["scr_webcrypto_aes.c", all("cipher", "webcrypto")], ["scr_fetch.c", "nativeFetch"], diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs index cbe744e4e..2a2766f4b 100644 --- a/packages/runtime-wasm32-wasi/scripts/build.mjs +++ b/packages/runtime-wasm32-wasi/scripts/build.mjs @@ -26,6 +26,7 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ "scr_pki.c", "scr_webcrypto_pki.c", "scr_ed25519.c", + "scr_tls_x509.c", "scr_webcrypto_aes.c", "scr_wasm.c", "scr_fetch.c", diff --git a/packages/runtime/src/scr_crypto.h b/packages/runtime/src/scr_crypto.h index 03a9dd109..15180e880 100644 --- a/packages/runtime/src/scr_crypto.h +++ b/packages/runtime/src/scr_crypto.h @@ -305,5 +305,36 @@ ScrDyn *scr_crypto_create_sign(ScrDyn *algorithm); ScrDyn *scr_crypto_create_verify(ScrDyn *algorithm); bool scr_crypto_is_asymmetric_key(const ScrDyn *value); const char *scr_crypto_asymmetric_key_type(const ScrDyn *value); +/* X509Certificate fields (scr_x509.c): raw decodes the constructor's input (+1 Buffer, + * or NULL with Node's PEM error pending); the others read that DER + * (borrowed) and never throw. subjectAltName, infoAccess and + * signatureAlgorithm answer "" where Node answers undefined. */ +ScrBytes *scr_crypto_x509_raw(ScrBytes *data); +ScrBytes *scr_crypto_x509_raw_der(ScrBytes *der); +ScrBytes *scr_crypto_x509_raw_str(ScrStr *pem); +ScrStr *scr_crypto_x509_subject(ScrBytes *der); +ScrStr *scr_crypto_x509_issuer(ScrBytes *der); +ScrStr *scr_crypto_x509_subject_alt_name(ScrBytes *der); +ScrStr *scr_crypto_x509_info_access(ScrBytes *der); +ScrStr *scr_crypto_x509_serial_number(ScrBytes *der); +ScrStr *scr_crypto_x509_fingerprint256(ScrBytes *der); +ScrStr *scr_crypto_x509_fingerprint512(ScrBytes *der); +bool scr_crypto_x509_ca(ScrBytes *der); +ScrStr *scr_crypto_x509_signature_algorithm(ScrBytes *der); +ScrStr *scr_crypto_x509_signature_algorithm_oid(ScrBytes *der); +ScrStr *scr_crypto_x509_pem(ScrBytes *der); +/* X509Certificate methods over the DER (borrowed). checkHost, checkEmail + * and checkIP answer the matching name ("" for none, Node's undefined) or + * NULL with Node's argument error pending (scr_x509.c). publicKey, verify + * and checkPrivateKey read keys (scr_pki.c): publicKey is +1 or NULL, and + * the two checks answer false with the error pending when they throw. */ +ScrStr *scr_crypto_x509_check_host(ScrBytes *der, ScrDyn *name, ScrDyn *options); +ScrStr *scr_crypto_x509_check_email(ScrBytes *der, ScrDyn *email, ScrDyn *options); +ScrStr *scr_crypto_x509_check_ip(ScrBytes *der, ScrDyn *ip, ScrDyn *options); +bool scr_crypto_x509_check_issued(ScrBytes *der, ScrBytes *issuer_der); +ScrDyn *scr_crypto_x509_legacy_object(ScrBytes *der); +ScrDyn *scr_crypto_x509_public_key(ScrBytes *der); +bool scr_crypto_x509_verify(ScrBytes *der, ScrDyn *key); +bool scr_crypto_x509_check_private_key(ScrBytes *der, ScrDyn *key); #endif /* SCR_CRYPTO_H */ diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 18d9c39e5..28e1bda07 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -10522,10 +10522,29 @@ static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_ *der = NULL; *der_len = 0; *decoded = NULL; + /* One whole DER SEQUENCE is the certificate itself: these members read + * the DER the constructor already decoded, whose extensions can carry + * PEM armor as text. */ + if (n >= 2 && in[0] == 0x30) { + size_t len = in[1], head = 2; + if (len & 0x80) { + size_t k = len & 0x7f; + len = 0; + head = 2 + k; + for (size_t i = 0; i < k && k <= 4 && head <= n; i++) len = (len << 8) | in[2 + i]; + if (k == 0 || k > 4) head = n + 1; + } + if (head <= n && len == n - head) { + *der = in; + *der_len = n; + return true; + } + } /* PEM: base64-decode the armor's body. */ const uint8_t *b = NULL; + /* OpenSSL's PEM reader looks for the armor at the start of a line. */ for (size_t i = 0; i + sizeof begin - 1 <= n; i++) { - if (memcmp(in + i, begin, sizeof begin - 1) == 0) { + if ((i == 0 || in[i - 1] == '\n') && memcmp(in + i, begin, sizeof begin - 1) == 0) { b = in + i + sizeof begin - 1; break; } diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 9ff9ba8b0..bd1233978 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -451,6 +451,9 @@ struct ScrNetServer { ScrStr *unix_path; /* listen(path): the bound socket file, removed at close */ size_t nconns; ScrNetLs conn_ls, err_ls, close_ls, listening_cbs, timeout_ls; + /* tls/https servers: 'tlsClientError' (err, tlsSocket), fired by the + * TLS unit for a handshake that fails */ + ScrNetLs client_error_ls; /* server.maxConnections (unset = undefined) and its 'drop' listeners */ bool max_conns_set; double max_conns; @@ -2134,6 +2137,53 @@ void scr_net_server_set_close_override(ScrNetServer *s, ScrClosure *ov /*moves*/ s->close_override = ov; } +void scr_net_server_on_client_error(ScrNetServer *s, ScrClosure *cb /*moves*/, void *fn, bool once) { + if (s->close_emitted) { + scr_closure_release(cb); + return; + } + scr_net_ls_add(&s->client_error_ls, cb, fn, once); +} + +typedef void (*ScrNetClientErrorFn)(ScrClosure *, ScrError *, ScrNetSocket *); + +/* Node emits 'tlsClientError' from the accepted connection's handshake, + * which has no AsyncLocalStorage store, not from the server's listen() + * context the connection's other events run in. */ +void scr_net_server_fire_client_error(ScrNetServer *s, ScrError *err /*borrowed*/, + ScrNetSocket *sock /*borrowed*/) { + if (s->client_error_ls.n == 0) return; + ScrNetL *snap; + size_t n = scr_net_ls_snapshot(&s->client_error_ls, &snap); + ScrAlsCtx *prev = scr_als_dispatch_enter(NULL); + scr_dyn_this_push(s, SCR_DYNH_NET_SERVER); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) ((ScrNetClientErrorFn)snap[i].fn)(snap[i].cb, err, sock); + scr_closure_release(snap[i].cb); + } + scr_dyn_this_pop(); + scr_als_dispatch_leave(prev); + free(snap); +} + +/* The 'tlsClientError' listener adapters by arity; the arguments arrive + * borrowed and the listener takes them +1. */ +void scr_net_client_error_thunk0(ScrClosure *cb, ScrError *err, ScrNetSocket *sock) { + (void)err; + (void)sock; + ((void (*)(ScrClosure *))cb->fn)(cb); +} + +void scr_net_client_error_thunk1(ScrClosure *cb, ScrError *err, ScrNetSocket *sock) { + (void)sock; + ((void (*)(ScrClosure *, ScrError *))cb->fn)(cb, scr_error_retain(err)); +} + +void scr_net_client_error_thunk2(ScrClosure *cb, ScrError *err, ScrNetSocket *sock) { + ((void (*)(ScrClosure *, ScrError *, ScrNetSocket *))cb->fn)(cb, scr_error_retain(err), + scr_net_sock_retain(sock)); +} + void scr_net_server_on_error(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) { if (s->close_emitted) { @@ -5318,6 +5368,17 @@ void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const c scr_net_sock_transport_error(s, msg); } +void scr_net_sock_transport_report(ScrNetSocket *s, const char *msg, const char *code) { + if (s->pending_err) return; + s->pending_err = scr_str_new(msg, strlen(msg)); + s->pending_code = code; +} + +void scr_net_sock_transport_ended(ScrNetSocket *s) { + s->rd_eof = true; + scr_net_fire0_this(&s->end_ls, s, SCR_DYNH_NET_SOCKET); +} + void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg) { if (msg != NULL) { if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg)); @@ -5405,6 +5466,7 @@ static void scr_net_server_settle(ScrNetServer *srv) { scr_net_ls_drop(&srv->close_ls); scr_net_ls_drop(&srv->listening_cbs); scr_net_ls_drop(&srv->timeout_ls); + scr_net_ls_drop(&srv->client_error_ls); if (srv->proto_settle && srv->http_ctx) srv->proto_settle(srv->http_ctx); scr_net_server_unregister(srv); } @@ -5450,6 +5512,9 @@ static bool scr_net_sock_settle(ScrNetSocket *sock) { scr_net_fire_arg_this(&sock->close_ls, had_error, sock, SCR_DYNH_NET_SOCKET); scr_dyn_release(had_error); } + /* the transport settles first: a listener its last events add (a TLS + * server's 'tlsClientError') drops with the rest */ + if (sock->tops && sock->tops->settle) sock->tops->settle(sock->tctx); /* settle: listeners drop (cycle story) */ scr_net_ls_drop(&sock->data_ls); scr_net_ls_drop(&sock->end_ls); @@ -5462,7 +5527,6 @@ static bool scr_net_sock_settle(ScrNetSocket *sock) { scr_net_ls_drop(&sock->wcb_ls); scr_net_ls_drop(&sock->ready_ls); scr_net_ls_drop(&sock->lookup_ls); - if (sock->tops && sock->tops->settle) sock->tops->settle(sock->tctx); if (sock->pipe_dst) { scr_net_sock_release(sock->pipe_dst); sock->pipe_dst = NULL; @@ -5716,6 +5780,7 @@ static bool scr_net_sock_event(ScrNetSocket *s, unsigned events) { if (!scr_exc_pending() && s->fd >= 0) scr_net_sock_update_write(s); } else { s->t_want_write = false; + if (s->tops && s->tops->drain) s->tops->drain(s->tctx); scr_net_sock_flush(s); } } diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index 9e6dd1e0b..95490aaa5 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -186,6 +186,14 @@ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullab * zero-arg wrapper); reassignment releases the old one. */ void scr_net_server_set_close_override(ScrNetServer *s, ScrClosure *ov /*moves*/); void scr_net_server_on_error(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once); +/* tls/https servers' 'tlsClientError': registration (fn is one of the + * thunks below, by the listener's arity) and the TLS unit's fire. */ +void scr_net_server_on_client_error(ScrNetServer *s, ScrClosure *cb /*moves*/, void *fn, bool once); +void scr_net_server_fire_client_error(ScrNetServer *s, ScrError *err /*borrowed*/, + ScrNetSocket *sock /*borrowed*/); +void scr_net_client_error_thunk0(ScrClosure *cb, ScrError *err, ScrNetSocket *sock); +void scr_net_client_error_thunk1(ScrClosure *cb, ScrError *err, ScrNetSocket *sock); +void scr_net_client_error_thunk2(ScrClosure *cb, ScrError *err, ScrNetSocket *sock); void scr_net_server_on_close(ScrNetServer *s, ScrClosure *cb /*moves*/, bool once); /* server.on('listening', cb): the deferred 'listening' list listen(port, * cb) rides — fires once per successful bind; late registrations on an @@ -362,6 +370,9 @@ typedef struct ScrNetTransportOps { /* nullable: runs when the closed socket settles, after its own * listeners drop; the transport drops the listeners it holds */ void (*settle)(void *tctx); + /* nullable: the established socket's fd is writable; the transport + * writes what it holds back (TLS holds its final handshake flight) */ + void (*drain)(void *tctx); } ScrNetTransportOps; void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops, void *tctx); /* new tls.TLSSocket(socket) and tls.connect({ socket }): a fresh socket @@ -406,7 +417,14 @@ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len); /* Transport-level failure: msg (borrowed, Node's message shape) defers * 'error' + teardown to the sweep; NULL tears down without an 'error' * event, Node's default for an unhandled server-side handshake failure. */ +/* The peer ended the stream before the transport's handshake finished: + * 'end' fires now, ahead of the failure the transport reports next. */ +void scr_net_sock_transport_ended(ScrNetSocket *s); void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg); +/* An error that leaves the socket open: 'error' fires from the sweep with + * `code` (static storage), and the socket ends when the peer closes, as + * Node's TLS socket reports a fatal alert after its handshake. */ +void scr_net_sock_transport_report(ScrNetSocket *s, const char *msg, const char *code); /* The same with the error's code given (static storage), for messages * whose text does not name it. */ void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const char *code); diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index a1d5b98a6..72136afdb 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -2804,3 +2804,220 @@ bool scr_pki_verify(ScrPkiKey *key, const char *hash, bool pss, int salt, const SpSigKey k = {.key = key, .padding = pss ? 6 : 1, .salt = salt, .p1363 = true}; return sp_verify_hash(&k, md, digest, mbedtls_md_get_size(info), signature, signature_len); } + +/* ── X509Certificate members over keys ───────────────────────────────── */ + +/* The certificate's public key, +1; NULL with the error pending. */ +static SpKey *sp_x509_key(ScrBytes *der) { + ScrBytes *view = der; + SCR_BYTES_SNAPSHOT(view); + const unsigned char *spki; + size_t spki_len; + if (!sp_cert_spki(view->data, (size_t)scr_bytes_byte_len(view), &spki, &spki_len)) { + sp_unsupported_input(); + return NULL; + } + return sp_parse(spki, spki_len, false); +} + +/* x509.publicKey: the certificate's public key as a new KeyObject. */ +ScrDyn *scr_crypto_x509_public_key(ScrBytes *der) { + SpKey *k = sp_x509_key(der); + return k ? sp_key_box(k) : NULL; +} + +/* The pkey argument of verify (a public key) and checkPrivateKey (a + * private key), borrowed; NULL with Node's TypeError pending. */ +static SpKey *sp_x509_pkey(const ScrDyn *value, bool private_key) { + SpKey *k = sp_key_of(value); + size_t len; + if (!k && !scr_key_object_bytes(value, &len)) { + scr_dyn_arg_type_fail("pkey", "an instance of KeyObject", value); + return NULL; + } + if (!k || k->private_key != private_key) { + const char *cls = !k ? "SecretKeyObject" : k->private_key ? "PrivateKeyObject" : "PublicKeyObject"; + scr_pki_throw_fmt(SCR_ERR_TYPE, "ERR_INVALID_ARG_VALUE", "The argument 'pkey' is invalid. Received %s [KeyObject] {}", + cls); + return NULL; + } + return k; +} + +/* The digest of a signature algorithm OID's last arc (RSA and ECDSA). */ +static mbedtls_md_type_t sp_x509_rsa_md(unsigned char arc) { + switch (arc) { + case 5: return MBEDTLS_MD_SHA1; + case 11: return MBEDTLS_MD_SHA256; + case 12: return MBEDTLS_MD_SHA384; + case 13: return MBEDTLS_MD_SHA512; + case 14: return MBEDTLS_MD_SHA224; + default: return MBEDTLS_MD_NONE; + } +} + +/* A hash AlgorithmIdentifier inside RSASSA-PSS-params. */ +static mbedtls_md_type_t sp_x509_hash_alg(unsigned char *p, const unsigned char *end) { + size_t n; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return MBEDTLS_MD_NONE; + mbedtls_asn1_buf oid = {0}; + oid.tag = MBEDTLS_ASN1_OID; + if (mbedtls_asn1_get_tag(&p, end, &oid.len, MBEDTLS_ASN1_OID) != 0) return MBEDTLS_MD_NONE; + oid.p = p; + mbedtls_md_type_t md; + return mbedtls_oid_get_md_alg(&oid, &md) == 0 ? md : MBEDTLS_MD_NONE; +} + +/* x509.verify(publicKey): whether publicKey verifies the certificate's + * signature. An algorithm the key cannot verify answers false. */ +bool scr_crypto_x509_verify(ScrBytes *der, ScrDyn *key) { + SpKey *k = sp_x509_pkey(key, false); + if (!k) return false; + ScrBytes *view = der; + SCR_BYTES_SNAPSHOT(view); + unsigned char *p = view->data; + const unsigned char *end = p + (size_t)scr_bytes_byte_len(view); + size_t n; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return false; + end = p + n; + const unsigned char *tbs = p; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return false; + p += n; + size_t tbs_len = (size_t)(p - tbs); + const unsigned char *outer_alg = p; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return false; + const unsigned char *alg_end = p + n; + /* As in OpenSSL's X509_verify, the TBSCertificate's signature field must + * be the same AlgorithmIdentifier as the outer one. */ + { + unsigned char *q = (unsigned char *)tbs; + const unsigned char *tbs_end = tbs + tbs_len; + size_t len; + if (mbedtls_asn1_get_tag(&q, tbs_end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return false; + if (q < tbs_end && *q == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { + if (mbedtls_asn1_get_tag(&q, tbs_end, &len, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) != 0) + return false; + q += len; + } + if (mbedtls_asn1_get_tag(&q, tbs_end, &len, MBEDTLS_ASN1_INTEGER) != 0) return false; + q += len; + const unsigned char *inner_alg = q; + if (mbedtls_asn1_get_tag(&q, tbs_end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) + return false; + size_t inner_len = (size_t)(q + len - inner_alg); + if (inner_len != (size_t)(alg_end - outer_alg) || memcmp(inner_alg, outer_alg, inner_len) != 0) return false; + } + size_t oid_len; + if (mbedtls_asn1_get_tag(&p, alg_end, &oid_len, MBEDTLS_ASN1_OID) != 0) return false; + const unsigned char *oid = p; + unsigned char *params = p + oid_len; + p = (unsigned char *)alg_end; + size_t sig_len; + if (mbedtls_asn1_get_tag(&p, end, &sig_len, MBEDTLS_ASN1_BIT_STRING) != 0 || sig_len < 1 || *p != 0) + return false; + const unsigned char *sig = p + 1; + sig_len -= 1; + static const unsigned char rsa_prefix[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01}; + static const unsigned char ecdsa_prefix[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04}; + static const unsigned char ed25519_oid[] = {0x2B, 0x65, 0x70}; + if (oid_len == sizeof ed25519_oid && memcmp(oid, ed25519_oid, oid_len) == 0) { + if (!sp_is_ed(k)) return false; + const ScrPkiEd25519 *ed = sp_ed(); + return ed != NULL && sp_ed_verify(ed, k->ed_pub, tbs, tbs_len, sig, sig_len); + } + if (sp_is_ed(k)) return false; + unsigned char hash[MBEDTLS_MD_MAX_SIZE]; + if (oid_len == sizeof rsa_prefix + 1 && memcmp(oid, rsa_prefix, sizeof rsa_prefix) == 0) { + if (!sp_is_rsa(k)) return false; + mbedtls_rsa_context *rsa = mbedtls_pk_rsa(k->pk); + if (oid[sizeof rsa_prefix] == 10) { /* RSASSA-PSS with its parameters */ + mbedtls_md_type_t md = MBEDTLS_MD_SHA1, mgf = MBEDTLS_MD_SHA1; + int salt = 20; + unsigned char *q = params; + size_t plen; + if (mbedtls_asn1_get_tag(&q, alg_end, &plen, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0) { + const unsigned char *pend = q + plen; + while (q < pend) { + unsigned char tag = *q; + size_t len; + if (mbedtls_asn1_get_tag(&q, pend, &len, tag) != 0) return false; + unsigned char *field = q; + q += len; + if (tag == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { + md = sp_x509_hash_alg(field, q); + } else if (tag == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) { + /* MGF1 with its hash: SEQUENCE { id-mgf1, AlgorithmIdentifier } */ + size_t mlen, ilen; + unsigned char *m = field; + if (mbedtls_asn1_get_tag(&m, q, &mlen, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0 || + mbedtls_asn1_get_tag(&m, q, &ilen, MBEDTLS_ASN1_OID) != 0) + return false; + mgf = sp_x509_hash_alg(m + ilen, q); + } else if (tag == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) { + if (mbedtls_asn1_get_int(&field, q, &salt) != 0) return false; + } + } + } + if (md == MBEDTLS_MD_NONE || mgf == MBEDTLS_MD_NONE || sig_len != mbedtls_rsa_get_len(rsa)) + return false; + const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md); + mbedtls_md(info, tbs, tbs_len, hash); + return mbedtls_rsa_rsassa_pss_verify_ext(rsa, md, mbedtls_md_get_size(info), hash, mgf, salt, + sig) == 0; + } + mbedtls_md_type_t md = sp_x509_rsa_md(oid[sizeof rsa_prefix]); + if (md == MBEDTLS_MD_NONE) return false; + const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md); + mbedtls_md(info, tbs, tbs_len, hash); + /* PKCS#1 v1.5, whatever padding the key's last signature used */ + if (mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE) != 0) return false; + return mbedtls_pk_verify(&k->pk, md, hash, mbedtls_md_get_size(info), sig, sig_len) == 0; + } + if (oid_len >= sizeof ecdsa_prefix + 1 && memcmp(oid, ecdsa_prefix, sizeof ecdsa_prefix) == 0) { + if (!sp_is_ec(k)) return false; + mbedtls_md_type_t md = MBEDTLS_MD_NONE; + if (oid_len == 7 && oid[6] == 0x01) md = MBEDTLS_MD_SHA1; + else if (oid_len == 8 && oid[6] == 0x03) { + static const mbedtls_md_type_t sha2[] = {MBEDTLS_MD_NONE, MBEDTLS_MD_SHA224, MBEDTLS_MD_SHA256, + MBEDTLS_MD_SHA384, MBEDTLS_MD_SHA512}; + if (oid[7] >= 1 && oid[7] <= 4) md = sha2[oid[7]]; + } + if (md == MBEDTLS_MD_NONE) return false; + const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md); + mbedtls_md(info, tbs, tbs_len, hash); + return mbedtls_pk_verify(&k->pk, md, hash, mbedtls_md_get_size(info), sig, sig_len) == 0; + } + return false; +} + +/* x509.checkPrivateKey(privateKey): whether privateKey is the private half + * of the certificate's public key. */ +bool scr_crypto_x509_check_private_key(ScrBytes *der, ScrDyn *key) { + SpKey *k = sp_x509_pkey(key, true); + if (!k) return false; + SpKey *cert = sp_x509_key(der); + if (!cert) return false; + bool same = false; + if (sp_is_ed(k) || sp_is_ed(cert)) { + same = sp_is_ed(k) && sp_is_ed(cert) && memcmp(k->ed_pub, cert->ed_pub, 32) == 0; + } else if (mbedtls_pk_get_type(&k->pk) == mbedtls_pk_get_type(&cert->pk)) { + SpKey *pub = sp_public_of(k); + if (pub) { + unsigned char *a = malloc(SP_DER_MAX), *b = malloc(SP_DER_MAX); + if (!a || !b) scr_trap("scriptc: out of memory\n"); + int na = sp_write_spki_der(pub, a, SP_DER_MAX); + int nb = sp_write_spki_der(cert, b, SP_DER_MAX); + same = na > 0 && na == nb && memcmp(a + SP_DER_MAX - na, b + SP_DER_MAX - nb, (size_t)na) == 0; + free(a); + free(b); + sp_key_release(pub); + } + } + sp_key_release(cert); + return same; +} diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index e7436ab69..2f448a417 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -64,10 +64,15 @@ * expired") through the socket's pending-error sweep — then 'close', * Node's order, exactly like ECONNREFUSED. * + * Client certificates: servers take requestCert, rejectUnauthorized and + * ca and report socket.authorized/authorizationError as Node does; + * clients send a cert and key when asked. A `ca` also completes the + * chain its owner sends, as OpenSSL's automatic chain does. + * * Bounds (SEMANTICS.md divergence 55): no session resumption or - * tickets, no renegotiation/post-handshake auth, no client - * certificates, and non-verify client handshake failures carry a generic - * message where Node prints an OpenSSL one. + * tickets, no renegotiation/post-handshake auth, and non-verify client + * handshake failures carry a generic message where Node prints an + * OpenSSL one. * * Certificate names: a client always checks the leaf against one name, * the servername when it is non-empty and the host otherwise. SNI carries @@ -82,6 +87,7 @@ #include #include #include +#include #include #include #ifdef _WIN32 @@ -202,6 +208,42 @@ static void scr_tls_bad_material(const char *what, int err) { _Exit(1); } +/* OpenSSL's automatic chain (ssl_add_cert_chain): a certificate option + * that holds only the leaf is sent with the issuers `store` holds, up to + * a self-issued one, as far as they are found. A chain given in the + * option is sent as it is. */ +static void scr_tls_auto_chain(mbedtls_x509_crt *own, const mbedtls_x509_crt *store) { + if (own->raw.len == 0 || own->next != NULL) return; + const mbedtls_x509_crt *at = own; + for (int depth = 0; depth < 8; depth++) { + if (at->issuer_raw.len == at->subject_raw.len && + memcmp(at->issuer_raw.p, at->subject_raw.p, at->subject_raw.len) == 0) + return; + const mbedtls_x509_crt *issuer = NULL; + for (const mbedtls_x509_crt *c = store; c != NULL && c->raw.len > 0 && issuer == NULL; c = c->next) { + if (c->subject_raw.len == at->issuer_raw.len && + memcmp(c->subject_raw.p, at->issuer_raw.p, c->subject_raw.len) == 0) + issuer = c; + } + if (issuer == NULL) return; + for (const mbedtls_x509_crt *c = own; c != NULL; c = c->next) { + if (c->raw.len == issuer->raw.len && memcmp(c->raw.p, issuer->raw.p, c->raw.len) == 0) return; + } + if (mbedtls_x509_crt_parse_der(own, issuer->raw.p, issuer->raw.len) != 0) return; + while (at->next != NULL) at = at->next; + } +} + +/* A copy of a certificate chain, completed from `store` as the automatic + * chain does, for a use that must leave the original as it is. */ +static void scr_tls_chained_copy(mbedtls_x509_crt *out, const mbedtls_x509_crt *chain, + const mbedtls_x509_crt *store) { + for (const mbedtls_x509_crt *c = chain; c != NULL && c->raw.len > 0; c = c->next) { + if (mbedtls_x509_crt_parse_der(out, c->raw.p, c->raw.len) != 0) scr_tls_bad_material("certificate", 0); + } + scr_tls_auto_chain(out, store); +} + /* ── SecureContext (tls.createSecureContext) ─────────────────────────── */ /* An opaque refcounted parsed cert/key pair — what an SNI callback @@ -210,6 +252,9 @@ struct ScrSecureCtx { size_t rc; mbedtls_x509_crt cert; mbedtls_pk_context pk; + /* the ca option: a socket created from this context (a TLSSocket's + * secureContext) sends the chain it completes */ + mbedtls_x509_crt ca; }; #ifdef SCR_RC_AUDIT @@ -224,6 +269,7 @@ ScrSecureCtx *scr_tls_create_secure_context(const char *cert, size_t cert_len, if (!c) scr_tls_oom(); c->rc = 1; mbedtls_x509_crt_init(&c->cert); + mbedtls_x509_crt_init(&c->ca); mbedtls_pk_init(&c->pk); int r = scr_tls_parse_crt(&c->cert, cert, cert_len); if (r != 0) scr_tls_bad_material("certificate", r); @@ -243,6 +289,7 @@ ScrSecureCtx *scr_secure_ctx_retain(ScrSecureCtx *c) { void scr_secure_ctx_release(ScrSecureCtx *c) { if (!c || --c->rc != 0) return; mbedtls_x509_crt_free(&c->cert); + mbedtls_x509_crt_free(&c->ca); mbedtls_pk_free(&c->pk); free(c); #ifdef SCR_RC_AUDIT @@ -372,6 +419,29 @@ typedef struct ScrTlsSrv { * certificate and key this config serves (+1; NULL when cert/key are the * config's own) */ ScrSecureCtx *ctx; + /* client certificates: requestCert asks for one and verifies it against + * `ca` (the default store without one); with rejectUnauthorized a client + * that sends none fails the handshake and one that fails verification + * is dropped before 'secureConnection', as in Node */ + bool request_cert; + bool reject_unauthorized; + bool has_ca; + mbedtls_x509_crt ca; + /* new tls.TLSSocket(socket, { isServer }): Node verifies client + * certificates only in tls.Server, so a standalone socket reports no + * verdict (authorized false, authorizationError null) and drops no + * client for one; rejectUnauthorized there only requires a certificate */ + bool standalone; + /* server.addContext(servername, context): lowercased name patterns + * ('*' matches within one label) with their contexts (+1), newest + * last; server.setSecureContext's replacement default (+1, NULL for the + * server's own). Both serve through scr_tls_f_cert. */ + struct ScrTlsNamedCtx { + char *pattern; + ScrSecureCtx *ctx; + } *contexts; + size_t ncontexts; + ScrSecureCtx *default_ctx; } ScrTlsSrv; /* The ALPN answers (static storage — mbedTLS keeps the pointers). Plain @@ -389,6 +459,7 @@ static ScrTlsSrv *scr_tls_srv_new(const char *cert, size_t cert_len, const char if (!s) scr_tls_oom(); s->rc = 1; mbedtls_x509_crt_init(&s->cert); + mbedtls_x509_crt_init(&s->ca); mbedtls_pk_init(&s->pk); mbedtls_ssl_config_init(&s->conf); int r = scr_tls_parse_crt(&s->cert, cert, cert_len); @@ -401,7 +472,8 @@ static ScrTlsSrv *scr_tls_srv_new(const char *cert, size_t cert_len, const char scr_tls_oom(); } mbedtls_ssl_conf_rng(&s->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg); - mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_NONE); /* no client certs, Node's default */ + /* no client certificate unless requestCert asks (scr_tls_srv_client_auth) */ + mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_NONE); if (mbedtls_ssl_conf_own_cert(&s->conf, &s->cert, &s->pk) != 0) { scr_tls_bad_material("certificate/key pair", 0); } @@ -416,6 +488,7 @@ static ScrTlsSrv *scr_tls_srv_from_ctx(ScrSecureCtx *ctx /*borrowed*/) { if (!s) scr_tls_oom(); s->rc = 1; mbedtls_x509_crt_init(&s->cert); + mbedtls_x509_crt_init(&s->ca); mbedtls_pk_init(&s->pk); mbedtls_ssl_config_init(&s->conf); if (mbedtls_ssl_config_defaults(&s->conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, @@ -425,7 +498,13 @@ static ScrTlsSrv *scr_tls_srv_from_ctx(ScrSecureCtx *ctx /*borrowed*/) { mbedtls_ssl_conf_rng(&s->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg); mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_NONE); s->ctx = scr_secure_ctx_retain(ctx); - if (mbedtls_ssl_conf_own_cert(&s->conf, &ctx->cert, &ctx->pk) != 0) { + /* the context's ca completes the chain a socket created from it sends */ + const mbedtls_x509_crt *own = &ctx->cert; + if (ctx->ca.raw.len > 0) { + scr_tls_chained_copy(&s->cert, &ctx->cert, &ctx->ca); + own = &s->cert; + } + if (mbedtls_ssl_conf_own_cert(&s->conf, (mbedtls_x509_crt *)own, &ctx->pk) != 0) { scr_tls_bad_material("certificate/key pair", 0); } return s; @@ -443,14 +522,53 @@ static void scr_tls_srv_release(ScrTlsSrv *s) { scr_closure_release(s->sni_cb); mbedtls_ssl_config_free(&s->conf); mbedtls_x509_crt_free(&s->cert); + mbedtls_x509_crt_free(&s->ca); mbedtls_pk_free(&s->pk); scr_tls_alpn_free(s->alpn); scr_secure_ctx_release(s->ctx); + for (size_t i = 0; i < s->ncontexts; i++) { + free(s->contexts[i].pattern); + scr_secure_ctx_release(s->contexts[i].ctx); + } + free(s->contexts); + scr_secure_ctx_release(s->default_ctx); free(s); } static void scr_tls_srv_release_v(void *p) { scr_tls_srv_release((ScrTlsSrv *)p); } +/* The client-certificate options of a server: requestCert, rejectUnauthorized + * (Node's true default) and ca (+1, NULL for none). */ +typedef struct { + bool request; + bool reject; + ScrBytes *ca; +} ScrTlsClientAuth; + +static mbedtls_x509_crt *scr_tls_system_ca(void); + +/* Applies a server's client-certificate options. A `ca` also completes the + * server's own chain, as OpenSSL's automatic chain sends it. */ +static void scr_tls_srv_client_auth(ScrTlsSrv *s, const ScrTlsClientAuth *auth) { + if (auth == NULL) return; + /* An empty ca Buffer is an empty trust store, not the default one. */ + if (auth->ca != NULL) { + if (auth->ca->len > 0) { + int r = scr_tls_parse_crt(&s->ca, (const char *)auth->ca->data, auth->ca->len); + if (r != 0) scr_tls_bad_material("ca certificate", r); + if (s->ctx == NULL) scr_tls_auto_chain(&s->cert, &s->ca); + } + s->has_ca = true; + } + if (!auth->request) return; + s->request_cert = true; + s->reject_unauthorized = auth->reject; + /* OPTIONAL: the handshake records the verdict and goes on, as Node's + * verify callback does; scr_tls_handshake applies rejectUnauthorized */ + mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_ca_chain(&s->conf, s->has_ca ? &s->ca : scr_tls_system_ca(), NULL); +} + /* ── the client config (one per https.request, owned by its tctx) ────── */ typedef struct ScrTlsCli { @@ -463,6 +581,7 @@ typedef struct ScrTlsCli { * VERIFY_NONE socket (the https client's rejectUnauthorized:false) * never verified, so authorized answers false like Node's. */ bool verify_recorded; + bool reject; /* rejectUnauthorized: a verification failure fails the handshake */ /* The ALPNProtocols option's names, NULL-terminated, in the caller's * order (mbedtls_ssl_conf_alpn_protocols keeps the pointer); NULL when * none are offered. */ @@ -470,6 +589,11 @@ typedef struct ScrTlsCli { /* No TLS version is in the configured range: the handshake fails as * soon as it starts (Node's ERR_SSL_NO_PROTOCOLS_AVAILABLE). */ bool no_protocols; + /* the client certificate (cert and key options), sent when the server + * asks for one */ + bool has_own; + mbedtls_x509_crt own; + mbedtls_pk_context own_pk; } ScrTlsCli; /* The default trust anchors when no `ca` option is given: the host trust @@ -562,6 +686,8 @@ static ScrTlsCli *scr_tls_cli_new2(ScrStr *host, bool reject_unauthorized, const if (!c) scr_tls_oom(); mbedtls_ssl_config_init(&c->conf); mbedtls_x509_crt_init(&c->ca); + mbedtls_x509_crt_init(&c->own); + mbedtls_pk_init(&c->own_pk); if (mbedtls_ssl_config_defaults(&c->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) { @@ -569,6 +695,7 @@ static ScrTlsCli *scr_tls_cli_new2(ScrStr *host, bool reject_unauthorized, const } mbedtls_ssl_conf_rng(&c->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg); c->verify_recorded = reject_unauthorized || verify_optional; + c->reject = reject_unauthorized; if (ca_len > 0) { int r = scr_tls_parse_crt(&c->ca, ca, ca_len); if (r != 0) scr_tls_bad_material("ca certificate", r); @@ -620,11 +747,27 @@ static void scr_tls_cli_free(ScrTlsCli *c) { mbedtls_ssl_config_free(&c->conf); scr_tls_alpn_free(c->alpn); mbedtls_x509_crt_free(&c->ca); + mbedtls_x509_crt_free(&c->own); + mbedtls_pk_free(&c->own_pk); free(c->hostname); free(c->verify_name); free(c); } +/* The cert and key options of a client: the certificate (with the issuers + * its `ca` holds, OpenSSL's automatic chain) is sent when the server asks + * for one. */ +static void scr_tls_cli_set_own(ScrTlsCli *c, const ScrBytes *cert, const ScrBytes *key) { + int r = scr_tls_parse_crt(&c->own, (const char *)cert->data, cert->len); + if (r != 0) scr_tls_bad_material("certificate", r); + r = scr_tls_parse_key(&c->own_pk, (const char *)key->data, key->len); + if (r != 0) scr_tls_bad_material("private key", r); + if (c->ca.raw.len > 0) scr_tls_auto_chain(&c->own, &c->ca); + if (mbedtls_ssl_conf_own_cert(&c->conf, &c->own, &c->own_pk) != 0) + scr_tls_bad_material("certificate/key pair", 0); + c->has_own = true; +} + /* ── the per-socket transport ────────────────────────────────────────── */ /* The SNI pre-parse states: NONE (no callback on the server — the @@ -665,8 +808,32 @@ typedef struct ScrTlsSock { /* the 'secure' event's listeners */ ScrNetLs secure_ls; bool settled; /* the socket closed: listeners added later never fire */ + /* a TLS 1.3 client sent no certificate to a server that requires one; + * the alert goes out once the handshake is over */ + bool cert_missing; + /* a fatal alert was reported without closing: reads end the stream */ + bool alert_reported; + /* a server socket: the accepting server (+1), which 'tlsClientError' + * fires on, and a hang-up waiting for the socket to close (Node emits + * ECONNRESET 'socket hang up' from the socket's 'close') */ + ScrNetServer *server; + bool hang_up_pending; + /* a client whose verification failed: like Node's, it closes without + * sending the alert, so later writes are dropped */ + bool quiet_failure; + /* the served context's chain completed from the server's ca */ + mbedtls_x509_crt served_chain; + /* what the handshake's last step and the established callbacks write: + * Node flushes it after the nextTick queue and before the immediates + * queued in those callbacks, so a destroy() there drops it. `holding` + * collects; held[held_off..held_len) waits for the fd. */ + bool holding; + unsigned char *held; + size_t held_len, held_off, held_cap; } ScrTlsSock; +static bool scr_tls_alert_error(ScrTlsSock *t, unsigned char alert); + /* checkServerIdentity for an IP literal: the address must be one of the * certificate's IP SANs (no DNS SAN or CN applies). */ static bool scr_tls_leaf_has_ip(const mbedtls_x509_crt *crt, const char *ip) { @@ -711,6 +878,9 @@ static int scr_tls_vrfy_cb(void *ctx, mbedtls_x509_crt *crt, int depth, uint32_t *flags |= name_flags & MBEDTLS_X509_BADCERT_CN_MISMATCH; } } + /* a rejecting client fails the handshake on any flag; Node's client + * then closes without sending an alert (its Finished is discarded) */ + if (*flags != 0 && t->cli != NULL && t->cli->reject) t->quiet_failure = true; bool self = crt->issuer_raw.len == crt->subject_raw.len && memcmp(crt->issuer_raw.p, crt->subject_raw.p, crt->subject_raw.len) == 0; if (*flags & MBEDTLS_X509_BADCERT_EXPIRED) t->vrfy_expired = true; @@ -725,8 +895,52 @@ static int scr_tls_vrfy_cb(void *ctx, mbedtls_x509_crt *crt, int depth, uint32_t /* The BIOs: nonblocking fd I/O in mbedTLS's own convention. A send that * would block arms the socket's write filter so the handshake resumes on * writability. */ +static void scr_tls_hold(ScrTlsSock *t, const unsigned char *buf, size_t len) { + if (t->held_len + len > t->held_cap) { + size_t cap = t->held_cap ? t->held_cap : 4096; + while (cap < t->held_len + len) cap *= 2; + unsigned char *grown = realloc(t->held, cap); + if (grown == NULL) scr_tls_oom(); + t->held = grown; + t->held_cap = cap; + } + memcpy(t->held + t->held_len, buf, len); + t->held_len += len; +} + +/* Writes what is held: 1 once it is all out, 0 when the fd would block + * (the write filter is armed for the rest), -1 when the connection is + * gone (the bytes are dropped). */ +static int scr_tls_flush_held(ScrTlsSock *t) { + int fd = scr_net_sock_fd(t->sock); + while (t->held_off < t->held_len) { + if (fd < 0) break; + ssize_t n = write(fd, t->held + t->held_off, t->held_len - t->held_off); + if (n > 0) { + t->held_off += (size_t)n; + continue; + } + if (n < 0 && errno == EINTR) continue; + if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + scr_net_sock_transport_want_write(t->sock); + return 0; + } + break; + } + bool out = t->held_off == t->held_len; + t->held_off = t->held_len = 0; + return out ? 1 : -1; +} + static int scr_tls_bio_send(void *ctx, const unsigned char *buf, size_t len) { ScrTlsSock *t = (ScrTlsSock *)ctx; + if (t->quiet_failure) return (int)len; + if (t->holding) { + scr_tls_hold(t, buf, len); + return (int)len; + } + /* held bytes go first; mbedTLS retries this record once they are out */ + if (t->held_len > t->held_off && scr_tls_flush_held(t) == 0) return MBEDTLS_ERR_SSL_WANT_WRITE; int fd = scr_net_sock_fd(t->sock); if (fd < 0) return MBEDTLS_ERR_NET_CONN_RESET; ssize_t n = write(fd, buf, len); @@ -742,7 +956,7 @@ static int scr_tls_bio_send(void *ctx, const unsigned char *buf, size_t len) { /* Keeps the first bytes a wrapped socket's peer sends: a handshake that * fails on them reports what they looked like, as OpenSSL does. */ static void scr_tls_note_head(ScrTlsSock *t, const unsigned char *buf, size_t n) { - if (!t->wrapped || t->head_len == sizeof t->head) return; + if (t->srv == NULL || t->head_len == sizeof t->head) return; size_t take = sizeof t->head - t->head_len; if (take > n) take = n; memcpy(t->head + t->head_len, buf, take); @@ -770,6 +984,21 @@ static int scr_tls_bio_recv(void *ctx, unsigned char *buf, size_t len) { return MBEDTLS_ERR_NET_RECV_FAILED; } +/* Node reads what the kernel holds (one 64 KiB libuv read) before + * OpenSSL sees the records, so a failed handshake closes with nothing + * unread and the peer gets a FIN. Closing with the rest of, say, an HTTP + * request still queued would make the kernel send a reset instead. */ +static void scr_tls_drain_unread(ScrTlsSock *t) { + int fd = scr_net_sock_fd(t->sock); + if (fd < 0) return; + unsigned char sink[4096]; + for (size_t total = 0; total < 65536; ) { + ssize_t n = read(fd, sink, sizeof sink); + if (n <= 0) break; + total += (size_t)n; + } +} + /* ── the transport ops ───────────────────────────────────────────────── */ static void scr_tls_fire_session(ScrTlsSock *t); @@ -793,6 +1022,14 @@ static ssize_t scr_tls_xread(void *tctx, char *buf, size_t n) { errno = EAGAIN; return -1; } + if (t->alert_reported) return 0; /* the server closing after its alert */ + /* a TLS 1.3 server rejects a missing client certificate after the + * client finished its handshake */ + if (r == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE && t->cli != NULL && + t->ssl.MBEDTLS_PRIVATE(in_msg) != NULL && scr_tls_alert_error(t, t->ssl.MBEDTLS_PRIVATE(in_msg)[1])) { + errno = EAGAIN; + return -1; + } errno = ECONNRESET; /* protocol-level death reads as a reset */ return -1; } @@ -1081,6 +1318,99 @@ void scr_tls_sni_answer(ScrClosure *self, bool has_err, ScrSecureCtx *ctx /*move * ECONNRESET, and bytes that are not a TLS record name what they were * (OpenSSL's reasons without the per-process prefix and source location, * SEMANTICS.md divergence 55). */ +/* The OpenSSL reason (without its per-process prefix and source location, + * SEMANTICS.md divergence 55) and Node's code for a server handshake that + * failed with mbedTLS error `r`; false when the peer closed or reset the + * connection instead. A failure mbedTLS reports without a reason OpenSSL + * would give answers a coded SC2020 refusal. */ +static bool scr_tls_server_reason(ScrTlsSock *t, int r, const char **msg, const char **code) { + if (r == MBEDTLS_ERR_SSL_CONN_EOF || r == MBEDTLS_ERR_NET_CONN_RESET) return false; + if (r == MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE) { + *msg = "error:0A0000C7:SSL routines:tls_process_client_certificate:peer did not return a certificate"; + *code = "ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE"; + return true; + } + const unsigned char *h = t->head; + bool record = t->head_len >= 3 && h[0] >= 20 && h[0] <= 23 && h[1] == 3; + if (t->head_len >= 5 && !record) { + static const char *const http[] = { "GET ", "POST ", "HEAD ", "PATCH", "OPTIO", + "DELET", "TRACE", "PUT ", NULL }; + *msg = "error:0A00010B:SSL routines:tls_validate_record_header:wrong version number"; + *code = "ERR_SSL_WRONG_VERSION_NUMBER"; + for (size_t i = 0; http[i] != NULL; i++) { + if (memcmp(h, http[i], strlen(http[i])) == 0) { + *msg = "error:0A00009C:SSL routines:tls_validate_record_header:http request"; + *code = "ERR_SSL_HTTP_REQUEST"; + } + } + if (memcmp(h, "CONNE", 5) == 0) { + *msg = "error:0A00009B:SSL routines:tls_validate_record_header:https proxy request"; + *code = "ERR_SSL_HTTPS_PROXY_REQUEST"; + } + return true; + } + if (r == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE && t->ssl.MBEDTLS_PRIVATE(in_msg) != NULL) { + /* an alert the client sent: OpenSSL's reason is 1000 + its number */ + static const struct { unsigned char alert; const char *msg, *code; } alerts[] = { + { 40, "error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure", + "ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE" }, + { 42, "error:0A000412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate", + "ERR_SSL_SSLV3_ALERT_BAD_CERTIFICATE" }, + { 45, "error:0A000415:SSL routines:ssl3_read_bytes:sslv3 alert certificate expired", + "ERR_SSL_SSLV3_ALERT_CERTIFICATE_EXPIRED" }, + { 46, "error:0A000416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown", + "ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN" }, + { 48, "error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca", + "ERR_SSL_TLSV1_ALERT_UNKNOWN_CA" }, + { 49, "error:0A000419:SSL routines:ssl3_read_bytes:tlsv1 alert access denied", + "ERR_SSL_TLSV1_ALERT_ACCESS_DENIED" }, + { 51, "error:0A00041B:SSL routines:ssl3_read_bytes:tlsv1 alert decrypt error", + "ERR_SSL_TLSV1_ALERT_DECRYPT_ERROR" }, + { 70, "error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version", + "ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION" }, + { 80, "error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error", + "ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR" }, + }; + unsigned char alert = t->ssl.MBEDTLS_PRIVATE(in_msg)[1]; + for (size_t i = 0; i < sizeof alerts / sizeof alerts[0]; i++) { + if (alerts[i].alert == alert) { + *msg = alerts[i].msg; + *code = alerts[i].code; + return true; + } + } + } + if (r == MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION) { + *msg = "error:0A000102:SSL routines:tls_early_post_process_client_hello:unsupported protocol"; + *code = "ERR_SSL_UNSUPPORTED_PROTOCOL"; + return true; + } + if (r == MBEDTLS_ERR_SSL_NO_APPLICATION_PROTOCOL) { + *msg = "error:0A0000EB:SSL routines:tls_handle_alpn:no application protocol"; + *code = "ERR_SSL_NO_APPLICATION_PROTOCOL"; + return true; + } + static SCR_TL char refusal[128]; + snprintf(refusal, sizeof refusal, + "a TLS handshake failure (mbedTLS error -0x%04X) without the reason OpenSSL gives is not supported yet", + (unsigned)-r); + *msg = refusal; + *code = "SC2020"; + return true; +} + +/* 'tlsClientError' on the accepting server: Node's Error with `code`, and + * the socket. Without a listener the socket is only destroyed. */ +static void scr_tls_client_error(ScrTlsSock *t, const char *msg, const char *code) { + if (t->server == NULL) return; + ScrStr *m = scr_str_new(msg, strlen(msg)); + ScrError *e = scr_error_new(SCR_ERR_ERROR, m); + scr_str_release(m); + scr_error_set_code(e, code); + scr_net_server_fire_client_error(t->server, e, t->sock); + scr_error_release(e); +} + static void scr_tls_wrapped_failure(ScrTlsSock *t, int r) { if (r == MBEDTLS_ERR_SSL_CONN_EOF) { scr_net_sock_transport_eof(t->sock); @@ -1090,27 +1420,86 @@ static void scr_tls_wrapped_failure(ScrTlsSock *t, int r) { scr_net_sock_transport_error(t->sock, "read ECONNRESET"); return; } - const unsigned char *h = t->head; - bool record = t->head_len >= 3 && h[0] >= 20 && h[0] <= 23 && h[1] == 3; - if (t->head_len >= 5 && !record) { - static const char *const http[] = { "GET ", "POST ", "HEAD ", "PATCH", "OPTIO", - "DELET", "TRACE", "PUT ", NULL }; - const char *reason = "error:0A00010B:SSL routines:tls_validate_record_header:wrong version number"; - const char *code = "ERR_SSL_WRONG_VERSION_NUMBER"; - for (size_t i = 0; http[i] != NULL; i++) { - if (memcmp(h, http[i], strlen(http[i])) == 0) { - reason = "error:0A00009C:SSL routines:tls_validate_record_header:http request"; - code = "ERR_SSL_HTTP_REQUEST"; - } - } - if (memcmp(h, "CONNE", 5) == 0) { - reason = "error:0A00009B:SSL routines:tls_validate_record_header:https proxy request"; - code = "ERR_SSL_HTTPS_PROXY_REQUEST"; - } - scr_net_sock_transport_error_code(t->sock, reason, code); - return; + const char *msg, *code; + (void)scr_tls_server_reason(t, r, &msg, &code); + scr_net_sock_transport_error_code(t->sock, msg, code); +} + +/* A fatal alert a server sends a client without the certificate it + * requires: the client's error is OpenSSL's reason (without its + * per-process prefix and source location, SEMANTICS.md divergence 55) and + * Node's code. False for other alerts. */ +static bool scr_tls_alert_error(ScrTlsSock *t, unsigned char alert) { + if (alert == MBEDTLS_SSL_ALERT_MSG_CERT_REQUIRED) { + /* after the client's handshake: Node emits 'error' and the socket + * then ends when the server closes, with close(false) */ + scr_net_sock_transport_report(t->sock, + "error:0A00045C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required", + "ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED"); + t->alert_reported = true; + return true; } - scr_net_sock_transport_error(t->sock, "server TLS handshake failed"); + if (alert == MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE) { + scr_net_sock_transport_error_code(t->sock, + "error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure", + "ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE"); + return true; + } + return false; +} + +/* A server that requires a client certificate (requestCert with + * rejectUnauthorized) runs its handshake a step at a time, so a client + * that sends none fails as soon as its empty Certificate is read, before + * the server's Finished in TLS 1.2, with OpenSSL's alert: certificate + * required in TLS 1.3, handshake failure in TLS 1.2. */ +static int scr_tls_handshake_requiring_cert(ScrTlsSock *t) { + while (!mbedtls_ssl_is_handshake_over(&t->ssl)) { + int r = mbedtls_ssl_handshake_step(&t->ssl); + if (r != 0) return r; + if (t->cert_missing) continue; + /* all ones until the client's Certificate is read */ + uint32_t verdict = mbedtls_ssl_get_verify_result(&t->ssl); + if (verdict == UINT32_MAX || !(verdict & MBEDTLS_X509_BADCERT_MISSING)) continue; + if (mbedtls_ssl_get_version_number(&t->ssl) != MBEDTLS_SSL_VERSION_TLS1_3) { + (void)mbedtls_ssl_send_alert_message(&t->ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, + MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE); + return MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE; + } + /* TLS 1.3: the client has finished and reads under the server's + * application keys, which mbedTLS writes with only once the handshake + * is over, so the alert waits for that */ + t->cert_missing = true; + } + if (t->cert_missing) { + (void)mbedtls_ssl_send_alert_message(&t->ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, + MBEDTLS_SSL_ALERT_MSG_CERT_REQUIRED); + return MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE; + } + return 0; +} + +static ScrTlsSock *scr_tls_sock_of(ScrNetSocket *s); + +/* The immediate that ends the hold: queued as the handshake finishes, + * before the established callbacks run, so it comes ahead of any + * immediate they queue. A socket destroyed by then has no fd, and what + * was held is dropped. */ +static void scr_tls_release_hold(ScrClosure *self) { + ScrNetSocket *s = scr_box_get_ref(self->caps[0]); /* +1 */ + ScrTlsSock *t = scr_tls_sock_of(s); + if (t != NULL && t->holding) { + t->holding = false; + if (t->held_len > t->held_off) (void)scr_tls_flush_held(t); + } + scr_net_sock_release(s); +} + +static void scr_tls_hold_until_immediate(ScrTlsSock *t) { + ScrClosure *cb = scr_closure_new((void *)&scr_tls_release_hold, 1); + cb->caps[0] = scr_box_new_obj(&scr_net_sock_retain_v, &scr_net_sock_release_v, NULL); + scr_box_set_ref(cb->caps[0], scr_net_sock_retain(t->sock)); + (void)scr_set_immediate(cb); } static int scr_tls_handshake(void *tctx) { @@ -1129,8 +1518,27 @@ static int scr_tls_handshake(void *tctx) { } if (t->sni_state == SCR_SNI_WAIT) return 0; /* the callback is deciding */ scr_tls_current = t; - int r = mbedtls_ssl_handshake(&t->ssl); + t->holding = true; + int r = t->srv != NULL && t->srv->request_cert && t->srv->reject_unauthorized + ? scr_tls_handshake_requiring_cert(t) + : mbedtls_ssl_handshake(&t->ssl); scr_tls_current = NULL; + if (r == 0) { + scr_tls_hold_until_immediate(t); + } else { + /* a step that did not finish the handshake sends what it wrote now */ + t->holding = false; + if (t->held_len > t->held_off) (void)scr_tls_flush_held(t); + } + if (r == 0 && t->srv != NULL && t->srv->request_cert && t->srv->reject_unauthorized && + !t->srv->standalone && mbedtls_ssl_get_verify_result(&t->ssl) != 0) { + /* a client certificate that fails verification: Node's server + * destroys the socket before 'secureConnection', and its + * 'tlsClientError' is the hang-up */ + scr_net_sock_transport_error(t->sock, NULL); + t->hang_up_pending = true; + return -1; + } if (r == 0) return 1; if (r == MBEDTLS_ERR_SSL_WANT_READ || r == MBEDTLS_ERR_SSL_WANT_WRITE) return 0; if (t->srv != NULL && t->wrapped) { @@ -1138,21 +1546,38 @@ static int scr_tls_handshake(void *tctx) { return -1; } if (t->srv != NULL) { - /* Node's server default: 'tlsClientError' with no listener destroys - * the socket without an error event — exactly what portless (no - * listener) sees. */ + /* the socket is destroyed without an error event, and the server's + * 'tlsClientError' gets the failure: now, before the socket closes, + * for an OpenSSL reason; once it closes for a hang-up, as Node + * emits ECONNRESET from the socket's 'close' */ + const char *msg, *code; + bool reason = scr_tls_server_reason(t, r, &msg, &code); + /* Node's socket still has its handle when an OpenSSL reason is + * emitted, so remoteAddress answers; the address is cached before + * the fd closes */ + if (reason) scr_str_release(scr_net_sock_remote_address(t->sock)); + scr_tls_drain_unread(t); scr_net_sock_transport_error(t->sock, NULL); + if (reason) scr_tls_client_error(t, msg, code); + else t->hang_up_pending = true; return -1; } char msg[160]; if (r == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { scr_tls_verify_msg(t, msg, sizeof msg); - } else if (r == MBEDTLS_ERR_SSL_CONN_EOF || r == MBEDTLS_ERR_NET_CONN_RESET) { - /* The peer closed or reset the connection mid-handshake: Node's - * ECONNRESET for tls.connect and https.request alike. */ + } else if (r == MBEDTLS_ERR_SSL_CONN_EOF) { + /* The peer closed the connection mid-handshake: Node's socket ends, + * then fails with ECONNRESET, for tls.connect and https.request + * alike. */ + scr_net_sock_transport_ended(t->sock); + if (scr_exc_pending()) return -1; scr_net_sock_transport_error( t->sock, "Client network socket disconnected before secure TLS connection was established"); return -1; + } else if (r == MBEDTLS_ERR_NET_CONN_RESET) { + /* a reset is the read's own error, as in Node */ + scr_net_sock_transport_error(t->sock, "read ECONNRESET"); + return -1; } else if (r == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE && t->ssl.MBEDTLS_PRIVATE(in_msg) != NULL && t->ssl.MBEDTLS_PRIVATE(in_msg)[1] == MBEDTLS_SSL_ALERT_MSG_NO_APPLICATION_PROTOCOL) { @@ -1167,6 +1592,9 @@ static int scr_tls_handshake(void *tctx) { t->ssl.MBEDTLS_PRIVATE(in_msg)[1] == MBEDTLS_SSL_ALERT_MSG_PROTOCOL_VERSION) { /* The server accepts none of the offered versions. */ snprintf(msg, sizeof msg, "error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version"); + } else if (r == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE && t->ssl.MBEDTLS_PRIVATE(in_msg) != NULL && + scr_tls_alert_error(t, t->ssl.MBEDTLS_PRIVATE(in_msg)[1])) { + return -1; } else { /* non-verify handshake failures: honest but not Node's exact openssl * string (SEMANTICS.md divergence 55) */ @@ -1215,6 +1643,9 @@ static void scr_tls_shutdown_write(void *tctx) { ScrTlsSock *t = (ScrTlsSock *)tctx; if (t->notify_sent) return; t->notify_sent = true; + /* end() sends what is held, then the close_notify, ahead of the FIN */ + t->holding = false; + if (t->held_len > t->held_off) (void)scr_tls_flush_held(t); (void)mbedtls_ssl_close_notify(&t->ssl); /* best effort; EAGAIN drops it */ } @@ -1227,9 +1658,18 @@ static void scr_tls_free(void *tctx) { scr_net_ls_drop(&t->secure_ls); free(t->hello); scr_secure_ctx_release(t->sni_ctx); + mbedtls_x509_crt_free(&t->served_chain); + if (t->server) scr_net_server_release(t->server); + free(t->held); free(t); } +/* The fd is writable again: the rest of what was held goes out. */ +static void scr_tls_drain(void *tctx) { + ScrTlsSock *t = (ScrTlsSock *)tctx; + if (!t->holding && t->held_len > t->held_off) (void)scr_tls_flush_held(t); +} + /* The 'secure' event: after a client's secureConnect listeners, as Node * emits it. */ static void scr_tls_after_established(void *tctx) { @@ -1247,6 +1687,10 @@ static void scr_tls_settle(void *tctx) { t->settled = true; scr_net_ls_drop(&t->secure_ls); scr_net_ls_drop(&t->session_ls); + if (t->hang_up_pending) { + t->hang_up_pending = false; + scr_tls_client_error(t, "socket hang up", "ECONNRESET"); + } } static const ScrNetTransportOps SCR_TLS_OPS = { @@ -1259,6 +1703,7 @@ static const ScrNetTransportOps SCR_TLS_OPS = { .free = scr_tls_free, .settle = scr_tls_settle, .after_established = scr_tls_after_established, + .drain = scr_tls_drain, }; /* ── the server surface ──────────────────────────────────────────────── */ @@ -1272,8 +1717,11 @@ static void scr_tls_srv_on_conn(void *ctx, ScrNetSocket *sock) { if (!t) scr_tls_oom(); mbedtls_ssl_init(&t->ssl); if (mbedtls_ssl_setup(&t->ssl, &srv->conf) != 0) scr_tls_oom(); + if (srv->request_cert) mbedtls_ssl_set_verify(&t->ssl, &scr_tls_vrfy_cb, t); t->sock = sock; t->srv = scr_tls_srv_retain(srv); + ScrNetServer *server = scr_net_sock_server(sock); + t->server = server != NULL ? scr_net_server_retain(server) : NULL; if (srv->sni_cb != NULL || srv->max_tls12) t->sni_state = SCR_SNI_COLLECT; /* pre-parse the hello */ mbedtls_ssl_set_bio(&t->ssl, t, scr_tls_bio_send, scr_tls_bio_recv, NULL); scr_net_sock_set_transport(sock, &SCR_TLS_OPS, t); @@ -1285,8 +1733,10 @@ static ScrNetServer *scr_tls_create_server_alpn(const char *cert, size_t cert_le ScrNetConnFn fn, char **alpn /*moves, nullable*/, int min_version, int max_version, ScrClosure *sni_cb /*moves, nullable*/, - void *sni_answer_fn) { + void *sni_answer_fn, + const ScrTlsClientAuth *auth /*nullable*/) { ScrTlsSrv *srv = scr_tls_srv_new(cert, cert_len, key, key_len, false); + scr_tls_srv_client_auth(srv, auth); if (sni_cb != NULL) { srv->sni_cb = sni_cb; /* moves; released with srv */ srv->sni_answer_fn = sni_answer_fn; @@ -1319,7 +1769,7 @@ ScrNetServer *scr_tls_create_server(const char *cert, size_t cert_len, const cha size_t key_len, ScrClosure *handler /*moves, nullable*/, ScrNetConnFn fn) { return scr_tls_create_server_alpn(cert, cert_len, key, key_len, handler, fn, NULL, 12, 13, NULL, - NULL); + NULL, NULL); } /* tls.createServer({ cert, key, SNICallback }): the pre-parse machinery @@ -1332,14 +1782,15 @@ ScrNetServer *scr_tls_create_server_sni(const char *cert, size_t cert_len, const ScrNetConnFn fn, ScrClosure *sni_cb /*moves, nullable*/, void *sni_answer_fn) { return scr_tls_create_server_alpn(cert, cert_len, key, key_len, handler, fn, NULL, 12, 13, sni_cb, - sni_answer_fn); + sni_answer_fn, NULL); } -ScrNetServer *scr_https_create_server(const char *cert, size_t cert_len, const char *key, - size_t key_len, ScrClosure *handler /*moves*/, - ScrHttpReqFn fn) { +static ScrNetServer *scr_https_create_server_auth(const char *cert, size_t cert_len, const char *key, + size_t key_len, ScrClosure *handler /*moves*/, + ScrHttpReqFn fn, const ScrTlsClientAuth *auth /*nullable*/) { ScrNetServer *s = scr_http_create_server(handler, fn); ScrTlsSrv *srv = scr_tls_srv_new(cert, cert_len, key, key_len, true /* ALPN http/1.1 */); + scr_tls_srv_client_auth(srv, auth); /* wrap the parser hook: TLS first, parser at establishment */ scr_net_server_get_native_conn(s, &srv->inner_conn, &srv->inner_ctx, &srv->inner_ctx_free); scr_net_server_set_native_conn(s, &scr_tls_srv_on_conn, srv, &scr_tls_srv_release_v); @@ -1347,6 +1798,12 @@ ScrNetServer *scr_https_create_server(const char *cert, size_t cert_len, const c return s; } +ScrNetServer *scr_https_create_server(const char *cert, size_t cert_len, const char *key, + size_t key_len, ScrClosure *handler /*moves*/, + ScrHttpReqFn fn) { + return scr_https_create_server_auth(cert, cert_len, key, key_len, handler, fn, NULL); +} + /* The SNI-callback form of the allowHTTP1 compatibility server: the same * handler-less https server, plus the pre-parse machinery (design note * atop the SNI section). A NULL sni_cb — the conditional spread's @@ -1505,12 +1962,22 @@ static int scr_tls_tunnel_bio_recv(void *ctx, unsigned char *buf, size_t len) { return scr_tls_tunnel_read((ScrTlsTunnel *)ctx, buf, len); } +/* A tunnel leg's handshake has finished. The tunnel runs no established + * callbacks before it writes the CONNECT request or the request itself, so + * the last flight goes out now instead of waiting for the immediate that + * ends a socket's hold (which does not see the tunnel's legs). */ +static void scr_tls_tunnel_end_hold(ScrTlsSock *t) { + t->holding = false; + if (t->held_len > t->held_off) (void)scr_tls_flush_held(t); +} + static int scr_tls_tunnel_handshake(void *tctx) { ScrTlsTunnel *tun = (ScrTlsTunnel *)tctx; ScrNetSocket *sock = tun->outer->sock; if (tun->stage == SCR_TUN_OUTER) { int r = scr_tls_handshake(tun->outer); if (r <= 0) return r; + scr_tls_tunnel_end_hold(tun->outer); tun->outer->established = true; tun->stage = SCR_TUN_SEND; } @@ -1579,6 +2046,7 @@ static int scr_tls_tunnel_handshake(void *tctx) { if (tun->stage == SCR_TUN_INNER) { int r = scr_tls_handshake(tun->inner); if (r <= 0) return r; + scr_tls_tunnel_end_hold(tun->inner); tun->stage = SCR_TUN_UP; } return 1; @@ -1625,12 +2093,16 @@ static void scr_tls_tunnel_free(void *tctx) { free(tun); } +/* The fd is writable again: the rest of what the outer leg held. */ +static void scr_tls_tunnel_drain(void *tctx) { scr_tls_drain(((ScrTlsTunnel *)tctx)->outer); } + static const ScrNetTransportOps SCR_TLS_TUNNEL_OPS = { .xread = scr_tls_tunnel_xread, .xwrite = scr_tls_tunnel_xwrite, .handshake = scr_tls_tunnel_handshake, .on_established = scr_tls_tunnel_on_established, .pending = scr_tls_tunnel_pending, + .drain = scr_tls_tunnel_drain, .shutdown_write = scr_tls_tunnel_shutdown_write, .free = scr_tls_tunnel_free, }; @@ -1822,10 +2294,11 @@ static ScrBytes *scr_tls_pem_dyn(const ScrDyn *v, const char *what, bool concat) /* https.request/get with a runtime options record: the http walk, then * the TLS options the literal https path lowers (rejectUnauthorized, ca, - * servername). Other tls.connect options fence when they carry a value. */ + * servername) and a client certificate (cert and key). Other tls.connect + * options fence when they carry a value. */ static const char *const SCR_HTTPS_REQ_FENCED_OPTIONS[] = { - "ALPNProtocols", "cert", "checkServerIdentity", "ciphers", "crl", "dhparam", "ecdhCurve", - "honorCipherOrder", "key", "maxVersion", "minVersion", "passphrase", "pfx", "secureContext", + "ALPNProtocols", "checkServerIdentity", "ciphers", "crl", "dhparam", "ecdhCurve", + "honorCipherOrder", "maxVersion", "minVersion", "passphrase", "pfx", "secureContext", "secureOptions", "secureProtocol", "session", "sessionIdContext", "sigalgs", NULL, }; @@ -1840,6 +2313,7 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au const ScrDyn *rec = o.view != NULL ? o.view : opts; bool reject = true; ScrBytes *ca = NULL; + ScrBytes *own_cert = NULL, *own_key = NULL; ScrStr *servername = NULL; bool ok = true; if (rec != NULL && rec->kind == SCR_DYN_OBJ) { @@ -1848,7 +2322,7 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au const ScrDyn *v = scr_dyn_obj_get(rec, key, strlen(key)); if (v != NULL && scr_tls_dyn_truthy(v)) { scr_http_req_opts_fence("https.request", key, - "rejectUnauthorized, ca, and servername are the supported TLS options"); + "rejectUnauthorized, ca, servername, cert, and key are the supported TLS options"); ok = false; } } @@ -1859,6 +2333,21 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au ca = scr_tls_pem_dyn(c, "an https.request 'ca' option", true); if (ca == NULL) ok = false; } + const ScrDyn *oc = ok ? scr_dyn_obj_get(rec, "cert", 4) : NULL; + if (oc != NULL && oc->kind != SCR_DYN_UNDEF && oc->kind != SCR_DYN_NULL) { + own_cert = scr_tls_pem_dyn(oc, "an https.request 'cert' option", false); + if (own_cert == NULL) ok = false; + } + const ScrDyn *ok_ = ok ? scr_dyn_obj_get(rec, "key", 3) : NULL; + if (ok_ != NULL && ok_->kind != SCR_DYN_UNDEF && ok_->kind != SCR_DYN_NULL) { + own_key = scr_tls_pem_dyn(ok_, "an https.request 'key' option", false); + if (own_key == NULL) ok = false; + } + if (ok && (own_cert == NULL) != (own_key == NULL)) { + scr_http_req_opts_fence("https.request", own_cert != NULL ? "cert" : "key", + "a client certificate lowers with both cert and key"); + ok = false; + } const ScrDyn *sn = ok ? scr_dyn_obj_get(rec, "servername", 10) : NULL; if (sn != NULL && sn->kind != SCR_DYN_UNDEF && sn->kind != SCR_DYN_NULL) { if (sn->kind != SCR_DYN_STR) { @@ -1871,6 +2360,8 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au } if (!ok) { if (ca) scr_bytes_release(ca); + scr_bytes_release(own_cert); + scr_bytes_release(own_key); scr_http_req_opts_free(&o); if (cb) scr_closure_release(cb); return NULL; @@ -1879,6 +2370,22 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au size_t ca_len = ca ? ca->len : 0; ScrTlsCli *cli = scr_tls_cli_for(o.host, reject, ca_data, ca_len, servername); ScrStr *key = scr_tls_pool_key(o.host, reject, ca_data, ca_len, servername); + if (own_cert != NULL) { + /* Node's agent keys pooled sockets by the client certificate too */ + scr_tls_cli_set_own(cli, own_cert, own_key); + uint64_t h = 1469598103934665603ull; + for (size_t i = 0; i < own_cert->len; i++) h = (h ^ own_cert->data[i]) * 1099511628211ull; + for (size_t i = 0; i < own_key->len; i++) h = (h ^ own_key->data[i]) * 1099511628211ull; + char suffix[24]; + int n = snprintf(suffix, sizeof suffix, ":c%016llx", (unsigned long long)h); + ScrStr *tail = scr_str_new(suffix, (size_t)n); + ScrStr *keyed = scr_str_concat(key, tail); + scr_str_release(tail); + scr_str_release(key); + key = keyed; + scr_bytes_release(own_cert); + scr_bytes_release(own_key); + } scr_str_release(servername); ScrHttpClientReq *req = scr_http_req_opts_send(&o, auto_end, cb, fn, add_abort, &scr_tls_cli_wrap, cli, key); @@ -2014,9 +2521,22 @@ static bool scr_tls_opts_validate(const ScrDyn *opts) { /* `server_ok`: tls.createServer reads ALPNProtocols, minVersion and * maxVersion itself; the walk's other callers keep their fences. */ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrBytes **cert_out, - ScrBytes **key_out, bool server_ok) { + ScrBytes **key_out, bool server_ok, ScrTlsClientAuth *auth) { + if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) { + /* a typed record (an options binding): its fields, as a plain object */ + ScrDyn *plain = scr_dyn_typed_ref_materialize(opts); + if (plain == NULL) { + *cert_out = NULL; + *key_out = NULL; + return false; + } + bool ok = scr_tls_srv_opts_walk_ex(plain, api, cert_out, key_out, server_ok, auth); + scr_dyn_release(plain); + return ok; + } *cert_out = NULL; *key_out = NULL; + if (auth != NULL) *auth = (ScrTlsClientAuth){.request = false, .reject = true, .ca = NULL}; if (opts == NULL || opts->kind != SCR_DYN_OBJ) { scr_dyn_arg_type_fail("options", "of type object", opts ? opts : scr_dyn_undefined()); return false; @@ -2038,20 +2558,39 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt continue; } if (strcmp(e->key, "ca") == 0) { - /* the server's `ca` verifies CLIENT certificates — inert without - * requestCert (which fences below), so Node-observably droppable */ + /* the server's `ca` verifies client certificates and completes the + * server's own chain (scr_tls_srv_client_auth); a walk without + * client certificates drops it */ + if (auth != NULL) { + char what[96]; + snprintf(what, sizeof what, "a %s 'ca' option", api); + scr_bytes_release(auth->ca); + /* An empty string is no ca, as Node reads it; an empty Buffer is + * an empty trust store. */ + if (v->kind == SCR_DYN_STR && v->v.str->len == 0) { + auth->ca = NULL; + } else { + auth->ca = scr_tls_pem_dyn(v, what, true); + if (auth->ca == NULL) ok = false; + } + } continue; } if (strcmp(e->key, "requestCert") == 0) { + if (auth != NULL) { + auth->request = scr_tls_dyn_truthy(v); + continue; + } if (scr_tls_dyn_truthy(v)) { scr_tls_opt_fence(api, "requestCert", - "client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert stays at Node's false default"); + "requestCert lowers on tls.createServer, https.createServer and new tls.TLSSocket"); ok = false; } continue; } if (strcmp(e->key, "rejectUnauthorized") == 0) { - continue; /* meaningful only with requestCert, which fences */ + if (auth != NULL) auth->reject = scr_tls_dyn_truthy(v); + continue; /* without client certificates it has no effect */ } if (server_ok && (strcmp(e->key, "ALPNProtocols") == 0 || strcmp(e->key, "minVersion") == 0 || strcmp(e->key, "maxVersion") == 0)) @@ -2085,13 +2624,17 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt scr_bytes_release(*key_out); *cert_out = NULL; *key_out = NULL; + if (auth != NULL) { + scr_bytes_release(auth->ca); + auth->ca = NULL; + } } return ok; } bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes **cert_out, ScrBytes **key_out) { - return scr_tls_srv_opts_walk_ex(opts, api, cert_out, key_out, false); + return scr_tls_srv_opts_walk_ex(opts, api, cert_out, key_out, false, NULL); } /* createSecureContext over a RUNTIME options record: Node's typed @@ -2101,9 +2644,22 @@ bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes **cert_ * +1, or NULL with the exception pending. */ ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts /*borrowed*/) { ScrBytes *cert, *key; - if (!scr_tls_srv_opts_walk(opts, "tls.createSecureContext", &cert, &key)) return NULL; + /* requestCert and rejectUnauthorized are no context options: Node's + * createSecureContext ignores them */ + ScrTlsClientAuth auth; + if (!scr_tls_srv_opts_walk_ex(opts, "tls.createSecureContext", &cert, &key, false, &auth)) return NULL; ScrSecureCtx *c = scr_tls_create_secure_context((const char *)cert->data, cert->len, (const char *)key->data, key->len); + if (auth.ca != NULL) { + /* kept for sockets created from the context (a TLSSocket's + * secureContext); an SNI answer's chain comes from the server's + * context in OpenSSL, so the answer's ca is not used there */ + if (auth.ca->len > 0) { + int r = scr_tls_parse_crt(&c->ca, (const char *)auth.ca->data, auth.ca->len); + if (r != 0) scr_tls_bad_material("ca certificate", r); + } + scr_bytes_release(auth.ca); + } scr_bytes_release(cert); scr_bytes_release(key); return c; @@ -2142,7 +2698,8 @@ ScrNetServer *scr_tls_create_server_dyn(const ScrDyn *opts /*borrowed*/, return NULL; } ScrBytes *cert, *key; - if (!scr_tls_srv_opts_walk_ex(opts, "tls.createServer", &cert, &key, true)) { + ScrTlsClientAuth auth; + if (!scr_tls_srv_opts_walk_ex(opts, "tls.createServer", &cert, &key, true, &auth)) { scr_closure_release(handler); return NULL; } @@ -2153,12 +2710,14 @@ ScrNetServer *scr_tls_create_server_dyn(const ScrDyn *opts /*borrowed*/, if (protocols != NULL && !scr_tls_alpn_dyn(protocols, &alpn)) { scr_bytes_release(cert); scr_bytes_release(key); + scr_bytes_release(auth.ca); scr_closure_release(handler); return NULL; } ScrNetServer *s = scr_tls_create_server_alpn((const char *)cert->data, cert->len, (const char *)key->data, key->len, handler, fn, - alpn, min_version, max_version, NULL, NULL); + alpn, min_version, max_version, NULL, NULL, &auth); + scr_bytes_release(auth.ca); scr_bytes_release(cert); scr_bytes_release(key); return s; @@ -2189,18 +2748,146 @@ ScrNetServer *scr_https_create_server_dyn(const ScrDyn *opts /*borrowed*/, } } ScrBytes *cert, *key; - if (!scr_tls_srv_opts_walk(opts, "https.createServer", &cert, &key)) { + ScrTlsClientAuth auth; + if (!scr_tls_srv_opts_walk_ex(opts, "https.createServer", &cert, &key, false, &auth)) { scr_closure_release(handler); return NULL; } - ScrNetServer *s = scr_https_create_server((const char *)cert->data, cert->len, - (const char *)key->data, key->len, handler, fn); + ScrNetServer *s = scr_https_create_server_auth((const char *)cert->data, cert->len, + (const char *)key->data, key->len, handler, fn, &auth); + scr_bytes_release(auth.ca); if (has_timeout_buffer) scr_net_server_timeout_set(s, 4, timeout_buffer); scr_bytes_release(cert); scr_bytes_release(key); return s; } +/* ── server.addContext and server.setSecureContext ───────────────────── */ + +/* Node's addContext pattern: the name with '*' as [^.]* (any run within a + * label, empty too), matched without regard to case. */ +static bool scr_tls_name_match(const char *pattern, const unsigned char *name, size_t len) { + if (*pattern == '\0') return len == 0; + if (*pattern == '*') { + for (size_t k = 0;; k++) { + if (scr_tls_name_match(pattern + 1, name + k, len - k)) return true; + if (k == len || name[k] == '.') return false; + } + } + return len > 0 && (unsigned char)tolower(name[0]) == (unsigned char)*pattern && + scr_tls_name_match(pattern + 1, name + 1, len - 1); +} + +/* The certificate a server handshake serves when the server has contexts + * (mbedTLS's certificate callback, after the SNI name is read): the + * newest addContext entry whose pattern matches the name, else the + * setSecureContext default, else the server's own. An SNICallback answer + * (or a server with an SNICallback, whose contexts Node does not read) + * keeps its own path. OpenSSL completes the served chain from the store + * of the connection's context, the server's current default, and verifies + * client certificates against the served context's. */ +static int scr_tls_f_cert(mbedtls_ssl_context *ssl) { + ScrTlsSock *t = scr_tls_current; + if (t == NULL || t->srv == NULL || t->sni_ctx != NULL) return 0; + ScrTlsSrv *s = t->srv; + ScrSecureCtx *pick = NULL; + if (s->sni_cb == NULL) { + size_t len = 0; + const unsigned char *name = mbedtls_ssl_get_hs_sni(ssl, &len); + for (size_t i = s->ncontexts; name != NULL && i-- > 0;) { + if (scr_tls_name_match(s->contexts[i].pattern, name, len)) { + pick = s->contexts[i].ctx; + break; + } + } + } + if (pick == NULL) pick = s->default_ctx; + if (pick == NULL) return 0; + t->sni_ctx = scr_secure_ctx_retain(pick); + const mbedtls_x509_crt *store = s->default_ctx != NULL ? &s->default_ctx->ca : &s->ca; + mbedtls_x509_crt *own = &pick->cert; + if (store->raw.len > 0 && pick->cert.next == NULL) { + scr_tls_chained_copy(&t->served_chain, &pick->cert, store); + own = &t->served_chain; + } + if (s->request_cert) + mbedtls_ssl_set_hs_ca_chain(ssl, pick->ca.raw.len > 0 ? &pick->ca : scr_tls_system_ca(), NULL); + return mbedtls_ssl_set_hs_own_cert(ssl, own, &pick->pk); +} + +/* The TLS config of a tls or https server, or NULL with the fence thrown. */ +static ScrTlsSrv *scr_tls_srv_of_server(ScrNetServer *server, const char *what) { + ScrNetNativeConnFn fn = NULL; + void *ctx = NULL; + void (*ctx_free)(void *) = NULL; + scr_net_server_get_native_conn(server, &fn, &ctx, &ctx_free); + if (fn == &scr_tls_srv_on_conn && ctx != NULL) return (ScrTlsSrv *)ctx; + char msg[160]; + int n = snprintf(msg, sizeof msg, "%s on a server that is not a tls or https server is not supported yet", what); + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)(n < (int)sizeof msg ? n : (int)sizeof msg - 1), "SC2020"); + return NULL; +} + +/* server.addContext(servername, context): `context` is a SecureContext + * (borrowed, nullable) or the options createSecureContext reads. Node + * builds a RegExp from the name, so a name with regular-expression groups + * or alternation throws the fence. */ +void scr_tls_server_add_context(ScrNetServer *server, const ScrDyn *servername, + const ScrDyn *options /*nullable*/, ScrSecureCtx *ctx /*nullable*/) { + ScrTlsSrv *s = scr_tls_srv_of_server(server, "server.addContext"); + if (s == NULL) return; + if (!scr_tls_dyn_truthy(servername)) { + static const char msg[] = "\"servername\" is required parameter for Server.addContext"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "ERR_TLS_REQUIRED_SERVER_NAME"); + return; + } + if (servername->kind != SCR_DYN_STR) { + scr_tls_opt_fence("server.addContext", "servername", "the servername lowers as a string"); + return; + } + const ScrStr *name = servername->v.str; + for (size_t i = 0; i < name->len; i++) { + if (name->data[i] == '(' || name->data[i] == ')' || name->data[i] == '|') { + scr_tls_opt_fence("server.addContext", "servername", + "a servername with regular-expression groups or alternation has no lowering"); + return; + } + /* the RegExp's case folding beyond ASCII differs from tolower's */ + if ((unsigned char)name->data[i] >= 0x80) { + scr_tls_opt_fence("server.addContext", "servername", "a servername outside ASCII has no lowering"); + return; + } + } + ScrSecureCtx *c = ctx != NULL ? scr_secure_ctx_retain(ctx) : scr_tls_create_secure_context_dyn(options); + if (c == NULL) return; + char *pattern = malloc(name->len + 1); + struct ScrTlsNamedCtx *grown = realloc(s->contexts, (s->ncontexts + 1) * sizeof *s->contexts); + if (pattern == NULL || grown == NULL) scr_tls_oom(); + for (size_t i = 0; i < name->len; i++) pattern[i] = (char)tolower((unsigned char)name->data[i]); + pattern[name->len] = '\0'; + s->contexts = grown; + s->contexts[s->ncontexts].pattern = pattern; + s->contexts[s->ncontexts].ctx = c; + s->ncontexts++; + mbedtls_ssl_conf_cert_cb(&s->conf, &scr_tls_f_cert); +} + +/* server.setSecureContext(options): new connections serve a context made + * from `options`; open ones keep theirs. */ +void scr_tls_server_set_secure_context(ScrNetServer *server, const ScrDyn *options) { + ScrTlsSrv *s = scr_tls_srv_of_server(server, "server.setSecureContext"); + if (s == NULL) return; + if (options == NULL || (options->kind != SCR_DYN_OBJ && options->kind != SCR_DYN_TYPED_REF)) { + scr_dyn_arg_type_fail("options", "of type object", options != NULL ? options : scr_dyn_undefined()); + return; + } + ScrSecureCtx *c = scr_tls_create_secure_context_dyn(options); + if (c == NULL) return; + scr_secure_ctx_release(s->default_ctx); + s->default_ctx = c; + mbedtls_ssl_conf_cert_cb(&s->conf, &scr_tls_f_cert); +} + /* ── new tls.TLSSocket(socket, options) ───────────────────────────────── */ /* The server-mode wrap: a fresh TLS socket takes over raw's connection @@ -2226,7 +2913,7 @@ static bool scr_tls_wrap_socket_opts_check(const ScrDyn *record, const char *api if (strcmp(e->key, "requestCert") == 0) { if (!scr_tls_dyn_truthy(e->value)) continue; scr_tls_opt_fence(api, "requestCert", - "client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert stays at Node's false default"); + "requestCert lowers with cert and key options, not beside a secureContext"); return false; } if (strcmp(e->key, "SNICallback") == 0 || strcmp(e->key, "ALPNCallback") == 0) { @@ -2271,13 +2958,17 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow /* Node serves a secureContext as it is: the record's cert, key and * other context options are not read then. The socket's own options * still apply, so the ones without a lowering fence either way: - * requestCert asks for a client certificate this runtime cannot - * verify. */ + * requestCert lowers with cert and key, whose ca the walk reads. */ int min_version = 12, max_version = 13; ScrBytes *cert = NULL, *key = NULL; + ScrTlsClientAuth auth = {.request = false, .reject = false, .ca = NULL}; if (ctx == NULL) { if (!scr_tls_versions_dyn(record, &min_version, &max_version)) return NULL; - if (!scr_tls_srv_opts_walk_ex(record, api, &cert, &key, true)) return NULL; + if (!scr_tls_srv_opts_walk_ex(record, api, &cert, &key, true, &auth)) return NULL; + /* a standalone socket requires a certificate only for a truthy + * rejectUnauthorized; tls.Server's true default does not apply */ + const ScrDyn *reject = scr_dyn_obj_get(record, "rejectUnauthorized", 18); + auth.reject = reject != NULL && scr_tls_dyn_truthy(reject); } else if (!scr_tls_wrap_socket_opts_check(record, api)) { return NULL; } @@ -2286,6 +2977,7 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow if (protocols != NULL && protocols->kind != SCR_DYN_UNDEF && !scr_tls_alpn_dyn(protocols, &alpn)) { scr_bytes_release(cert); scr_bytes_release(key); + scr_bytes_release(auth.ca); return NULL; } ScrTlsSrv *srv = ctx != NULL ? scr_tls_srv_from_ctx(ctx) @@ -2293,6 +2985,9 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow (const char *)key->data, key->len, false); scr_bytes_release(cert); scr_bytes_release(key); + srv->standalone = true; + scr_tls_srv_client_auth(srv, ctx == NULL ? &auth : NULL); + scr_bytes_release(auth.ca); srv->alpn = alpn; /* moves */ if (alpn != NULL) mbedtls_ssl_conf_alpn_protocols(&srv->conf, (const char **)srv->alpn); if (!scr_tls_conf_versions(&srv->conf, min_version, max_version)) { @@ -2314,6 +3009,7 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow if (!ts) scr_tls_oom(); mbedtls_ssl_init(&ts->ssl); if (mbedtls_ssl_setup(&ts->ssl, &srv->conf) != 0) scr_tls_oom(); + if (srv->request_cert) mbedtls_ssl_set_verify(&ts->ssl, &scr_tls_vrfy_cb, ts); ts->sock = t; ts->srv = srv; /* moves */ ts->wrapped = true; @@ -2468,10 +3164,10 @@ static bool scr_tls_alpn_dyn(const ScrDyn *v, char ***out) { * throws (the server walk's stance). */ static const char *const SCR_TLS_CONNECT_FENCED_OPTIONS[] = { "allowHalfOpen", "autoSelectFamily", - "autoSelectFamilyAttemptTimeout", "cert", "certificateCompression", "checkServerIdentity", + "autoSelectFamilyAttemptTimeout", "certificateCompression", "checkServerIdentity", "ciphers", "clientCertEngine", "crl", "dhparam", "ecdhCurve", "enableTrace", "family", "fd", "hints", "highWaterMark", - "honorCipherOrder", "keepAlive", "keepAliveInitialDelay", "key", + "honorCipherOrder", "keepAlive", "keepAliveInitialDelay", "localAddress", "localPort", "lookup", "minDHSize", "noDelay", "onread", "passphrase", "path", "pfx", "privateKeyEngine", "privateKeyIdentifier", "pskCallback", "readable", "secureContext", "secureOptions", "secureProtocol", "session", @@ -2479,12 +3175,32 @@ static const char *const SCR_TLS_CONNECT_FENCED_OPTIONS[] = { "writable", NULL, }; +static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed, nullable*/, + const ScrDyn *opts /*borrowed, nullable*/, + ScrClosure *cb /*moves, nullable*/); + ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable*/, const ScrDyn *opts /*borrowed, nullable*/, ScrClosure *cb /*moves, nullable*/) { + if (opts == NULL || opts->kind != SCR_DYN_TYPED_REF) return scr_tls_connect_plain(port, host, opts, cb); + /* a typed record (an options binding): its fields, as a plain object */ + ScrDyn *plain = scr_dyn_typed_ref_materialize(opts); + if (plain == NULL) { + scr_closure_release(cb); + return NULL; + } + ScrNetSocket *s = scr_tls_connect_plain(port, host, plain, cb); + scr_dyn_release(plain); + return s; +} + +static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed, nullable*/, + const ScrDyn *opts /*borrowed, nullable*/, + ScrClosure *cb /*moves, nullable*/) { scr_tls_rng_init(); bool reject = true; ScrBytes *ca = NULL; + ScrBytes *own_cert = NULL, *own_key = NULL; /* the client certificate */ const ScrStr *servername = NULL; const ScrStr *opt_host = NULL; char **alpn = NULL; @@ -2549,6 +3265,14 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable if (ca == NULL) ok = false; continue; } + if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) { + ScrBytes **slot = e->key[0] == 'c' ? &own_cert : &own_key; + scr_bytes_release(*slot); + *slot = scr_tls_pem_dyn(v, e->key[0] == 'c' ? "a tls.connect 'cert' option" : "a tls.connect 'key' option", + false); + if (*slot == NULL) ok = false; + continue; + } if (strcmp(e->key, "ALPNProtocols") == 0) { alpn_value = v; /* converted after the context options, as TLSSocket does */ continue; @@ -2607,6 +3331,11 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable timeout_ms = timeout_value->v.num; } } + if (ok && (own_cert == NULL) != (own_key == NULL)) { + scr_tls_opt_fence("tls.connect", own_cert != NULL ? "cert" : "key", + "a client certificate lowers with both cert and key"); + ok = false; + } if (ok && port < 0 && wrap == NULL) { scr_dyn_arg_type_fail("options.port", "of type number", scr_dyn_undefined()); ok = false; @@ -2616,6 +3345,8 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable if (!ok) { scr_tls_alpn_free(alpn); scr_bytes_release(ca); + scr_bytes_release(own_cert); + scr_bytes_release(own_key); scr_closure_release(cb); return NULL; } @@ -2639,6 +3370,8 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable if (s == NULL) { scr_tls_alpn_free(alpn); scr_bytes_release(ca); + scr_bytes_release(own_cert); + scr_bytes_release(own_key); scr_closure_release(cb); scr_str_release(localhost_fallback); scr_str_release(socket_host); @@ -2656,6 +3389,9 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable ca != NULL ? (const char *)ca->data : NULL, ca != NULL ? ca->len : 0, !reject); if (servername != NULL) scr_tls_cli_set_servername(cli, servername); + if (own_cert != NULL) scr_tls_cli_set_own(cli, own_cert, own_key); + scr_bytes_release(own_cert); + scr_bytes_release(own_key); if (alpn != NULL) { cli->alpn = alpn; /* moves */ mbedtls_ssl_conf_alpn_protocols(&cli->conf, (const char **)cli->alpn); @@ -2731,6 +3467,20 @@ static const char *scr_tls_auth_code(ScrTlsSock *t) { return "UNABLE_TO_VERIFY_LEAF_SIGNATURE"; } +/* A server's authorizationError for the client certificate. OpenSSL + * keeps the last error its verification met: the validity period is + * checked last, the purpose (clientAuth) before it, the chain first. A + * client that sent none is UNABLE_TO_GET_ISSUER_CERT, as Node reports it. */ +static const char *scr_tls_client_cert_code(ScrTlsSock *t) { + uint32_t vr = mbedtls_ssl_get_verify_result(&t->ssl); + if (vr & MBEDTLS_X509_BADCERT_MISSING) return "UNABLE_TO_GET_ISSUER_CERT"; + if (t->vrfy_expired) return "CERT_HAS_EXPIRED"; + if (vr & (MBEDTLS_X509_BADCERT_EXT_KEY_USAGE | MBEDTLS_X509_BADCERT_KEY_USAGE)) return "INVALID_PURPOSE"; + if (t->vrfy_leaf_self) return "DEPTH_ZERO_SELF_SIGNED_CERT"; + if (t->vrfy_chain_self) return "SELF_SIGNED_CERT_IN_CHAIN"; + return "UNABLE_TO_VERIFY_LEAF_SIGNATURE"; +} + static ScrTlsSock *scr_tls_sock_of(ScrNetSocket *s) { return (ScrTlsSock *)scr_net_sock_transport_ctx_for(s, &SCR_TLS_OPS); } @@ -2775,22 +3525,106 @@ static void scr_tls_fire_session(ScrTlsSock *t) { /* The STATIC-lane TLSSocket members (the lowered socket surface). */ +/* Whether the socket verified its peer: a client against its trust + * anchors, a server that requested a client certificate against its. */ +static bool scr_tls_sock_verifies(const ScrTlsSock *t) { + return t->established && ((t->cli != NULL && t->cli->verify_recorded) || + (t->srv != NULL && t->srv->request_cert && !t->srv->standalone)); +} + bool scr_tls_sock_authorized(ScrNetSocket *s) { ScrTlsSock *t = scr_tls_sock_of(s); - return t != NULL && t->established && t->cli != NULL && t->cli->verify_recorded && - mbedtls_ssl_get_verify_result(&t->ssl) == 0; + return t != NULL && scr_tls_sock_verifies(t) && mbedtls_ssl_get_verify_result(&t->ssl) == 0; } ScrStr *scr_tls_sock_auth_error(ScrNetSocket *s) { ScrTlsSock *t = scr_tls_sock_of(s); - if (t == NULL || !t->established || t->cli == NULL || !t->cli->verify_recorded || - mbedtls_ssl_get_verify_result(&t->ssl) == 0) { + if (t == NULL || !scr_tls_sock_verifies(t) || mbedtls_ssl_get_verify_result(&t->ssl) == 0) { return NULL; /* the null arm — authorized, or never verified */ } - const char *code = scr_tls_auth_code(t); + const char *code = t->srv != NULL ? scr_tls_client_cert_code(t) : scr_tls_auth_code(t); return scr_str_new(code, strlen(code)); } +/* ── certificates for the X509Certificate accessors ────────────────── + * + * getPeerCertificate, getCertificate and their X509Certificate forms read + * the DER below (scr_tls_x509.c builds the objects, so this unit needs no + * X.509 code). Node answers from the socket's handle, which a destroyed + * socket no longer has. */ + +bool scr_tls_sock_has_handle(ScrNetSocket *s) { + return scr_tls_sock_of(s) != NULL && !scr_net_sock_destroyed(s); +} + +/* The certificates the peer sent, leaf first (borrowed DER), up to `max`; + * none before the handshake. Answers the count. */ +size_t scr_tls_sock_peer_chain(ScrNetSocket *s, const uint8_t **der, size_t *len, size_t max) { + ScrTlsSock *t = scr_tls_sock_of(s); + if (t == NULL || !t->established) return 0; + size_t n = 0; + for (const mbedtls_x509_crt *c = mbedtls_ssl_get_peer_cert(&t->ssl); c != NULL && c->raw.len > 0 && n < max; + c = c->next) { + der[n] = c->raw.p; + len[n] = c->raw.len; + n++; + } + return n; +} + +/* The certificate the socket serves (borrowed DER), or NULL: a server's + * SNI answer, secureContext or own certificate. Clients send none. */ +const uint8_t *scr_tls_sock_own_cert(ScrNetSocket *s, size_t *len) { + ScrTlsSock *t = scr_tls_sock_of(s); + if (t != NULL && t->cli != NULL && t->cli->has_own) { + *len = t->cli->own.raw.len; + return t->cli->own.raw.p; + } + if (t == NULL || t->srv == NULL) return NULL; + const mbedtls_x509_crt *c = t->sni_ctx != NULL ? &t->sni_ctx->cert + : t->srv->ctx != NULL ? &t->srv->ctx->cert + : &t->srv->cert; + if (c->raw.len == 0) return NULL; + *len = c->raw.len; + return c->raw.p; +} + +/* Visits the trust anchors the socket verified its peer against (its + * `ca`, or the default store) until `visit` answers true. */ +void scr_tls_sock_each_trusted(ScrNetSocket *s, bool (*visit)(void *ctx, const uint8_t *der, size_t len), + void *ctx) { + ScrTlsSock *t = scr_tls_sock_of(s); + if (t == NULL || (t->cli == NULL && t->srv == NULL)) return; + const mbedtls_x509_crt *c = t->cli != NULL ? (t->cli->ca.raw.len > 0 ? &t->cli->ca : scr_tls_system_ca()) + : t->srv->has_ca ? &t->srv->ca + : scr_tls_system_ca(); + for (; c != NULL && c->raw.len > 0; c = c->next) + if (visit(ctx, c->raw.p, c->raw.len)) return; +} + +static ScrBytes *scr_tls_der_buffer(const uint8_t *der, size_t len) { + ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len); + b->is_buffer = true; + if (len) memcpy(b->data, der, len); + return b; +} + +/* getPeerX509Certificate() and getX509Certificate(): the certificate's + * DER (+1 Buffer), empty when there is none (undefined in Node). */ +ScrBytes *scr_tls_sock_peer_cert_der(ScrNetSocket *s) { + const uint8_t *der; + size_t len; + if (!scr_tls_sock_has_handle(s) || scr_tls_sock_peer_chain(s, &der, &len, 1) == 0) + return scr_tls_der_buffer(NULL, 0); + return scr_tls_der_buffer(der, len); +} + +ScrBytes *scr_tls_sock_own_cert_der(ScrNetSocket *s) { + size_t len = 0; + const uint8_t *der = scr_tls_sock_has_handle(s) ? scr_tls_sock_own_cert(s, &len) : NULL; + return scr_tls_der_buffer(der, der ? len : 0); +} + /* tlsSocket.getProtocol(): the negotiated version ("TLSv1.2", * "TLSv1.3"); before the handshake the highest version on offer, as * OpenSSL answers, and null after close. +1. */ diff --git a/packages/runtime/src/scr_tls.h b/packages/runtime/src/scr_tls.h index cc8e3336e..94bf41d33 100644 --- a/packages/runtime/src/scr_tls.h +++ b/packages/runtime/src/scr_tls.h @@ -58,6 +58,19 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable bool scr_tls_sock_authorized(ScrNetSocket *s); ScrStr *scr_tls_sock_auth_error(ScrNetSocket *s); /* +1 or NULL */ ScrDyn *scr_tls_sock_get_protocol(ScrNetSocket *s); /* +1: string or null */ +/* The certificates behind getPeerCertificate, getCertificate and their + * X509Certificate forms (scr_tls_x509.c reads them). */ +bool scr_tls_sock_has_handle(ScrNetSocket *s); +size_t scr_tls_sock_peer_chain(ScrNetSocket *s, const uint8_t **der, size_t *len, size_t max); +const uint8_t *scr_tls_sock_own_cert(ScrNetSocket *s, size_t *len); +void scr_tls_sock_each_trusted(ScrNetSocket *s, bool (*visit)(void *ctx, const uint8_t *der, size_t len), + void *ctx); +ScrBytes *scr_tls_sock_peer_cert_der(ScrNetSocket *s); /* +1 Buffer, empty for none */ +ScrBytes *scr_tls_sock_own_cert_der(ScrNetSocket *s); /* +1 Buffer, empty for none */ +/* scr_tls_x509.c: getPeerCertificate([detailed]) and getCertificate(), +1 + * (an object, {} or null). */ +ScrDyn *scr_tls_sock_get_peer_certificate(ScrNetSocket *s, const ScrDyn *detailed); +ScrDyn *scr_tls_sock_get_certificate(ScrNetSocket *s); ScrDyn *scr_tls_sock_get_cipher(ScrNetSocket *s); /* +1: object, undefined or null */ ScrDyn *scr_tls_sock_alpn_protocol(ScrNetSocket *s); /* +1: string, false or null */ void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once); @@ -97,6 +110,12 @@ ScrSecureCtx *scr_tls_create_secure_context(const char *cert, size_t cert_len, c * validations first, then the pem walk (+1, or NULL with the exception * pending). Borrowed. */ ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts); +/* server.addContext(servername, context) (a SecureContext, or options when + * NULL) and server.setSecureContext(options) on a tls or https server; + * Node's errors pending on failure. */ +void scr_tls_server_add_context(ScrNetServer *server, const ScrDyn *servername, + const ScrDyn *options /*nullable*/, ScrSecureCtx *ctx /*nullable*/); +void scr_tls_server_set_secure_context(ScrNetServer *server, const ScrDyn *options); /* new tls.TLSSocket(socket, options) in server mode: a fresh TLS socket * takes over socket's connection and runs the server handshake, serving * ctx (options.secureContext; borrowed, nullable) or the options record's diff --git a/packages/runtime/src/scr_tls_x509.c b/packages/runtime/src/scr_tls_x509.c new file mode 100644 index 000000000..d1dfd819b --- /dev/null +++ b/packages/runtime/src/scr_tls_x509.c @@ -0,0 +1,119 @@ +/* tlsSocket.getPeerCertificate([detailed]) and getCertificate(): Node's + * legacy certificate objects, built by scr_x509.c's toLegacyObject over + * the certificates scr_tls.c keeps. Linked into programs that use both + * units, so neither unit depends on the other. */ +#include "scr_runtime.h" + +#include +#include + +/* The peer chain entries read; a longer chain is cut, as no real chain is + * this long. */ +enum { SCR_TLS_X509_MAX_CHAIN = 16 }; + +static ScrBytes *scr_tls_x509_bytes(const uint8_t *der, size_t len) { + ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len); + if (len) memcpy(b->data, der, len); + return b; +} + +static bool scr_tls_x509_same(const ScrBytes *a, const ScrBytes *b) { + return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; +} + +/* tlsSocket.getCertificate(): the certificate the socket serves, {} + * without one, null once the socket is destroyed. +1. */ +ScrDyn *scr_tls_sock_get_certificate(ScrNetSocket *s) { + if (!scr_tls_sock_has_handle(s)) return scr_dyn_new_null(); + size_t len = 0; + const uint8_t *der = scr_tls_sock_own_cert(s, &len); + if (der == NULL) return scr_dyn_new_obj(); + ScrBytes *cert = scr_tls_x509_bytes(der, len); + ScrDyn *result = scr_crypto_x509_legacy_object(cert); + scr_bytes_release(cert); + return result; +} + +typedef struct { + ScrBytes *subject; /* borrowed */ + ScrBytes *found; /* +1, NULL until found */ +} ScrTlsX509Lookup; + +/* SSL_CTX_get_issuer: the first trust anchor that issued the subject. */ +static bool scr_tls_x509_issuer_visit(void *ctx, const uint8_t *der, size_t len) { + ScrTlsX509Lookup *lookup = ctx; + ScrBytes *candidate = scr_tls_x509_bytes(der, len); + if (scr_crypto_x509_check_issued(lookup->subject, candidate)) { + lookup->found = candidate; + return true; + } + scr_bytes_release(candidate); + return false; +} + +/* Sets `chain`'s issuerCertificate to the legacy object of `issuer` + * (borrowed) and answers that object (borrowed; `chain` holds it). */ +static ScrDyn *scr_tls_x509_link(ScrDyn *chain, ScrBytes *issuer) { + ScrDyn *object = scr_crypto_x509_legacy_object(issuer); + scr_dyn_obj_set(chain, "issuerCertificate", 17, object); + return object; +} + +/* tlsSocket.getPeerCertificate([detailed]): the peer's certificate, {} + * before the handshake or without one, null once the socket is destroyed. + * With `detailed`, issuerCertificate links each certificate to its issuer + * as Node's GetPeerCert does: first from the certificates the peer sent, + * in their order of issue, then from the trust anchors; a self-issued + * last certificate is its own issuerCertificate. +1. */ +ScrDyn *scr_tls_sock_get_peer_certificate(ScrNetSocket *s, const ScrDyn *detailed_arg) { + /* Node reads the argument with IsTrue(): only true itself. */ + bool detailed = detailed_arg->kind == SCR_DYN_BOOL && detailed_arg->v.b; + if (!scr_tls_sock_has_handle(s)) return scr_dyn_new_null(); + const uint8_t *der[SCR_TLS_X509_MAX_CHAIN]; + size_t len[SCR_TLS_X509_MAX_CHAIN]; + size_t n = scr_tls_sock_peer_chain(s, der, len, SCR_TLS_X509_MAX_CHAIN); + if (n == 0) return scr_dyn_new_obj(); + ScrBytes *cert = scr_tls_x509_bytes(der[0], len[0]); + ScrDyn *result = scr_crypto_x509_legacy_object(cert); + if (!detailed) { + scr_bytes_release(cert); + return result; + } + ScrBytes *pool[SCR_TLS_X509_MAX_CHAIN]; + size_t pooled = 0; + for (size_t i = 1; i < n; i++) pool[pooled++] = scr_tls_x509_bytes(der[i], len[i]); + ScrDyn *chain = result; + /* AddIssuerChainToObject: take the issuer out of the peer's list, and + * stop when none issued the current certificate or the issuer taken was + * the list's last entry. */ + for (;;) { + size_t i; + for (i = 0; i < pooled; i++) { + if (!scr_crypto_x509_check_issued(cert, pool[i])) continue; + chain = scr_tls_x509_link(chain, pool[i]); + scr_bytes_release(cert); + cert = pool[i]; + memmove(pool + i, pool + i + 1, (pooled - i - 1) * sizeof pool[0]); + pooled--; + break; + } + if (i == pooled) break; + } + for (size_t i = 0; i < pooled; i++) scr_bytes_release(pool[i]); + /* GetLastIssuedCert: then the trust anchors, until a self-issued one + * (bounded, for anchors that issued each other). */ + for (size_t step = 0; step < SCR_TLS_X509_MAX_CHAIN && !scr_crypto_x509_check_issued(cert, cert); step++) { + ScrTlsX509Lookup lookup = {cert, NULL}; + scr_tls_sock_each_trusted(s, scr_tls_x509_issuer_visit, &lookup); + if (lookup.found == NULL) break; + chain = scr_tls_x509_link(chain, lookup.found); + bool same = scr_tls_x509_same(cert, lookup.found); + scr_bytes_release(cert); + cert = lookup.found; + if (same) break; + } + if (scr_crypto_x509_check_issued(cert, cert)) + scr_dyn_obj_set(chain, "issuerCertificate", 17, scr_dyn_retain(chain)); + scr_bytes_release(cert); + return result; +} diff --git a/packages/runtime/src/scr_x509.c b/packages/runtime/src/scr_x509.c new file mode 100644 index 000000000..7b853d68a --- /dev/null +++ b/packages/runtime/src/scr_x509.c @@ -0,0 +1,1820 @@ +/* X509Certificate fields (raw, subject, issuer, subjectAltName, + * infoAccess, serialNumber, fingerprint256, fingerprint512, ca, + * signatureAlgorithm, signatureAlgorithmOid), linked only into programs + * that read them (the x509 runtime feature). */ +#include "scr_runtime.h" + +#include +#include +#include + +/* Copies of scr_lib.c's PEM front half and DER header reader for the + * X509Certificate members it keeps, so that unit is unchanged for + * programs that do not read these fields. */ + +/* PEM armor → DER (or raw-DER passthrough), the shared front half of + * every X509Certificate member. On success `*der`/`*der_len` hold the + * certificate bytes and `*decoded` the malloc'd base64 buffer to free + * (NULL for raw-DER input); unparseable input throws Node's exact PEM + * error and answers false with the exception pending. */ +static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_t *der_len, + uint8_t **decoded) { + static const char begin[] = "-----BEGIN CERTIFICATE-----"; + static const char end[] = "-----END CERTIFICATE-----"; + *der = NULL; + *der_len = 0; + *decoded = NULL; + /* PEM: base64-decode the armor's body. */ + const uint8_t *b = NULL; + /* OpenSSL's PEM reader looks for the armor at the start of a line. */ + for (size_t i = 0; i + sizeof begin - 1 <= n; i++) { + if ((i == 0 || in[i - 1] == '\n') && memcmp(in + i, begin, sizeof begin - 1) == 0) { + b = in + i + sizeof begin - 1; + break; + } + } + if (b != NULL) { + const uint8_t *stop = in + n; + for (const uint8_t *p = b; p + sizeof end - 1 <= in + n; p++) { + if (memcmp(p, end, sizeof end - 1) == 0) { + stop = p; + break; + } + } + uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3); + if (!out) { + scr_trap("scriptc: out of memory\n"); + } + size_t o = 0; + unsigned acc = 0; + int have = 0; + for (const uint8_t *p = b; p < stop; p++) { + int v; + uint8_t c = *p; + if (c >= 'A' && c <= 'Z') v = c - 'A'; + else if (c >= 'a' && c <= 'z') v = c - 'a' + 26; + else if (c >= '0' && c <= '9') v = c - '0' + 52; + else if (c == '+') v = 62; + else if (c == '/') v = 63; + else continue; /* whitespace, '=' */ + acc = (acc << 6) | (unsigned)v; + if (++have == 4) { + out[o++] = (uint8_t)(acc >> 16); + out[o++] = (uint8_t)(acc >> 8); + out[o++] = (uint8_t)acc; + acc = 0; + have = 0; + } + } + if (have == 2) out[o++] = (uint8_t)(acc >> 4); + else if (have == 3) { + out[o++] = (uint8_t)(acc >> 10); + out[o++] = (uint8_t)(acc >> 2); + } + *decoded = out; + *der = out; + *der_len = o; + return true; + } + if (n > 0 && in[0] == 0x30) { + /* Raw DER: the certificate SEQUENCE, without bytes after it, as + * d2i_X509 reads it. */ + *der = in; + *der_len = n; + if (n >= 2) { + size_t len = in[1], head = 2; + if (len & 0x80) { + size_t k = len & 0x7f; + len = 0; + head = 2 + k; + for (size_t i = 0; i < k && k <= 4 && head <= n; i++) len = (len << 8) | in[2 + i]; + if (k == 0 || k > 4) head = n + 1; + } + if (head <= n && len <= n - head) *der_len = head + len; + } + return true; + } + scr_throw_error_msg_code(SCR_ERR_ERROR, + "error:0480006C:PEM routines::no start line", + 42, "ERR_OSSL_PEM_NO_START_LINE"); + return false; /* exception pending */ +} + +/* Reads one DER TL header at p (before end): tag to *tag, content length + * to *len, and answers the content pointer (NULL on malformed/overlong). */ +static const uint8_t *scr_der_tl(const uint8_t *p, const uint8_t *end, uint8_t *tag, size_t *len) { + if (p == NULL || end - p < 2) return NULL; + *tag = p[0]; + uint8_t l0 = p[1]; + const uint8_t *content = p + 2; + size_t l; + if (l0 < 0x80) { + l = l0; + } else { + size_t nb = l0 & 0x7f; + if (nb == 0 || nb > sizeof(size_t) || end - content < (ptrdiff_t)nb) return NULL; + l = 0; + for (size_t i = 0; i < nb; i++) l = (l << 8) | content[i]; + content += nb; + } + if ((size_t)(end - content) < l) return NULL; + *tag = p[0]; + *len = l; + return content; +} + +/* ── X509Certificate fields over the DER ───────────────────────────────── + * + * `raw` decodes the constructor's input once (scr_x509_der); every other + * field reads those DER bytes. The walks follow RFC 5280's Certificate ::= + * SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue } and + * render each value as Node does: names through X509_NAME_print_ex (one + * attribute per line, RFC 2253 escaping), alternative names and access + * descriptions through Node's PrintGeneralName, the serial number through + * BN_bn2hex. A field whose part of the certificate is missing or malformed + * answers Node's value for an absent part. */ + +typedef struct { + char *data; + size_t len, cap; +} ScrX509Buf; + +static void scr_x509_put(ScrX509Buf *b, const void *p, size_t n) { + if (b->len + n > b->cap) { + size_t cap = b->cap ? b->cap * 2 : 128; + while (cap < b->len + n) cap *= 2; + char *grown = realloc(b->data, cap); + if (!grown) scr_trap("scriptc: out of memory\n"); + b->data = grown; + b->cap = cap; + } + memcpy(b->data + b->len, p, n); + b->len += n; +} + +static void scr_x509_puts(ScrX509Buf *b, const char *s) { scr_x509_put(b, s, strlen(s)); } + +static void scr_x509_putc(ScrX509Buf *b, char c) { scr_x509_put(b, &c, 1); } + +static ScrStr *scr_x509_buf_str(ScrX509Buf *b) { + ScrStr *s = scr_str_new(b->data ? b->data : "", b->len); + free(b->data); + return s; +} + +/* The DER of a certificate field's input (raw or the bytes constructor + * input). */ +#define SCR_X509_DER(bytes) \ + ((const uint8_t *)(bytes)->data), ((bytes)->len * scr_bytes_elem_size((bytes)->elem)) + +typedef struct { + const uint8_t *tbs, *tbs_end; /* tbsCertificate contents */ + const uint8_t *sig_alg; /* outer signatureAlgorithm contents */ + size_t sig_alg_len; + const uint8_t *serial; /* serialNumber contents */ + size_t serial_len; + const uint8_t *issuer, *subject; /* Name contents */ + size_t issuer_len, subject_len; + const uint8_t *validity; /* Validity contents */ + size_t validity_len; + const uint8_t *exts; /* Extensions SEQUENCE contents, or NULL */ + size_t exts_len; + const uint8_t *spki; /* SubjectPublicKeyInfo contents, or NULL */ + size_t spki_len; + const uint8_t *spki_tlv; /* the whole SubjectPublicKeyInfo DER */ + size_t spki_tlv_len; +} ScrX509Parts; + +/* Splits the certificate into the parts the fields read; false when the + * outer structure does not parse. */ +static bool scr_x509_parts(const uint8_t *der, size_t n, ScrX509Parts *out) { + memset(out, 0, sizeof *out); + const uint8_t *end = der + n; + uint8_t tag; + size_t len; + const uint8_t *cert = scr_der_tl(der, end, &tag, &len); + if (!cert || tag != 0x30) return false; + const uint8_t *cert_end = cert + len; + const uint8_t *tbs = scr_der_tl(cert, cert_end, &tag, &len); + if (!tbs || tag != 0x30) return false; + out->tbs = tbs; + out->tbs_end = tbs + len; + const uint8_t *alg = scr_der_tl(tbs + len, cert_end, &tag, &len); + if (alg && tag == 0x30) { + out->sig_alg = alg; + out->sig_alg_len = len; + } + const uint8_t *p = tbs; + const uint8_t *c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (c && tag == 0xA0) { /* [0] version */ + p = c + len; + c = scr_der_tl(p, out->tbs_end, &tag, &len); + } + if (!c || tag != 0x02) return true; + out->serial = c; + out->serial_len = len; + p = c + len; + /* signature AlgorithmIdentifier */ + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (!c) return true; + p = c + len; + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (!c || tag != 0x30) return true; + out->issuer = c; + out->issuer_len = len; + p = c + len; + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (!c || tag != 0x30) return true; + out->validity = c; + out->validity_len = len; + p = c + len; + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (!c || tag != 0x30) return true; + out->subject = c; + out->subject_len = len; + p = c + len; + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (c && tag == 0x30) { + out->spki = c; + out->spki_len = len; + out->spki_tlv = p; + out->spki_tlv_len = (size_t)(c + len - p); + } + /* subjectPublicKeyInfo, then the optional [1] [2] unique IDs and [3] + * extensions */ + while (p < out->tbs_end) { + c = scr_der_tl(p, out->tbs_end, &tag, &len); + if (!c) break; + if (tag == 0xA3) { + const uint8_t *seq = scr_der_tl(c, c + len, &tag, &len); + if (seq && tag == 0x30) { + out->exts = seq; + out->exts_len = len; + } + break; + } + p = c + len; + } + return true; +} + +/* Finds the extension with the DER-encoded OID `oid` (contents only): + * its extnValue contents, or NULL. */ +static const uint8_t *scr_x509_ext(const ScrX509Parts *parts, const uint8_t *oid, size_t oid_len, + size_t *value_len) { + if (!parts->exts) return NULL; + const uint8_t *p = parts->exts; + const uint8_t *end = parts->exts + parts->exts_len; + uint8_t tag; + size_t len; + while (p < end) { + const uint8_t *ext = scr_der_tl(p, end, &tag, &len); + if (!ext || tag != 0x30) return NULL; + const uint8_t *ext_end = ext + len; + p = ext_end; + const uint8_t *id = scr_der_tl(ext, ext_end, &tag, &len); + if (!id || tag != 0x06) continue; + bool match = len == oid_len && memcmp(id, oid, len) == 0; + const uint8_t *q = id + len; + const uint8_t *v = scr_der_tl(q, ext_end, &tag, &len); + if (v && tag == 0x01) { /* critical */ + q = v + len; + v = scr_der_tl(q, ext_end, &tag, &len); + } + if (match && v && tag == 0x04) { + *value_len = len; + return v; + } + } + return NULL; +} + +/* An OID's dotted text (OBJ_obj2txt with no_name). */ +static void scr_x509_oid_dotted(ScrX509Buf *b, const uint8_t *oid, size_t n) { + char tmp[24]; + uint64_t v = 0; + bool first = true; + for (size_t i = 0; i < n; i++) { + v = (v << 7) | (oid[i] & 0x7f); + if (oid[i] & 0x80) continue; + if (first) { + unsigned top = v < 40 ? 0 : v < 80 ? 1 : 2; + snprintf(tmp, sizeof tmp, "%u.%llu", top, (unsigned long long)(v - top * 40)); + first = false; + } else { + snprintf(tmp, sizeof tmp, ".%llu", (unsigned long long)v); + } + scr_x509_puts(b, tmp); + v = 0; + } +} + +typedef struct { + const char *oid; /* dotted */ + const char *name; +} ScrX509OidName; + +/* OpenSSL's short names for the attribute types names use (XN_FLAG_FN_SN). */ +static const ScrX509OidName scr_x509_attr_names[] = { + {"2.5.4.3", "CN"}, + {"2.5.4.4", "SN"}, + {"2.5.4.5", "serialNumber"}, + {"2.5.4.6", "C"}, + {"2.5.4.7", "L"}, + {"2.5.4.8", "ST"}, + {"2.5.4.9", "street"}, + {"2.5.4.10", "O"}, + {"2.5.4.11", "OU"}, + {"2.5.4.12", "title"}, + {"2.5.4.13", "description"}, + {"2.5.4.15", "businessCategory"}, + {"2.5.4.16", "postalAddress"}, + {"2.5.4.17", "postalCode"}, + {"2.5.4.18", "postOfficeBox"}, + {"2.5.4.20", "telephoneNumber"}, + {"2.5.4.41", "name"}, + {"2.5.4.42", "GN"}, + {"2.5.4.43", "initials"}, + {"2.5.4.44", "generationQualifier"}, + {"2.5.4.45", "x500UniqueIdentifier"}, + {"2.5.4.46", "dnQualifier"}, + {"2.5.4.65", "pseudonym"}, + {"2.5.4.72", "role"}, + {"2.5.4.97", "organizationIdentifier"}, + {"1.2.840.113549.1.9.1", "emailAddress"}, + {"1.2.840.113549.1.9.2", "unstructuredName"}, + {"0.9.2342.19200300.100.1.1", "UID"}, + {"0.9.2342.19200300.100.1.25", "DC"}, + {"1.3.6.1.4.1.311.60.2.1.1", "jurisdictionL"}, + {"1.3.6.1.4.1.311.60.2.1.2", "jurisdictionST"}, + {"1.3.6.1.4.1.311.60.2.1.3", "jurisdictionC"}, +}; + +/* OpenSSL's long names for the signature algorithms certificates use. */ +static const ScrX509OidName scr_x509_sig_names[] = { + {"1.2.840.113549.1.1.4", "md5WithRSAEncryption"}, + {"1.2.840.113549.1.1.5", "sha1WithRSAEncryption"}, + {"1.2.840.113549.1.1.10", "rsassaPss"}, + {"1.2.840.113549.1.1.11", "sha256WithRSAEncryption"}, + {"1.2.840.113549.1.1.12", "sha384WithRSAEncryption"}, + {"1.2.840.113549.1.1.13", "sha512WithRSAEncryption"}, + {"1.2.840.113549.1.1.14", "sha224WithRSAEncryption"}, + {"1.2.840.10045.4.1", "ecdsa-with-SHA1"}, + {"1.2.840.10045.4.3.1", "ecdsa-with-SHA224"}, + {"1.2.840.10045.4.3.2", "ecdsa-with-SHA256"}, + {"1.2.840.10045.4.3.3", "ecdsa-with-SHA384"}, + {"1.2.840.10045.4.3.4", "ecdsa-with-SHA512"}, + {"1.2.840.10040.4.3", "dsaWithSHA1"}, + {"2.16.840.1.101.3.4.3.1", "dsa_with_SHA224"}, + {"2.16.840.1.101.3.4.3.2", "dsa_with_SHA256"}, + {"1.3.101.112", "ED25519"}, + {"1.3.101.113", "ED448"}, +}; + +/* OpenSSL's long names for the access methods infoAccess prints. */ +static const ScrX509OidName scr_x509_access_names[] = { + {"1.3.6.1.5.5.7.48.1", "OCSP"}, + {"1.3.6.1.5.5.7.48.2", "CA Issuers"}, + {"1.3.6.1.5.5.7.48.3", "AD Time Stamping"}, + {"1.3.6.1.5.5.7.48.5", "CA Repository"}, +}; + +/* Appends the table name of an OID, or its dotted text; answers whether + * the table named it. */ +static bool scr_x509_oid_name(ScrX509Buf *b, const uint8_t *oid, size_t n, + const ScrX509OidName *table, size_t count) { + ScrX509Buf dotted = {0}; + scr_x509_oid_dotted(&dotted, oid, n); + for (size_t i = 0; i < count; i++) { + if (strlen(table[i].oid) == dotted.len && memcmp(table[i].oid, dotted.data, dotted.len) == 0) { + scr_x509_puts(b, table[i].name); + free(dotted.data); + return true; + } + } + scr_x509_put(b, dotted.data, dotted.len); + free(dotted.data); + return false; +} + +/* Appends one code point as UTF-8. */ +static void scr_x509_put_utf8(ScrX509Buf *b, uint32_t c) { + char u[4]; + size_t n; + if (c < 0x80) { + u[0] = (char)c; + n = 1; + } else if (c < 0x800) { + u[0] = (char)(0xC0 | (c >> 6)); + u[1] = (char)(0x80 | (c & 0x3F)); + n = 2; + } else if (c < 0x10000) { + u[0] = (char)(0xE0 | (c >> 12)); + u[1] = (char)(0x80 | ((c >> 6) & 0x3F)); + u[2] = (char)(0x80 | (c & 0x3F)); + n = 3; + } else { + u[0] = (char)(0xF0 | (c >> 18)); + u[1] = (char)(0x80 | ((c >> 12) & 0x3F)); + u[2] = (char)(0x80 | ((c >> 6) & 0x3F)); + u[3] = (char)(0x80 | (c & 0x3F)); + n = 4; + } + scr_x509_put(b, u, n); +} + +/* An attribute value as UTF-8 bytes (ASN1_STRFLGS_UTF8_CONVERT): a + * UTF8String as stored, BMPString and UniversalString decoded, every other + * type read as Latin-1. */ +static void scr_x509_value_utf8(ScrX509Buf *out, uint8_t tag, const uint8_t *v, size_t n) { + if (tag == 0x0C) { + scr_x509_put(out, v, n); + } else if (tag == 0x1E) { + for (size_t i = 0; i + 1 < n; i += 2) scr_x509_put_utf8(out, ((uint32_t)v[i] << 8) | v[i + 1]); + } else if (tag == 0x1C) { + for (size_t i = 0; i + 3 < n; i += 4) + scr_x509_put_utf8(out, ((uint32_t)v[i] << 24) | ((uint32_t)v[i + 1] << 16) | + ((uint32_t)v[i + 2] << 8) | v[i + 3]); + } else { + for (size_t i = 0; i < n; i++) scr_x509_put_utf8(out, v[i]); + } +} + +/* Appends a value with ASN1_STRFLGS_ESC_2253 escaping: the characters + * `"+,;<>\` get a backslash, as do a leading '#' or space and a trailing + * space; with `esc_ctrl`, control characters print as \XX. Bytes above + * 0x7f pass through. */ +static void scr_x509_put_escaped(ScrX509Buf *b, const uint8_t *s, size_t n, bool esc_ctrl) { + static const char hex[] = "0123456789ABCDEF"; + for (size_t i = 0; i < n; i++) { + uint8_t c = s[i]; + bool first = i == 0; + bool last = i + 1 == n; + if (c == '"' || c == '+' || c == ',' || c == ';' || c == '<' || c == '>' || c == '\\' || + (first && (c == '#' || c == ' ')) || (last && c == ' ')) { + scr_x509_putc(b, '\\'); + scr_x509_putc(b, (char)c); + } else if (esc_ctrl && (c < 0x20 || c == 0x7f)) { + char e[3] = {'\\', hex[c >> 4], hex[c & 0x0f]}; + scr_x509_put(b, e, 3); + } else { + scr_x509_putc(b, (char)c); + } + } +} + +/* Prints a Name. `multiline` is Node's subject/issuer form (attributes in + * order, one RDN per line, ESC_CTRL); otherwise the RFC 2253 form DirName + * alternative names use (RDNs reversed and comma-separated, unknown + * attribute types dumped as #hex of the value's DER). RDNs with several + * attributes join them with " + " or "+". */ +static void scr_x509_name_print(ScrX509Buf *b, const uint8_t *name, size_t n, bool multiline) { + const uint8_t *end = name + n; + const uint8_t *rdns[64]; + size_t rdn_lens[64]; + size_t count = 0; + uint8_t tag; + size_t len; + for (const uint8_t *p = name; p < end && count < 64;) { + const uint8_t *set = scr_der_tl(p, end, &tag, &len); + if (!set || tag != 0x31) break; + rdns[count] = set; + rdn_lens[count++] = len; + p = set + len; + } + for (size_t k = 0; k < count; k++) { + size_t r = multiline ? k : count - 1 - k; + if (k > 0) scr_x509_puts(b, multiline ? "\n" : ","); + const uint8_t *set_end = rdns[r] + rdn_lens[r]; + bool first_attr = true; + for (const uint8_t *q = rdns[r]; q < set_end;) { + const uint8_t *atv = scr_der_tl(q, set_end, &tag, &len); + if (!atv || tag != 0x30) break; + const uint8_t *atv_end = atv + len; + q = atv_end; + const uint8_t *oid = scr_der_tl(atv, atv_end, &tag, &len); + if (!oid || tag != 0x06) continue; + size_t oid_len = len; + const uint8_t *value_tl = oid + len; + uint8_t vtag; + size_t vlen; + const uint8_t *value = scr_der_tl(value_tl, atv_end, &vtag, &vlen); + if (!value) continue; + if (!first_attr) scr_x509_puts(b, multiline ? " + " : "+"); + first_attr = false; + bool known = scr_x509_oid_name(b, oid, oid_len, scr_x509_attr_names, + sizeof scr_x509_attr_names / sizeof scr_x509_attr_names[0]); + scr_x509_putc(b, '='); + if (!multiline && !known) { + static const char hex[] = "0123456789ABCDEF"; + scr_x509_putc(b, '#'); + for (const uint8_t *d = value_tl; d < value + vlen; d++) { + scr_x509_putc(b, hex[*d >> 4]); + scr_x509_putc(b, hex[*d & 0x0f]); + } + continue; + } + ScrX509Buf text = {0}; + scr_x509_value_utf8(&text, vtag, value, vlen); + scr_x509_put_escaped(b, (const uint8_t *)text.data, text.len, multiline); + free(text.data); + } + } +} + +/* Node's PrintAltName: a name with none of `"\,'` and only printable + * characters (in UTF-8 names, only non-control bytes) prints as is; + * otherwise it prints as a JSON string, `prefix:` inside the quotes, with + * `\\`, `\"` and \u00XX for commas, control characters and (outside + * UTF-8 names) bytes above 0x7e. */ +static void scr_x509_put_alt_name(ScrX509Buf *b, const uint8_t *s, size_t n, bool utf8, + const char *prefix) { + bool safe = true; + for (size_t i = 0; i < n && safe; i++) { + uint8_t c = s[i]; + if (c == '"' || c == '\\' || c == ',' || c == '\'') safe = false; + else if (utf8 ? (c < ' ' || c == 0x7f) : (c < ' ' || c > '~')) safe = false; + } + if (safe) { + if (prefix) { + scr_x509_puts(b, prefix); + scr_x509_putc(b, ':'); + } + scr_x509_put(b, s, n); + return; + } + static const char hex[] = "0123456789abcdef"; + scr_x509_putc(b, '"'); + if (prefix) { + scr_x509_puts(b, prefix); + scr_x509_putc(b, ':'); + } + for (size_t i = 0; i < n; i++) { + uint8_t c = s[i]; + if (c == '\\') { + scr_x509_puts(b, "\\\\"); + } else if (c == '"') { + scr_x509_puts(b, "\\\""); + } else if ((c >= ' ' && c != ',' && c <= '~') || (utf8 && (c & 0x80))) { + scr_x509_putc(b, (char)c); + } else { + char u[6] = {'\\', 'u', '0', '0', hex[c >> 4], hex[c & 0x0f]}; + scr_x509_put(b, u, 6); + } + } + scr_x509_putc(b, '"'); +} + +static const uint8_t SCR_X509_OID_UPN[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03}; +static const uint8_t SCR_X509_OID_XMPP[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x05}; +static const uint8_t SCR_X509_OID_SRV[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x07}; +static const uint8_t SCR_X509_OID_NAI[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x08}; +static const uint8_t SCR_X509_OID_SMTP_UTF8[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x09}; + +/* Node's PrintGeneralName for one GeneralName element (tag and contents). */ +static void scr_x509_general_name(ScrX509Buf *b, uint8_t tag, const uint8_t *v, size_t n) { + switch (tag) { + case 0x81: /* rfc822Name */ + scr_x509_puts(b, "email:"); + scr_x509_put_alt_name(b, v, n, false, NULL); + return; + case 0x82: /* dNSName */ + scr_x509_puts(b, "DNS:"); + scr_x509_put_alt_name(b, v, n, false, NULL); + return; + case 0x86: /* uniformResourceIdentifier */ + scr_x509_puts(b, "URI:"); + scr_x509_put_alt_name(b, v, n, false, NULL); + return; + case 0x87: { /* iPAddress */ + char tmp[48]; + scr_x509_puts(b, "IP Address:"); + if (n == 4) { + snprintf(tmp, sizeof tmp, "%d.%d.%d.%d", v[0], v[1], v[2], v[3]); + scr_x509_puts(b, tmp); + } else if (n == 16) { + for (unsigned j = 0; j < 8; j++) { + snprintf(tmp, sizeof tmp, j == 0 ? "%X" : ":%X", (unsigned)((v[2 * j] << 8) | v[2 * j + 1])); + scr_x509_puts(b, tmp); + } + } else { + snprintf(tmp, sizeof tmp, "", n); + scr_x509_puts(b, tmp); + } + return; + } + case 0x88: /* registeredID */ + scr_x509_puts(b, "Registered ID:"); + scr_x509_oid_dotted(b, v, n); + return; + case 0xA4: { /* directoryName: [4] EXPLICIT Name */ + uint8_t ntag; + size_t nlen; + const uint8_t *name = scr_der_tl(v, v + n, &ntag, &nlen); + ScrX509Buf text = {0}; + if (name && ntag == 0x30) scr_x509_name_print(&text, name, nlen, false); + scr_x509_puts(b, "DirName:"); + scr_x509_put_alt_name(b, (const uint8_t *)(text.data ? text.data : ""), text.len, true, NULL); + free(text.data); + return; + } + case 0xA0: { /* otherName: type-id OID, [0] EXPLICIT value */ + uint8_t t; + size_t len; + const uint8_t *end = v + n; + const uint8_t *oid = scr_der_tl(v, end, &t, &len); + const char *prefix = NULL; + bool unicode = true; + if (oid && t == 0x06) { +#define SCR_X509_OID_IS(o) (len == sizeof(o) && memcmp(oid, (o), sizeof(o)) == 0) + if (SCR_X509_OID_IS(SCR_X509_OID_SMTP_UTF8)) prefix = "SmtpUTF8Mailbox"; + else if (SCR_X509_OID_IS(SCR_X509_OID_XMPP)) prefix = "XmppAddr"; + else if (SCR_X509_OID_IS(SCR_X509_OID_SRV)) { + prefix = "SRVName"; + unicode = false; + } else if (SCR_X509_OID_IS(SCR_X509_OID_UPN)) prefix = "UPN"; + else if (SCR_X509_OID_IS(SCR_X509_OID_NAI)) prefix = "NAIRealm"; +#undef SCR_X509_OID_IS + } + const uint8_t *value = NULL; + size_t value_len = 0; + uint8_t vtag = 0; + if (oid && prefix) { + const uint8_t *wrap = scr_der_tl(oid + len, end, &t, &len); + if (wrap && t == 0xA0) value = scr_der_tl(wrap, wrap + len, &vtag, &value_len); + } + if (!value || vtag != (unicode ? 0x0C : 0x16)) { + scr_x509_puts(b, "othername:"); + } else { + scr_x509_puts(b, "othername:"); + scr_x509_put_alt_name(b, value, value_len, unicode, prefix); + } + return; + } + case 0xA3: + scr_x509_puts(b, "X400Name:"); + return; + case 0xA5: + scr_x509_puts(b, "EdiPartyName:"); + return; + default: + return; + } +} + +/* The DER contents of the TBSCertificate parts, or an empty string when + * the certificate does not parse. */ +static ScrStr *scr_x509_name_field(ScrBytes *der, bool subject) { + ScrX509Parts parts; + ScrX509Buf b = {0}; + if (scr_x509_parts(SCR_X509_DER(der), &parts)) { + const uint8_t *name = subject ? parts.subject : parts.issuer; + if (name) scr_x509_name_print(&b, name, subject ? parts.subject_len : parts.issuer_len, true); + } + return scr_x509_buf_str(&b); +} + +/* new X509Certificate(data).raw: the certificate's DER as a fresh Buffer. + * Unparseable input throws Node's PEM error (exception pending, NULL). */ +static ScrBytes *scr_x509_raw(const uint8_t *in, size_t n) { + const uint8_t *der; + size_t der_len; + uint8_t *decoded; + if (!scr_x509_der(in, n, &der, &der_len, &decoded)) return NULL; + ScrBytes *out = scr_buffer_alloc_unsafe_slow((double)der_len); + memcpy(out->data, der, der_len); + free(decoded); + return out; +} + +ScrBytes *scr_crypto_x509_raw(ScrBytes *data) { + SCR_BYTES_SNAPSHOT(data); + return scr_x509_raw(SCR_X509_DER(data)); +} + +/* raw of a certificate whose DER a TLS handshake delivered: a copy, with + * no search for PEM armor that its extensions could carry. */ +ScrBytes *scr_crypto_x509_raw_der(ScrBytes *der) { + SCR_BYTES_SNAPSHOT(der); + size_t n = (size_t)scr_bytes_byte_len(der); + ScrBytes *out = scr_buffer_alloc_unsafe_slow((double)n); + memcpy(out->data, der->data, n); + return out; +} + +ScrBytes *scr_crypto_x509_raw_str(ScrStr *pem) { + return scr_x509_raw((const uint8_t *)pem->data, pem->len); +} + +ScrStr *scr_crypto_x509_subject(ScrBytes *der) { return scr_x509_name_field(der, true); } + +ScrStr *scr_crypto_x509_issuer(ScrBytes *der) { return scr_x509_name_field(der, false); } + +static const uint8_t SCR_X509_OID_SAN[] = {0x55, 0x1D, 0x11}; +static const uint8_t SCR_X509_OID_BASIC_CONSTRAINTS[] = {0x55, 0x1D, 0x13}; +static const uint8_t SCR_X509_OID_KEY_USAGE[] = {0x55, 0x1D, 0x0F}; +static const uint8_t SCR_X509_OID_AIA[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01}; + +/* subjectAltName: the GeneralNames joined with ", "; an empty string when + * the certificate has no subjectAltName extension (Node's undefined). */ +ScrStr *scr_crypto_x509_subject_alt_name(ScrBytes *der) { + ScrX509Parts parts; + ScrX509Buf b = {0}; + size_t n; + const uint8_t *ext = scr_x509_parts(SCR_X509_DER(der), &parts) + ? scr_x509_ext(&parts, SCR_X509_OID_SAN, sizeof SCR_X509_OID_SAN, &n) + : NULL; + uint8_t tag; + size_t len; + const uint8_t *names = ext ? scr_der_tl(ext, ext + n, &tag, &len) : NULL; + if (names && tag == 0x30) { + const uint8_t *end = names + len; + bool first = true; + for (const uint8_t *p = names; p < end;) { + const uint8_t *v = scr_der_tl(p, end, &tag, &len); + if (!v) break; + if (!first) scr_x509_puts(&b, ", "); + first = false; + scr_x509_general_name(&b, tag, v, len); + p = v + len; + } + } + return scr_x509_buf_str(&b); +} + +/* infoAccess: one " - " line per AccessDescription; + * an empty string when the extension is absent (Node's undefined). */ +ScrStr *scr_crypto_x509_info_access(ScrBytes *der) { + ScrX509Parts parts; + ScrX509Buf b = {0}; + size_t n; + const uint8_t *ext = scr_x509_parts(SCR_X509_DER(der), &parts) + ? scr_x509_ext(&parts, SCR_X509_OID_AIA, sizeof SCR_X509_OID_AIA, &n) + : NULL; + uint8_t tag; + size_t len; + const uint8_t *descs = ext ? scr_der_tl(ext, ext + n, &tag, &len) : NULL; + if (descs && tag == 0x30) { + const uint8_t *end = descs + len; + bool first = true; + for (const uint8_t *p = descs; p < end;) { + const uint8_t *desc = scr_der_tl(p, end, &tag, &len); + if (!desc || tag != 0x30) break; + const uint8_t *desc_end = desc + len; + p = desc_end; + const uint8_t *method = scr_der_tl(desc, desc_end, &tag, &len); + if (!method || tag != 0x06) continue; + if (!first) scr_x509_putc(&b, '\n'); + first = false; + scr_x509_oid_name(&b, method, len, scr_x509_access_names, + sizeof scr_x509_access_names / sizeof scr_x509_access_names[0]); + scr_x509_puts(&b, " - "); + const uint8_t *location = scr_der_tl(method + len, desc_end, &tag, &len); + if (location) scr_x509_general_name(&b, tag, location, len); + } + } + return scr_x509_buf_str(&b); +} + +/* serialNumber: BN_bn2hex of the INTEGER ("0" for zero, whole bytes in + * uppercase hex without leading zero bytes, '-' before a negative). */ +ScrStr *scr_crypto_x509_serial_number(ScrBytes *der) { + ScrX509Parts parts; + ScrX509Buf b = {0}; + if (scr_x509_parts(SCR_X509_DER(der), &parts) && parts.serial_len > 0) { + static const char hex[] = "0123456789ABCDEF"; + size_t n = parts.serial_len; + uint8_t *mag = malloc(n); + if (!mag) scr_trap("scriptc: out of memory\n"); + memcpy(mag, parts.serial, n); + bool neg = (mag[0] & 0x80) != 0; + if (neg) { /* two's complement magnitude */ + for (size_t i = 0; i < n; i++) mag[i] = (uint8_t)~mag[i]; + for (size_t i = n; i-- > 0;) { + if (++mag[i] != 0) break; + } + } + size_t i = 0; + while (i < n && mag[i] == 0) i++; + if (i == n) { + scr_x509_putc(&b, '0'); + } else { + if (neg) scr_x509_putc(&b, '-'); + for (; i < n; i++) { + scr_x509_putc(&b, hex[mag[i] >> 4]); + scr_x509_putc(&b, hex[mag[i] & 0x0f]); + } + } + free(mag); + } + return scr_x509_buf_str(&b); +} + +static ScrStr *scr_x509_fingerprint_alg(ScrBytes *der, const char *alg) { + unsigned char digest[64]; + size_t dn = scr_crypto_digest_raw(alg, SCR_X509_DER(der), digest); + static const char hex[] = "0123456789ABCDEF"; + char buf[192]; + size_t o = 0; + for (size_t i = 0; i < dn; i++) { + if (i > 0) buf[o++] = ':'; + buf[o++] = hex[digest[i] >> 4]; + buf[o++] = hex[digest[i] & 0x0f]; + } + return scr_str_new(buf, o); +} + +ScrStr *scr_crypto_x509_fingerprint256(ScrBytes *der) { + return scr_x509_fingerprint_alg(der, "sha256"); +} + +ScrStr *scr_crypto_x509_fingerprint512(ScrBytes *der) { + return scr_x509_fingerprint_alg(der, "sha512"); +} + +/* ca: X509_check_ca() == 1 — basicConstraints with cA TRUE, and a + * keyUsage extension, when present, that allows keyCertSign. */ +bool scr_crypto_x509_ca(ScrBytes *der) { + ScrX509Parts parts; + if (!scr_x509_parts(SCR_X509_DER(der), &parts)) return false; + uint8_t tag; + size_t len, n; + const uint8_t *ku = scr_x509_ext(&parts, SCR_X509_OID_KEY_USAGE, sizeof SCR_X509_OID_KEY_USAGE, &n); + if (ku) { + const uint8_t *bits = scr_der_tl(ku, ku + n, &tag, &len); + if (!bits || tag != 0x03 || len < 2 || (bits[1] & 0x04) == 0) return false; + } + const uint8_t *bc = scr_x509_ext(&parts, SCR_X509_OID_BASIC_CONSTRAINTS, + sizeof SCR_X509_OID_BASIC_CONSTRAINTS, &n); + if (!bc) return false; + const uint8_t *seq = scr_der_tl(bc, bc + n, &tag, &len); + if (!seq || tag != 0x30) return false; + const uint8_t *flag = scr_der_tl(seq, seq + len, &tag, &len); + return flag && tag == 0x01 && len == 1 && flag[0] != 0; +} + +/* The signature AlgorithmIdentifier's OID contents, or NULL. */ +static const uint8_t *scr_x509_sig_oid(ScrBytes *der, size_t *n) { + ScrX509Parts parts; + if (!scr_x509_parts(SCR_X509_DER(der), &parts) || !parts.sig_alg) return NULL; + uint8_t tag; + const uint8_t *oid = scr_der_tl(parts.sig_alg, parts.sig_alg + parts.sig_alg_len, &tag, n); + return oid && tag == 0x06 ? oid : NULL; +} + +/* signatureAlgorithm: OpenSSL's long name for the signature algorithm; an + * empty string for an algorithm OpenSSL does not name (Node's + * undefined). */ +ScrStr *scr_crypto_x509_signature_algorithm(ScrBytes *der) { + size_t n; + const uint8_t *oid = scr_x509_sig_oid(der, &n); + ScrX509Buf b = {0}; + if (oid && !scr_x509_oid_name(&b, oid, n, scr_x509_sig_names, + sizeof scr_x509_sig_names / sizeof scr_x509_sig_names[0])) + b.len = 0; + return scr_x509_buf_str(&b); +} + +/* signatureAlgorithmOid: the signature algorithm's dotted OID. */ +ScrStr *scr_crypto_x509_signature_algorithm_oid(ScrBytes *der) { + size_t n; + const uint8_t *oid = scr_x509_sig_oid(der, &n); + ScrX509Buf b = {0}; + if (oid) scr_x509_oid_dotted(&b, oid, n); + return scr_x509_buf_str(&b); +} + +/* toString() and toJSON(): PEM_write_bio_X509's armor over the DER, 64 + * base64 characters a line. */ +ScrStr *scr_crypto_x509_pem(ScrBytes *der) { + static const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const uint8_t *d = (const uint8_t *)der->data; + size_t n = der->len * scr_bytes_elem_size(der->elem); + ScrX509Buf b = {0}; + scr_x509_puts(&b, "-----BEGIN CERTIFICATE-----\n"); + size_t col = 0; + for (size_t i = 0; i < n; i += 3) { + uint32_t v = (uint32_t)d[i] << 16; + if (i + 1 < n) v |= (uint32_t)d[i + 1] << 8; + if (i + 2 < n) v |= d[i + 2]; + char q[4] = {digits[(v >> 18) & 63], digits[(v >> 12) & 63], i + 1 < n ? digits[(v >> 6) & 63] : '=', + i + 2 < n ? digits[v & 63] : '='}; + for (int k = 0; k < 4; k++) { + scr_x509_putc(&b, q[k]); + if (++col == 64) { + scr_x509_putc(&b, '\n'); + col = 0; + } + } + } + if (col) scr_x509_putc(&b, '\n'); + scr_x509_puts(&b, "-----END CERTIFICATE-----\n"); + return scr_x509_buf_str(&b); +} + +/* ── checkHost, checkEmail and checkIP ───────────────────────────────── */ + +/* X509_CHECK_FLAG_* as Node's getFlags sets them. */ +enum { + SCR_X509_ALWAYS_CHECK_SUBJECT = 0x1, + SCR_X509_NO_WILDCARDS = 0x2, + SCR_X509_NO_PARTIAL_WILDCARDS = 0x4, + SCR_X509_MULTI_LABEL_WILDCARDS = 0x8, + SCR_X509_SINGLE_LABEL_SUBDOMAINS = 0x10, + SCR_X509_NEVER_CHECK_SUBJECT = 0x20, +}; + +/* Node's getFlags(options): validateObject, then the subject string and + * the four booleans, in Node's order. -1 with the error pending. */ +static int scr_x509_check_flags(const ScrDyn *options) { + if (options == NULL || options->kind == SCR_DYN_UNDEF) return 0; + if (options->kind == SCR_DYN_TYPED_REF) { + ScrDyn *value = scr_dyn_typed_ref_materialize((ScrDyn *)options); + if (!value) return -1; + int flags = scr_x509_check_flags(value); + scr_dyn_release(value); + return flags; + } + if (options->kind != SCR_DYN_OBJ) { + scr_dyn_arg_type_fail("options", "of type object", options); + return -1; + } + const ScrDyn *subject = scr_dyn_obj_get(options, "subject", 7); + static const char *const bool_names[] = {"wildcards", "partialWildcards", "multiLabelWildcards", + "singleLabelSubdomains"}; + static const bool bool_defaults[] = {true, true, false, false}; + bool values[4]; + if (subject != NULL && subject->kind != SCR_DYN_UNDEF && subject->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.subject", "of type string", subject); + return -1; + } + for (size_t i = 0; i < 4; i++) { + const ScrDyn *v = scr_dyn_obj_get(options, bool_names[i], strlen(bool_names[i])); + if (v == NULL || v->kind == SCR_DYN_UNDEF) { + values[i] = bool_defaults[i]; + continue; + } + if (v->kind != SCR_DYN_BOOL) { + char name[48]; + snprintf(name, sizeof name, "options.%s", bool_names[i]); + scr_dyn_prop_type_fail(name, "of type boolean", v); + return -1; + } + values[i] = v->v.b; + } + int flags = 0; + if (subject != NULL && subject->kind == SCR_DYN_STR) { + const ScrStr *s = subject->v.str; + if (s->len == 6 && memcmp(s->data, "always", 6) == 0) flags |= SCR_X509_ALWAYS_CHECK_SUBJECT; + else if (s->len == 5 && memcmp(s->data, "never", 5) == 0) flags |= SCR_X509_NEVER_CHECK_SUBJECT; + else if (!(s->len == 7 && memcmp(s->data, "default", 7) == 0)) { + scr_dyn_arg_value_fail("options.subject", NULL, subject); + return -1; + } + } + if (!values[0]) flags |= SCR_X509_NO_WILDCARDS; + if (!values[1]) flags |= SCR_X509_NO_PARTIAL_WILDCARDS; + if (values[2]) flags |= SCR_X509_MULTI_LABEL_WILDCARDS; + if (values[3]) flags |= SCR_X509_SINGLE_LABEL_SUBDOMAINS; + return flags; +} + +static bool scr_x509_ieq(const uint8_t *a, const uint8_t *b, size_t n) { + for (size_t i = 0; i < n; i++) { + uint8_t x = a[i], y = b[i]; + if (x >= 'A' && x <= 'Z') x = (uint8_t)(x + 32); + if (y >= 'A' && y <= 'Z') y = (uint8_t)(y + 32); + if (x != y) return false; + } + return true; +} + +/* OpenSSL's equal_nocase after skip_prefix: a host that starts with '.' + * compares with the name's equal-length suffix when the characters + * skipped hold no NUL (and, under singleLabelSubdomains, no dot). */ +static bool scr_x509_host_equal(const uint8_t *pattern, size_t plen, const uint8_t *host, + size_t hlen, int flags) { + if (hlen > 1 && host[0] == '.') { + const uint8_t *p = pattern; + size_t n = plen; + while (n > hlen && *p) { + if ((flags & SCR_X509_SINGLE_LABEL_SUBDOMAINS) && *p == '.') break; + p++; + n--; + } + if (n == hlen) { + pattern = p; + plen = n; + } + } + return plen == hlen && memchr(pattern, 0, plen) == NULL && scr_x509_ieq(pattern, host, hlen); +} + +static bool scr_x509_label_char(uint8_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); +} + +/* OpenSSL's valid_star: the '*' of a wildcard pattern, or NULL when the + * pattern is not a valid wildcard (the star outside the first label, in + * an IDNA label, partial when partial wildcards are off, or fewer than two + * labels after it). */ +static const uint8_t *scr_x509_valid_star(const uint8_t *p, size_t len, int flags) { + const uint8_t *star = NULL; + int dots = 0; + bool atstart = true, idna = false, first = true; + for (size_t i = 0; i < len; i++) { + if (p[i] == '*') { + bool atend = i == len - 1 || p[i + 1] == '.'; + if (star != NULL || !first || idna) return NULL; + if ((flags & SCR_X509_NO_PARTIAL_WILDCARDS) && (!atstart || !atend)) return NULL; + if (!atstart && !atend) return NULL; + star = &p[i]; + atstart = false; + } else if (p[i] == '.') { + if (atstart) return NULL; + idna = false; + dots++; + atstart = true; + first = false; + } else if (p[i] == '-' || scr_x509_label_char(p[i])) { + if (atstart && len - i >= 4 && scr_x509_ieq(&p[i], (const uint8_t *)"xn--", 4)) idna = true; + atstart = false; + } else { + return NULL; + } + } + if (star == NULL || dots < 2 || atstart) return NULL; + return star; +} + +/* OpenSSL's wildcard_match: prefix and suffix around the star match the + * host case-insensitively, the star covers at least one character, never + * a dot unless multi-label wildcards are on, and never an IDNA label. */ +static bool scr_x509_wildcard_match(const uint8_t *prefix, size_t prefix_len, + const uint8_t *suffix, size_t suffix_len, + const uint8_t *host, size_t hlen, int flags) { + if (hlen < prefix_len + suffix_len) return false; + if (!scr_x509_ieq(prefix, host, prefix_len)) return false; + const uint8_t *wild_start = host + prefix_len; + const uint8_t *wild_end = host + hlen - suffix_len; + if (!scr_x509_ieq(wild_end, suffix, suffix_len)) return false; + if (prefix_len == 0 && suffix_len > 0 && *suffix == '.') { + if (wild_start == wild_end) return false; + if (wild_end - wild_start > 4 && scr_x509_ieq(wild_start, (const uint8_t *)"xn--", 4)) + return false; + } + bool allow_multi = (flags & SCR_X509_MULTI_LABEL_WILDCARDS) != 0; + for (const uint8_t *p = wild_start; p != wild_end; p++) { + if (!scr_x509_label_char(*p) && *p != '-' && !(*p == '.' && allow_multi)) return false; + } + return true; +} + +static bool scr_x509_host_match(const uint8_t *pattern, size_t plen, const uint8_t *host, + size_t hlen, int flags) { + /* a host that starts with '.' matches a wildcard only as a suffix */ + if (!(flags & SCR_X509_NO_WILDCARDS) && !(hlen > 1 && host[0] == '.')) { + const uint8_t *star = scr_x509_valid_star(pattern, plen, flags); + if (star != NULL) + return scr_x509_wildcard_match(pattern, (size_t)(star - pattern), star + 1, + plen - (size_t)(star - pattern) - 1, host, hlen, flags); + } + return scr_x509_host_equal(pattern, plen, host, hlen, flags); +} + +/* OpenSSL's equal_email: the local part as is, the domain case- + * insensitively. */ +static bool scr_x509_email_match(const uint8_t *pattern, size_t plen, const uint8_t *email, + size_t elen) { + if (plen != elen || memchr(pattern, 0, plen) != NULL) return false; + size_t at = elen; + while (at > 0 && email[at - 1] != '@') at--; + if (at == 0) return scr_x509_ieq(pattern, email, elen); + return memcmp(pattern, email, at) == 0 && scr_x509_ieq(pattern + at, email + at, elen - at); +} + +enum { SCR_X509_GEN_EMAIL = 0x81, SCR_X509_GEN_DNS = 0x82, SCR_X509_GEN_IP = 0x87 }; + +/* do_x509_check over the subjectAltName entries of one type, then the + * subject's CN (hosts) or emailAddress (emails) entries when the flags + * allow. Writes the matching certificate name into *out. */ +static bool scr_x509_check(ScrBytes *der, uint8_t gen, const uint8_t *chk, size_t chk_len, + int flags, ScrX509Buf *out) { + ScrX509Parts parts; + if (!scr_x509_parts(SCR_X509_DER(der), &parts)) return false; + bool san_present = false; + size_t n; + const uint8_t *ext = scr_x509_ext(&parts, SCR_X509_OID_SAN, sizeof SCR_X509_OID_SAN, &n); + uint8_t tag; + size_t len; + const uint8_t *names = ext ? scr_der_tl(ext, ext + n, &tag, &len) : NULL; + if (names && tag == 0x30) { + const uint8_t *end = names + len; + for (const uint8_t *p = names; p < end;) { + const uint8_t *v = scr_der_tl(p, end, &tag, &len); + if (!v) break; + p = v + len; + if (tag != gen) continue; + san_present = true; + bool hit = gen == SCR_X509_GEN_IP ? len == chk_len && memcmp(v, chk, len) == 0 + : gen == SCR_X509_GEN_DNS ? scr_x509_host_match(v, len, chk, chk_len, flags) + : scr_x509_email_match(v, len, chk, chk_len); + if (hit) { + scr_x509_put(out, v, len); + return true; + } + } + } + if (gen == SCR_X509_GEN_IP || (flags & SCR_X509_NEVER_CHECK_SUBJECT) || + (san_present && !(flags & SCR_X509_ALWAYS_CHECK_SUBJECT)) || !parts.subject) + return false; + /* the subject's CN (2.5.4.3) or emailAddress (1.2.840.113549.1.9.1) */ + static const uint8_t cn[] = {0x55, 0x04, 0x03}; + static const uint8_t email[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01}; + const uint8_t *want = gen == SCR_X509_GEN_DNS ? cn : email; + size_t want_len = gen == SCR_X509_GEN_DNS ? sizeof cn : sizeof email; + const uint8_t *end = parts.subject + parts.subject_len; + for (const uint8_t *p = parts.subject; p < end;) { + const uint8_t *set = scr_der_tl(p, end, &tag, &len); + if (!set || tag != 0x31) break; + const uint8_t *set_end = set + len; + p = set_end; + for (const uint8_t *q = set; q < set_end;) { + const uint8_t *atv = scr_der_tl(q, set_end, &tag, &len); + if (!atv || tag != 0x30) break; + const uint8_t *atv_end = atv + len; + q = atv_end; + const uint8_t *oid = scr_der_tl(atv, atv_end, &tag, &len); + if (!oid || tag != 0x06 || len != want_len || memcmp(oid, want, len) != 0) continue; + uint8_t vtag; + size_t vlen; + const uint8_t *value = scr_der_tl(oid + len, atv_end, &vtag, &vlen); + if (!value) continue; + ScrX509Buf text = {0}; + scr_x509_value_utf8(&text, vtag, value, vlen); + bool hit = gen == SCR_X509_GEN_DNS + ? scr_x509_host_match((const uint8_t *)text.data, text.len, chk, chk_len, flags) + : scr_x509_email_match((const uint8_t *)text.data, text.len, chk, chk_len); + if (hit) scr_x509_put(out, text.data, text.len); + free(text.data); + if (hit) return true; + } + } + return false; +} + +/* x509.checkHost(name[, options]) and x509.checkEmail(email[, options]): + * the certificate name that matched (checkHost) or the argument + * (checkEmail), "" for none (Node's undefined), or NULL with the + * validation error pending. */ +static ScrStr *scr_x509_check_name(ScrBytes *der, const ScrDyn *value, const ScrDyn *options, + const char *arg, uint8_t gen) { + if (value->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail(arg, "of type string", value); + return NULL; + } + int flags = scr_x509_check_flags(options); + if (flags < 0) return NULL; + ScrX509Buf b = {0}; + const ScrStr *s = value->v.str; + bool hit = s->len > 0 && scr_x509_check(der, gen, (const uint8_t *)s->data, s->len, flags, &b); + if (hit && gen == SCR_X509_GEN_EMAIL) { + /* checkEmail answers its argument (X509_check_email reports no name) */ + free(b.data); + return scr_str_retain((ScrStr *)s); + } + return scr_x509_buf_str(&b); +} + +ScrStr *scr_crypto_x509_check_host(ScrBytes *der, ScrDyn *name, ScrDyn *options) { + return scr_x509_check_name(der, name, options, "name", SCR_X509_GEN_DNS); +} + +ScrStr *scr_crypto_x509_check_email(ScrBytes *der, ScrDyn *email, ScrDyn *options) { + return scr_x509_check_name(der, email, options, "email", SCR_X509_GEN_EMAIL); +} + +/* inet_pton's IPv4 form, which Node's checkIP parses: four decimal parts, + * each at most 255, without leading zeros. */ +static bool scr_x509_ipv4(const char *s, size_t n, uint8_t out[4]) { + size_t part = 0, i = 0; + while (part < 4) { + if (i >= n || s[i] < '0' || s[i] > '9') return false; + unsigned v = 0; + size_t digits = 0; + if (s[i] == '0' && i + 1 < n && s[i + 1] >= '0' && s[i + 1] <= '9') return false; + while (i < n && s[i] >= '0' && s[i] <= '9') { + v = v * 10 + (unsigned)(s[i++] - '0'); + if (++digits > 3 || v > 255) return false; + } + out[part++] = (uint8_t)v; + if (part < 4) { + if (i >= n || s[i] != '.') return false; + i++; + } + } + return i == n; +} + +/* inet_pton's IPv6 form: hex groups with at most one "::", and an IPv4 + * address in place of the last two groups. */ +static bool scr_x509_ipv6(const char *s, size_t n, uint8_t out[16]) { + uint8_t head[16], tail[16]; + size_t head_len = 0, tail_len = 0; + bool compressed = false; + size_t i = 0; + if (n >= 2 && s[0] == ':' && s[1] == ':') { + compressed = true; + i = 2; + if (i == n) { + memset(out, 0, 16); + return true; + } + } else if (n > 0 && s[0] == ':') { + return false; + } + while (i < n) { + uint8_t *dst = compressed ? tail : head; + size_t *dst_len = compressed ? &tail_len : &head_len; + /* an IPv4 tail */ + const char *dot = memchr(s + i, '.', n - i); + const char *colon = memchr(s + i, ':', n - i); + if (dot != NULL && colon == NULL) { + if (*dst_len + 4 > 16 || !scr_x509_ipv4(s + i, n - i, dst + *dst_len)) return false; + *dst_len += 4; + i = n; + break; + } + unsigned v = 0; + size_t digits = 0; + while (i < n && s[i] != ':') { + char c = s[i++]; + unsigned d = c >= '0' && c <= '9' ? (unsigned)(c - '0') + : c >= 'a' && c <= 'f' ? (unsigned)(c - 'a' + 10) + : c >= 'A' && c <= 'F' ? (unsigned)(c - 'A' + 10) + : 16; + if (d == 16 || ++digits > 4) return false; + v = (v << 4) | d; + } + if (digits == 0 || *dst_len + 2 > 16) return false; + dst[(*dst_len)++] = (uint8_t)(v >> 8); + dst[(*dst_len)++] = (uint8_t)v; + if (i == n) break; + i++; /* ':' */ + if (i < n && s[i] == ':') { + if (compressed) return false; + compressed = true; + i++; + if (i == n) break; + } else if (i == n) { + return false; + } + } + if (compressed) { + if (head_len + tail_len > 14) return false; + } else if (head_len != 16) { + return false; + } + memset(out, 0, 16); + memcpy(out, head, head_len); + memcpy(out + 16 - tail_len, tail, tail_len); + return true; +} + +/* x509.checkIP(ip[, options]): the argument itself when an iPAddress + * alternative name equals it, "" for none, NULL with the error pending. + * The options are validated like checkHost's and have no effect. */ +ScrStr *scr_crypto_x509_check_ip(ScrBytes *der, ScrDyn *ip, ScrDyn *options) { + if (ip->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("ip", "of type string", ip); + return NULL; + } + if (scr_x509_check_flags(options) < 0) return NULL; + const ScrStr *s = ip->v.str; + uint8_t addr[16]; + size_t addr_len = 0; + if (memchr(s->data, ':', s->len) != NULL) { + if (scr_x509_ipv6(s->data, s->len, addr)) addr_len = 16; + } else if (scr_x509_ipv4(s->data, s->len, addr)) { + addr_len = 4; + } + if (addr_len == 0) { + static const char msg[] = "Invalid name"; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_VALUE"); + return NULL; + } + ScrX509Buf b = {0}; + if (scr_x509_check(der, SCR_X509_GEN_IP, addr, addr_len, 0, &b)) { + free(b.data); + return scr_str_retain((ScrStr *)s); + } + return scr_x509_buf_str(&b); +} + +/* ── checkIssued ──────────────────────────────────────────────────────── */ + +/* The string types OpenSSL canonicalizes before comparing names + * (ASN1_MASK_CANON). */ +static bool scr_x509_canon_type(uint8_t tag) { + return tag == 0x0C || tag == 0x1E || tag == 0x1C || tag == 0x13 || tag == 0x14 || tag == 0x16 || + tag == 0x1A; +} + +static bool scr_x509_space(uint8_t c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +/* asn1_string_canon: UTF-8, leading and trailing whitespace dropped, + * inner runs of whitespace as one space, ASCII in lower case. */ +static void scr_x509_canon(ScrX509Buf *out, uint8_t tag, const uint8_t *v, size_t n) { + ScrX509Buf text = {0}; + scr_x509_value_utf8(&text, tag, v, n); + const uint8_t *p = (const uint8_t *)text.data; + size_t len = text.len; + while (len > 0 && scr_x509_space(*p)) p++, len--; + while (len > 0 && scr_x509_space(p[len - 1])) len--; + for (size_t i = 0; i < len; i++) { + uint8_t c = p[i]; + if (scr_x509_space(c)) { + scr_x509_putc(out, ' '); + while (i + 1 < len && scr_x509_space(p[i + 1])) i++; + } else { + scr_x509_putc(out, (char)(c >= 'A' && c <= 'Z' ? c + 32 : c)); + } + } + free(text.data); +} + +/* One Name as comparable text: per RDN, each attribute's OID and its + * canonical value (or raw DER for non-string types), with separators that + * keep the RDN grouping. */ +static void scr_x509_name_canon(ScrX509Buf *out, const uint8_t *name, size_t n) { + const uint8_t *end = name + n; + uint8_t tag; + size_t len; + for (const uint8_t *p = name; p < end;) { + const uint8_t *set = scr_der_tl(p, end, &tag, &len); + if (!set || tag != 0x31) break; + const uint8_t *set_end = set + len; + p = set_end; + scr_x509_putc(out, '\x01'); + for (const uint8_t *q = set; q < set_end;) { + const uint8_t *atv = scr_der_tl(q, set_end, &tag, &len); + if (!atv || tag != 0x30) break; + const uint8_t *atv_end = atv + len; + q = atv_end; + const uint8_t *oid = scr_der_tl(atv, atv_end, &tag, &len); + if (!oid || tag != 0x06) continue; + scr_x509_putc(out, '\x02'); + scr_x509_put(out, oid, len); + uint8_t vtag; + size_t vlen; + const uint8_t *value_tl = oid + len; + const uint8_t *value = scr_der_tl(value_tl, atv_end, &vtag, &vlen); + if (!value) continue; + scr_x509_putc(out, '\x03'); + if (scr_x509_canon_type(vtag)) scr_x509_canon(out, vtag, value, vlen); + else scr_x509_put(out, value_tl, (size_t)(value + vlen - value_tl)); + } + } +} + +static bool scr_x509_name_eq(const uint8_t *a, size_t an, const uint8_t *b, size_t bn) { + ScrX509Buf x = {0}, y = {0}; + scr_x509_name_canon(&x, a, an); + scr_x509_name_canon(&y, b, bn); + bool eq = x.len == y.len && (x.len == 0 || memcmp(x.data, y.data, x.len) == 0); + free(x.data); + free(y.data); + return eq; +} + +static const uint8_t SCR_X509_OID_SKID[] = {0x55, 0x1D, 0x0E}; +static const uint8_t SCR_X509_OID_AKID[] = {0x55, 0x1D, 0x23}; + +/* The public key algorithm a signature algorithm needs: 1 RSA, 2 EC, + * 3 Ed25519, 4 Ed448, 0 unknown. */ +static int scr_x509_sig_key_kind(const uint8_t *oid, size_t n) { + static const uint8_t rsa_prefix[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01}; + static const uint8_t ecdsa_prefix[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04}; + if (n == sizeof rsa_prefix + 1 && memcmp(oid, rsa_prefix, sizeof rsa_prefix) == 0) { + uint8_t last = oid[sizeof rsa_prefix]; + return last == 4 || last == 5 || (last >= 10 && last <= 14) ? 1 : 0; + } + if (n >= sizeof ecdsa_prefix + 1 && memcmp(oid, ecdsa_prefix, sizeof ecdsa_prefix) == 0) + return 2; + if (n == 3 && oid[0] == 0x2B && oid[1] == 0x65 && oid[2] == 0x70) return 3; + if (n == 3 && oid[0] == 0x2B && oid[1] == 0x65 && oid[2] == 0x71) return 4; + return 0; +} + +/* The kind (as above) of a SubjectPublicKeyInfo's algorithm. */ +static int scr_x509_spki_kind(const ScrX509Parts *parts) { + if (!parts->spki) return 0; + uint8_t tag; + size_t len; + const uint8_t *alg = scr_der_tl(parts->spki, parts->spki + parts->spki_len, &tag, &len); + if (!alg || tag != 0x30) return 0; + const uint8_t *oid = scr_der_tl(alg, alg + len, &tag, &len); + if (!oid || tag != 0x06) return 0; + static const uint8_t rsa[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; + static const uint8_t pss[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A}; + static const uint8_t ec[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01}; + if ((len == sizeof rsa && memcmp(oid, rsa, len) == 0) || + (len == sizeof pss && memcmp(oid, pss, len) == 0)) + return 1; + if (len == sizeof ec && memcmp(oid, ec, len) == 0) return 2; + if (len == 3 && oid[0] == 0x2B && oid[1] == 0x65 && oid[2] == 0x70) return 3; + if (len == 3 && oid[0] == 0x2B && oid[1] == 0x65 && oid[2] == 0x71) return 4; + return 0; +} + +/* X509_check_akid: the subject's authorityKeyIdentifier against the + * issuer's subjectKeyIdentifier, serial number and issuer name. */ +static bool scr_x509_akid_ok(const ScrX509Parts *issuer, const ScrX509Parts *subject) { + size_t n; + const uint8_t *akid = scr_x509_ext(subject, SCR_X509_OID_AKID, sizeof SCR_X509_OID_AKID, &n); + if (!akid) return true; + uint8_t tag; + size_t len; + const uint8_t *seq = scr_der_tl(akid, akid + n, &tag, &len); + if (!seq || tag != 0x30) return true; + const uint8_t *end = seq + len; + for (const uint8_t *p = seq; p < end;) { + const uint8_t *v = scr_der_tl(p, end, &tag, &len); + if (!v) break; + p = v + len; + if (tag == 0x80) { /* keyIdentifier */ + size_t sn; + const uint8_t *skid = scr_x509_ext(issuer, SCR_X509_OID_SKID, sizeof SCR_X509_OID_SKID, &sn); + uint8_t stag; + size_t slen; + const uint8_t *id = skid ? scr_der_tl(skid, skid + sn, &stag, &slen) : NULL; + if (id && stag == 0x04 && (slen != len || memcmp(id, v, len) != 0)) return false; + } else if (tag == 0x82) { /* authorityCertSerialNumber */ + if (issuer->serial && (issuer->serial_len != len || memcmp(issuer->serial, v, len) != 0)) + return false; + } else if (tag == 0xA1 && issuer->issuer) { /* authorityCertIssuer */ + bool found = false; + const uint8_t *names_end = v + len; + for (const uint8_t *q = v; q < names_end && !found;) { + uint8_t gtag; + size_t glen; + const uint8_t *g = scr_der_tl(q, names_end, >ag, &glen); + if (!g) break; + q = g + glen; + if (gtag != 0xA4) continue; + uint8_t ntag; + size_t nlen; + const uint8_t *name = scr_der_tl(g, g + glen, &ntag, &nlen); + if (name && ntag == 0x30 && scr_x509_name_eq(name, nlen, issuer->issuer, issuer->issuer_len)) + found = true; + } + if (!found) return false; + } + } + return true; +} + +/* x509.checkIssued(otherCert): X509_check_issued(otherCert, x509) — the + * names match, the authority key identifier fits, the signature algorithm + * suits the issuer's key, and the issuer's keyUsage, when present, allows + * certificate signing. */ +bool scr_crypto_x509_check_issued(ScrBytes *der, ScrBytes *issuer_der) { + ScrX509Parts subject, issuer; + if (!scr_x509_parts(SCR_X509_DER(der), &subject) || + !scr_x509_parts(SCR_X509_DER(issuer_der), &issuer)) + return false; + if (!subject.issuer || !issuer.subject || + !scr_x509_name_eq(issuer.subject, issuer.subject_len, subject.issuer, subject.issuer_len)) + return false; + if (!scr_x509_akid_ok(&issuer, &subject)) return false; + uint8_t tag; + size_t len; + const uint8_t *sig_oid = subject.sig_alg + ? scr_der_tl(subject.sig_alg, subject.sig_alg + subject.sig_alg_len, + &tag, &len) + : NULL; + if (!sig_oid || tag != 0x06) return false; + int need = scr_x509_sig_key_kind(sig_oid, len); + if (need == 0 || need != scr_x509_spki_kind(&issuer)) return false; + size_t n; + const uint8_t *ku = scr_x509_ext(&issuer, SCR_X509_OID_KEY_USAGE, sizeof SCR_X509_OID_KEY_USAGE, &n); + if (ku) { + const uint8_t *bits = scr_der_tl(ku, ku + n, &tag, &len); + if (!bits || tag != 0x03 || len < 2 || (bits[1] & 0x04) == 0) return false; + } + return true; +} + +/* ── toLegacyObject ───────────────────────────────────────────────────── */ + +static ScrDyn *scr_x509_dyn_text(const char *s, size_t n) { + ScrStr *str = scr_str_new(s, n); + ScrDyn *d = scr_dyn_new_str(str); + scr_str_release(str); + return d; +} + +/* A fresh Buffer holding n bytes. */ +static ScrDyn *scr_x509_dyn_buffer(const uint8_t *p, size_t n) { + ScrBytes *b = scr_buffer_alloc_unsafe_slow((double)n); + if (n) memcpy(b->data, p, n); + ScrDyn *d = scr_dyn_new_bytes(b); + scr_bytes_release(b); + return d; +} + +/* A Name as Node's legacy object: one property per attribute, keyed by + * OpenSSL's short name (the dotted OID otherwise), its value the UTF-8 + * text; a repeated key collects its values in an array. */ +static ScrDyn *scr_x509_name_object(const uint8_t *name, size_t n) { + ScrDyn *obj = scr_dyn_new_obj_null_proto(); + const uint8_t *end = name + n; + uint8_t tag; + size_t len; + for (const uint8_t *p = name; p < end;) { + const uint8_t *set = scr_der_tl(p, end, &tag, &len); + if (!set || tag != 0x31) break; + const uint8_t *set_end = set + len; + p = set_end; + for (const uint8_t *q = set; q < set_end;) { + const uint8_t *atv = scr_der_tl(q, set_end, &tag, &len); + if (!atv || tag != 0x30) break; + const uint8_t *atv_end = atv + len; + q = atv_end; + const uint8_t *oid = scr_der_tl(atv, atv_end, &tag, &len); + if (!oid || tag != 0x06) continue; + size_t oid_len = len; + uint8_t vtag; + size_t vlen; + const uint8_t *value = scr_der_tl(oid + len, atv_end, &vtag, &vlen); + if (!value) continue; + ScrX509Buf key = {0}, text = {0}; + scr_x509_oid_name(&key, oid, oid_len, scr_x509_attr_names, + sizeof scr_x509_attr_names / sizeof scr_x509_attr_names[0]); + scr_x509_value_utf8(&text, vtag, value, vlen); + ScrDyn *v = scr_x509_dyn_text(text.data ? text.data : "", text.len); + const ScrDyn *prior = scr_dyn_obj_get(obj, key.data, key.len); + if (prior == NULL) { + scr_dyn_obj_set(obj, key.data, key.len, v); + } else if (prior->kind == SCR_DYN_ARR) { + scr_dyn_arr_push((ScrDyn *)prior, v); + } else { + ScrDyn *arr = scr_dyn_new_arr(); + scr_dyn_arr_push(arr, scr_dyn_retain((ScrDyn *)prior)); + scr_dyn_arr_push(arr, v); + scr_dyn_obj_set(obj, key.data, key.len, arr); + } + free(key.data); + free(text.data); + } + } + return obj; +} + +/* The JSON string literal at s (a quoted alternative name), decoded. */ +static void scr_x509_json_unquote(ScrX509Buf *out, const char *s, size_t n) { + for (size_t i = 1; i < n; i++) { + char c = s[i]; + if (c == '"') return; + if (c != '\\' || i + 1 >= n) { + scr_x509_putc(out, c); + continue; + } + c = s[++i]; + if (c == 'u' && i + 4 < n) { + unsigned v = 0; + for (int k = 1; k <= 4; k++) { + char h = s[i + k]; + v = v * 16 + (unsigned)(h <= '9' ? h - '0' : (h | 0x20) - 'a' + 10); + } + scr_x509_put_utf8(out, v); + i += 4; + } else { + scr_x509_putc(out, c == 'n' ? '\n' : c == 't' ? '\t' : c); + } + } +} + +/* Node's legacy infoAccess: the infoAccess lines split at their first + * colon into " - " keys, each holding its values in an + * array (a quoted value is JSON-decoded). */ +static ScrDyn *scr_x509_info_access_object(ScrBytes *der) { + ScrStr *text = scr_crypto_x509_info_access(der); + if (text->len == 0) { + scr_str_release(text); + return scr_dyn_undefined(); + } + ScrDyn *obj = scr_dyn_new_obj_null_proto(); + const char *s = text->data; + size_t n = text->len; + for (size_t start = 0; start < n;) { + size_t eol = start; + while (eol < n && s[eol] != '\n') eol++; + size_t colon = start; + while (colon < eol && s[colon] != ':') colon++; + if (colon < eol) { + const char *val = s + colon + 1; + size_t val_len = eol - colon - 1; + ScrX509Buf v = {0}; + if (val_len > 0 && val[0] == '"') scr_x509_json_unquote(&v, val, val_len); + else scr_x509_put(&v, val, val_len); + ScrDyn *item = scr_x509_dyn_text(v.data ? v.data : "", v.len); + free(v.data); + const ScrDyn *prior = scr_dyn_obj_get(obj, s + start, colon - start); + if (prior != NULL && prior->kind == SCR_DYN_ARR) { + scr_dyn_arr_push((ScrDyn *)prior, item); + } else { + ScrDyn *arr = scr_dyn_new_arr(); + scr_dyn_arr_push(arr, item); + scr_dyn_obj_set(obj, s + start, colon - start, arr); + } + } + start = eol + 1; + } + scr_str_release(text); + return obj; +} + +/* Hex of a big-endian magnitude: BN_bn2hex (whole bytes, upper case, no + * leading zero bytes) or, with `lower_trim`, %llx-style text. */ +static void scr_x509_put_hex(ScrX509Buf *b, const uint8_t *p, size_t n, bool lower_trim) { + const char *hex = lower_trim ? "0123456789abcdef" : "0123456789ABCDEF"; + while (n > 0 && *p == 0) p++, n--; + if (n == 0) { + scr_x509_putc(b, '0'); + return; + } + for (size_t i = 0; i < n; i++) { + if (!(lower_trim && i == 0 && (p[i] >> 4) == 0)) scr_x509_putc(b, hex[p[i] >> 4]); + scr_x509_putc(b, hex[p[i] & 0x0f]); + } +} + +typedef struct { + const char *oid; + const char *name; + const char *nist; + int bits; +} ScrX509Curve; + +static const ScrX509Curve scr_x509_curves[] = { + {"1.2.840.10045.3.1.1", "prime192v1", "P-192", 192}, + {"1.3.132.0.33", "secp224r1", "P-224", 224}, + {"1.2.840.10045.3.1.7", "prime256v1", "P-256", 256}, + {"1.3.132.0.34", "secp384r1", "P-384", 384}, + {"1.3.132.0.35", "secp521r1", "P-521", 521}, + {"1.3.132.0.31", "secp192k1", NULL, 192}, + {"1.3.132.0.32", "secp224k1", NULL, 225}, + {"1.3.132.0.10", "secp256k1", NULL, 256}, + {"1.3.36.3.3.2.8.1.1.7", "brainpoolP256r1", NULL, 256}, + {"1.3.36.3.3.2.8.1.1.11", "brainpoolP384r1", NULL, 384}, + {"1.3.36.3.3.2.8.1.1.13", "brainpoolP512r1", NULL, 512}, +}; + +/* The public key members of the legacy object: modulus, exponent, + * pubkey, bits, asn1Curve and nistCurve (each undefined where the key + * type has none). */ +static void scr_x509_key_fields(const ScrX509Parts *parts, ScrDyn *slots[6]) { + for (int i = 0; i < 6; i++) slots[i] = scr_dyn_undefined(); + if (!parts->spki) return; + int kind = scr_x509_spki_kind(parts); + uint8_t tag; + size_t len; + const uint8_t *spki_end = parts->spki + parts->spki_len; + const uint8_t *alg = scr_der_tl(parts->spki, spki_end, &tag, &len); + if (!alg || tag != 0x30) return; + const uint8_t *alg_end = alg + len; + const uint8_t *oid = scr_der_tl(alg, alg_end, &tag, &len); + if (!oid) return; + const uint8_t *params = oid + len; + uint8_t ptag; + size_t plen; + const uint8_t *param = scr_der_tl(params, alg_end, &ptag, &plen); + const uint8_t *bits = scr_der_tl(alg_end, spki_end, &tag, &len); + if (!bits || tag != 0x03 || len < 1) return; + const uint8_t *key = bits + 1; + size_t key_len = len - 1; + static const uint8_t rsa_oid[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; + bool rsa = kind == 1 && (size_t)(params - oid) == sizeof rsa_oid && + memcmp(oid, rsa_oid, sizeof rsa_oid) == 0; + if (rsa) { + const uint8_t *seq = scr_der_tl(key, key + key_len, &tag, &len); + if (!seq || tag != 0x30) return; + const uint8_t *seq_end = seq + len; + size_t nlen, elen; + const uint8_t *nmod = scr_der_tl(seq, seq_end, &tag, &nlen); + if (!nmod || tag != 0x02) return; + const uint8_t *nexp = scr_der_tl(nmod + nlen, seq_end, &tag, &elen); + if (!nexp || tag != 0x02) return; + ScrX509Buf m = {0}, e = {0}; + scr_x509_put_hex(&m, nmod, nlen, false); + scr_x509_puts(&e, "0x"); + scr_x509_put_hex(&e, nexp, elen, true); + slots[0] = scr_x509_dyn_text(m.data, m.len); + slots[1] = scr_x509_dyn_text(e.data, e.len); + free(m.data); + free(e.data); + slots[2] = scr_x509_dyn_buffer(parts->spki_tlv, parts->spki_tlv_len); + size_t i = 0; + while (i < nlen && nmod[i] == 0) i++; + int nbits = 0; + if (i < nlen) { + nbits = (int)((nlen - i - 1) * 8); + for (uint8_t top = nmod[i]; top; top >>= 1) nbits++; + } + slots[3] = scr_dyn_new_num(nbits); + return; + } + if (kind == 2 && param && ptag == 0x06) { + ScrX509Buf dotted = {0}; + scr_x509_oid_dotted(&dotted, param, plen); + for (size_t c = 0; c < sizeof scr_x509_curves / sizeof scr_x509_curves[0]; c++) { + const ScrX509Curve *curve = &scr_x509_curves[c]; + if (strlen(curve->oid) != dotted.len || memcmp(curve->oid, dotted.data, dotted.len) != 0) + continue; + slots[2] = scr_x509_dyn_buffer(key, key_len); + slots[3] = scr_dyn_new_num(curve->bits); + slots[4] = scr_x509_dyn_text(curve->name, strlen(curve->name)); + if (curve->nist) slots[5] = scr_x509_dyn_text(curve->nist, strlen(curve->nist)); + break; + } + free(dotted.data); + } +} + +static const uint8_t SCR_X509_OID_EKU[] = {0x55, 0x1D, 0x25}; + +/* x509.toLegacyObject(): Node's X509ToObject, member order included. */ +ScrDyn *scr_crypto_x509_legacy_object(ScrBytes *der) { + ScrX509Parts parts; + ScrDyn *obj = scr_dyn_new_obj(); + bool ok = scr_x509_parts(SCR_X509_DER(der), &parts); + if (!ok) memset(&parts, 0, sizeof parts); + scr_dyn_obj_set(obj, "subject", 7, + parts.subject ? scr_x509_name_object(parts.subject, parts.subject_len) + : scr_dyn_new_obj_null_proto()); + scr_dyn_obj_set(obj, "issuer", 6, + parts.issuer ? scr_x509_name_object(parts.issuer, parts.issuer_len) + : scr_dyn_new_obj_null_proto()); + ScrStr *san = scr_crypto_x509_subject_alt_name(der); + scr_dyn_obj_set(obj, "subjectaltname", 14, san->len ? scr_dyn_new_str(san) : scr_dyn_undefined()); + scr_str_release(san); + scr_dyn_obj_set(obj, "infoAccess", 10, scr_x509_info_access_object(der)); + scr_dyn_obj_set(obj, "ca", 2, scr_dyn_new_bool(scr_crypto_x509_ca(der))); + ScrDyn *key[6]; + scr_x509_key_fields(&parts, key); + scr_dyn_obj_set(obj, "modulus", 7, key[0]); + scr_dyn_obj_set(obj, "exponent", 8, key[1]); + scr_dyn_obj_set(obj, "pubkey", 6, key[2]); + scr_dyn_obj_set(obj, "bits", 4, key[3]); + static const struct { + const char *key; + ScrStr *(*field)(ScrBytes *); + } texts[] = { + {"valid_from", scr_crypto_x509_valid_from}, + {"valid_to", scr_crypto_x509_valid_to}, + {"fingerprint", scr_crypto_x509_fingerprint}, + {"fingerprint256", scr_crypto_x509_fingerprint256}, + {"fingerprint512", scr_crypto_x509_fingerprint512}, + }; + for (size_t i = 0; i < sizeof texts / sizeof texts[0]; i++) { + ScrStr *s = texts[i].field(der); + scr_dyn_obj_set(obj, texts[i].key, strlen(texts[i].key), scr_dyn_new_str(s)); + scr_str_release(s); + } + size_t n; + const uint8_t *eku = ok ? scr_x509_ext(&parts, SCR_X509_OID_EKU, sizeof SCR_X509_OID_EKU, &n) : NULL; + uint8_t tag; + size_t len; + const uint8_t *purposes = eku ? scr_der_tl(eku, eku + n, &tag, &len) : NULL; + if (purposes && tag == 0x30) { + ScrDyn *arr = scr_dyn_new_arr(); + const uint8_t *end = purposes + len; + for (const uint8_t *p = purposes; p < end;) { + const uint8_t *oid = scr_der_tl(p, end, &tag, &len); + if (!oid) break; + p = oid + len; + if (tag != 0x06) continue; + ScrX509Buf dotted = {0}; + scr_x509_oid_dotted(&dotted, oid, len); + scr_dyn_arr_push(arr, scr_x509_dyn_text(dotted.data, dotted.len)); + free(dotted.data); + } + scr_dyn_obj_set(obj, "ext_key_usage", 13, arr); + } else { + scr_dyn_obj_set(obj, "ext_key_usage", 13, scr_dyn_undefined()); + } + ScrStr *serial = scr_crypto_x509_serial_number(der); + scr_dyn_obj_set(obj, "serialNumber", 12, scr_dyn_new_str(serial)); + scr_str_release(serial); + { + ScrBytes *view = der; + SCR_BYTES_SNAPSHOT(view); + scr_dyn_obj_set(obj, "raw", 3, scr_x509_dyn_buffer(SCR_X509_DER(view))); + } + scr_dyn_obj_set(obj, "asn1Curve", 9, key[4]); + scr_dyn_obj_set(obj, "nistCurve", 9, key[5]); + return obj; +} diff --git a/tests/corpus/6936-x509-certificate-fields.ts b/tests/corpus/6936-x509-certificate-fields.ts new file mode 100644 index 000000000..de4b78f9a --- /dev/null +++ b/tests/corpus/6936-x509-certificate-fields.ts @@ -0,0 +1,189 @@ +// X509Certificate fields match Node: subject and issuer (one attribute +// per line, RFC 2253 escaping), subjectAltName and infoAccess (undefined +// without the extension), serialNumber, the SHA-1/256/512 fingerprints, ca, +// the signature algorithm, the validity window as strings and Dates, and +// raw, over PEM Buffer, PEM string and DER input. +import * as crypto from "node:crypto"; + +// An RSA CA with a multi-attribute subject. +const CA = `-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIIASNFZ4mrze8wDQYJKoZIhvcNAQELBQAwgZwxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRQwEgYDVQQKDAtFeGFtcGxlIE9yZzERMA8GA1UECwwIRGV2IFRlYW0xGDAW +BgNVBAMMD0V4YW1wbGUgUm9vdCBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs +ZS5jb20wIBcNMjYxMDIxMTYzMzUyWhgPMjEyNjA5MjcxNjMzNTJaMIGcMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j +aXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBUZWFtMRgw +FgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0YGcCr0pxR8d +6qDdLuILcoT7vhtAjyHwfkoChnH6N3TLWzDUugnBp9NbDb7gDLqbRT4uTDoNb6zS +tEJZ4Q7/hhd0jDvoPmOy0Y3Kyj9uNsdf25GdbvwwEnlddELJCZprTZ4OhEg6w2s0 +Yy4SlviLzDXLmsggTypxeS+K8JHT9onMipi5L4GFu8n1QZbzXvSS4BkRgMXEyrtr +FOE9qSo2qkubFyd/WNKRTRIpTSc5/GGsqJ0FKDDJnN5adotcUucbJFn4noNnFUWh +M8mhBEj7ENDdJsD2EVFsI6H958jA2SbNNu4ngzAOs2AKNBYBfQK02ZQAdRNOSL3K +5FkfbII5LwIDAQABo2MwYTAdBgNVHQ4EFgQUP+yQyItpNYEls26Jfv+NnciGaK8w +HwYDVR0jBBgwFoAUP+yQyItpNYEls26Jfv+NnciGaK8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBABDw3Va/C+RFc/Hs +TJnHtQJtxFL0ZhnHUqg4DcDA3m+JDdIDNL8sNpLEbtn/pr0VeEWgG+MImWAQlrUW +IAZkLj9/Zg1wSO0oZ86ZyZJZmp54lUYXgG17CNl4g+F7VHt3bBwUJHTbWye/FKM6 +5OVZetCYKVj7Z+5BYuf7cumzvRqcn32fXidB/kjRX14SJ4JDPn2yFZWCr2ygIqWQ +SOuEmWbNAbYGaVZkQr2Ma8U28DWY2jb0f6nP8jLt49t9o6N/e6Fwd6I5t/mx9Mfj +0403omkxmr9vqN2kBamzVnun+2mDkRZcKPCr72Q4sfbvmDi/W5Nf1Iu2cQW8VfDO +SqpyT54= +-----END CERTIFICATE----- +`; + +// An EC leaf with alternative names, access descriptions and extended key usage, signed by CA. +const LEAF = `-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIPAPAN+s4AESIzRFVmd4iZMA0GCSqGSIb3DQEBDAUAMIGc +MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu +IEZyYW5jaXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBU +ZWFtMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNh +QGV4YW1wbGUuY29tMB4XDTI2MTAyMTE2MzM1MloXDTM2MTAxODE2MzM1MlowJzER +MA8GA1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABJVfVy9EH/VHQuRqpFFnXk0F00ncDht6APgVPDoybK8H +PlcM4T5H1yAOMHnlH29LK1jtMqU1aAOiiQRzPdCvqoujggE3MIIBMzAJBgNVHRME +AjAAMGgGA1UdEQRhMF+CCWxvY2FsaG9zdIINKi5leGFtcGxlLmNvbYcEfwAAAYcQ +AAAAAAAAAAAAAAAAAAAAAYERYWRtaW5AZXhhbXBsZS5jb22GGGh0dHBzOi8vZXhh +bXBsZS5jb20vcGF0aDBdBggrBgEFBQcBAQRRME8wIwYIKwYBBQUHMAGGF2h0dHA6 +Ly9vY3NwLmV4YW1wbGUuY29tMCgGCCsGAQUFBzAChhxodHRwOi8vY2EuZXhhbXBs +ZS5jb20vY2EuY3J0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV +HQ4EFgQUdKIx6ULFLPNP/kzwUFjSa5D19RswHwYDVR0jBBgwFoAUP+yQyItpNYEl +s26Jfv+NnciGaK8wDQYJKoZIhvcNAQEMBQADggEBABmQ8f2VsXNaMM9X1MEhi27D +LjwcOaaGEINNZq7xu/HL6UljMb1Odsuz66DkiH3Exd3MrWy6OlOpJJF0VzsqMsoD +0CRBxXF28o6Jil3vOGVwlYNmRidpkAkj4VyWDvv8FCS9VbsBtXaSHwu9wcCwePE0 +cGJRfTKoZ6KJ8vQ57DBEZ/ZszuhehOZYqrTmoUdjZ123kXdxQvK+ALn4SlIXHBhN +9unhd+8i00EmkXO/REw8paMcMVVGR+ioOEjyNHqZo6vkRqgpKggn1Yq8cTTDOMnS +P1o42EQZLlbuvx0kqInBqwqjDx4nZiPaifoDc9BF6iZPBvm8dhrEfghfB1tkNk0= +-----END CERTIFICATE----- +`; + +// Escaped subject characters, UTF-8 and a comma in a DNS name. +const ODD = `-----BEGIN CERTIFICATE----- +MIICXzCCAeWgAwIBAgIBATAKBggqhkjOPQQDAjBvMQ0wCwYDVQQDDARhLCBiMQ4w +DAYDVQQHDAUjbGVhZDESMBAGA1UECAwJICBzcGFjZSAgMSYwJAYDVQQKDB1RdW90 +ZSBxIGJhY2tcc2xhc2g7IHNlbWkgPGx0PjESMBAGA1UECwwJQ2Fmw6kg4oKsMB4X +DTI2MTAyMTE2MzM1MloXDTI3MTAyMTE2MzM1MlowbzENMAsGA1UEAwwEYSwgYjEO +MAwGA1UEBwwFI2xlYWQxEjAQBgNVBAgMCSAgc3BhY2UgIDEmMCQGA1UECgwdUXVv +dGUgcSBiYWNrXHNsYXNoOyBzZW1pIDxsdD4xEjAQBgNVBAsMCUNhZsOpIOKCrDB2 +MBAGByqGSM49AgEGBSuBBAAiA2IABMLLzwDsSeezCzScnC9xTIFl35dFAX+1Uagk +0y4Y72rOOgVFA13SMIbMfv2InIyg2vXOdqn0y7RqiaaqqnAYZB2RHHL0SD7t2MVL +K3BQajy7Oi6IeDDRGK1jM4D6nk/1AKNVMFMwMgYDVR0RBCswKYILYSxiLmV4YW1w +bGWCDXBsYWluLmV4YW1wbGWBBXhAeS56hwQKAAABMB0GA1UdDgQWBBSR4U6nIpTt +5rLoIZZxfxSyx9Uc9zAKBggqhkjOPQQDAgNoADBlAjEAlznFL0Nrt1jjZrT3/GN0 +yN/8gxO3SawyqoWrfP3uk1GScTh0PlrTEOrpSyuq3wl5AjBRntVI1ZoUM46pCwIw +0Vl3c3lns7OljcQFiR7o8yRhEW9ejdiR2W6PLBrjCly6U3I= +-----END CERTIFICATE----- +`; + +// A multi-valued RDN and serial number 0. +const MULTI = `-----BEGIN CERTIFICATE----- +MIICNzCCAaCgAwIBAgIBADANBgkqhkiG9w0BAQsFADA3MSgwCgYDVQQLDANyZG4w +DAYDVQQDDAVtdWx0aTAMBgNVBAoMBXRocmVlMQswCQYDVQQGEwJERTAeFw0yNjEw +MjExNjM0MDVaFw0yNjEwMzExNjM0MDVaMDcxKDAKBgNVBAsMA3JkbjAMBgNVBAMM +BW11bHRpMAwGA1UECgwFdGhyZWUxCzAJBgNVBAYTAkRFMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDdv64EOPdpcWdjVMAlEQvNzm3ZbQYXrhOwRF9yzQp25SuA +3uKvStZOt8hyTifAd30oDXIfgyYI39xKHEkHfARTV1E2Jwyiz86/pDHmFypPFJuI +v/vOjYLLeisiVi8TFfHgmoGqGvySR83fwiUnoOOWj9Bqt/5EVBQbBeWrU9qH6QID +AQABo1MwUTAdBgNVHQ4EFgQUZrGU0gkgqCkmegRtu6vQ3h7kpPUwHwYDVR0jBBgw +FoAUZrGU0gkgqCkmegRtu6vQ3h7kpPUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQsFAAOBgQAMzFkzJElc9VJYhGx7AngO8Yve22iCTOVLO3/be8w55klb/HJX +vfESQz+EJLYVaP5Q/uChoE0I27NytkeUSpJFwmjyqoQq2j6kJWGKONqhQSnXDrms +HMPA7iik/C5hOqpwzDZ98wNU/e86TrqZxzpFkwbnZx9MSfiGr690s5BfpA== +-----END CERTIFICATE----- +`; + +// An Ed25519 certificate with otherName, directoryName, registeredID and IPv6 names, a BMPString subject, a negative serial and keyUsage without keyCertSign. +const NAMES = `-----BEGIN CERTIFICATE----- +MIICSDCCAfqgAwIBAgIB+zAFBgMrZXAwLDEVMBMGA1UEAx4MAGIAbQBwACAA6U4t +MRMwEQYDVQQKHgoAcABsAGEAaQBuMB4XDTI2MTAyMTE2NDQzM1oXDTI2MTEyMDE2 +NDQzM1owLDEVMBMGA1UEAx4MAGIAbQBwACAA6U4tMRMwEQYDVQQKHgoAcABsAGEA +aQBuMCowBQYDK2VwAyEAP+2Vcp8nwu2AhFvSczYTxGs1jV/ckA76Jqvwj9QDGqij +ggE/MIIBOzCBtwYDVR0RBIGvMIGsoBsGCisGAQQBgjcUAgOgDQwLdXNlckBkb21h +aW6gEAYDKgMEoAkMB3Vua25vd26kRTBDMQswCQYDVQQGEwJVSzEZMBcGA1UECh4Q +AEQAaQByACwAIABPAHIAZzEZMBcGA1UEAx4QAGQAaQByACAAbgBhAG0AZYgEKgME +BYcQ/oAAAAAAAAAAAAAAAAEAAocQIAENuAAAAAAAAP8AAEKDKYYKaHR0cDovL3gv +cTAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIHgDBFBggrBgEFBQcBAQQ5MDcwIAYI +KwYBBQUHMAWGFGh0dHA6Ly9yZXBvLmV4YW1wbGUvMBMGBCoDBAWCC29kZC5leGFt +cGxlMB0GA1UdDgQWBBQYMJ0fQJ1Nx0PmatIfTJusAEA9WTAFBgMrZXADQQCixubE +nuunBZYYPXF52+joQSX/a5DFkOEDmzRTuH7IIpPFWPo37rmJOSdPA/PV8v9clzYF +LP3EjM4YOAatcbsO +-----END CERTIFICATE----- +`; + +// An RSA-PSS signature and a control character in the subject. +const PSS = `-----BEGIN CERTIFICATE----- +MIIDdTCCAiigAwIBAgICAIAwQgYJKoZIhvcNAQEKMDWgDzANBglghkgBZQMEAgEF +AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIEAgIA3jAeMQwwCgYDVQQD +DANwc3MxDjAMBgNVBAoMBWN0bAF4MB4XDTI2MTAyMTE2NDQzM1oXDTI2MTEyMDE2 +NDQzM1owHjEMMAoGA1UEAwwDcHNzMQ4wDAYDVQQKDAVjdGwBeDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL2pno8Ybwihdv3hnMhWPPt85SXQmFAJpFoS +E+sT9H6FbwFCIyPkY+SWk8H7y8V+D+388tNvJZmGjX3uvWpK2Wx6anSB3/WMpkUR +2ExP4cpSxZNr9EFzC/cdqLvsxJcHMHkc/uJuFbtjsuiH1ygOe3nyhWSNiPwGdwHE +cKkBFkzW3j2C/4Jkgt4k+sMe3gy5litKtMi4zZB8ixCm5oGhveGhUZy0Kb01twgZ +RP4FEd32sSRqq3NCx4cB0kyreTuzmk6U7BkxcdPzaj6DifjRznTQmjMdoCNZQRCj +G3KBOMHHyvmV2B4lNGDuMlcSap/TpYq7n32AH5sDvELrUnPwVzsCAwEAAaNTMFEw +HQYDVR0OBBYEFC/QJvAQsbQ25sErc1To2G1VuNZ0MB8GA1UdIwQYMBaAFC/QJvAQ +sbQ25sErc1To2G1VuNZ0MA8GA1UdEwEB/wQFMAMBAf8wQgYJKoZIhvcNAQEKMDWg +DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIE +AgIA3gOCAQEAu3lP2Q3Bb5fWUQtgFr/82Oke1ZRW/GRbj03mZ30izADHC7yk0OYm +02f7B5sub04Jk6yuRVmEBjJHSPNwAo0RJWtwvD77KLhG8aObr7Z3+pkfFvpGzqeS +MQ4FtK2MPps3kA1a5D4XLwkwDJtHWZVuVyXpPgxkptfLzZgrieKrpNyOKR8Pz/PO +Wlwv6bF7SXvAV8Vrd7KDvTqwQ5iq9UFhP6GeMDWezYtzLdjlvje7MhmZ96zgSY6m +qumNWOPHmiN4eoOE4M69B+0fa3WyMzubW0nVGM/sDhtS0k5zHs6Y4RLn11HolhXH +LvbvDgqtl/m5i0uHxEld+Z1uhzJscqFQRg== +-----END CERTIFICATE----- +`; + +// A version 1 certificate without extensions. +const V1 = `-----BEGIN CERTIFICATE----- +MIIBSTCB7wIUI3kYI5NpR07YqZe6RwOx22je9IowCgYIKoZIzj0EAwIwJzERMA8G +A1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNjEwMjExNjMz +NTJaFw0yNjExMjAxNjMzNTJaMCcxETAPBgNVBAoMCExlYWYgT3JnMRIwEAYDVQQD +DAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASVX1cvRB/1R0Lk +aqRRZ15NBdNJ3A4begD4FTw6MmyvBz5XDOE+R9cgDjB55R9vSytY7TKlNWgDookE +cz3Qr6qLMAoGCCqGSM49BAMCA0kAMEYCIQCFfFOw9qzzfSOQO1zRnT0sOXibjLWl +hHDyqecxhY7gtQIhAPMgqg+p1JXV5bgGpqgDfP3sE53luxMGlnAC39uhwBUh +-----END CERTIFICATE----- +`; + + +function show(label: string, cert: crypto.X509Certificate): void { + console.log(label, "subject", JSON.stringify(cert.subject)); + console.log(label, "issuer", JSON.stringify(cert.issuer)); + console.log(label, "subjectAltName", JSON.stringify(cert.subjectAltName), typeof cert.subjectAltName); + console.log(label, "infoAccess", JSON.stringify(cert.infoAccess), cert.infoAccess === undefined); + console.log(label, "serialNumber", cert.serialNumber); + console.log(label, "fingerprints", cert.fingerprint, cert.fingerprint256); + console.log(label, "fingerprint512", cert.fingerprint512); + console.log(label, "ca", cert.ca); + console.log(label, "signature", cert.signatureAlgorithm, cert.signatureAlgorithmOid); + console.log(label, "validity", cert.validFrom, "|", cert.validTo); + console.log(label, "dates", cert.validFromDate.toISOString(), cert.validToDate.getTime()); + console.log(label, "raw", cert.raw.length, Buffer.isBuffer(cert.raw), cert.raw.subarray(0, 4).toString("hex")); +} + +const pems: [string, string][] = [ + ["ca", CA], + ["leaf", LEAF], + ["odd", ODD], + ["multi", MULTI], + ["names", NAMES], + ["pss", PSS], + ["v1", V1], +]; +for (const [label, pem] of pems) show(label, new crypto.X509Certificate(Buffer.from(pem))); + +// A PEM string, and the DER bytes of raw. +const fromString = new crypto.X509Certificate(LEAF); +show("string", fromString); +const fromDer = new crypto.X509Certificate(fromString.raw); +console.log("der", fromDer.subject === fromString.subject, fromDer.fingerprint256 === fromString.fingerprint256); +console.log("der raw copy", fromDer.raw !== fromString.raw, fromDer.raw.equals(fromString.raw)); + +try { + new crypto.X509Certificate(Buffer.from("not a certificate")); +} catch (e) { + const err = e as NodeJS.ErrnoException; + console.log("garbage", err.name, err.code); +} diff --git a/tests/corpus/7090-x509-check-names.ts b/tests/corpus/7090-x509-check-names.ts new file mode 100644 index 000000000..31ec7244f --- /dev/null +++ b/tests/corpus/7090-x509-check-names.ts @@ -0,0 +1,141 @@ +// X509Certificate.checkHost, checkEmail and checkIP match Node: the +// subject alternative names of the asked kind first, then the subject's +// common name or emailAddress when the certificate has none (or as the +// subject option says), the wildcard rules and their options, case rules, +// IPv4 and IPv6 parsing, and an invalid subject option. +import { X509Certificate, type X509CheckOptions } from "node:crypto"; + +// DNS names with every wildcard form, an IP address and an email name. +const WILD = `-----BEGIN CERTIFICATE----- +MIIB/TCCAaOgAwIBAgIBCTAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAwqLmNuLmV4 +YW1wbGUwIBcNMjYxMDIyMTAwMTMxWhgPMjEyNjA5MjgxMDAxMzFaMBcxFTATBgNV +BAMMDCouY24uZXhhbXBsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGJ1KKs0 +givD5gd5LXXbPc37WfT64D0ePORDm7dB9mxEYJ6K8IQxBKzn5xQT+e/lZZ1raRBZ +q/E8aW4cW1iZLXSjgd0wgdowgbgGA1UdEQSBsDCBrYIOZiouZXhhbXBsZS5jb22C +Dypvby5leGFtcGxlLm5ldIIXKi54bi0tYmNoZXIta3ZhLmV4YW1wbGWCDXhuLS0q +LmV4YW1wbGWCBSouY29tgg9hLiouZXhhbXBsZS5vcmeCASqCDyouTWl4ZWQuRXhh +bXBsZYIMLmRvdC5leGFtcGxlgg9iKnouZXhhbXBsZS5lZHWHBMCoAQGBEU1peGVk +QEV4YW1wbGUuQ09NMB0GA1UdDgQWBBR8lJ0Uw8GcVD1cGUSprHt346HjDDAKBggq +hkjOPQQDAgNIADBFAiALkTbNe6bIavd8T9qKlAqzmEPQrFRZUVoOHSQPI/zV5QIh +AM0et8JtbWP+4ySiUPPqCXtlup00GfhcqGXy+lSl32vL +-----END CERTIFICATE----- +`; + +// No alternative names; the subject has a wildcard CN and an emailAddress. +const CN = `-----BEGIN CERTIFICATE----- +MIIBVDCB+gIBCjAKBggqhkjOPQQDAjA1MRcwFQYDVQQDDA5zZWNvbmQuZXhhbXBs +ZTEaMBgGCSqGSIb3DQEJARYLYUBiLmV4YW1wbGUwIBcNMjYxMDIyMTAwMTMxWhgP +MjEyNjA5MjgxMDAxMzFaMDUxFzAVBgNVBAMMDnNlY29uZC5leGFtcGxlMRowGAYJ +KoZIhvcNAQkBFgthQGIuZXhhbXBsZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA +BGJ1KKs0givD5gd5LXXbPc37WfT64D0ePORDm7dB9mxEYJ6K8IQxBKzn5xQT+e/l +ZZ1raRBZq/E8aW4cW1iZLXQwCgYIKoZIzj0EAwIDSQAwRgIhAIE0IFtuCNllBodV +go5WWgb8h1McrS/o31V5K0JrpF7QAiEAil8AEHuMiVtef/txZsEKPPojDKfPTYC8 +zplMuFd9gQA= +-----END CERTIFICATE----- +`; + +// A leaf with DNS, email, IPv4, IPv6 and URI names. +const LEAF = `-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgICCwswDQYJKoZIhvcNAQELBQAwPjELMAkGA1UEBhMCVVMx +FDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBSb290 +MCAXDTI2MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjBfMQswCQYDVQQGEwJV +UzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNv +bTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20wWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARidSirNIIrw+YHeS112z3N+1n0+uA9HjzkQ5u3QfZsRGCe +ivCEMQSs5+cUE/nv5WWda2kQWavxPGluHFtYmS10o4IBWDCCAVQwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHsG +A1UdEQR0MHKCDSouZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tghV4bi0tYmNoZXIt +a3ZhLmV4YW1wbGWBD29wc0BleGFtcGxlLmNvbYcECgAAAYcQIAENuAAAAAAAAAAA +AAAAAYYUaHR0cHM6Ly9leGFtcGxlLmNvbS8wXgYIKwYBBQUHAQEEUjBQMCQGCCsG +AQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wKAYIKwYBBQUHMAKGHGh0 +dHA6Ly9jYS5leGFtcGxlLmNvbS9jYS5jcnQwHQYDVR0OBBYEFHyUnRTDwZxUPVwZ +RKmse3fjoeMMMB8GA1UdIwQYMBaAFAgaWVveRbA7VKqcHzjMqBPzi1/LMA0GCSqG +SIb3DQEBCwUAA4IBAQAfMTcpidoH47PXOQVxRttfrG7K9BcntyXP4+5hUAoSfMr6 +Rxy7X/9ba7R0yCGlNsUCusObaBpbdvwX8v9TiTYKVL+C9rEVGrTHnh6kbgZaaCQc +L3oWbfRrYnEBYuSlFuadjVvX42aqzHV28E4ITgywCxwubu2hAbacbcUHpnRSqLDq +lV+VZSQA/6cPNct7PR6AkJU80D+5wVOk7RVi4rsfbw1wFDeksCHu+0EThs9zjzL+ +S7HTCjB6AOHpbu1Sx+0d5YEKSE7EPF+dSFATNbhjTHzYQySnHK+Lm8nON8wmY1rC +7opOhvUlid1jA1d2BKO2ZxlxLir4pyIehTC1ddG2 +-----END CERTIFICATE----- +`; + +// A self-signed certificate without alternative names. +const PLAIN = `-----BEGIN CERTIFICATE----- +MIIC8jCCAdoCAQcwDQYJKoZIhvcNAQELBQAwPjEaMBgGA1UEAwwRcGxhaW4uZXhh +bXBsZS5vcmcxIDAeBgkqhkiG9w0BCQEWEXBsYWluQGV4YW1wbGUub3JnMCAXDTI2 +MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjA+MRowGAYDVQQDDBFwbGFpbi5l +eGFtcGxlLm9yZzEgMB4GCSqGSIb3DQEJARYRcGxhaW5AZXhhbXBsZS5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq01NcNAy9Ng04mVuAFvmZUvsm +rY7aOaMgtODmptulgZ2qw25E09RgGYCn1S+4IYDu9hH9X4s+28Ag7Khmj4nmeoXN +of5095wche8DR2AAUElqyr/Nmgm2kTLdIJX4wl0eWXHIlmSPdOUNovA6JjGKH99b +8W7a2TqY/TsSadpPSvvN2esr7Jr4KgW7MXPUOnwNbcBOVcQP9rtFrduAWz01q0HM +02LboclnUnk/e58BXAj8vgdVojljDcc0bhbdz/Hh1/5gaaNILjJNOd4AgwZaCT02 +KojGz4Nb6MSgFmON2iMgAZjY1fXXkJ7wsfVPytjW73e7uV/RFuqSHQZenxupAgMB +AAEwDQYJKoZIhvcNAQELBQADggEBAJX1DGuiSLoCKBDI3oCgAu7X86G01ovEgZJr +8JrmGHW5q7ujjo53t46h8XPm3LJ8UV9/MZ+Yry43xB2txXYv3b3g/i+7JdTWLgUD +INA5VWvVkEZfm0lBEpU1Agpy4lV5uUCKZTgP/3MY5hnJ0YhQIs4BOjbwueMRFshL +yyHVb73MMbpWaZ7+T368seafa+ekeuY6gQly/HacuszjoYd6KKV3n8eDewenClCF +HoOF1Y4I+CvmR+a6B3iBczJ+a4tceFIIWsZUIeqzvGHpM7G9YwlmbvGprfLgR8Nv +igUib/D9tU3hdL6VWN5nMjdXV/8Le4nDnBjVBjYDkVbsnmT91AQ= +-----END CERTIFICATE----- +`; + +const wild = new X509Certificate(WILD); +const cn = new X509Certificate(Buffer.from(CN)); +const leaf = new X509Certificate(LEAF); +const plain = new X509Certificate(PLAIN); + +const hosts = [ + "foo.example.com", "f.example.com", "fx.example.com", "a.b.example.com", "foo.example.net", + "oo.example.net", "x.xn--bcher-kva.example", "xn--abc.example", "example.com", + "a.b.example.org", "x", "a.mixed.example", "A.MIXED.EXAMPLE", "a.dot.example", ".dot.example", + "dot.example", "bz.example.edu", "baz.example.edu", "b.z.example.edu", "x.cn.example", + "second.example", "192.168.1.1", ".example.com", ".mixed.example", "a.b.mixed.example", +]; +const opts: [string, X509CheckOptions | undefined][] = [ + ["default", undefined], + ["nowild", { wildcards: false }], + ["nopartial", { partialWildcards: false }], + ["multi", { multiLabelWildcards: true }], + ["single", { singleLabelSubdomains: true }], + ["always", { subject: "always" }], + ["never", { subject: "never" }], +]; +for (const [label, o] of opts) { + console.log(label, hosts.map((h) => `${h}=${wild.checkHost(h, o)}`).join(" ")); + console.log(label, "cn", hosts.map((h) => `${h}=${cn.checkHost(h, o)}`).join(" ")); +} + +for (const h of ["www.example.com", "example.com", "EXAMPLE.COM", "xn--bcher-kva.example", "", "foo.example.com."]) { + console.log("leaf", JSON.stringify(h), leaf.checkHost(h), leaf.checkHost(h, { subject: "always" })); +} +console.log("plain", plain.checkHost("plain.example.org"), plain.checkHost("plain.example.org", { subject: "never" })); +const found: string | undefined = leaf.checkHost("api.example.com"); +console.log("typed", found === undefined ? "none" : found.toUpperCase()); + +for (const e of ["Mixed@example.com", "mixed@example.com", "Mixed@EXAMPLE.COM", "a@b.example", "a@B.EXAMPLE", "A@b.example"]) { + console.log("email", e, wild.checkEmail(e), cn.checkEmail(e), cn.checkEmail(e, { subject: "never" })); +} +for (const e of ["ops@example.com", "OPS@EXAMPLE.COM", "admin@example.com"]) console.log("email", e, leaf.checkEmail(e)); +console.log("email plain", plain.checkEmail("plain@example.org"), plain.checkEmail("plain@example.org", { subject: "always" })); + +const ips = [ + "192.168.1.1", "192.168.001.001", "192.168.1.1.", "1.2.3", "::", "::ffff:192.168.1.1", + "0:0:0:0:0:0:0:0", "1::2::3", "fe80::1%eth0", " 192.168.1.1", "192.168.1.256", + "10.0.0.1", "2001:db8::1", "2001:DB8:0:0:0:0:0:1", "2001:db8:0::0:1", "::ffff:10.0.0.1", "", +]; +for (const ip of ips) { + try { + console.log("ip", JSON.stringify(ip), wild.checkIP(ip), leaf.checkIP(ip)); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log("ip", JSON.stringify(ip), err.name, err.code, err.message); + } +} + +try { + leaf.checkHost("www.example.com", { subject: "sometimes" as "always" }); +} catch (e) { + const err = e as NodeJS.ErrnoException; + console.log("subject", err.name, err.code, err.message); +} diff --git a/tests/corpus/7091-x509-certificate-keys.ts b/tests/corpus/7091-x509-certificate-keys.ts new file mode 100644 index 000000000..b9b89ae1e --- /dev/null +++ b/tests/corpus/7091-x509-certificate-keys.ts @@ -0,0 +1,332 @@ +// X509Certificate.publicKey, verify, checkIssued and checkPrivateKey match +// Node: the public key as a cached KeyObject, signature checks for RSA +// PKCS#1, RSA-PSS, ECDSA and Ed25519 signatures, issuer checks by name, +// key identifier and key type, private key matches, and the pkey errors. +import { + X509Certificate, + constants, + createPrivateKey, + createPublicKey, + createSecretKey, + generateKeyPairSync, + sign, + verify, + type KeyObject, +} from "node:crypto"; + +// An RSA root and the EC leaf it signed. +const ROOT = `-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIDChHOMA0GCSqGSIb3DQEBCwUAMD4xCzAJBgNVBAYTAlVT +MRQwEgYDVQQKDAtFeGFtcGxlIE9yZzEZMBcGA1UEAwwQRXhhbXBsZSBSU0EgUm9v +dDAgFw0yNjEwMjIwOTQ2NDFaGA8yMTI2MDkyODA5NDY0MVowPjELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwsfidupnXQcn8PUF +XaCKRYw8XKI1GUn29U5/IUGjAnJVaRmcB6BPTmblbKt4y7gLvU4h1Hxuypx8FZSZ +30eZOS1ixyrymDthG5zUHqzXk5zAGsNvZeXjN3YxTC9vX5CpQ9Uz2i+bLOi3PJsE +qyKVZ+GGT9fpIaALG11YrWmXTUko/a0AKGot0w8O/RaMpXQpkbeyTBF6f4pHNMqx +QGy+KlpC/+yIJ9psT6qyU33HF3Wa+DOy+0txAFEQNw9m1j7adOLnU0zBpkM/bnP2 +fSwxMkDB2Di2P5H6PkdqLT4zBf/BLdg7WqpxxSqqgVmaSXhEqUQ6xjf51yLR3wy/ +xBWhMQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQUCBpZW95FsDtUqpwfOMyoE/OLX8swDQYJKoZIhvcNAQELBQADggEB +AChUtpNi8dPPxA4sf4EpZhEcYBN53X3WUcG3KJGtSf8JbPKru9lK9O+sZtJADbph +WoMPdWh8g+fEMk5D6l8Y5pDk/OR++oWNY7XCLFNyigmUGn+4SopJynBQiDk2dYPV +b6swDPPiV85TuAHVn79l766rtFdHdYwRe5HvfEkiXax7Egonlx7NR73xxm/XgvHQ +jhKbTZN9k/DEjkQuo5rt9ldVupUMM0iMhDMKX5oWEx9nvRwHPbjESS+Shy/FA4nA +2WHWO++4IU+imO40an1B/8SmFF+X9hj3UE6qaZLynVZRUiouBZibwZCcdCpibFM3 +kmB2s+NZXRvzhCjIxO97Ib4= +-----END CERTIFICATE----- +`; +const LEAF = `-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgICCwswDQYJKoZIhvcNAQELBQAwPjELMAkGA1UEBhMCVVMx +FDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBSb290 +MCAXDTI2MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjBfMQswCQYDVQQGEwJV +UzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNv +bTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20wWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARidSirNIIrw+YHeS112z3N+1n0+uA9HjzkQ5u3QfZsRGCe +ivCEMQSs5+cUE/nv5WWda2kQWavxPGluHFtYmS10o4IBWDCCAVQwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHsG +A1UdEQR0MHKCDSouZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tghV4bi0tYmNoZXIt +a3ZhLmV4YW1wbGWBD29wc0BleGFtcGxlLmNvbYcECgAAAYcQIAENuAAAAAAAAAAA +AAAAAYYUaHR0cHM6Ly9leGFtcGxlLmNvbS8wXgYIKwYBBQUHAQEEUjBQMCQGCCsG +AQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wKAYIKwYBBQUHMAKGHGh0 +dHA6Ly9jYS5leGFtcGxlLmNvbS9jYS5jcnQwHQYDVR0OBBYEFHyUnRTDwZxUPVwZ +RKmse3fjoeMMMB8GA1UdIwQYMBaAFAgaWVveRbA7VKqcHzjMqBPzi1/LMA0GCSqG +SIb3DQEBCwUAA4IBAQAfMTcpidoH47PXOQVxRttfrG7K9BcntyXP4+5hUAoSfMr6 +Rxy7X/9ba7R0yCGlNsUCusObaBpbdvwX8v9TiTYKVL+C9rEVGrTHnh6kbgZaaCQc +L3oWbfRrYnEBYuSlFuadjVvX42aqzHV28E4ITgywCxwubu2hAbacbcUHpnRSqLDq +lV+VZSQA/6cPNct7PR6AkJU80D+5wVOk7RVi4rsfbw1wFDeksCHu+0EThs9zjzL+ +S7HTCjB6AOHpbu1Sx+0d5YEKSE7EPF+dSFATNbhjTHzYQySnHK+Lm8nON8wmY1rC +7opOhvUlid1jA1d2BKO2ZxlxLir4pyIehTC1ddG2 +-----END CERTIFICATE----- +`; +// A self-signed RSA certificate and a self-signed Ed25519 certificate. +const PLAIN = `-----BEGIN CERTIFICATE----- +MIIC8jCCAdoCAQcwDQYJKoZIhvcNAQELBQAwPjEaMBgGA1UEAwwRcGxhaW4uZXhh +bXBsZS5vcmcxIDAeBgkqhkiG9w0BCQEWEXBsYWluQGV4YW1wbGUub3JnMCAXDTI2 +MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjA+MRowGAYDVQQDDBFwbGFpbi5l +eGFtcGxlLm9yZzEgMB4GCSqGSIb3DQEJARYRcGxhaW5AZXhhbXBsZS5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq01NcNAy9Ng04mVuAFvmZUvsm +rY7aOaMgtODmptulgZ2qw25E09RgGYCn1S+4IYDu9hH9X4s+28Ag7Khmj4nmeoXN +of5095wche8DR2AAUElqyr/Nmgm2kTLdIJX4wl0eWXHIlmSPdOUNovA6JjGKH99b +8W7a2TqY/TsSadpPSvvN2esr7Jr4KgW7MXPUOnwNbcBOVcQP9rtFrduAWz01q0HM +02LboclnUnk/e58BXAj8vgdVojljDcc0bhbdz/Hh1/5gaaNILjJNOd4AgwZaCT02 +KojGz4Nb6MSgFmON2iMgAZjY1fXXkJ7wsfVPytjW73e7uV/RFuqSHQZenxupAgMB +AAEwDQYJKoZIhvcNAQELBQADggEBAJX1DGuiSLoCKBDI3oCgAu7X86G01ovEgZJr +8JrmGHW5q7ujjo53t46h8XPm3LJ8UV9/MZ+Yry43xB2txXYv3b3g/i+7JdTWLgUD +INA5VWvVkEZfm0lBEpU1Agpy4lV5uUCKZTgP/3MY5hnJ0YhQIs4BOjbwueMRFshL +yyHVb73MMbpWaZ7+T368seafa+ekeuY6gQly/HacuszjoYd6KKV3n8eDewenClCF +HoOF1Y4I+CvmR+a6B3iBczJ+a4tceFIIWsZUIeqzvGHpM7G9YwlmbvGprfLgR8Nv +igUib/D9tU3hdL6VWN5nMjdXV/8Le4nDnBjVBjYDkVbsnmT91AQ= +-----END CERTIFICATE----- +`; +const ED = `-----BEGIN CERTIFICATE----- +MIIBljCCAUigAwIBAgIUNFuRt3zKS19UAUMdLWewqHrGBigwBQYDK2VwMC8xFDAS +BgNVBAoMC0V4YW1wbGUgT3JnMRcwFQYDVQQDDA5lZC5leGFtcGxlLm5ldDAgFw0y +NjEwMjIxMDEwMTJaGA8yMTI2MDkyODEwMTAxMlowLzEUMBIGA1UECgwLRXhhbXBs +ZSBPcmcxFzAVBgNVBAMMDmVkLmV4YW1wbGUubmV0MCowBQYDK2VwAyEAOCSLKkel +kOH/5aqUwI3PNJVUUNyhgu2s19LFC254fNCjdDByMB0GA1UdDgQWBBR+50zUcqL8 +739jh6/rEYojULDmlTAfBgNVHSMEGDAWgBR+50zUcqL8739jh6/rEYojULDmlTAP +BgNVHRMBAf8EBTADAQH/MB8GA1UdEQQYMBaCDmVkLmV4YW1wbGUubmV0hwTAAAIH +MAUGAytlcANBAC/c0Bi0SVUnHCg/1pn7MaCgVivSHuoISi2SCq/0EnLefRdgsIir +0QxjVnxeuKN2Y1yfEO/R1W8Nwovo1l2M1gQ= +-----END CERTIFICATE----- +`; +// A second RSA root with its EC leaf, a self-signed RSA-PSS certificate +// and a self-signed v1 certificate with the second leaf's subject. +const ROOT2 = `-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIIASNFZ4mrze8wDQYJKoZIhvcNAQELBQAwgZwxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRQwEgYDVQQKDAtFeGFtcGxlIE9yZzERMA8GA1UECwwIRGV2IFRlYW0xGDAW +BgNVBAMMD0V4YW1wbGUgUm9vdCBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs +ZS5jb20wIBcNMjYxMDIxMTYzMzUyWhgPMjEyNjA5MjcxNjMzNTJaMIGcMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j +aXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBUZWFtMRgw +FgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0YGcCr0pxR8d +6qDdLuILcoT7vhtAjyHwfkoChnH6N3TLWzDUugnBp9NbDb7gDLqbRT4uTDoNb6zS +tEJZ4Q7/hhd0jDvoPmOy0Y3Kyj9uNsdf25GdbvwwEnlddELJCZprTZ4OhEg6w2s0 +Yy4SlviLzDXLmsggTypxeS+K8JHT9onMipi5L4GFu8n1QZbzXvSS4BkRgMXEyrtr +FOE9qSo2qkubFyd/WNKRTRIpTSc5/GGsqJ0FKDDJnN5adotcUucbJFn4noNnFUWh +M8mhBEj7ENDdJsD2EVFsI6H958jA2SbNNu4ngzAOs2AKNBYBfQK02ZQAdRNOSL3K +5FkfbII5LwIDAQABo2MwYTAdBgNVHQ4EFgQUP+yQyItpNYEls26Jfv+NnciGaK8w +HwYDVR0jBBgwFoAUP+yQyItpNYEls26Jfv+NnciGaK8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBABDw3Va/C+RFc/Hs +TJnHtQJtxFL0ZhnHUqg4DcDA3m+JDdIDNL8sNpLEbtn/pr0VeEWgG+MImWAQlrUW +IAZkLj9/Zg1wSO0oZ86ZyZJZmp54lUYXgG17CNl4g+F7VHt3bBwUJHTbWye/FKM6 +5OVZetCYKVj7Z+5BYuf7cumzvRqcn32fXidB/kjRX14SJ4JDPn2yFZWCr2ygIqWQ +SOuEmWbNAbYGaVZkQr2Ma8U28DWY2jb0f6nP8jLt49t9o6N/e6Fwd6I5t/mx9Mfj +0403omkxmr9vqN2kBamzVnun+2mDkRZcKPCr72Q4sfbvmDi/W5Nf1Iu2cQW8VfDO +SqpyT54= +-----END CERTIFICATE----- +`; +const LEAF2 = `-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIPAPAN+s4AESIzRFVmd4iZMA0GCSqGSIb3DQEBDAUAMIGc +MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu +IEZyYW5jaXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBU +ZWFtMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNh +QGV4YW1wbGUuY29tMB4XDTI2MTAyMTE2MzM1MloXDTM2MTAxODE2MzM1MlowJzER +MA8GA1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABJVfVy9EH/VHQuRqpFFnXk0F00ncDht6APgVPDoybK8H +PlcM4T5H1yAOMHnlH29LK1jtMqU1aAOiiQRzPdCvqoujggE3MIIBMzAJBgNVHRME +AjAAMGgGA1UdEQRhMF+CCWxvY2FsaG9zdIINKi5leGFtcGxlLmNvbYcEfwAAAYcQ +AAAAAAAAAAAAAAAAAAAAAYERYWRtaW5AZXhhbXBsZS5jb22GGGh0dHBzOi8vZXhh +bXBsZS5jb20vcGF0aDBdBggrBgEFBQcBAQRRME8wIwYIKwYBBQUHMAGGF2h0dHA6 +Ly9vY3NwLmV4YW1wbGUuY29tMCgGCCsGAQUFBzAChhxodHRwOi8vY2EuZXhhbXBs +ZS5jb20vY2EuY3J0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV +HQ4EFgQUdKIx6ULFLPNP/kzwUFjSa5D19RswHwYDVR0jBBgwFoAUP+yQyItpNYEl +s26Jfv+NnciGaK8wDQYJKoZIhvcNAQEMBQADggEBABmQ8f2VsXNaMM9X1MEhi27D +LjwcOaaGEINNZq7xu/HL6UljMb1Odsuz66DkiH3Exd3MrWy6OlOpJJF0VzsqMsoD +0CRBxXF28o6Jil3vOGVwlYNmRidpkAkj4VyWDvv8FCS9VbsBtXaSHwu9wcCwePE0 +cGJRfTKoZ6KJ8vQ57DBEZ/ZszuhehOZYqrTmoUdjZ123kXdxQvK+ALn4SlIXHBhN +9unhd+8i00EmkXO/REw8paMcMVVGR+ioOEjyNHqZo6vkRqgpKggn1Yq8cTTDOMnS +P1o42EQZLlbuvx0kqInBqwqjDx4nZiPaifoDc9BF6iZPBvm8dhrEfghfB1tkNk0= +-----END CERTIFICATE----- +`; +const PSS = `-----BEGIN CERTIFICATE----- +MIIDdTCCAiigAwIBAgICAIAwQgYJKoZIhvcNAQEKMDWgDzANBglghkgBZQMEAgEF +AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIEAgIA3jAeMQwwCgYDVQQD +DANwc3MxDjAMBgNVBAoMBWN0bAF4MB4XDTI2MTAyMTE2NDQzM1oXDTI2MTEyMDE2 +NDQzM1owHjEMMAoGA1UEAwwDcHNzMQ4wDAYDVQQKDAVjdGwBeDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL2pno8Ybwihdv3hnMhWPPt85SXQmFAJpFoS +E+sT9H6FbwFCIyPkY+SWk8H7y8V+D+388tNvJZmGjX3uvWpK2Wx6anSB3/WMpkUR +2ExP4cpSxZNr9EFzC/cdqLvsxJcHMHkc/uJuFbtjsuiH1ygOe3nyhWSNiPwGdwHE +cKkBFkzW3j2C/4Jkgt4k+sMe3gy5litKtMi4zZB8ixCm5oGhveGhUZy0Kb01twgZ +RP4FEd32sSRqq3NCx4cB0kyreTuzmk6U7BkxcdPzaj6DifjRznTQmjMdoCNZQRCj +G3KBOMHHyvmV2B4lNGDuMlcSap/TpYq7n32AH5sDvELrUnPwVzsCAwEAAaNTMFEw +HQYDVR0OBBYEFC/QJvAQsbQ25sErc1To2G1VuNZ0MB8GA1UdIwQYMBaAFC/QJvAQ +sbQ25sErc1To2G1VuNZ0MA8GA1UdEwEB/wQFMAMBAf8wQgYJKoZIhvcNAQEKMDWg +DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIE +AgIA3gOCAQEAu3lP2Q3Bb5fWUQtgFr/82Oke1ZRW/GRbj03mZ30izADHC7yk0OYm +02f7B5sub04Jk6yuRVmEBjJHSPNwAo0RJWtwvD77KLhG8aObr7Z3+pkfFvpGzqeS +MQ4FtK2MPps3kA1a5D4XLwkwDJtHWZVuVyXpPgxkptfLzZgrieKrpNyOKR8Pz/PO +Wlwv6bF7SXvAV8Vrd7KDvTqwQ5iq9UFhP6GeMDWezYtzLdjlvje7MhmZ96zgSY6m +qumNWOPHmiN4eoOE4M69B+0fa3WyMzubW0nVGM/sDhtS0k5zHs6Y4RLn11HolhXH +LvbvDgqtl/m5i0uHxEld+Z1uhzJscqFQRg== +-----END CERTIFICATE----- +`; +const V1 = `-----BEGIN CERTIFICATE----- +MIIBSTCB7wIUI3kYI5NpR07YqZe6RwOx22je9IowCgYIKoZIzj0EAwIwJzERMA8G +A1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNjEwMjExNjMz +NTJaFw0yNjExMjAxNjMzNTJaMCcxETAPBgNVBAoMCExlYWYgT3JnMRIwEAYDVQQD +DAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASVX1cvRB/1R0Lk +aqRRZ15NBdNJ3A4begD4FTw6MmyvBz5XDOE+R9cgDjB55R9vSytY7TKlNWgDookE +cz3Qr6qLMAoGCCqGSM49BAMCA0kAMEYCIQCFfFOw9qzzfSOQO1zRnT0sOXibjLWl +hHDyqecxhY7gtQIhAPMgqg+p1JXV5bgGpqgDfP3sE53luxMGlnAC39uhwBUh +-----END CERTIFICATE----- +`; + +const ROOT_KEY = `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCx+J26mddByfw +9QVdoIpFjDxcojUZSfb1Tn8hQaMCclVpGZwHoE9OZuVsq3jLuAu9TiHUfG7KnHwV +lJnfR5k5LWLHKvKYO2EbnNQerNeTnMAaw29l5eM3djFML29fkKlD1TPaL5ss6Lc8 +mwSrIpVn4YZP1+khoAsbXVitaZdNSSj9rQAoai3TDw79FoyldCmRt7JMEXp/ikc0 +yrFAbL4qWkL/7Ign2mxPqrJTfccXdZr4M7L7S3EAURA3D2bWPtp04udTTMGmQz9u +c/Z9LDEyQMHYOLY/kfo+R2otPjMF/8Et2DtaqnHFKqqBWZpJeESpRDrGN/nXItHf +DL/EFaExAgMBAAECggEADsvlabgryvbYs2bBlhe0eU0KZtURqI6kWoUwhiO40N5D +m7zMzA2DCMcrRYKPJhagmaxeSIh9WBnkGTWVLM+BFcVbXBrom+Ti3xieSohBxh5h +v4a6Jyj8BkINw6ZU1RMlsJf5HOmB5br19H4oMCHr7JzGubkCpkHEpjsEmgPL8Rxs +LYb9palDyTD1z5xVAQDAkvnCYQMX2DsiH8RuO4Ih0OoWMIsAGasS1dTQCkOzN9wF +cMg+hmJvQLN5xLYI+VPy/iZn+bmerCx8/NQBrbo+zibmnv7cDK1U9emVD70/Nd1d +B7ZEm+a4gReaxWVQGEkFMvs29u/TDWXYRnw0PGjUYwKBgQDrmKEx+ZiZwNNk/S3l +s8FgVrLFDr/+t5X33jaD5nVuSN6/AoR3qfh9piIyCrdNKdHicAcCnWP6skUFYLq3 +/tyMBATX3j+f3PHW3i2ZUAK26hgfp66F/3IyowePZbBDlGSmGsffVlc8on5fCk1Q +k3G0znktbrMCNxeeY9GSi1CHewKBgQDTpled6pZepZ54OGnKVJFo6DbQC0l4HQ6y +Rwv9BMtAz+AxWXda1oYK5bbJZrCmHyVP85VxkzRHOMxicv7M2kR9y2zZsJD7INqr +Nvt+lo2yGtIGv7kOdwWzvs4Pn7aCWSApzYBjuKrpP1xDzaMHhusAQtkk77YGCZCu +cCAQ/FjEQwKBgBq/1KWI8lOauiXAPULUWwNmNJv9Jb10+LkQ6N2m//RKn8fOMF4p +1UXm5AEMiG1hbywnjhQ07JIdRYrrxad4b3ASUL51xIGRI4BC76h1lilQgPavIjii +nUO0kPouEIpLwfW+Gq137yW6SWDfGZIpe4KM4uNOq1J4EN1x9eyOltEbAoGAP4bj +L5QVFE2T1jPglQlzJ9DjeuiYYcQfACXRFF3XABolCcr3xz0yuJygk60OAp7AnVHY +nmF3xJToy/gGQMiW3Ect4d1cdvQyjkePjzxzRW1Fcx8i1ZOmE4CG6yueJVvJCqMT +EVuBeUc6U2Oqtnyplsd509tbgE5oreMbzN1ha28CgYAHpG10ZQRDeqWFIo/ttk6Q +Q1Gf53eqZFCgh8GO1hBJ7+Fp+Ec5S9UPMBDjZb6GET7ifF8Zit/lR4gN2PuELwJA +YaXTIehCugH7Q3oTjNcQtls6tW+/+jEGjbjSHyZvwk8EFHc0J7PWRszB36s+qM+O +YPorNipExSeWti1506ljLQ== +-----END PRIVATE KEY----- +`; +const LEAF_KEY = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINqbifA6kN/DSN0cMD2s7fXiNtPXZqipFrbCs/3mCGbtoAoGCCqGSM49 +AwEHoUQDQgAEYnUoqzSCK8PmB3ktdds9zftZ9PrgPR485EObt0H2bERgnorwhDEE +rOfnFBP57+VlnWtpEFmr8TxpbhxbWJktdA== +-----END EC PRIVATE KEY----- +`; +const PLAIN_KEY = `-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq01NcNAy9Ng04 +mVuAFvmZUvsmrY7aOaMgtODmptulgZ2qw25E09RgGYCn1S+4IYDu9hH9X4s+28Ag +7Khmj4nmeoXNof5095wche8DR2AAUElqyr/Nmgm2kTLdIJX4wl0eWXHIlmSPdOUN +ovA6JjGKH99b8W7a2TqY/TsSadpPSvvN2esr7Jr4KgW7MXPUOnwNbcBOVcQP9rtF +rduAWz01q0HM02LboclnUnk/e58BXAj8vgdVojljDcc0bhbdz/Hh1/5gaaNILjJN +Od4AgwZaCT02KojGz4Nb6MSgFmON2iMgAZjY1fXXkJ7wsfVPytjW73e7uV/RFuqS +HQZenxupAgMBAAECggEAAxY5ACZHoaanpAFvbBfeMEVV2PvTZEGc30MtejWDLBE0 +yGhqJm++e3MShlXdnCHs2sSTDCVNFphxdxbXryzCVOobX10hRDtSNJi9+izwwOiy +fu3FfAFf0g77IJBNbXaTqK/8lJ+VFxuJZQ7v8NkOVZzgVCC/fFBnFOskhV9ozLx6 +Uk+5Rg/UFenUhhHqrzwxCLKR3TAX8pBdidmbJF61ypxt6Cn3pOA/pB1gUBi76nau +w4kefdNj8AXzSjzOfVEg0quywfeoOq6ESdy3p4RQMt6NZ3wnCgzr/uuWjVQzJbJU +Mg2TVlrKzbiinel2ZqKV8lpymhFTlRaWNzK7PzD91QKBgQDaSDpdQIk2+CJMAVzU +LsI5tdZa0cMDbAZzxBmg1W6kwIrMzYVeMo7RPBarhCuQ/3gJFVvA7LdvXR87BbYa +UuDXbvO4Gv+2wqxxX961sWK85VqhwAqXhFbkS9KNJ0twmlmAcX9lvl0Zn4ciO3c+ +AMJT4JN4WR6wI/dt1TCHLa1wtQKBgQDIV9bgKinpUzhSy9byTH0CwghwudAPx2CA +9gJ6VcjyUKeBdgJ0PQMTwppyrPs0fz+NgJ6+LiTvp9BS9vrXmtnjbD1oAxFcC9uU +NL7BJSRY+wgmnPgbldA25xq1TRJmB8YMr1CO4gYKGSNM7I1HlNv4dvMcS295RhTG +cdDkl4T7pQKBgQCzJmygdIMplmWvs76Ydvf5nYAcceR4PoTdJqlSE1oH5e8FP/ih +4yTMHWENzCBllQbTjbAMmzvCLs+fBYT7/eGDbxCgnagaO5SESIbk4H2FQ0grUQ5g +ZMFcBc79Aq8r78JdZMmGm8KVcRTbx6qKPN1BMiRWte416o78meiGSbr7sQKBgQCJ +2AY8TkZwB5ZlQWMva3lIAIEWLoL9mEzJ7npd+dA9CUl3Rd8CY0hOvELQTDy7LiJo +FesjJ1S6oFgx/UUyO5VXaQ4HFrVHA9Gc832cHxYXMwG0r6P2NuoAHJq/y7uxAABA +kg93gARvxkQHlWrPqZiAKDe1bCHS62k38O+tmKMjEQKBgHF7a9tqZVoc8CqUL/I8 +FXRMRmdzC4RoWC2NDqMazW9qxXpFZmPux1q9ds6ikoG0v3pAxZmRH0eMTIujtLPn +chtAnvTvkg5Q+/xa429PFThJ2b04zsHCmnzfdMaIPvqhBqoPmyAmptjUeMB+yzQU +d3AigXwTBk4l4yPleDD4qUbO +-----END PRIVATE KEY----- +`; +const ED_KEY = `-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEID8VP7HLPAW9FXkVxo3eYbqZ8zkXsPFi3c2RwdDdwy/O +-----END PRIVATE KEY----- +`; + +const certs: [string, X509Certificate][] = [ + ["root", new X509Certificate(ROOT)], + ["leaf", new X509Certificate(Buffer.from(LEAF))], + ["plain", new X509Certificate(PLAIN)], + ["ed", new X509Certificate(ED)], + ["root2", new X509Certificate(ROOT2)], + ["leaf2", new X509Certificate(LEAF2)], + ["pss", new X509Certificate(PSS)], + ["v1", new X509Certificate(V1)], +]; + +function describe(key: KeyObject): string { + const details = key.asymmetricKeyDetails ?? {}; + const size = key.asymmetricKeyType === "ed25519" ? "32 bytes" : `${details.modulusLength ?? details.namedCurve}`; + return `${key.type} ${key.asymmetricKeyType} ${size}`; +} + +for (const [name, cert] of certs) { + const key = cert.publicKey; + const spki = key.export({ type: "spki", format: "der" }); + console.log(name, describe(key), key === cert.publicKey, spki.length, createPublicKey(cert.toString()).equals(key)); +} + +for (const [name, cert] of certs) { + const by: string[] = []; + const issued: string[] = []; + for (const [other, signer] of certs) { + if (cert.verify(signer.publicKey)) by.push(other); + if (cert.checkIssued(signer)) issued.push(other); + } + console.log(name, "verifies with", by.join(",") || "-", "issued by", issued.join(",") || "-"); +} + +const keys: [string, KeyObject][] = [ + ["root", createPrivateKey(ROOT_KEY)], + ["leaf", createPrivateKey(Buffer.from(LEAF_KEY))], + ["plain", createPrivateKey({ key: PLAIN_KEY, format: "pem" })], + ["ed", createPrivateKey(ED_KEY)], + ["fresh", generateKeyPairSync("ec", { namedCurve: "prime256v1" }).privateKey], +]; +for (const [name, cert] of certs) { + console.log(name, "private", keys.filter(([, key]) => cert.checkPrivateKey(key)).map(([k]) => k).join(",") || "-"); +} + +const leaf = certs[1]![1]; +function attempt(label: string, f: () => boolean): void { + try { + console.log(label, f()); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log(label, err.name, err.code, err.message); + } +} +attempt("verify private", () => leaf.verify(keys[0]![1])); +attempt("verify secret", () => leaf.verify(createSecretKey(Buffer.alloc(16)))); +attempt("check public", () => leaf.checkPrivateKey(leaf.publicKey)); +attempt("check secret", () => leaf.checkPrivateKey(createSecretKey(Buffer.alloc(16)))); + +// A PSS signature check on the root's public key leaves no padding behind +// for the certificate checks that follow. +const root = certs[0]![1]; +const message = Buffer.from("pss"); +const pssSignature = sign("sha256", message, { key: keys[0]![1], padding: constants.RSA_PKCS1_PSS_PADDING }); +const pssValid = verify("sha256", message, { key: root.publicKey, padding: constants.RSA_PKCS1_PSS_PADDING }, pssSignature); +console.log("after pss", pssValid, root.verify(root.publicKey), leaf.verify(root.publicKey)); + +// An outer signatureAlgorithm that differs from the TBSCertificate's, here +// without its NULL parameters, does not verify. +function header(der: Buffer, at: number): [number, number] { + const first = der[at + 1]!; + if (first < 0x80) return [2, first]; + let length = 0; + for (let i = 0; i < (first & 0x7f); i++) length = length * 256 + der[at + 2 + i]!; + return [2 + (first & 0x7f), length]; +} +const rootDer = root.raw; +const [outerHeader] = header(rootDer, 0); +const [tbsHeader, tbsLength] = header(rootDer, outerHeader); +const algAt = outerHeader + tbsHeader + tbsLength; +const [algHeader, algLength] = header(rootDer, algAt); +const algorithm = rootDer.subarray(algAt, algAt + algHeader + algLength); +const oid = algorithm.subarray(2, 2 + 2 + algorithm[3]!); +const bare = Buffer.concat([Buffer.from([0x30, oid.length]), oid]); +const body = Buffer.concat([rootDer.subarray(outerHeader, algAt), bare, rootDer.subarray(algAt + algHeader + algLength)]); +const altered = new X509Certificate(Buffer.concat([Buffer.from([0x30, 0x82, body.length >> 8, body.length & 0xff]), body])); +console.log("outer algorithm", algorithm.toString("hex"), "->", bare.toString("hex"), altered.subject.split("\n").join("/"), altered.verify(root.publicKey)); diff --git a/tests/corpus/7092-x509-legacy-object.ts b/tests/corpus/7092-x509-legacy-object.ts new file mode 100644 index 000000000..6b423860a --- /dev/null +++ b/tests/corpus/7092-x509-legacy-object.ts @@ -0,0 +1,158 @@ +// X509Certificate.toLegacyObject and toString match Node: the +// legacy object's members in Node's order (subject and issuer objects +// with repeated attributes as arrays, alternative names, access +// descriptions by method, the RSA modulus, exponent and SPKI or the EC +// point and curve names, the validity window, fingerprints, extended key +// usages, serial number and raw bytes), its inspect output, and the PEM +// text of toString, String() and template literals. +import { X509Certificate } from "node:crypto"; + +// An EC leaf with every alternative name kind, and the RSA root that signed it. +const LEAF = `-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgICCwswDQYJKoZIhvcNAQELBQAwPjELMAkGA1UEBhMCVVMx +FDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBSb290 +MCAXDTI2MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjBfMQswCQYDVQQGEwJV +UzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNv +bTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20wWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARidSirNIIrw+YHeS112z3N+1n0+uA9HjzkQ5u3QfZsRGCe +ivCEMQSs5+cUE/nv5WWda2kQWavxPGluHFtYmS10o4IBWDCCAVQwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHsG +A1UdEQR0MHKCDSouZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tghV4bi0tYmNoZXIt +a3ZhLmV4YW1wbGWBD29wc0BleGFtcGxlLmNvbYcECgAAAYcQIAENuAAAAAAAAAAA +AAAAAYYUaHR0cHM6Ly9leGFtcGxlLmNvbS8wXgYIKwYBBQUHAQEEUjBQMCQGCCsG +AQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wKAYIKwYBBQUHMAKGHGh0 +dHA6Ly9jYS5leGFtcGxlLmNvbS9jYS5jcnQwHQYDVR0OBBYEFHyUnRTDwZxUPVwZ +RKmse3fjoeMMMB8GA1UdIwQYMBaAFAgaWVveRbA7VKqcHzjMqBPzi1/LMA0GCSqG +SIb3DQEBCwUAA4IBAQAfMTcpidoH47PXOQVxRttfrG7K9BcntyXP4+5hUAoSfMr6 +Rxy7X/9ba7R0yCGlNsUCusObaBpbdvwX8v9TiTYKVL+C9rEVGrTHnh6kbgZaaCQc +L3oWbfRrYnEBYuSlFuadjVvX42aqzHV28E4ITgywCxwubu2hAbacbcUHpnRSqLDq +lV+VZSQA/6cPNct7PR6AkJU80D+5wVOk7RVi4rsfbw1wFDeksCHu+0EThs9zjzL+ +S7HTCjB6AOHpbu1Sx+0d5YEKSE7EPF+dSFATNbhjTHzYQySnHK+Lm8nON8wmY1rC +7opOhvUlid1jA1d2BKO2ZxlxLir4pyIehTC1ddG2 +-----END CERTIFICATE----- +`; +const ROOT = `-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIDChHOMA0GCSqGSIb3DQEBCwUAMD4xCzAJBgNVBAYTAlVT +MRQwEgYDVQQKDAtFeGFtcGxlIE9yZzEZMBcGA1UEAwwQRXhhbXBsZSBSU0EgUm9v +dDAgFw0yNjEwMjIwOTQ2NDFaGA8yMTI2MDkyODA5NDY0MVowPjELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwsfidupnXQcn8PUF +XaCKRYw8XKI1GUn29U5/IUGjAnJVaRmcB6BPTmblbKt4y7gLvU4h1Hxuypx8FZSZ +30eZOS1ixyrymDthG5zUHqzXk5zAGsNvZeXjN3YxTC9vX5CpQ9Uz2i+bLOi3PJsE +qyKVZ+GGT9fpIaALG11YrWmXTUko/a0AKGot0w8O/RaMpXQpkbeyTBF6f4pHNMqx +QGy+KlpC/+yIJ9psT6qyU33HF3Wa+DOy+0txAFEQNw9m1j7adOLnU0zBpkM/bnP2 +fSwxMkDB2Di2P5H6PkdqLT4zBf/BLdg7WqpxxSqqgVmaSXhEqUQ6xjf51yLR3wy/ +xBWhMQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQUCBpZW95FsDtUqpwfOMyoE/OLX8swDQYJKoZIhvcNAQELBQADggEB +AChUtpNi8dPPxA4sf4EpZhEcYBN53X3WUcG3KJGtSf8JbPKru9lK9O+sZtJADbph +WoMPdWh8g+fEMk5D6l8Y5pDk/OR++oWNY7XCLFNyigmUGn+4SopJynBQiDk2dYPV +b6swDPPiV85TuAHVn79l766rtFdHdYwRe5HvfEkiXax7Egonlx7NR73xxm/XgvHQ +jhKbTZN9k/DEjkQuo5rt9ldVupUMM0iMhDMKX5oWEx9nvRwHPbjESS+Shy/FA4nA +2WHWO++4IU+imO40an1B/8SmFF+X9hj3UE6qaZLynVZRUiouBZibwZCcdCpibFM3 +kmB2s+NZXRvzhCjIxO97Ib4= +-----END CERTIFICATE----- +`; +// An EC leaf with access descriptions and extended key usages. +const ACCESS = `-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIPAPAN+s4AESIzRFVmd4iZMA0GCSqGSIb3DQEBDAUAMIGc +MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu +IEZyYW5jaXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBU +ZWFtMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNh +QGV4YW1wbGUuY29tMB4XDTI2MTAyMTE2MzM1MloXDTM2MTAxODE2MzM1MlowJzER +MA8GA1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABJVfVy9EH/VHQuRqpFFnXk0F00ncDht6APgVPDoybK8H +PlcM4T5H1yAOMHnlH29LK1jtMqU1aAOiiQRzPdCvqoujggE3MIIBMzAJBgNVHRME +AjAAMGgGA1UdEQRhMF+CCWxvY2FsaG9zdIINKi5leGFtcGxlLmNvbYcEfwAAAYcQ +AAAAAAAAAAAAAAAAAAAAAYERYWRtaW5AZXhhbXBsZS5jb22GGGh0dHBzOi8vZXhh +bXBsZS5jb20vcGF0aDBdBggrBgEFBQcBAQRRME8wIwYIKwYBBQUHMAGGF2h0dHA6 +Ly9vY3NwLmV4YW1wbGUuY29tMCgGCCsGAQUFBzAChhxodHRwOi8vY2EuZXhhbXBs +ZS5jb20vY2EuY3J0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV +HQ4EFgQUdKIx6ULFLPNP/kzwUFjSa5D19RswHwYDVR0jBBgwFoAUP+yQyItpNYEl +s26Jfv+NnciGaK8wDQYJKoZIhvcNAQEMBQADggEBABmQ8f2VsXNaMM9X1MEhi27D +LjwcOaaGEINNZq7xu/HL6UljMb1Odsuz66DkiH3Exd3MrWy6OlOpJJF0VzsqMsoD +0CRBxXF28o6Jil3vOGVwlYNmRidpkAkj4VyWDvv8FCS9VbsBtXaSHwu9wcCwePE0 +cGJRfTKoZ6KJ8vQ57DBEZ/ZszuhehOZYqrTmoUdjZ123kXdxQvK+ALn4SlIXHBhN +9unhd+8i00EmkXO/REw8paMcMVVGR+ioOEjyNHqZo6vkRqgpKggn1Yq8cTTDOMnS +P1o42EQZLlbuvx0kqInBqwqjDx4nZiPaifoDc9BF6iZPBvm8dhrEfghfB1tkNk0= +-----END CERTIFICATE----- +`; +// A multi-valued RDN, an Ed25519 key and names that need escaping. +const MULTI = `-----BEGIN CERTIFICATE----- +MIICNzCCAaCgAwIBAgIBADANBgkqhkiG9w0BAQsFADA3MSgwCgYDVQQLDANyZG4w +DAYDVQQDDAVtdWx0aTAMBgNVBAoMBXRocmVlMQswCQYDVQQGEwJERTAeFw0yNjEw +MjExNjM0MDVaFw0yNjEwMzExNjM0MDVaMDcxKDAKBgNVBAsMA3JkbjAMBgNVBAMM +BW11bHRpMAwGA1UECgwFdGhyZWUxCzAJBgNVBAYTAkRFMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDdv64EOPdpcWdjVMAlEQvNzm3ZbQYXrhOwRF9yzQp25SuA +3uKvStZOt8hyTifAd30oDXIfgyYI39xKHEkHfARTV1E2Jwyiz86/pDHmFypPFJuI +v/vOjYLLeisiVi8TFfHgmoGqGvySR83fwiUnoOOWj9Bqt/5EVBQbBeWrU9qH6QID +AQABo1MwUTAdBgNVHQ4EFgQUZrGU0gkgqCkmegRtu6vQ3h7kpPUwHwYDVR0jBBgw +FoAUZrGU0gkgqCkmegRtu6vQ3h7kpPUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQsFAAOBgQAMzFkzJElc9VJYhGx7AngO8Yve22iCTOVLO3/be8w55klb/HJX +vfESQz+EJLYVaP5Q/uChoE0I27NytkeUSpJFwmjyqoQq2j6kJWGKONqhQSnXDrms +HMPA7iik/C5hOqpwzDZ98wNU/e86TrqZxzpFkwbnZx9MSfiGr690s5BfpA== +-----END CERTIFICATE----- +`; +const ED = `-----BEGIN CERTIFICATE----- +MIICSDCCAfqgAwIBAgIB+zAFBgMrZXAwLDEVMBMGA1UEAx4MAGIAbQBwACAA6U4t +MRMwEQYDVQQKHgoAcABsAGEAaQBuMB4XDTI2MTAyMTE2NDQzM1oXDTI2MTEyMDE2 +NDQzM1owLDEVMBMGA1UEAx4MAGIAbQBwACAA6U4tMRMwEQYDVQQKHgoAcABsAGEA +aQBuMCowBQYDK2VwAyEAP+2Vcp8nwu2AhFvSczYTxGs1jV/ckA76Jqvwj9QDGqij +ggE/MIIBOzCBtwYDVR0RBIGvMIGsoBsGCisGAQQBgjcUAgOgDQwLdXNlckBkb21h +aW6gEAYDKgMEoAkMB3Vua25vd26kRTBDMQswCQYDVQQGEwJVSzEZMBcGA1UECh4Q +AEQAaQByACwAIABPAHIAZzEZMBcGA1UEAx4QAGQAaQByACAAbgBhAG0AZYgEKgME +BYcQ/oAAAAAAAAAAAAAAAAEAAocQIAENuAAAAAAAAP8AAEKDKYYKaHR0cDovL3gv +cTAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIHgDBFBggrBgEFBQcBAQQ5MDcwIAYI +KwYBBQUHMAWGFGh0dHA6Ly9yZXBvLmV4YW1wbGUvMBMGBCoDBAWCC29kZC5leGFt +cGxlMB0GA1UdDgQWBBQYMJ0fQJ1Nx0PmatIfTJusAEA9WTAFBgMrZXADQQCixubE +nuunBZYYPXF52+joQSX/a5DFkOEDmzRTuH7IIpPFWPo37rmJOSdPA/PV8v9clzYF +LP3EjM4YOAatcbsO +-----END CERTIFICATE----- +`; +const ODD = `-----BEGIN CERTIFICATE----- +MIICXzCCAeWgAwIBAgIBATAKBggqhkjOPQQDAjBvMQ0wCwYDVQQDDARhLCBiMQ4w +DAYDVQQHDAUjbGVhZDESMBAGA1UECAwJICBzcGFjZSAgMSYwJAYDVQQKDB1RdW90 +ZSBxIGJhY2tcc2xhc2g7IHNlbWkgPGx0PjESMBAGA1UECwwJQ2Fmw6kg4oKsMB4X +DTI2MTAyMTE2MzM1MloXDTI3MTAyMTE2MzM1MlowbzENMAsGA1UEAwwEYSwgYjEO +MAwGA1UEBwwFI2xlYWQxEjAQBgNVBAgMCSAgc3BhY2UgIDEmMCQGA1UECgwdUXVv +dGUgcSBiYWNrXHNsYXNoOyBzZW1pIDxsdD4xEjAQBgNVBAsMCUNhZsOpIOKCrDB2 +MBAGByqGSM49AgEGBSuBBAAiA2IABMLLzwDsSeezCzScnC9xTIFl35dFAX+1Uagk +0y4Y72rOOgVFA13SMIbMfv2InIyg2vXOdqn0y7RqiaaqqnAYZB2RHHL0SD7t2MVL +K3BQajy7Oi6IeDDRGK1jM4D6nk/1AKNVMFMwMgYDVR0RBCswKYILYSxiLmV4YW1w +bGWCDXBsYWluLmV4YW1wbGWBBXhAeS56hwQKAAABMB0GA1UdDgQWBBSR4U6nIpTt +5rLoIZZxfxSyx9Uc9zAKBggqhkjOPQQDAgNoADBlAjEAlznFL0Nrt1jjZrT3/GN0 +yN/8gxO3SawyqoWrfP3uk1GScTh0PlrTEOrpSyuq3wl5AjBRntVI1ZoUM46pCwIw +0Vl3c3lns7OljcQFiR7o8yRhEW9ejdiR2W6PLBrjCly6U3I= +-----END CERTIFICATE----- +`; + +const certs: [string, X509Certificate][] = [ + ["leaf", new X509Certificate(LEAF)], + ["root", new X509Certificate(Buffer.from(ROOT))], + ["access", new X509Certificate(ACCESS)], + ["multi", new X509Certificate(MULTI)], + ["ed", new X509Certificate(ED)], + ["odd", new X509Certificate(ODD)], +]; + +for (const [name, cert] of certs) { + const legacy = cert.toLegacyObject(); + const shown = JSON.stringify(legacy, (key: string, value: unknown) => { + if (value === undefined) return ""; + if (key === "raw" || key === "pubkey") return `<${(value as { data: number[] }).data.length} bytes>`; + return value; + }); + console.log(name, shown); + const absent = Object.keys(legacy).filter((key) => (legacy as unknown as Record)[key] === undefined); + console.log(name, "absent", absent.join(","), Buffer.isBuffer(legacy.raw), legacy.raw.equals(cert.raw), legacy.raw === cert.raw); + const spki: Buffer = cert.publicKey.export({ type: "spki", format: "der" }); + if (legacy.pubkey !== undefined) console.log(name, "pubkey", legacy.pubkey.equals(spki)); +} + +console.log(certs[0]![1].toLegacyObject()); +console.log(certs[3]![1].toLegacyObject()); + +for (const [name, cert] of certs) { + const pem = cert.toString(); + console.log(name, pem.length, pem === String(cert), `${cert}` === pem, new X509Certificate(pem).fingerprint256 === cert.fingerprint256); +} +console.log(certs[3]![1].toString()); diff --git a/tests/corpus/7093-x509-methods-js.mjs b/tests/corpus/7093-x509-methods-js.mjs new file mode 100644 index 000000000..a8144d071 --- /dev/null +++ b/tests/corpus/7093-x509-methods-js.mjs @@ -0,0 +1,158 @@ +// X509Certificate's methods from JavaScript match Node: publicKey, +// verify, checkIssued, checkPrivateKey, checkHost, checkEmail, checkIP, +// toLegacyObject, toString and toJSON, including the TypeErrors for +// arguments of the wrong type. +import { X509Certificate, createPrivateKey, createPublicKey, createSecretKey } from "node:crypto"; + +// An RSA root, the EC leaf it signed, and a self-signed RSA certificate. +const ROOT = `-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIDChHOMA0GCSqGSIb3DQEBCwUAMD4xCzAJBgNVBAYTAlVT +MRQwEgYDVQQKDAtFeGFtcGxlIE9yZzEZMBcGA1UEAwwQRXhhbXBsZSBSU0EgUm9v +dDAgFw0yNjEwMjIwOTQ2NDFaGA8yMTI2MDkyODA5NDY0MVowPjELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwsfidupnXQcn8PUF +XaCKRYw8XKI1GUn29U5/IUGjAnJVaRmcB6BPTmblbKt4y7gLvU4h1Hxuypx8FZSZ +30eZOS1ixyrymDthG5zUHqzXk5zAGsNvZeXjN3YxTC9vX5CpQ9Uz2i+bLOi3PJsE +qyKVZ+GGT9fpIaALG11YrWmXTUko/a0AKGot0w8O/RaMpXQpkbeyTBF6f4pHNMqx +QGy+KlpC/+yIJ9psT6qyU33HF3Wa+DOy+0txAFEQNw9m1j7adOLnU0zBpkM/bnP2 +fSwxMkDB2Di2P5H6PkdqLT4zBf/BLdg7WqpxxSqqgVmaSXhEqUQ6xjf51yLR3wy/ +xBWhMQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQUCBpZW95FsDtUqpwfOMyoE/OLX8swDQYJKoZIhvcNAQELBQADggEB +AChUtpNi8dPPxA4sf4EpZhEcYBN53X3WUcG3KJGtSf8JbPKru9lK9O+sZtJADbph +WoMPdWh8g+fEMk5D6l8Y5pDk/OR++oWNY7XCLFNyigmUGn+4SopJynBQiDk2dYPV +b6swDPPiV85TuAHVn79l766rtFdHdYwRe5HvfEkiXax7Egonlx7NR73xxm/XgvHQ +jhKbTZN9k/DEjkQuo5rt9ldVupUMM0iMhDMKX5oWEx9nvRwHPbjESS+Shy/FA4nA +2WHWO++4IU+imO40an1B/8SmFF+X9hj3UE6qaZLynVZRUiouBZibwZCcdCpibFM3 +kmB2s+NZXRvzhCjIxO97Ib4= +-----END CERTIFICATE----- +`; +const LEAF = `-----BEGIN CERTIFICATE----- +MIIDqjCCApKgAwIBAgICCwswDQYJKoZIhvcNAQELBQAwPjELMAkGA1UEBhMCVVMx +FDASBgNVBAoMC0V4YW1wbGUgT3JnMRkwFwYDVQQDDBBFeGFtcGxlIFJTQSBSb290 +MCAXDTI2MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjBfMQswCQYDVQQGEwJV +UzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNv +bTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5jb20wWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARidSirNIIrw+YHeS112z3N+1n0+uA9HjzkQ5u3QfZsRGCe +ivCEMQSs5+cUE/nv5WWda2kQWavxPGluHFtYmS10o4IBWDCCAVQwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHsG +A1UdEQR0MHKCDSouZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tghV4bi0tYmNoZXIt +a3ZhLmV4YW1wbGWBD29wc0BleGFtcGxlLmNvbYcECgAAAYcQIAENuAAAAAAAAAAA +AAAAAYYUaHR0cHM6Ly9leGFtcGxlLmNvbS8wXgYIKwYBBQUHAQEEUjBQMCQGCCsG +AQUFBzABhhhodHRwOi8vb2NzcC5leGFtcGxlLmNvbS8wKAYIKwYBBQUHMAKGHGh0 +dHA6Ly9jYS5leGFtcGxlLmNvbS9jYS5jcnQwHQYDVR0OBBYEFHyUnRTDwZxUPVwZ +RKmse3fjoeMMMB8GA1UdIwQYMBaAFAgaWVveRbA7VKqcHzjMqBPzi1/LMA0GCSqG +SIb3DQEBCwUAA4IBAQAfMTcpidoH47PXOQVxRttfrG7K9BcntyXP4+5hUAoSfMr6 +Rxy7X/9ba7R0yCGlNsUCusObaBpbdvwX8v9TiTYKVL+C9rEVGrTHnh6kbgZaaCQc +L3oWbfRrYnEBYuSlFuadjVvX42aqzHV28E4ITgywCxwubu2hAbacbcUHpnRSqLDq +lV+VZSQA/6cPNct7PR6AkJU80D+5wVOk7RVi4rsfbw1wFDeksCHu+0EThs9zjzL+ +S7HTCjB6AOHpbu1Sx+0d5YEKSE7EPF+dSFATNbhjTHzYQySnHK+Lm8nON8wmY1rC +7opOhvUlid1jA1d2BKO2ZxlxLir4pyIehTC1ddG2 +-----END CERTIFICATE----- +`; +const PLAIN = `-----BEGIN CERTIFICATE----- +MIIC8jCCAdoCAQcwDQYJKoZIhvcNAQELBQAwPjEaMBgGA1UEAwwRcGxhaW4uZXhh +bXBsZS5vcmcxIDAeBgkqhkiG9w0BCQEWEXBsYWluQGV4YW1wbGUub3JnMCAXDTI2 +MTAyMjA5NDY0MVoYDzIxMjYwOTI4MDk0NjQxWjA+MRowGAYDVQQDDBFwbGFpbi5l +eGFtcGxlLm9yZzEgMB4GCSqGSIb3DQEJARYRcGxhaW5AZXhhbXBsZS5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq01NcNAy9Ng04mVuAFvmZUvsm +rY7aOaMgtODmptulgZ2qw25E09RgGYCn1S+4IYDu9hH9X4s+28Ag7Khmj4nmeoXN +of5095wche8DR2AAUElqyr/Nmgm2kTLdIJX4wl0eWXHIlmSPdOUNovA6JjGKH99b +8W7a2TqY/TsSadpPSvvN2esr7Jr4KgW7MXPUOnwNbcBOVcQP9rtFrduAWz01q0HM +02LboclnUnk/e58BXAj8vgdVojljDcc0bhbdz/Hh1/5gaaNILjJNOd4AgwZaCT02 +KojGz4Nb6MSgFmON2iMgAZjY1fXXkJ7wsfVPytjW73e7uV/RFuqSHQZenxupAgMB +AAEwDQYJKoZIhvcNAQELBQADggEBAJX1DGuiSLoCKBDI3oCgAu7X86G01ovEgZJr +8JrmGHW5q7ujjo53t46h8XPm3LJ8UV9/MZ+Yry43xB2txXYv3b3g/i+7JdTWLgUD +INA5VWvVkEZfm0lBEpU1Agpy4lV5uUCKZTgP/3MY5hnJ0YhQIs4BOjbwueMRFshL +yyHVb73MMbpWaZ7+T368seafa+ekeuY6gQly/HacuszjoYd6KKV3n8eDewenClCF +HoOF1Y4I+CvmR+a6B3iBczJ+a4tceFIIWsZUIeqzvGHpM7G9YwlmbvGprfLgR8Nv +igUib/D9tU3hdL6VWN5nMjdXV/8Le4nDnBjVBjYDkVbsnmT91AQ= +-----END CERTIFICATE----- +`; +const ROOT_KEY = `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCx+J26mddByfw +9QVdoIpFjDxcojUZSfb1Tn8hQaMCclVpGZwHoE9OZuVsq3jLuAu9TiHUfG7KnHwV +lJnfR5k5LWLHKvKYO2EbnNQerNeTnMAaw29l5eM3djFML29fkKlD1TPaL5ss6Lc8 +mwSrIpVn4YZP1+khoAsbXVitaZdNSSj9rQAoai3TDw79FoyldCmRt7JMEXp/ikc0 +yrFAbL4qWkL/7Ign2mxPqrJTfccXdZr4M7L7S3EAURA3D2bWPtp04udTTMGmQz9u +c/Z9LDEyQMHYOLY/kfo+R2otPjMF/8Et2DtaqnHFKqqBWZpJeESpRDrGN/nXItHf +DL/EFaExAgMBAAECggEADsvlabgryvbYs2bBlhe0eU0KZtURqI6kWoUwhiO40N5D +m7zMzA2DCMcrRYKPJhagmaxeSIh9WBnkGTWVLM+BFcVbXBrom+Ti3xieSohBxh5h +v4a6Jyj8BkINw6ZU1RMlsJf5HOmB5br19H4oMCHr7JzGubkCpkHEpjsEmgPL8Rxs +LYb9palDyTD1z5xVAQDAkvnCYQMX2DsiH8RuO4Ih0OoWMIsAGasS1dTQCkOzN9wF +cMg+hmJvQLN5xLYI+VPy/iZn+bmerCx8/NQBrbo+zibmnv7cDK1U9emVD70/Nd1d +B7ZEm+a4gReaxWVQGEkFMvs29u/TDWXYRnw0PGjUYwKBgQDrmKEx+ZiZwNNk/S3l +s8FgVrLFDr/+t5X33jaD5nVuSN6/AoR3qfh9piIyCrdNKdHicAcCnWP6skUFYLq3 +/tyMBATX3j+f3PHW3i2ZUAK26hgfp66F/3IyowePZbBDlGSmGsffVlc8on5fCk1Q +k3G0znktbrMCNxeeY9GSi1CHewKBgQDTpled6pZepZ54OGnKVJFo6DbQC0l4HQ6y +Rwv9BMtAz+AxWXda1oYK5bbJZrCmHyVP85VxkzRHOMxicv7M2kR9y2zZsJD7INqr +Nvt+lo2yGtIGv7kOdwWzvs4Pn7aCWSApzYBjuKrpP1xDzaMHhusAQtkk77YGCZCu +cCAQ/FjEQwKBgBq/1KWI8lOauiXAPULUWwNmNJv9Jb10+LkQ6N2m//RKn8fOMF4p +1UXm5AEMiG1hbywnjhQ07JIdRYrrxad4b3ASUL51xIGRI4BC76h1lilQgPavIjii +nUO0kPouEIpLwfW+Gq137yW6SWDfGZIpe4KM4uNOq1J4EN1x9eyOltEbAoGAP4bj +L5QVFE2T1jPglQlzJ9DjeuiYYcQfACXRFF3XABolCcr3xz0yuJygk60OAp7AnVHY +nmF3xJToy/gGQMiW3Ect4d1cdvQyjkePjzxzRW1Fcx8i1ZOmE4CG6yueJVvJCqMT +EVuBeUc6U2Oqtnyplsd509tbgE5oreMbzN1ha28CgYAHpG10ZQRDeqWFIo/ttk6Q +Q1Gf53eqZFCgh8GO1hBJ7+Fp+Ec5S9UPMBDjZb6GET7ifF8Zit/lR4gN2PuELwJA +YaXTIehCugH7Q3oTjNcQtls6tW+/+jEGjbjSHyZvwk8EFHc0J7PWRszB36s+qM+O +YPorNipExSeWti1506ljLQ== +-----END PRIVATE KEY----- +`; +const LEAF_KEY = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINqbifA6kN/DSN0cMD2s7fXiNtPXZqipFrbCs/3mCGbtoAoGCCqGSM49 +AwEHoUQDQgAEYnUoqzSCK8PmB3ktdds9zftZ9PrgPR485EObt0H2bERgnorwhDEE +rOfnFBP57+VlnWtpEFmr8TxpbhxbWJktdA== +-----END EC PRIVATE KEY----- +`; + +const ca = new X509Certificate(ROOT); +const leaf = new X509Certificate(Buffer.from(LEAF)); +const plain = new X509Certificate(PLAIN); +const caKey = createPrivateKey(ROOT_KEY); +const leafKey = createPrivateKey(LEAF_KEY); +function t(label, f) { + try { + const v = f(); + console.log(label, typeof v === "string" ? JSON.stringify(v) : v); + } catch (e) { + console.log(label, "THROW", e.name, e.code, e.message); + } +} +t("pub", () => [leaf.publicKey.asymmetricKeyType, ca.publicKey.asymmetricKeyType, ca.publicKey.asymmetricKeyDetails.modulusLength]); +t("pub same", () => leaf.publicKey === leaf.publicKey); +t("pub equals", () => leaf.publicKey.equals(createPublicKey(leafKey))); +t("verify ca", () => leaf.verify(ca.publicKey)); +t("verify self", () => leaf.verify(leaf.publicKey)); +t("verify ca self", () => ca.verify(ca.publicKey)); +t("verify private", () => leaf.verify(caKey)); +t("verify string", () => leaf.verify("x")); +t("verify secret", () => leaf.verify(createSecretKey(Buffer.alloc(16)))); +t("issued", () => [leaf.checkIssued(ca), ca.checkIssued(leaf), ca.checkIssued(ca), plain.checkIssued(plain), leaf.checkIssued(plain)]); +t("issued bad", () => leaf.checkIssued({})); +t("pk", () => [leaf.checkPrivateKey(leafKey), leaf.checkPrivateKey(caKey), ca.checkPrivateKey(caKey)]); +t("pk public", () => leaf.checkPrivateKey(leaf.publicKey)); +t("pk string", () => leaf.checkPrivateKey("x")); +for (const h of ["www.example.com", "foo.example.com", "a.b.example.com", "example.com", "EXAMPLE.COM", "xn--bcher-kva.example", "other.org", "10.0.0.1", "", "foo.example.com."]) t("host " + h, () => leaf.checkHost(h)); +t("host plain", () => plain.checkHost("plain.example.org")); +t("host plain never", () => plain.checkHost("plain.example.org", { subject: "never" })); +t("host leaf always", () => leaf.checkHost("www.example.com", { subject: "always" })); +t("host nowild", () => leaf.checkHost("foo.example.com", { wildcards: false })); +t("host partial", () => leaf.checkHost("foo.example.com", { partialWildcards: false })); +t("host multi", () => leaf.checkHost("a.b.example.com", { multiLabelWildcards: true })); +t("host single", () => leaf.checkHost("example.com", { singleLabelSubdomains: true })); +t("host num", () => leaf.checkHost(1)); +t("host opts bad", () => leaf.checkHost("x", { subject: "sometimes" })); +t("host opts type", () => leaf.checkHost("x", 5)); +t("host wild type", () => leaf.checkHost("x", { wildcards: "yes" })); +for (const e of ["ops@example.com", "OPS@EXAMPLE.COM", "admin@example.com", "nobody@example.com"]) t("email " + e, () => leaf.checkEmail(e)); +t("email plain", () => plain.checkEmail("plain@example.org")); +t("email plain never", () => plain.checkEmail("plain@example.org", { subject: "never" })); +t("email num", () => leaf.checkEmail(1)); +for (const ip of ["10.0.0.1", "2001:db8::1", "2001:DB8:0:0:0:0:0:1", "10.0.0.2", "::ffff:10.0.0.1", "bogus", ""]) t("ip " + ip, () => leaf.checkIP(ip)); +t("ip num", () => leaf.checkIP(1)); +t("ip opts", () => leaf.checkIP("10.0.0.1", {})); +for (const cert of [leaf, ca, plain]) { + const legacy = cert.toLegacyObject(); + console.log(Object.keys(legacy).join(","), legacy.subject.CN, legacy.bits, legacy.raw.length); +} +t("pem", () => [leaf.toString() === leaf.toJSON(), String(ca) === `${ca}`, ca.toString().split("\n").length]); +t("pem round trip", () => new X509Certificate(plain.toJSON()).fingerprint === plain.fingerprint); diff --git a/tests/corpus/7095-tls-peer-certificates.ts b/tests/corpus/7095-tls-peer-certificates.ts new file mode 100644 index 000000000..eee3c9ac8 --- /dev/null +++ b/tests/corpus/7095-tls-peer-certificates.ts @@ -0,0 +1,188 @@ +// tlsSocket.getPeerCertificate([detailed]), getCertificate(), +// getPeerX509Certificate() and getX509Certificate() match Node: the +// legacy certificate objects, {} before a handshake or without a +// certificate and null once the socket is destroyed; the detailed +// issuerCertificate chain from the certificates the peer sent and then +// the trust anchors, in any order, ending at a self-issued root that is its +// own issuer; and the certificates served by SNI answers, secureContext +// wraps and https servers. +import { X509Certificate } from "node:crypto"; +import { readFileSync } from "node:fs"; +import * as https from "node:https"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const d = "tests/fixtures/server/certs"; +const read = (f: string): Buffer => readFileSync(`${d}/${f}`); +const ca = read("ca.pem"), ca2 = read("ca2.pem"), cert = read("localhost.pem"), key = read("localhost-key.pem"); +const self = read("selfsigned.pem"), selfKey = read("selfsigned-key.pem"), alt = read("alt.pem"), altKey = read("alt-key.pem"); + +function describe(label: string, o: tls.PeerCertificate | tls.DetailedPeerCertificate | object | null): string { + if (o === null) return `${label} null`; + const keys = Object.keys(o); + if (keys.length === 0) return `${label} {}`; + const p = o as tls.DetailedPeerCertificate; + const issuer = p.issuerCertificate; + const link = issuer ? ` issuerCert:${issuer.subject.CN} self:${issuer === issuer.issuerCertificate} same:${issuer === p}` : ""; + return `${label} ${keys.join(",")} ${p.subject.CN} ${p.issuer.CN}${link}`; +} +function show(label: string, o: tls.PeerCertificate | tls.DetailedPeerCertificate | object | null): void { + console.log(describe(label, o)); +} +// A server's lines print once its client has the reply, so the output +// order does not depend on which side's callback runs first. +const serverLines: string[] = []; +function flushServer(): void { + for (const line of serverLines.splice(0)) console.log(line); +} +await new Promise((done) => { +const server = tls.createServer({ cert, key }, (s) => { + serverLines.push(describe("server peer", s.getPeerCertificate())); + serverLines.push(describe("server peer detailed", s.getPeerCertificate(true))); + serverLines.push(`server peerX509 ${s.getPeerX509Certificate()?.subject}`); + serverLines.push(describe("server own", s.getCertificate())); + serverLines.push(`server ownX509 ${s.getX509Certificate()?.subject}`); + s.end("hi"); +}); +server.listen(0, () => { + const c = tls.connect({ port: (server.address() as AddressInfo).port, ca, servername: "localhost" }, () => { + show("client peer", c.getPeerCertificate()); + const det = c.getPeerCertificate(true); + show("client peer detailed", det); + show("client issuer", det.issuerCertificate); + const x = c.getPeerX509Certificate(); + if (x) console.log("client peerX509", x.subject, x.issuer, x === c.getPeerX509Certificate(), x.checkIssued(new X509Certificate(ca)), x.checkHost("localhost")); + show("client own", c.getCertificate()); + console.log("client ownX509", c.getX509Certificate()?.subject); + console.log("peer raw eq", c.getPeerCertificate().raw.equals(x!.raw), c.getPeerCertificate() === c.getPeerCertificate()); + }); + c.on("data", () => {}); + c.on("end", () => { + flushServer(); + show("after end peer", c.getPeerCertificate()); + c.destroy(); + }); + c.on("close", () => { + show("after close peer", c.getPeerCertificate()); + console.log("after close X509", c.getPeerX509Certificate()?.subject, c.getCertificate()); + server.close(() => done()); + }); + show("before handshake", c.getPeerCertificate()); + console.log("before X509", c.getPeerX509Certificate()?.subject, c.getCertificate(), c.getX509Certificate()?.subject); +}); +}); + +function chain(o: tls.DetailedPeerCertificate | null): string { + if (o === null) return "null"; + if (Object.keys(o).length === 0) return "{}"; + const names: string[] = []; + let at: tls.DetailedPeerCertificate | undefined = o; + for (let i = 0; at !== undefined && i < 6; i++) { + names.push(at.subject.CN as string); + const next: tls.DetailedPeerCertificate | undefined = at.issuerCertificate; + if (next === at) { + names.push("(self)"); + break; + } + at = next; + } + return names.join(" -> "); +} +async function run(label: string, served: Buffer, servedKey: Buffer, trust: Buffer | null): Promise { + await new Promise((done) => { + const server = tls.createServer({ cert: served, key: servedKey }, (s) => s.end()); + server.listen(0, () => { + const port = (server.address() as AddressInfo).port; + const socket = + trust === null + ? tls.connect({ port, servername: "localhost", rejectUnauthorized: false }) + : tls.connect({ port, servername: "localhost", ca: trust, rejectUnauthorized: false }); + socket.on("secureConnect", () => { + console.log(label, socket.authorized, chain(socket.getPeerCertificate(true))); + console.log(label, "abbreviated", "issuerCertificate" in socket.getPeerCertificate(), "issuerCertificate" in socket.getPeerCertificate(false)); + socket.destroy(); + console.log(label, "destroyed", socket.getPeerCertificate(), socket.getPeerX509Certificate()?.subject, socket.getCertificate()); + server.close(() => done()); + }); + socket.on("error", (e) => console.log(label, "error", e.message)); + }); + }); +} +await run("leaf", cert, key, ca); +await run("sent chain", Buffer.concat([cert, ca]), key, ca); +await run("sent chain, default store", Buffer.concat([cert, ca]), key, null); +await run("out of order", Buffer.concat([cert, ca2, ca]), key, ca); +await run("self-signed", self, selfKey, null); +await run("wrong ca", cert, key, ca2); +await run("leaf, default store", cert, key, null); + +// SNI and secureContext: the certificate the server socket serves. +await new Promise((done) => { + const altCtx = tls.createSecureContext({ cert: alt, key: altKey }); + const server = tls.createServer({ cert, key, SNICallback: (name: string, cb: (err: Error | null, ctx?: tls.SecureContext) => void) => cb(null, name === "alt.localhost" ? altCtx : undefined) }, (s) => { + serverLines.push(`sni own ${(s.getCertificate() as tls.PeerCertificate).subject.CN} ${s.getX509Certificate()?.subject}`); + s.end("x"); + }); + server.listen(0, () => { + const port = (server.address() as AddressInfo).port; + const a = tls.connect({ port, ca, servername: "alt.localhost" }, () => { + console.log("sni peer", a.getPeerCertificate().subject.CN, a.getPeerX509Certificate()?.subjectAltName); + }); + a.resume(); + a.on("end", () => { + flushServer(); + a.destroy(); + const b = tls.connect({ port, ca, servername: "localhost" }, () => { + console.log("sni default peer", b.getPeerCertificate().subject.CN); + }); + b.resume(); + b.on("end", () => { + flushServer(); + b.destroy(); + server.close(() => done()); + }); + }); + }); +}); + +// new tls.TLSSocket over a net connection, with a secureContext. +await new Promise((done) => { + const context = tls.createSecureContext({ cert: alt, key: altKey }); + const server = net.createServer((raw) => { + const secure = new tls.TLSSocket(raw, { isServer: true, secureContext: context }); + console.log("wrap before", (secure.getCertificate() as tls.PeerCertificate).subject?.CN, secure.getX509Certificate()?.subject, JSON.stringify(secure.getPeerCertificate())); + secure.on("secure", () => { + serverLines.push(`wrap secure ${(secure.getCertificate() as tls.PeerCertificate).subject.CN} ${secure.getPeerX509Certificate()?.subject}`); + secure.end("x"); + }); + }); + server.listen(0, () => { + const c = tls.connect({ port: (server.address() as AddressInfo).port, ca, servername: "alt.localhost" }, () => { + console.log("wrap client", chain(c.getPeerCertificate(true))); + }); + c.resume(); + c.on("end", () => { + flushServer(); + c.destroy(); + server.close(() => done()); + }); + }); +}); + +// https: both sides of a request. +await new Promise((done) => { + const server = https.createServer({ cert, key }, (req, res) => { + const s = req.socket as tls.TLSSocket; + console.log("https server", (s.getCertificate() as tls.PeerCertificate).subject.CN, JSON.stringify(s.getPeerCertificate())); + res.end("ok"); + }); + server.listen(0, () => { + https.get({ port: (server.address() as AddressInfo).port, ca, host: "localhost", path: "/" }, (res) => { + const s = res.socket as tls.TLSSocket; + console.log("https client", chain(s.getPeerCertificate(true)), s.getPeerX509Certificate()?.issuer); + res.resume(); + res.on("end", () => server.close(() => done())); + }); + }); +}); diff --git a/tests/corpus/7096-tls-peer-certificates.mjs b/tests/corpus/7096-tls-peer-certificates.mjs new file mode 100644 index 000000000..ef1bfeb2a --- /dev/null +++ b/tests/corpus/7096-tls-peer-certificates.mjs @@ -0,0 +1,35 @@ +// tlsSocket.getPeerCertificate from JavaScript matches Node: only a +// detailed argument that is true itself adds the issuerCertificate chain, +// and the legacy object is a plain object with a Buffer raw and arrays for +// extended key usages. +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const d = "tests/fixtures/server/certs"; +const ca = readFileSync(`${d}/ca.pem`); +const cert = readFileSync(`${d}/localhost.pem`); +const key = readFileSync(`${d}/localhost-key.pem`); + +const server = tls.createServer({ cert, key }, (s) => s.end("x")); +server.listen(0, () => { + const c = tls.connect({ port: server.address().port, ca, servername: "localhost" }, () => { + for (const arg of [true, false, 1, "yes", null, undefined, {}]) { + const peer = c.getPeerCertificate(arg); + console.log(String(arg), "issuerCertificate" in peer, peer.issuer.CN); + } + const legacy = c.getPeerCertificate(); + console.log(Object.keys(legacy).length, Buffer.isBuffer(legacy.raw), legacy.ext_key_usage, JSON.stringify(legacy.subject)); + const detailed = c.getPeerCertificate(true); + const root = detailed.issuerCertificate; + console.log(root.subject.CN, root.issuerCertificate === root, root.ca, Buffer.isBuffer(root.raw)); + const x509 = c.getPeerX509Certificate(); + if (x509) console.log(x509.subject, x509.checkHost("localhost"), x509.checkIssued(x509), legacy.raw.equals(x509.raw)); + console.log(c.getCertificate(), c.getX509Certificate() === undefined); + }); + c.resume(); + c.on("end", () => { + c.destroy(); + console.log(c.getPeerCertificate(), c.getPeerX509Certificate() === undefined, c.getCertificate()); + server.close(); + }); +}); diff --git a/tests/corpus/7097-tls-client-certificates.ts b/tests/corpus/7097-tls-client-certificates.ts new file mode 100644 index 000000000..b45560084 --- /dev/null +++ b/tests/corpus/7097-tls-client-certificates.ts @@ -0,0 +1,84 @@ +// Client certificates match Node: tls.createServer's requestCert, +// rejectUnauthorized and ca against clients that send no certificate, a +// valid one, one without the clientAuth purpose and a self-signed one +// (socket.authorized and authorizationError on the server, the peer +// certificate, the client's own certificate), the client's chain completed +// from its ca, and a server that requires a certificate rejecting a client +// without one in TLS 1.3 and TLS 1.2 (the client's events and codes). +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), ca2 = r("ca2.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const client = r("client.pem"), clientKey = r("client-key.pem"); +const self = r("selfsigned.pem"), selfKey = r("selfsigned-key.pem"); + +type Handler = (s: tls.TLSSocket) => void; +const servers: [string, (h: Handler) => tls.Server][] = [ + ["request", (h) => tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca }, h)], + ["reject", (h) => tls.createServer({ cert, key, requestCert: true, ca }, h)], + ["request-noca", (h) => tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false }, h)], + ["request-ca2", (h) => tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca: ca2 }, h)], + // an empty Buffer is an empty trust store; an empty string is no ca + ["request-empty", (h) => tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca: Buffer.alloc(0) }, h)], + ["request-emptystr", (h) => tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca: "" }, h)], + ["plain", (h) => tls.createServer({ cert, key }, h)], +]; +const clients: [string, Buffer | null, Buffer | null][] = [ + ["none", null, null], + ["valid", client, clientKey], + ["serverEku", cert, key], + ["self", self, selfKey], +]; +async function run(label: string, make: (h: Handler) => tls.Server, name: string, clientCert: Buffer | null, clientKeyPem: Buffer | null): Promise { + await new Promise((done) => { + const events: string[] = []; + const server = make((s) => { + events.push(`server secureConnection authorized=${s.authorized} error=${s.authorizationError} peer=${(s.getPeerCertificate() as tls.PeerCertificate).subject?.CN} x509=${s.getPeerX509Certificate()?.subject}`); + s.end("ok"); + }); + server.listen(0, () => { + const port = (server.address() as AddressInfo).port; + const c = + clientCert === null || clientKeyPem === null + ? tls.connect({ port, ca, servername: "localhost" }) + : tls.connect({ port, ca, servername: "localhost", cert: clientCert, key: clientKeyPem }); + c.on("secureConnect", () => events.push(`client secureConnect own=${(c.getCertificate() as tls.PeerCertificate | null)?.subject?.CN}`)); + c.on("data", (d: Buffer) => events.push(`client data ${d}`)); + c.on("end", () => events.push("client end")); + c.on("error", (e: NodeJS.ErrnoException) => events.push(`client error ${e.code}`)); + c.on("close", () => { + setTimeout(() => { + console.log(label, name, "|", events.join(" ; ")); + server.close(() => done()); + }, 50); + }); + }); + }); +} +for (const [label, make] of servers) { + for (const [name, c, k] of clients) await run(label, make, name, c, k); +} + +// A server that requires a certificate, per protocol version: TLS 1.3 +// rejects after the client's handshake, TLS 1.2 during it. +for (const maxVersion of ["TLSv1.3", "TLSv1.2"] as const) { + await new Promise((done) => { + const server = tls.createServer({ cert, key, requestCert: true, ca, maxVersion }, (s) => s.end("ok")); + server.listen(0, () => { + const events: string[] = []; + const c = tls.connect({ port: (server.address() as AddressInfo).port, ca, servername: "localhost" }); + c.on("secureConnect", () => events.push("secureConnect")); + c.on("data", () => events.push("data")); + c.on("end", () => events.push("end")); + c.on("error", (e: NodeJS.ErrnoException) => events.push(`error ${e.code}`)); + c.on("close", (hadError: boolean) => { + events.push(`close ${hadError}`); + console.log(maxVersion, events.join(" ; ")); + server.close(() => done()); + }); + }); + }); +} diff --git a/tests/corpus/7098-tls-client-certificates-paths.ts b/tests/corpus/7098-tls-client-certificates-paths.ts new file mode 100644 index 000000000..b71e72b6f --- /dev/null +++ b/tests/corpus/7098-tls-client-certificates-paths.ts @@ -0,0 +1,161 @@ +// Client certificates on the other paths match Node: a server's (and a +// SecureContext's) ca completes the certificate chain it sends, as +// OpenSSL's automatic chain does; new tls.TLSSocket with requestCert and +// rejectUnauthorized, where Node reports no verdict; and https on both +// sides, including an agent that pools sockets per client certificate. +import { readFileSync } from "node:fs"; +import * as http from "node:http"; +import * as https from "node:https"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), ca2 = r("ca2.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const client = r("client.pem"), clientKey = r("client-key.pem"); + +// A server's ca completes the chain it sends. +for (const [label, make] of [ + ["no ca", () => tls.createServer({ cert, key }, (s) => s.end())], + ["ca", () => tls.createServer({ cert, key, ca }, (s) => s.end())], + ["other ca", () => tls.createServer({ cert, key, ca: ca2 }, (s) => s.end())], + // An SNI answer's own ca does not complete its chain. + ["sni ctx ca, server no ca", () => { + const context = tls.createSecureContext({ cert, key, ca }); + return tls.createServer({ cert, key, SNICallback: (name: string, cb: (err: Error | null, ctx?: tls.SecureContext) => void) => cb(null, context) }, (s) => s.end()); + }], +] as [string, () => tls.Server][]) { + await new Promise((done) => { + const server = make(); + server.listen(0, () => { + const c = tls.connect({ port: (server.address() as AddressInfo).port, ca: ca2, servername: "localhost", rejectUnauthorized: false }, () => { + const names: string[] = []; + let p: tls.DetailedPeerCertificate | undefined = c.getPeerCertificate(true); + for (let i = 0; p !== undefined && i < 4; i++) { + names.push(p.subject.CN as string); + if (p.issuerCertificate === p) break; + p = p.issuerCertificate; + } + console.log(label, c.authorizationError, names.join(" -> ")); + c.destroy(); + server.close(() => done()); + }); + }); + }); +} + +// A TLSSocket's secureContext sends the chain its ca completes. +for (const [label, context] of [ + ["wrap ctx ca", tls.createSecureContext({ cert, key, ca })], + ["wrap ctx", tls.createSecureContext({ cert, key })], +] as [string, tls.SecureContext][]) { + await new Promise((done) => { + const server = net.createServer((raw) => { + const s = new tls.TLSSocket(raw, { isServer: true, secureContext: context }); + s.on("secure", () => s.end()); + }); + server.listen(0, () => { + const c = tls.connect({ port: (server.address() as AddressInfo).port, ca: ca2, servername: "localhost", rejectUnauthorized: false }, () => { + const names: string[] = []; + let p: tls.DetailedPeerCertificate | undefined = c.getPeerCertificate(true); + for (let i = 0; p !== undefined && i < 4; i++) { + names.push(p.subject.CN as string); + if (p.issuerCertificate === p) break; + p = p.issuerCertificate; + } + console.log(label, c.authorizationError, names.join(" -> ")); + c.destroy(); + server.close(() => done()); + }); + }); + }); +} + +// new tls.TLSSocket with client certificates: Node reports no verdict there. +for (const [label, wrap] of [ + ["wrap request", (raw: net.Socket) => new tls.TLSSocket(raw, { isServer: true, cert, key, requestCert: true, ca })], + ["wrap reject", (raw: net.Socket) => new tls.TLSSocket(raw, { isServer: true, cert, key, requestCert: true, rejectUnauthorized: true, ca })], +] as [string, (raw: net.Socket) => tls.TLSSocket][]) { + for (const withCert of [false, true]) { + await new Promise((done) => { + const events: string[] = []; + const server = net.createServer((raw) => { + const s = wrap(raw); + s.on("secure", () => { + events.push(`wrap secure authorized=${s.authorized} error=${s.authorizationError} peer=${(s.getPeerCertificate() as tls.PeerCertificate).subject?.CN}`); + s.end("ok"); + }); + s.on("error", (e: NodeJS.ErrnoException) => events.push(`wrap error ${e.code}`)); + }); + server.listen(0, () => { + const port = (server.address() as AddressInfo).port; + const c = withCert + ? tls.connect({ port, ca, servername: "localhost", cert: client, key: clientKey }) + : tls.connect({ port, ca, servername: "localhost" }); + c.on("secureConnect", () => events.push("client secureConnect")); + c.on("data", (d: Buffer) => events.push(`client data ${d}`)); + c.on("error", (e: NodeJS.ErrnoException) => events.push(`client error ${e.code}`)); + c.on("close", () => { + setTimeout(() => { + console.log(label, withCert ? "cert" : "none", "|", events.join(" ; ")); + server.close(() => done()); + }, 50); + }); + }); + }); + } +} + +// https with client certificates, on both sides. +await new Promise((done) => { + const server = https.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca }, (req, res) => { + const s = req.socket as tls.TLSSocket; + res.end(`${s.authorized} ${s.authorizationError} ${(s.getPeerCertificate() as tls.PeerCertificate).subject?.CN}`); + }); + server.listen(0, () => { + const port = (server.address() as AddressInfo).port; + https.get({ port, ca, host: "localhost", path: "/", cert: client, key: clientKey }, (res) => { + let body = ""; + res.on("data", (d: Buffer) => (body += d)); + res.on("end", () => { + console.log("https with cert", body); + https.get({ port, ca, host: "localhost", path: "/", agent: false }, (res2) => { + let body2 = ""; + res2.on("data", (d: Buffer) => (body2 += d)); + res2.on("end", () => { + console.log("https without cert", body2); + server.close(() => done()); + }); + }); + }); + }); + }); +}); + +// An agent pools sockets per client certificate. +let connections = 0; +const server = https.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca }, (req, res) => { + const s = req.socket as tls.TLSSocket; + res.end(`${s.authorized} ${(s.getPeerCertificate() as tls.PeerCertificate).subject?.CN}`); +}); +server.on("secureConnection", () => connections++); +function get(port: number, withCert: boolean): Promise { + return new Promise((resolve) => { + const done = (res: http.IncomingMessage) => { + let body = ""; + res.on("data", (d: Buffer) => (body += d)); + res.on("end", () => resolve(body)); + }; + if (withCert) https.get({ port, ca, host: "localhost", path: "/", cert: client, key: clientKey }, done); + else https.get({ port, ca, host: "localhost", path: "/" }, done); + }); +} +server.listen(0, async () => { + // the agent keeps one socket per client certificate + const port = (server.address() as AddressInfo).port; + for (const withCert of [true, true, false, false, true]) console.log(withCert, await get(port, withCert)); + console.log("connections", connections); + server.close(); + https.globalAgent.destroy(); +}); diff --git a/tests/corpus/7099-tls-client-certificates.mjs b/tests/corpus/7099-tls-client-certificates.mjs new file mode 100644 index 000000000..0c9eeed2f --- /dev/null +++ b/tests/corpus/7099-tls-client-certificates.mjs @@ -0,0 +1,34 @@ +// Client certificates from JavaScript match Node: a server that requests +// one reads socket.authorized, authorizationError and the peer +// certificate's chain, and a client sends its cert and key. +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const D = "tests/fixtures/server/certs"; +const ca = readFileSync(`${D}/ca.pem`); +const cert = readFileSync(`${D}/localhost.pem`); +const key = readFileSync(`${D}/localhost-key.pem`); +const clientCert = readFileSync(`${D}/client.pem`); +const clientKey = readFileSync(`${D}/client-key.pem`); + +const lines = []; +const server = tls.createServer({ cert, key, ca, requestCert: true, rejectUnauthorized: false }, (socket) => { + const peer = socket.getPeerCertificate(true); + lines.push(`server ${socket.authorized} ${socket.authorizationError} ${peer.subject?.CN} ${peer.issuerCertificate?.subject.CN} ${peer.ext_key_usage}`); + socket.end("hello"); +}); +server.listen(0, () => { + const port = server.address().port; + const first = tls.connect({ port, ca, servername: "localhost", cert: clientCert, key: clientKey }, () => { + lines.push(`client ${first.authorized} ${first.getCertificate().subject.CN} ${first.getX509Certificate()?.subject}`); + }); + first.resume(); + first.on("end", () => { + const second = tls.connect({ port, ca, servername: "localhost" }, () => lines.push(`anonymous ${second.authorized}`)); + second.resume(); + second.on("end", () => { + for (const line of lines) console.log(line); + server.close(); + }); + }); +}); diff --git a/tests/corpus/7440-tls-server-add-context.ts b/tests/corpus/7440-tls-server-add-context.ts new file mode 100644 index 000000000..3cb29ebb3 --- /dev/null +++ b/tests/corpus/7440-tls-server-add-context.ts @@ -0,0 +1,119 @@ +// tls.Server's addContext and setSecureContext match Node: SNI names +// matched against exact and wildcard patterns (a "*" covers one label, +// case-insensitively, the newest match winning), the default context when +// nothing matches or an SNICallback is set, setSecureContext replacing the +// default context, the argument errors, the chain each context sends +// (completed from the default context's ca), and the store that verifies +// client certificates after each change. +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), ca2 = r("ca2.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const alt = r("alt.pem"), altKey = r("alt-key.pem"), self = r("selfsigned.pem"), selfKey = r("selfsigned-key.pem"); +const client = r("client.pem"), clientKey = r("client-key.pem"); + +const listen = (server: tls.Server): Promise => + new Promise((d) => server.listen(0, () => d((server.address() as AddressInfo).port))); + +function peer(port: number, servername: string): Promise { + return new Promise((res) => { + const c = tls.connect({ port, servername, rejectUnauthorized: false }, () => { + const p = c.getPeerCertificate(); + res(`${p.subject.CN}${p.subject.CN === "localhost" && p.issuer.CN === "localhost" ? "(self)" : ""}`); + c.destroy(); + }); + c.on("error", (e: NodeJS.ErrnoException) => res(`error ${e.code}`)); + }); +} + +function chain(c: tls.TLSSocket): string { + const names: string[] = []; + let p: tls.DetailedPeerCertificate | undefined = c.getPeerCertificate(true); + for (let i = 0; p !== undefined && i < 4; i++) { + names.push(p.subject.CN as string); + if (p.issuerCertificate === p) break; + p = p.issuerCertificate; + } + return names.join(" -> "); +} + +function look(port: number, servername: string): Promise { + return new Promise((res) => { + const c = tls.connect({ port, servername, ca: ca2, rejectUnauthorized: false }, () => { + const line = `${c.authorizationError} ${chain(c)}`; + c.destroy(); + res(line); + }); + }); +} + +function attempt(label: string, run: () => void): void { + try { + run(); + console.log(label, "ok"); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log(label, err.name, err.code, err.message.split("\n")[0]); + } +} + +{ + const server = tls.createServer({ cert, key }); + server.addContext("alt.localhost", { cert: alt, key: altKey }); + server.addContext("*.wild.test", tls.createSecureContext({ cert: self, key: selfKey })); + server.addContext("a.b.wild.test", { cert: alt, key: altKey }); + const port = await listen(server); + const names = ["alt.localhost", "ALT.localhost", "x.wild.test", "a.b.wild.test", "c.b.wild.test", "wild.test", ".wild.test", "other", "alt.localhostx"]; + for (const name of names) console.log("sni", name, await peer(port, name)); + attempt("addContext empty", () => server.addContext("", { cert, key })); + attempt("addContext string ctx", () => server.addContext("x.test", "nope" as unknown as tls.SecureContextOptions)); + server.addContext("alt.localhost", { cert: self, key: selfKey }); + console.log("override", await peer(port, "alt.localhost")); + server.setSecureContext({ cert: alt, key: altKey }); + console.log("setSecureContext default", await peer(port, "nomatch"), await peer(port, "alt.localhost")); + attempt("setSecureContext string", () => server.setSecureContext("x" as unknown as tls.SecureContextOptions)); + server.close(); +} + +{ + const server = tls.createServer({ cert, key, SNICallback: (n: string, cb: (err: Error | null, ctx?: tls.SecureContext) => void) => cb(null, undefined) }); + server.addContext("alt.localhost", { cert: alt, key: altKey }); + console.log("with SNICallback", await peer(await listen(server), "alt.localhost")); + server.close(); +} + +{ + const server = tls.createServer({ cert, key, ca }); + server.addContext("alt.localhost", { cert: alt, key: altKey }); + const port = await listen(server); + console.log("server ca, sni", await look(port, "alt.localhost")); + server.setSecureContext({ cert, key }); + console.log("after set without ca, sni", await look(port, "alt.localhost")); + console.log("after set without ca, default", await look(port, "localhost")); + server.close(); +} + +{ + const lines: string[] = []; + const server = tls.createServer({ cert, key, ca, requestCert: true, rejectUnauthorized: false }, (s) => { + lines.push(`server ${s.authorized} ${s.authorizationError}`); + s.end(); + }); + const port = await listen(server); + const once = (servername: string): Promise => + new Promise((res) => { + const c = tls.connect({ port, servername, ca, cert: client, key: clientKey }); + c.on("data", () => {}); + c.on("close", () => res()); + }); + await once("localhost"); + server.setSecureContext({ cert, key, ca: ca2 }); + await once("localhost"); + server.addContext("alt.localhost", { cert: alt, key: altKey, ca }); + await once("alt.localhost"); + console.log(lines.join(" | ")); + server.close(); +} diff --git a/tests/corpus/7441-tls-client-error.ts b/tests/corpus/7441-tls-client-error.ts new file mode 100644 index 000000000..431ea9019 --- /dev/null +++ b/tests/corpus/7441-tls-client-error.ts @@ -0,0 +1,93 @@ +// tls.Server's 'tlsClientError' event matches Node: the error a failed +// handshake reports (OpenSSL's codes and messages for an HTTP or proxy +// request, a client that rejects the certificate, a missing required +// certificate, an unsupported version and no common ALPN protocol; a +// "socket hang up" ECONNRESET for a client that leaves), the socket it +// passes, what the client sees, and the same event on https.Server. +import * as https from "node:https"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import { readFileSync } from "node:fs"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), ca2 = r("ca2.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const clean = (m: string): string => m.replace(/^[0-9A-F]+:/, "").split(":../")[0]!; + +type Drive = (port: number, client: string[], fin: () => void) => void; + +async function scenario(label: string, make: (h: (s: tls.TLSSocket) => void) => tls.Server, drive: Drive): Promise { + await new Promise((done) => { + const events: string[] = []; + const client: string[] = []; + const server = make((s) => { + events.push("secureConnection"); + s.end("ok"); + }); + server.on("tlsClientError", (err: Error, socket: tls.TLSSocket) => { + events.push(`tlsClientError ${err.name} ${(err as NodeJS.ErrnoException).code} [${clean(err.message)}] destroyed=${socket.destroyed} remote=${typeof socket.remoteAddress}`); + socket.on("close", () => events.push("server socket close")); + }); + server.listen(0, () => { + drive((server.address() as AddressInfo).port, client, () => + setTimeout(() => { + console.log(label, "|", events.join(" ; "), "| client:", client.join(" ; ")); + server.close(() => done()); + }, 100), + ); + }); + }); +} + +const raw = (bytes: Buffer | null, endFirst: boolean): Drive => (port, client, fin) => { + const s = net.connect(port, "127.0.0.1", () => { + if (bytes !== null) s.write(bytes); + if (endFirst) s.end(); + }); + s.on("data", (d: Buffer) => client.push(`data ${d.length}`)); + s.on("end", () => client.push("end")); + s.on("error", (e: NodeJS.ErrnoException) => client.push(`error ${e.code}`)); + s.on("close", (hadError: boolean) => { + client.push(`close ${hadError}`); + fin(); + }); +}; + +const tlsc = (trust: Buffer, maxVersion: "TLSv1.2" | "TLSv1.3", alpn: boolean): Drive => (port, client, fin) => { + const c = alpn + ? tls.connect({ port, servername: "localhost", ca: trust, maxVersion, ALPNProtocols: ["http/1.1"] }) + : tls.connect({ port, servername: "localhost", ca: trust, maxVersion }); + c.on("secureConnect", () => client.push("secureConnect")); + c.on("error", (e: NodeJS.ErrnoException) => client.push(`error ${e.code}`)); + c.on("data", () => {}); + c.on("close", (hadError: boolean) => { + client.push(`close ${hadError}`); + fin(); + }); +}; + +const plain = (h: (s: tls.TLSSocket) => void): tls.Server => tls.createServer({ cert, key }, h); +await scenario("close before hello", plain, raw(null, true)); +await scenario("http request", plain, raw(Buffer.from("GET / HTTP/1.1\r\nHost: x\r\n\r\n"), false)); +await scenario("connect request", plain, raw(Buffer.from("CONNECT x:443 HTTP/1.1\r\n\r\n"), false)); +await scenario("half hello", plain, raw(Buffer.from([0x16, 0x03, 0x01, 0x02, 0x00, 0x01]), true)); +await scenario("client rejects cert", plain, tlsc(ca2, "TLSv1.3", false)); +await scenario("no cert, required", (h) => tls.createServer({ cert, key, requestCert: true, ca }, h), tlsc(ca, "TLSv1.3", false)); +await scenario("version", (h) => tls.createServer({ cert, key, minVersion: "TLSv1.3" }, h), tlsc(ca, "TLSv1.2", false)); +await scenario("alpn", (h) => tls.createServer({ cert, key, ALPNProtocols: ["h2"] }, h), tlsc(ca, "TLSv1.3", true)); +await scenario("ok", plain, tlsc(ca, "TLSv1.3", false)); + +{ + const server = https.createServer({ cert, key }, (req, res) => res.end("ok")); + server.on("tlsClientError", (err: Error, socket: tls.TLSSocket) => console.log("https tlsClientError", (err as NodeJS.ErrnoException).code, socket.destroyed)); + await new Promise((d) => server.listen(0, () => d())); + await new Promise((res) => { + const s = net.connect((server.address() as AddressInfo).port, "127.0.0.1", () => s.write("GET / HTTP/1.1\r\nHost: x\r\n\r\n")); + s.on("data", () => {}); + s.on("error", (e: NodeJS.ErrnoException) => console.log("https client error", e.code)); + s.on("close", () => res()); + }); + await new Promise((d) => setTimeout(d, 50)); + server.close(); +} diff --git a/tests/corpus/7442-tls-server-contexts.mjs b/tests/corpus/7442-tls-server-contexts.mjs new file mode 100644 index 000000000..97f797129 --- /dev/null +++ b/tests/corpus/7442-tls-server-contexts.mjs @@ -0,0 +1,51 @@ +// tls.Server's addContext, setSecureContext and 'tlsClientError' from +// JavaScript match Node: a wildcard SNI context, the default context +// replaced, and the errors a plain-text client and one that leaves +// before its hello report. +import * as net from "node:net"; +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const D = "tests/fixtures/server/certs"; +const cert = readFileSync(`${D}/localhost.pem`); +const key = readFileSync(`${D}/localhost-key.pem`); +const alt = readFileSync(`${D}/alt.pem`); +const altKey = readFileSync(`${D}/alt-key.pem`); + +const server = tls.createServer({ cert, key }, (socket) => socket.end()); +server.addContext("*.localhost", { cert: alt, key: altKey }); +server.on("tlsClientError", (err, socket) => { + const reason = err.message.replace(/^[0-9A-F]+:/, "").split(":../")[0]; + console.log("tlsClientError", err.code, reason, socket.destroyed); +}); + +function peer(port, servername) { + return new Promise((resolve) => { + const c = tls.connect({ port, servername, rejectUnauthorized: false }, () => { + resolve(c.getPeerCertificate().subject.CN); + c.end(); + }); + }); +} + +function raw(port, bytes) { + return new Promise((resolve) => { + const s = net.connect(port, "127.0.0.1", () => { + if (bytes) s.write(bytes); + else s.end(); + }); + s.on("data", () => {}); + s.on("error", (err) => console.log("client error", err.code)); + s.on("close", () => setTimeout(resolve, 50)); + }); +} + +server.listen(0, async () => { + const { port } = server.address(); + console.log("sni", await peer(port, "alt.localhost"), await peer(port, "localhost")); + server.setSecureContext({ cert: alt, key: altKey }); + console.log("after setSecureContext", await peer(port, "example.test")); + await raw(port, "GET / HTTP/1.1\r\nHost: x\r\n\r\n"); + await raw(port, null); + server.close(); +}); diff --git a/tests/corpus/7443-tls-final-flight.ts b/tests/corpus/7443-tls-final-flight.ts new file mode 100644 index 000000000..b4de4b4d4 --- /dev/null +++ b/tests/corpus/7443-tls-final-flight.ts @@ -0,0 +1,114 @@ +// The last handshake flight is flushed when Node flushes it: after the +// callbacks that see the handshake finish and their nextTick callbacks, +// before the immediates they queue. A destroy() in secureConnect or a tick +// drops a TLS 1.3 client's Finished (the server reports a hang-up), one in +// secureConnection drops a TLS 1.2 server's Finished (the client ends, then +// fails), and data written beside it is dropped too; end() and a destroy +// in an immediate let it go out. A client whose server closes or resets +// mid-handshake gets Node's events, from tls.connect and https.request. +import { readFileSync } from "node:fs"; +import * as https from "node:https"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const cert = readFileSync(`${D}/localhost.pem`), key = readFileSync(`${D}/localhost-key.pem`); +type Version = "TLSv1.2" | "TLSv1.3"; +const hows = ["destroy", "tick", "immediate", "write+destroy", "end"]; + +function act(s: tls.TLSSocket, how: string): void { + if (how === "destroy") s.destroy(); + else if (how === "tick") process.nextTick(() => s.destroy()); + else if (how === "immediate") setImmediate(() => s.destroy()); + else if (how === "write+destroy") { + s.write("hi"); + s.destroy(); + } else s.end("bye"); +} + +const listen = (server: net.Server): Promise => + new Promise((d) => server.listen(0, "127.0.0.1", () => d((server.address() as AddressInfo).port))); + +// the client acts in secureConnect; the server reports what arrived +for (const v of ["TLSv1.2", "TLSv1.3"] as Version[]) { + for (const how of hows) { + const ev: string[] = []; + const server = tls.createServer({ cert, key, maxVersion: v }, (s) => { + ev.push("secureConnection"); + s.on("data", (d: Buffer) => ev.push(`data ${d}`)); + // A client that closes before it reads the server's TLS 1.3 session + // tickets resets the connection. Whether the tickets are read first + // depends on the platform's timing, so that reset is not printed. + s.on("error", (e: NodeJS.ErrnoException) => { + if (e.code !== "ECONNRESET") ev.push(`server socket error ${e.code}`); + }); + }); + server.on("tlsClientError", (e: Error) => ev.push(`tlsClientError ${(e as NodeJS.ErrnoException).code}`)); + const port = await listen(server); + await new Promise((res) => { + const c = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }, () => act(c, how)); + c.on("close", () => setTimeout(res, 100)); + }); + console.log("client", v, how, "|", ev.join(" ; ")); + server.close(); + } +} + +// the server acts in secureConnection; the client reports what arrived +for (const v of ["TLSv1.2", "TLSv1.3"] as Version[]) { + for (const how of hows) { + const ev: string[] = []; + const server = tls.createServer({ cert, key, maxVersion: v }, (s) => { + s.on("error", () => {}); + act(s, how); + }); + const port = await listen(server); + await new Promise((res) => { + const c = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }, () => ev.push("secureConnect")); + c.on("data", (d: Buffer) => ev.push(`data ${d}`)); + c.on("end", () => ev.push("end")); + c.on("error", (e: NodeJS.ErrnoException) => ev.push(`error ${e.code} ${e.message}`)); + c.on("close", (hadError: boolean) => { + ev.push(`close ${hadError}`); + setTimeout(res, 50); + }); + }); + console.log("server", v, how, "|", ev.join(" ; ")); + server.close(); + } +} + +// a peer that closes or resets after the client's hello +for (const how of ["fin", "rst"]) { + for (const via of ["tls.connect", "https.request"]) { + const ev: string[] = []; + const server = net.createServer((s) => { + s.once("data", () => { + if (how === "rst") s.resetAndDestroy(); + else s.end(); + }); + }); + const port = await listen(server); + await new Promise((res) => { + if (via === "tls.connect") { + const c = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }); + c.on("end", () => ev.push("end")); + c.on("error", (e: NodeJS.ErrnoException) => ev.push(`error ${e.code} ${e.message}`)); + c.on("close", (hadError: boolean) => { + ev.push(`close ${hadError}`); + res(); + }); + } else { + const req = https.request({ host: "127.0.0.1", port, path: "/", rejectUnauthorized: false, agent: false }); + req.on("error", (e: NodeJS.ErrnoException) => { + ev.push(`request error ${e.code} ${e.message}`); + res(); + }); + req.end(); + } + }); + console.log("peer", how, via, "|", ev.join(" ; ")); + server.close(); + } +} diff --git a/tests/corpus/7444-tls-final-flight.mjs b/tests/corpus/7444-tls-final-flight.mjs new file mode 100644 index 000000000..e21de782c --- /dev/null +++ b/tests/corpus/7444-tls-final-flight.mjs @@ -0,0 +1,28 @@ +// The last handshake flight from JavaScript: a TLS 1.3 client that +// destroys its socket in secureConnect or a tick never sends its Finished, +// so the server reports a hang-up; one that waits for an immediate, or +// ends the socket, completes the server's handshake. +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const D = "tests/fixtures/server/certs"; +const cert = readFileSync(`${D}/localhost.pem`); +const key = readFileSync(`${D}/localhost-key.pem`); + +for (const how of ["destroy", "tick", "immediate", "end"]) { + const events = []; + const server = tls.createServer({ cert, key }, () => events.push("secureConnection")); + server.on("tlsClientError", (err) => events.push(`tlsClientError ${err.code}`)); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + await new Promise((resolve) => { + const client = tls.connect({ port: server.address().port, host: "127.0.0.1", rejectUnauthorized: false }, () => { + if (how === "destroy") client.destroy(); + else if (how === "tick") process.nextTick(() => client.destroy()); + else if (how === "immediate") setImmediate(() => client.destroy()); + else client.end(); + }); + client.on("close", () => setTimeout(resolve, 100)); + }); + console.log(how, events.join(" ; ")); + server.close(); +} diff --git a/tests/corpus/7454-tls-peer-embedded-pem.ts b/tests/corpus/7454-tls-peer-embedded-pem.ts new file mode 100644 index 000000000..1937320e9 --- /dev/null +++ b/tests/corpus/7454-tls-peer-embedded-pem.ts @@ -0,0 +1,46 @@ +// A peer certificate whose extension carries another certificate's PEM as +// text: getPeerX509Certificate() and getX509Certificate() give the +// certificate the handshake delivered, and its fields, not the embedded +// one. new X509Certificate() of the same DER reads the PEM inside it first, +// as Node's PEM-before-DER parse does. +import { X509Certificate } from "node:crypto"; +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const cert = readFileSync(`${D}/embedded.pem`), key = readFileSync(`${D}/embedded-key.pem`); +const pem = new X509Certificate(cert); + +function describe(label: string, x: X509Certificate | undefined): void { + if (!x) return console.log(label, "undefined"); + console.log(label, x.subject, x.serialNumber, x.fingerprint256 === pem.fingerprint256, x.fingerprint === pem.fingerprint, x.validTo, x.raw.length); +} + +// raw is the certificate SEQUENCE, without bytes after it, and PEM armor +// counts only at the start of a line +const trailing = new X509Certificate(Buffer.concat([pem.raw, Buffer.from([1, 2, 3])])); +console.log("trailing bytes", trailing.raw.length === pem.raw.length, trailing.fingerprint256 === pem.fingerprint256, trailing.fingerprint === pem.fingerprint, trailing.validFrom === pem.validFrom); +for (const [label, input] of [["after a line", Buffer.concat([Buffer.from("text\n"), cert])], ["inside a line", Buffer.concat([Buffer.from("text"), cert])]] as const) { + try { + console.log(label, new X509Certificate(input).subject); + } catch (e) { + console.log(label, (e as NodeJS.ErrnoException).code); + } +} + +const server = tls.createServer({ cert, key }, (s) => { + describe("server own", s.getX509Certificate()); + s.end(); +}); +server.listen(0, "127.0.0.1", () => { + const c = tls.connect({ port: (server.address() as AddressInfo).port, host: "127.0.0.1", rejectUnauthorized: false }, () => { + const peer = c.getPeerX509Certificate(); + describe("client peer", peer); + const legacy = c.getPeerCertificate(); + console.log("legacy", legacy.subject.CN, legacy.fingerprint256 === pem.fingerprint256); + if (peer) describe("rebuilt from raw", new X509Certificate(peer.raw)); + }); + c.resume(); + c.on("close", () => server.close()); +}); diff --git a/tests/corpus/7455-tls-als-contexts.mjs b/tests/corpus/7455-tls-als-contexts.mjs new file mode 100644 index 000000000..38783fdf9 --- /dev/null +++ b/tests/corpus/7455-tls-als-contexts.mjs @@ -0,0 +1,48 @@ +// The AsyncLocalStorage store in TLS server and client events: a server's +// connections run in its listen() context, except 'tlsClientError', which +// Node emits with no store; a client's events run in its connect() context. +import * as tls from "node:tls"; +import { AsyncLocalStorage } from "node:async_hooks"; +import { readFileSync } from "node:fs"; + +const D = "tests/fixtures/server/certs"; +const cert = readFileSync(`${D}/localhost.pem`); +const key = readFileSync(`${D}/localhost-key.pem`); +const als = new AsyncLocalStorage(); +const serverEvents = []; +const clientEvents = []; +const server = tls.createServer({ cert, key }, (socket) => { + serverEvents.push(`secureConnection ${als.getStore()}`); + socket.on("data", () => { + serverEvents.push(`data ${als.getStore()}`); + socket.end("pong"); + }); +}); +server.on("tlsClientError", (err) => serverEvents.push(`tlsClientError ${err.code} ${als.getStore()}`)); +await als.run("listen", () => new Promise((resolve) => server.listen(0, "127.0.0.1", () => { + serverEvents.push(`listening ${als.getStore()}`); + resolve(); +}))); +const { port } = server.address(); +await als.run("client", () => new Promise((resolve) => { + const client = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }, () => { + clientEvents.push(`secureConnect ${als.getStore()}`); + client.write("ping"); + }); + client.on("data", () => clientEvents.push(`data ${als.getStore()}`)); + client.on("close", () => { + clientEvents.push(`close ${als.getStore()}`); + resolve(); + }); +})); +await als.run("untrusted", () => new Promise((resolve) => { + const client = tls.connect({ port, host: "127.0.0.1" }); + client.on("error", (err) => { + clientEvents.push(`error ${err.code} ${als.getStore()}`); + setTimeout(resolve, 100); + }); +})); +server.close(() => { + console.log("server:", serverEvents.join(" ; ")); + console.log("client:", clientEvents.join(" ; ")); +}); diff --git a/tests/diagnostics/x509-fences.ts b/tests/diagnostics/x509-fences.ts new file mode 100644 index 000000000..416a88b59 --- /dev/null +++ b/tests/diagnostics/x509-fences.ts @@ -0,0 +1,21 @@ +// X509Certificate keeps its fields as prototype getters, so Node's own-key +// walks over a certificate see none of them. Those walks are refused by +// name instead of listing the fields. +import { X509Certificate } from "node:crypto"; +import { readFileSync } from "node:fs"; + +const cert = new X509Certificate(readFileSync("cert.pem")); +console.log(Object.keys(cert)); +console.log(Object.entries(cert)); +console.log({ ...cert }); +console.log(Object.prototype.hasOwnProperty.call(cert, "subject")); +for (const key in cert) console.log(key); + +// A certificate in an unknown or any slot would cross as a plain object of +// its fields, which Node's certificate is not. +const held: unknown = cert; +console.log(held); +function describe(value: unknown): string { + return typeof value; +} +console.log(describe([cert])); diff --git a/tests/fixtures/node-types/tls-client-certificates.ts b/tests/fixtures/node-types/tls-client-certificates.ts new file mode 100644 index 000000000..3268369f9 --- /dev/null +++ b/tests/fixtures/node-types/tls-client-certificates.ts @@ -0,0 +1,32 @@ +// Under @types/node: a mutual-TLS server that requires a client +// certificate signed by its CA, and a client that presents one. +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const certs = "tests/fixtures/server/certs"; +const ca = readFileSync(`${certs}/ca.pem`); + +const cert = readFileSync(`${certs}/localhost.pem`); +const key = readFileSync(`${certs}/localhost-key.pem`); +let serverLine = ""; +const server = tls.createServer({ cert, key, ca, requestCert: true, rejectUnauthorized: true }, (socket: tls.TLSSocket) => { + const peer: tls.PeerCertificate = socket.getPeerCertificate(); + serverLine = `server ${socket.authorized} ${String(socket.authorizationError)} ${peer.subject.CN}`; + socket.end("welcome"); +}); +server.listen(0, "127.0.0.1", () => { + const port = (server.address() as AddressInfo).port; + const clientCert = readFileSync(`${certs}/client.pem`); + const clientKey = readFileSync(`${certs}/client-key.pem`); + const client = tls.connect({ port, host: "127.0.0.1", servername: "localhost", ca, cert: clientCert, key: clientKey }, () => { + console.log("client", client.authorized, client.getX509Certificate()?.subject); + }); + let body = ""; + client.on("data", (chunk: Buffer) => (body += chunk.toString())); + client.on("end", () => { + console.log(serverLine); + console.log("body", body); + server.close(); + }); +}); diff --git a/tests/fixtures/node-types/tls-peer-certificates.ts b/tests/fixtures/node-types/tls-peer-certificates.ts new file mode 100644 index 000000000..d082cfead --- /dev/null +++ b/tests/fixtures/node-types/tls-peer-certificates.ts @@ -0,0 +1,37 @@ +// Under @types/node: a TLS client reads the server's certificate as the +// legacy object, its detailed issuer chain and an X509Certificate, and the +// server reads the certificate it serves. +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const certs = "tests/fixtures/server/certs"; +const ca = readFileSync(`${certs}/ca.pem`); +const cert = readFileSync(`${certs}/localhost.pem`); +const key = readFileSync(`${certs}/localhost-key.pem`); + +// The server's line prints once the client has its reply, so the output +// order does not depend on which side's callback runs first. +let serverLine = ""; +const server = tls.createServer({ cert, key }, (socket: tls.TLSSocket) => { + const own = socket.getCertificate() as tls.PeerCertificate; + serverLine = `server ${own.subject.CN} ${socket.getX509Certificate()?.subject} ${Object.keys(socket.getPeerCertificate()).length}`; + socket.end("done"); +}); +server.listen(0, "127.0.0.1", () => { + const port = (server.address() as AddressInfo).port; + const client = tls.connect({ port, host: "127.0.0.1", ca, servername: "localhost" }, () => { + const peer: tls.PeerCertificate = client.getPeerCertificate(); + const detailed: tls.DetailedPeerCertificate = client.getPeerCertificate(true); + console.log("client", peer.subject.CN, peer.subjectaltname, peer.bits, detailed.issuerCertificate.subject.CN); + console.log("root", detailed.issuerCertificate.issuerCertificate === detailed.issuerCertificate); + const x509 = client.getPeerX509Certificate(); + if (x509 !== undefined) console.log("x509", x509.subject, x509.checkIP("127.0.0.1"), x509.fingerprint256 === peer.fingerprint256); + }); + client.resume(); + client.on("end", () => { + console.log(serverLine); + client.destroy(); + server.close(); + }); +}); diff --git a/tests/fixtures/node-types/tls-server-contexts.ts b/tests/fixtures/node-types/tls-server-contexts.ts new file mode 100644 index 000000000..ff0425a56 --- /dev/null +++ b/tests/fixtures/node-types/tls-server-contexts.ts @@ -0,0 +1,38 @@ +// Under @types/node: a tls.Server that answers an SNI name from +// addContext, replaces its default context with setSecureContext, and +// reports a plain-text client through 'tlsClientError'. +import { readFileSync } from "node:fs"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const certs = "tests/fixtures/server/certs"; +const cert = readFileSync(`${certs}/localhost.pem`); +const key = readFileSync(`${certs}/localhost-key.pem`); +const alt = readFileSync(`${certs}/alt.pem`); +const altKey = readFileSync(`${certs}/alt-key.pem`); + +const server: tls.Server = tls.createServer({ cert, key }, (socket: tls.TLSSocket) => socket.end()); +server.addContext("*.localhost", tls.createSecureContext({ cert: alt, key: altKey })); +server.on("tlsClientError", (err: Error, socket: tls.TLSSocket) => { + console.log("tlsClientError", (err as NodeJS.ErrnoException).code, socket.destroyed); +}); + +function subject(port: number, servername: string): Promise { + return new Promise((resolve) => { + const client = tls.connect({ port, host: "127.0.0.1", servername, rejectUnauthorized: false }, () => { + resolve(String(client.getPeerCertificate().subject.CN)); + client.end(); + }); + }); +} + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as AddressInfo).port; + console.log("sni", await subject(port, "alt.localhost"), await subject(port, "localhost")); + server.setSecureContext({ cert: alt, key: altKey }); + console.log("default", await subject(port, "localhost")); + const raw = net.connect(port, "127.0.0.1", () => raw.write("GET / HTTP/1.1\r\nHost: x\r\n\r\n")); + raw.on("data", () => {}); + raw.on("close", () => setTimeout(() => server.close(), 50)); +}); diff --git a/tests/fixtures/node-types/x509-methods.ts b/tests/fixtures/node-types/x509-methods.ts new file mode 100644 index 000000000..8736ec990 --- /dev/null +++ b/tests/fixtures/node-types/x509-methods.ts @@ -0,0 +1,20 @@ +// Under @types/node: X509Certificate's methods over the test certificates: +// the issuer, the signature, the host and IP names, the private key, the +// legacy object and the PEM text. +import { X509Certificate, createPrivateKey } from "node:crypto"; +import { readFileSync } from "node:fs"; +import type { PeerCertificate } from "node:tls"; + +const certs = "tests/fixtures/server/certs"; +const ca = new X509Certificate(readFileSync(`${certs}/ca.pem`)); +const leaf = new X509Certificate(readFileSync(`${certs}/localhost.pem`)); +const key = createPrivateKey(readFileSync(`${certs}/localhost-key.pem`)); + +console.log(leaf.checkIssued(ca), leaf.verify(ca.publicKey), ca.verify(leaf.publicKey)); +const host: string | undefined = leaf.checkHost("localhost"); +console.log(host, leaf.checkHost("example.com"), leaf.checkIP("127.0.0.1"), leaf.checkIP("::1")); +console.log(leaf.checkEmail("root@localhost", { subject: "always" }), leaf.checkPrivateKey(key)); +const legacy: PeerCertificate = leaf.toLegacyObject(); +console.log(legacy.subject.CN, legacy.subjectaltname, legacy.bits, legacy.exponent, legacy.raw.equals(leaf.raw)); +console.log(leaf.publicKey.asymmetricKeyType, leaf.publicKey === leaf.publicKey); +console.log(leaf.toString() === readFileSync(`${certs}/localhost.pem`, "latin1"), String(ca).split("\n")[0]); diff --git a/tests/fixtures/server/certs/README.md b/tests/fixtures/server/certs/README.md index dd383f028..2ab5c1839 100644 --- a/tests/fixtures/server/certs/README.md +++ b/tests/fixtures/server/certs/README.md @@ -13,3 +13,15 @@ Minted ONCE with the openssl recipe below (see san.cnf) and committed with a 100 (ca2 repeats the ca recipe with its own key and CN.) `alt.pem`/`alt-key.pem` (the SNI fixtures' second identity: CN=alt.localhost, SAN DNS:alt.localhost) repeats the leaf recipe against the same `ca.pem`, with an ext section whose subjectAltName is `DNS:alt.localhost`. + +`client.pem`/`client-key.pem` (CN=scriptc test client) is a client certificate for the mutual-TLS fixtures: a P-256 leaf signed by `ca.pem` whose extended key usage is clientAuth (`client.cnf`, section `ext`): + + openssl ecparam -genkey -name prime256v1 -noout -out client-key.pem + openssl req -new -key client-key.pem -out client.csr -subj "/CN=scriptc test client" -config client.cnf + openssl x509 -req -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client.pem -days 36500 -extfile client.cnf -extensions ext + +`embedded.pem`/`embedded-key.pem` (CN=embedded pem) is a localhost leaf signed by `ca.pem` whose Netscape comment extension holds `alt.pem` as PEM text on one line (`embedded.cnf`, section `ext`). It checks that a peer certificate's DER is read as DER: + + openssl ecparam -genkey -name prime256v1 -noout -out embedded-key.pem + openssl req -new -key embedded-key.pem -out embedded.csr -subj "/CN=embedded pem" -config embedded.cnf + openssl x509 -req -in embedded.csr -CA ca.pem -CAkey ca-key.pem -set_serial 4242 -out embedded.pem -days 36500 -extfile embedded.cnf -extensions ext diff --git a/tests/fixtures/server/certs/client-key.pem b/tests/fixtures/server/certs/client-key.pem new file mode 100644 index 000000000..90f95c680 --- /dev/null +++ b/tests/fixtures/server/certs/client-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIE6TPp76NrDFeBw1tQSN7lygt9zixQ5xaGb0Xug5/aNVoAoGCCqGSM49 +AwEHoUQDQgAE9iN6ruUQ8IneIyb280iqEfRKAusDKrssfCJh7717mNISg8U7TKvG +XXxgsHD/8NKQjw1Z5SZye7FfNv0WetLRvg== +-----END EC PRIVATE KEY----- diff --git a/tests/fixtures/server/certs/client.cnf b/tests/fixtures/server/certs/client.cnf new file mode 100644 index 000000000..4b7151ef4 --- /dev/null +++ b/tests/fixtures/server/certs/client.cnf @@ -0,0 +1,7 @@ +[req] +distinguished_name = dn +[dn] +[ext] +basicConstraints = CA:FALSE +keyUsage = digitalSignature +extendedKeyUsage = clientAuth diff --git a/tests/fixtures/server/certs/client.pem b/tests/fixtures/server/certs/client.pem new file mode 100644 index 000000000..6458a0fe2 --- /dev/null +++ b/tests/fixtures/server/certs/client.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBqzCCAVKgAwIBAgIUBVrP4y4NCL3DYzbSrpkMIwuBg2owCgYIKoZIzj0EAwIw +GzEZMBcGA1UEAwwQdHNuYXRpdmUgdGVzdCBDQTAgFw0yNjEwMjIxNTUxMTZaGA8y +MTI2MDkyODE1NTExNlowHjEcMBoGA1UEAwwTc2NyaXB0YyB0ZXN0IGNsaWVudDBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABPYjeq7lEPCJ3iMm9vNIqhH0SgLrAyq7 +LHwiYe+9e5jSEoPFO0yrxl18YLBw//DSkI8NWeUmcnuxXzb9FnrS0b6jbzBtMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1Ud +DgQWBBS/BxjyTMQkc2IMuhvzyDUhflemADAfBgNVHSMEGDAWgBT7ISW3gYKaXUJu +ZYa2OrD6IBZbBTAKBggqhkjOPQQDAgNHADBEAiBy+DnJFvCUyNmxxeMaWW93n3vJ +XPzLMFQyyoe+hKhoYQIgIDT731Nwwcw/H0Mb+nsb7HMHYihYssPzK52XFAqKA9A= +-----END CERTIFICATE----- diff --git a/tests/fixtures/server/certs/embedded-key.pem b/tests/fixtures/server/certs/embedded-key.pem new file mode 100644 index 000000000..ddf76b1d3 --- /dev/null +++ b/tests/fixtures/server/certs/embedded-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMzj49L98KADiH/rd18MLx2kdJ8fMMd3Al4AWOE7Ma1VoAoGCCqGSM49 +AwEHoUQDQgAEIdpwV/6VqvmMJkIoZPPgnfNdn+e+NVxyLMMY5n90YMWRfy45AZwn +f/57ssBgq0FC1GDaLRe4dLL9YCSDq3pOzQ== +-----END EC PRIVATE KEY----- diff --git a/tests/fixtures/server/certs/embedded.cnf b/tests/fixtures/server/certs/embedded.cnf new file mode 100644 index 000000000..8d9556e43 --- /dev/null +++ b/tests/fixtures/server/certs/embedded.cnf @@ -0,0 +1,9 @@ +[req] +distinguished_name = dn +[dn] +[ext] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1 +nsComment = "-----BEGIN CERTIFICATE-----MIIBwzCCAWigAwIBAgIUSw5Q3SUK8/VNBnfXUCOkHzR4ShUwCgYIKoZIzj0EAwIwGzEZMBcGA1UEAwwQdHNuYXRpdmUgdGVzdCBDQTAgFw0yNjA3MTcwMDU2MjBaGA8yMTI2MDYyMzAwNTYyMFowGDEWMBQGA1UEAwwNYWx0LmxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJyVZDQSTyauFgEwMTksal/hOBhIYpCAzEBCyQq6e23ETTb4NNwIL/NI0ILAr6QmhJaInY31QJq1NFHDkl4N5YujgYowgYcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0RBBEwD4INYWx0LmxvY2FsaG9zdDAdBgNVHQ4EFgQU/Ho4zrglTx9pFwqI+09JFLH84LQwHwYDVR0jBBgwFoAU+yElt4GCml1CbmWGtjqw+iAWWwUwCgYIKoZIzj0EAwIDSQAwRgIhAOUMUtjnIJaYCnPV4YrDMrgIziihcbkq6ZqKBSwwbCMOAiEA0vfd3QpyrdTIKwuopJLg2gA6QPnRTXrZdYr0M3NmJ3Q=-----END CERTIFICATE-----" diff --git a/tests/fixtures/server/certs/embedded.pem b/tests/fixtures/server/certs/embedded.pem new file mode 100644 index 000000000..577ea39b8 --- /dev/null +++ b/tests/fixtures/server/certs/embedded.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEbzCCBBagAwIBAgICEJIwCgYIKoZIzj0EAwIwGzEZMBcGA1UEAwwQdHNuYXRp +dmUgdGVzdCBDQTAgFw0yNjEwMjMxNjEwMzVaGA8yMTI2MDkyOTE2MTAzNVowFzEV +MBMGA1UEAwwMZW1iZWRkZWQgcGVtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +IdpwV/6VqvmMJkIoZPPgnfNdn+e+NVxyLMMY5n90YMWRfy45AZwnf/57ssBgq0FC +1GDaLRe4dLL9YCSDq3pOzaOCA0owggNGMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWg +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAA +AYcQAAAAAAAAAAAAAAAAAAAAATCCAqcGCWCGSAGG+EIBDQSCApgWggKULS0tLS1C +RUdJTiBDRVJUSUZJQ0FURS0tLS0tTUlJQnd6Q0NBV2lnQXdJQkFnSVVTdzVRM1NV +SzgvVk5CbmZYVUNPa0h6UjRTaFV3Q2dZSUtvWkl6ajBFQXdJd0d6RVpNQmNHQTFV +RUF3d1FkSE51WVhScGRtVWdkR1Z6ZENCRFFUQWdGdzB5TmpBM01UY3dNRFUyTWpC +YUdBOHlNVEkyTURZeU16QXdOVFl5TUZvd0dERVdNQlFHQTFVRUF3d05ZV3gwTG14 +dlkyRnNhRzl6ZERCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkp5 +VlpEUVNUeWF1RmdFd01Ua3NhbC9oT0JoSVlwQ0F6RUJDeVFxNmUyM0VUVGI0Tk53 +SUwvTkkwSUxBcjZRbWhKYUluWTMxUUpxMU5GSERrbDRONVl1amdZb3dnWWN3Q1FZ +RFZSMFRCQUl3QURBTEJnTlZIUThFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZ +QkJRVUhBd0V3R0FZRFZSMFJCQkV3RDRJTllXeDBMbXh2WTJGc2FHOXpkREFkQmdO +VkhRNEVGZ1FVL0hvNHpyZ2xUeDlwRndxSSswOUpGTEg4NExRd0h3WURWUjBqQkJn +d0ZvQVUreUVsdDRHQ21sMUNibVdHdGpxdytpQVdXd1V3Q2dZSUtvWkl6ajBFQXdJ +RFNRQXdSZ0loQU9VTVV0am5JSmFZQ25QVjRZckRNcmdJemlpaGNia3E2WnFLQlN3 +d2JDTU9BaUVBMHZmZDNRcHlyZFRJS3d1b3BKTGcyZ0E2UVBuUlRYclpkWXIwTTNO +bUozUT0tLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tMB0GA1UdDgQWBBQ+Y3aDuoRS +0FRNT2st7Lzlo7BtDjAfBgNVHSMEGDAWgBT7ISW3gYKaXUJuZYa2OrD6IBZbBTAK +BggqhkjOPQQDAgNHADBEAiB3zl81cDKI4GvoRBDneF53eeLR6ftnaD9Pf6hCHUUA +zwIgauFJwF7rLQ0krjZXxdOMZEOl7i1sBrlOfGiIyGSn84s= +-----END CERTIFICATE----- diff --git a/tests/fixtures/tls/empty-ca.ts b/tests/fixtures/tls/empty-ca.ts new file mode 100644 index 000000000..7d1d30928 --- /dev/null +++ b/tests/fixtures/tls/empty-ca.ts @@ -0,0 +1,36 @@ +// A server's ca option as a trust store for client certificates, run with +// NODE_EXTRA_CA_CERTS naming the test CA, so the default store trusts the +// client: an empty Buffer is an empty store, an empty string and no ca use +// the default store. +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const cert = r("localhost.pem"), key = r("localhost-key.pem"), ca = r("ca.pem"); +const cases: [string, Buffer | string | undefined][] = [ + ["empty Buffer", Buffer.alloc(0)], + ["empty string", ""], + ["no ca", undefined], + ["ca", ca], +]; +for (const [label, value] of cases) { + await new Promise((done) => { + const report = (s: tls.TLSSocket): void => { + console.log(label, s.authorized, s.authorizationError); + s.end(); + }; + const server = + value === undefined + ? tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false }, report) + : tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: false, ca: value }, report); + server.listen(0, "127.0.0.1", () => { + const port = (server.address() as AddressInfo).port; + const c = tls.connect({ port, host: "127.0.0.1", ca, servername: "localhost", cert: r("client.pem"), key: r("client-key.pem") }); + c.on("error", () => {}); + c.resume(); + c.on("close", () => server.close(() => done())); + }); + }); +} diff --git a/tests/harness/__snapshots__/tls-sni-callback-fences.ts.txt b/tests/harness/__snapshots__/tls-sni-callback-fences.ts.txt index c45608bb9..80ace6df6 100644 --- a/tests/harness/__snapshots__/tls-sni-callback-fences.ts.txt +++ b/tests/harness/__snapshots__/tls-sni-callback-fences.ts.txt @@ -5,4 +5,4 @@ tls-sni-callback-fences.ts:13:54 - error SC2020: 'tls.createServer with SNICallb | ^~~~~~~~~~~~~~~~~~~~ 14 | - hint: SNICallback lowers with cert and key; ALPNProtocols, minVersion, maxVersion and the validated options are not combined with it yet \ No newline at end of file + hint: SNICallback lowers with cert and key; ALPNProtocols, minVersion, maxVersion, requestCert, ca and the validated options are not combined with it yet \ No newline at end of file diff --git a/tests/harness/__snapshots__/x509-fences.ts.txt b/tests/harness/__snapshots__/x509-fences.ts.txt new file mode 100644 index 000000000..2f5cc4e68 --- /dev/null +++ b/tests/harness/__snapshots__/x509-fences.ts.txt @@ -0,0 +1,48 @@ +x509-fences.ts:8:13 - error SC1090: Object.keys over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields) are not supported yet + + 7 | const cert = new X509Certificate(readFileSync("cert.pem")); + 8 | console.log(Object.keys(cert)); + | ^~~~~~~~~~~~~~~~~ + 9 | console.log(Object.entries(cert)); + +x509-fences.ts:9:13 - error SC1090: Object.entries over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields) are not supported yet + + 8 | console.log(Object.keys(cert)); + 9 | console.log(Object.entries(cert)); + | ^~~~~~~~~~~~~~~~~~~~ + 10 | console.log({ ...cert }); + +x509-fences.ts:10:15 - error SC1090: object spread over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields) are not supported yet + + 9 | console.log(Object.entries(cert)); + 10 | console.log({ ...cert }); + | ^~~~~~~ + 11 | console.log(Object.prototype.hasOwnProperty.call(cert, "subject")); + +x509-fences.ts:11:50 - error SC1090: Object.prototype.hasOwnProperty over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields) are not supported yet + + 10 | console.log({ ...cert }); + 11 | console.log(Object.prototype.hasOwnProperty.call(cert, "subject")); + | ^~~~ + 12 | for (const key in cert) console.log(key); + +x509-fences.ts:12:19 - error SC1090: for-in over X509Certificate values (Node keeps their fields as prototype getters, so own-key walks see none; read the fields) are not supported yet + + 11 | console.log(Object.prototype.hasOwnProperty.call(cert, "subject")); + 12 | for (const key in cert) console.log(key); + | ^~~~ + 13 | + +x509-fences.ts:16:23 - error SC1090: X509Certificate values in 'unknown' or 'any' slots (Node keeps a certificate's fields as prototype getters, and its toString() and toJSON() give the PEM; keep the X509Certificate type) are not supported yet + + 15 | // its fields, which Node's certificate is not. + 16 | const held: unknown = cert; + | ^~~~ + 17 | console.log(held); + +x509-fences.ts:21:23 - error SC1090: X509Certificate values in 'unknown' or 'any' slots (Node keeps a certificate's fields as prototype getters, and its toString() and toJSON() give the PEM; keep the X509Certificate type) are not supported yet + + 20 | } + 21 | console.log(describe([cert])); + | ^~~~ + 22 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/6936-x509-certificate-fields.ts.json b/tests/harness/corpus-costs/6936-x509-certificate-fields.ts.json new file mode 100644 index 000000000..73e8107eb --- /dev/null +++ b/tests/harness/corpus-costs/6936-x509-certificate-fields.ts.json @@ -0,0 +1 @@ +{ "plain": 3042, "san": 5637 } diff --git a/tests/harness/corpus-costs/7090-x509-check-names.ts.json b/tests/harness/corpus-costs/7090-x509-check-names.ts.json new file mode 100644 index 000000000..3ee86dd28 --- /dev/null +++ b/tests/harness/corpus-costs/7090-x509-check-names.ts.json @@ -0,0 +1 @@ +{ "plain": 4003, "san": 5700 } diff --git a/tests/harness/corpus-costs/7091-x509-certificate-keys.ts.json b/tests/harness/corpus-costs/7091-x509-certificate-keys.ts.json new file mode 100644 index 000000000..a9b159564 --- /dev/null +++ b/tests/harness/corpus-costs/7091-x509-certificate-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 4335, "san": 8298 } diff --git a/tests/harness/corpus-costs/7092-x509-legacy-object.ts.json b/tests/harness/corpus-costs/7092-x509-legacy-object.ts.json new file mode 100644 index 000000000..c7e0c4ee2 --- /dev/null +++ b/tests/harness/corpus-costs/7092-x509-legacy-object.ts.json @@ -0,0 +1 @@ +{ "plain": 3910, "san": 5665 } diff --git a/tests/harness/corpus-costs/7093-x509-methods-js.mjs.json b/tests/harness/corpus-costs/7093-x509-methods-js.mjs.json new file mode 100644 index 000000000..8f60594f1 --- /dev/null +++ b/tests/harness/corpus-costs/7093-x509-methods-js.mjs.json @@ -0,0 +1 @@ +{ "plain": 4421, "san": 5823 } diff --git a/tests/harness/corpus-costs/7095-tls-peer-certificates.ts.json b/tests/harness/corpus-costs/7095-tls-peer-certificates.ts.json new file mode 100644 index 000000000..68214a26c --- /dev/null +++ b/tests/harness/corpus-costs/7095-tls-peer-certificates.ts.json @@ -0,0 +1 @@ +{ "plain": 9277, "san": 14000 } diff --git a/tests/harness/corpus-costs/7096-tls-peer-certificates.mjs.json b/tests/harness/corpus-costs/7096-tls-peer-certificates.mjs.json new file mode 100644 index 000000000..ec7c1e0f4 --- /dev/null +++ b/tests/harness/corpus-costs/7096-tls-peer-certificates.mjs.json @@ -0,0 +1 @@ +{ "plain": 4576, "san": 8354 } diff --git a/tests/harness/corpus-costs/7097-tls-client-certificates.ts.json b/tests/harness/corpus-costs/7097-tls-client-certificates.ts.json new file mode 100644 index 000000000..2776a867c --- /dev/null +++ b/tests/harness/corpus-costs/7097-tls-client-certificates.ts.json @@ -0,0 +1 @@ +{ "plain": 8917, "san": 11888 } diff --git a/tests/harness/corpus-costs/7098-tls-client-certificates-paths.ts.json b/tests/harness/corpus-costs/7098-tls-client-certificates-paths.ts.json new file mode 100644 index 000000000..f55fef2c1 --- /dev/null +++ b/tests/harness/corpus-costs/7098-tls-client-certificates-paths.ts.json @@ -0,0 +1 @@ +{ "plain": 6188, "san": 10754 } diff --git a/tests/harness/corpus-costs/7099-tls-client-certificates.mjs.json b/tests/harness/corpus-costs/7099-tls-client-certificates.mjs.json new file mode 100644 index 000000000..02414f135 --- /dev/null +++ b/tests/harness/corpus-costs/7099-tls-client-certificates.mjs.json @@ -0,0 +1 @@ +{ "plain": 4389, "san": 5772 } diff --git a/tests/harness/corpus-costs/7440-tls-server-add-context.ts.json b/tests/harness/corpus-costs/7440-tls-server-add-context.ts.json new file mode 100644 index 000000000..f61acc3da --- /dev/null +++ b/tests/harness/corpus-costs/7440-tls-server-add-context.ts.json @@ -0,0 +1 @@ +{ "plain": 2787, "san": 2973 } diff --git a/tests/harness/corpus-costs/7441-tls-client-error.ts.json b/tests/harness/corpus-costs/7441-tls-client-error.ts.json new file mode 100644 index 000000000..a928ec1f1 --- /dev/null +++ b/tests/harness/corpus-costs/7441-tls-client-error.ts.json @@ -0,0 +1 @@ +{ "plain": 6387, "san": 6135 } diff --git a/tests/harness/corpus-costs/7442-tls-server-contexts.mjs.json b/tests/harness/corpus-costs/7442-tls-server-contexts.mjs.json new file mode 100644 index 000000000..8630193d3 --- /dev/null +++ b/tests/harness/corpus-costs/7442-tls-server-contexts.mjs.json @@ -0,0 +1 @@ +{ "plain": 2866, "san": 2854 } diff --git a/tests/harness/corpus-costs/7443-tls-final-flight.ts.json b/tests/harness/corpus-costs/7443-tls-final-flight.ts.json new file mode 100644 index 000000000..2eb48c40b --- /dev/null +++ b/tests/harness/corpus-costs/7443-tls-final-flight.ts.json @@ -0,0 +1 @@ +{ "plain": 8793, "san": 8889 } diff --git a/tests/harness/corpus-costs/7444-tls-final-flight.mjs.json b/tests/harness/corpus-costs/7444-tls-final-flight.mjs.json new file mode 100644 index 000000000..ddc1e1368 --- /dev/null +++ b/tests/harness/corpus-costs/7444-tls-final-flight.mjs.json @@ -0,0 +1 @@ +{ "plain": 3913, "san": 3715 } diff --git a/tests/harness/corpus-costs/7454-tls-peer-embedded-pem.ts.json b/tests/harness/corpus-costs/7454-tls-peer-embedded-pem.ts.json new file mode 100644 index 000000000..034b69448 --- /dev/null +++ b/tests/harness/corpus-costs/7454-tls-peer-embedded-pem.ts.json @@ -0,0 +1 @@ +{ "plain": 2535, "san": 3999 } diff --git a/tests/harness/corpus-costs/7455-tls-als-contexts.mjs.json b/tests/harness/corpus-costs/7455-tls-als-contexts.mjs.json new file mode 100644 index 000000000..53219fb76 --- /dev/null +++ b/tests/harness/corpus-costs/7455-tls-als-contexts.mjs.json @@ -0,0 +1 @@ +{ "plain": 3306, "san": 4617 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index f8068ac81..20231a94e 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -954,8 +954,8 @@ listener.listen(0, () => { }); // With a secureContext, the TLSSocket's own options still apply: a truthy -// requestCert, which asks for client certificates this runtime does not -// verify, and SNICallback throw the fence instead of being dropped. +// requestCert (which lowers with cert and key, whose ca the runtime reads) +// and SNICallback throw the fence instead of being dropped. describe("tls.TLSSocket socket options beside a secureContext", () => { test("requestCert and SNICallback throw the fence", async () => { const r = await compileAndRun( @@ -990,7 +990,7 @@ listener.listen(0, () => { ); expect(r.stderr).toBe(""); expect(r.stdout).toBe( - "requestCert new tls.TLSSocket option 'requestCert' has no static lowering — client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert stays at Node's false default\n" + + "requestCert new tls.TLSSocket option 'requestCert' has no static lowering — requestCert lowers with cert and key options, not beside a secureContext\n" + "SNICallback new tls.TLSSocket option 'SNICallback' has no static lowering — isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members\n" + "requestCert false no error\n" + "client closed\n", @@ -998,6 +998,93 @@ listener.listen(0, () => { }); }); +// A client certificate lowers as a cert and key pair; one without the +// other throws the fence before any connection is made. +describe("client certificates without both cert and key", () => { + test("tls.connect and https.request throw the fence", async () => { + const r = await compileAndRun( + "tls-client-certificate-fences", + `import { readFileSync } from "node:fs"; +import * as https from "node:https"; +import * as tls from "node:tls"; +const certs = "tests/fixtures/server/certs"; +const cert = readFileSync(\`\${certs}/client.pem\`); +const key = readFileSync(\`\${certs}/client-key.pem\`); +function report(label: string, run: () => void): void { + try { + run(); + console.log(label, "no error"); + } catch (error) { + console.log(label, (error as Error).message); + } +} +report("connect cert only", () => tls.connect({ port: 1, host: "127.0.0.1", cert })); +report("connect key only", () => tls.connect({ port: 1, host: "127.0.0.1", key })); +report("request key only", () => https.request({ host: "127.0.0.1", port: 1, path: "/", key })); +`, + ); + expect(r.stderr).toBe(""); + expect(r.stdout).toBe( + "connect cert only tls.connect option 'cert' has no static lowering — a client certificate lowers with both cert and key\n" + + "connect key only tls.connect option 'key' has no static lowering — a client certificate lowers with both cert and key\n" + + "request key only https.request option 'key' has no static lowering — a client certificate lowers with both cert and key\n", + ); + }); +}); + +// server.addContext and setSecureContext answer on tls and https servers; +// a plain net server, a servername Node's RegExp would read as groups or +// alternation, and one outside ASCII throw the fence. A handshake failure +// whose OpenSSL reason the runtime does not know reaches 'tlsClientError' +// as a coded SC2020 error. +describe("tls.Server contexts and client errors without a lowering", () => { + test("addContext and setSecureContext fences, and an unknown handshake failure", async () => { + const r = await compileAndRun( + "tls-server-context-fences", + `import { readFileSync } from "node:fs"; +import * as net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; +const certs = "tests/fixtures/server/certs"; +const cert = readFileSync(\`\${certs}/localhost.pem\`); +const key = readFileSync(\`\${certs}/localhost-key.pem\`); +function report(label: string, run: () => void): void { + try { + run(); + console.log(label, "no error"); + } catch (error) { + console.log(label, (error as Error).message); + } +} +const plain = net.createServer() as unknown as tls.Server; +report("net addContext", () => plain.addContext("a.test", { cert, key })); +report("net setSecureContext", () => plain.setSecureContext({ cert, key })); +const server = tls.createServer({ cert, key }); +report("alternation", () => server.addContext("a.test|b.test", { cert, key })); +report("group", () => server.addContext("(a).test", { cert, key })); +report("non-ASCII", () => server.addContext("\u00e9.test", { cert, key })); +server.on("tlsClientError", (err: Error) => console.log("tlsClientError", (err as NodeJS.ErrnoException).code, err.message)); +server.listen(0, "127.0.0.1", () => { + const raw = net.connect((server.address() as AddressInfo).port, "127.0.0.1", () => { + raw.write(Buffer.from([0x16, 0x03, 0x01, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00])); + }); + raw.on("data", () => {}); + raw.on("close", () => server.close()); +}); +`, + ); + expect(r.stderr).toBe(""); + expect(r.stdout).toBe( + "net addContext server.addContext on a server that is not a tls or https server is not supported yet\n" + + "net setSecureContext server.setSecureContext on a server that is not a tls or https server is not supported yet\n" + + "alternation server.addContext option 'servername' has no static lowering — a servername with regular-expression groups or alternation has no lowering\n" + + "group server.addContext option 'servername' has no static lowering — a servername with regular-expression groups or alternation has no lowering\n" + + "non-ASCII server.addContext option 'servername' has no static lowering — a servername outside ASCII has no lowering\n" + + "tlsClientError SC2020 a TLS handshake failure (mbedTLS error -0x7300) without the reason OpenSSL gives is not supported yet\n", + ); + }); +}); + // A curve Node lists in crypto.getCurves() but the runtime does not // implement throws a coded SC2020 error naming the curve when the name is // computed; literal names are refused at compile time (node-types-fenced). @@ -1989,6 +2076,70 @@ await attempt("subtle verify", () => crypto.subtle.verify(name, raw, new Uint8Ar }); }); +// An X509Certificate's Ed25519 key needs the same unit: without it the +// certificate's public key, issuer and names still read, while verify +// rejects with the coded SC2020 Error. 7091 pins the linked path. +describe("X509Certificate Ed25519 keys without the Ed25519 unit", () => { + test("verify refuses with SC2020 in a program that never names Ed25519", async () => { + const r = await compileAndRun( + "x509-ed25519-unit-not-linked", + `import { X509Certificate } from "node:crypto"; +const cert = new X509Certificate("-----BEGIN CERTIFICATE-----\\nMIIBljCCAUigAwIBAgIUNFuRt3zKS19UAUMdLWewqHrGBigwBQYDK2VwMC8xFDAS\\nBgNVBAoMC0V4YW1wbGUgT3JnMRcwFQYDVQQDDA5lZC5leGFtcGxlLm5ldDAgFw0y\\nNjEwMjIxMDEwMTJaGA8yMTI2MDkyODEwMTAxMlowLzEUMBIGA1UECgwLRXhhbXBs\\nZSBPcmcxFzAVBgNVBAMMDmVkLmV4YW1wbGUubmV0MCowBQYDK2VwAyEAOCSLKkel\\nkOH/5aqUwI3PNJVUUNyhgu2s19LFC254fNCjdDByMB0GA1UdDgQWBBR+50zUcqL8\\n739jh6/rEYojULDmlTAfBgNVHSMEGDAWgBR+50zUcqL8739jh6/rEYojULDmlTAP\\nBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQYMBaCDmVkLmV4YW1wbGUubmV0hwTAAAIH\\nMAUGAytlcANBAC/c0Bi0SVUnHCg/1pn7MaCgVivSHuoISi2SCq/0EnLefRdgsIir\\n0QxjVnxeuKN2Y1yfEO/R1W8Nwovo1l2M1gQ=\\n-----END CERTIFICATE-----\\n"); +console.log(cert.publicKey.asymmetricKeyType?.length, cert.checkIssued(cert), cert.checkHost("ed.example.net")); +try { + console.log(cert.verify(cert.publicKey)); +} catch (e) { + const err = e as NodeJS.ErrnoException; + console.log(err.name, err.code, err.message); +} +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stderr).toBe(""); + expect(r.stdout).toBe( + "7 true ed.example.net\n" + + "Error SC2020 Ed25519 keys are supported when a string literal in the program names 'ed25519'\n", + ); + }); +}); + +// A TLS socket held in an untyped JavaScript value has no lowering for its +// certificate accessors (they link the X.509 unit, which a dynamic call +// cannot ask for), so the call throws the fence; a socket whose type is +// known runs them (corpus 7095, 7096). +describe("tlsSocket.getPeerCertificate on a dynamic value (JS lane)", () => { + test("throws the fence", async () => { + const r = await compileAndRun( + "tls-peer-certificate-dynamic", + `import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; +const certs = "tests/fixtures/server/certs"; +const server = tls.createServer({ cert: readFileSync(\`\${certs}/localhost.pem\`), key: readFileSync(\`\${certs}/localhost-key.pem\`) }, (s) => s.end()); +function peer(socket) { + try { + return socket.getPeerCertificate().subject.CN; + } catch (e) { + return e.message; + } +} +server.listen(0, () => { + const c = tls.connect({ port: server.address().port, ca: readFileSync(\`\${certs}/ca.pem\`), servername: "localhost" }, () => { + console.log(peer(c)); + c.destroy(); + server.close(); + }); +}); +`, + "mjs", + ); + expect(r.exitCode).toBe(0); + expect(r.stderr).toBe(""); + expect(r.stdout).toBe( + "'TLSSocket.prototype.getPeerCertificate' on a dynamic value is not supported yet\n", + ); + }); +}); + describe("inspecting error subclasses with properties created at run time (JS lane)", () => { test("an instance whose constructor created a property is a named fence", async () => { // Node prints `[Conditional [Error]: m] { extra: 'x' }`. The property diff --git a/tests/harness/library-asyncfree.test.ts b/tests/harness/library-asyncfree.test.ts index d924f26d1..9b569c584 100644 --- a/tests/harness/library-asyncfree.test.ts +++ b/tests/harness/library-asyncfree.test.ts @@ -106,6 +106,15 @@ describe("async_free detection over the IR", () => { ).toContain("asymmetric KeyObjects"); }); + test("X509Certificate's key members refuse", async () => { + expect( + await surfaceOf( + "x509-key", + `import { X509Certificate } from "node:crypto";\nimport { readFileSync } from "node:fs";\nconst cert = new X509Certificate(readFileSync("cert.pem"));\nconsole.log(cert.verify(cert.publicKey));\n`, + ), + ).toContain("X509Certificate publicKey, verify and checkPrivateKey"); + }); + test("process signal listeners refuse", async () => { expect( await surfaceOf( diff --git a/tests/harness/library-mode.test.ts b/tests/harness/library-mode.test.ts index db4fabb3e..47b77f0fd 100644 --- a/tests/harness/library-mode.test.ts +++ b/tests/harness/library-mode.test.ts @@ -299,6 +299,21 @@ wrap empty: len 2 bytes 60 62 expect(run.stdout).toBe(BUFFERS_EXPECTED); }); + /* ── X509Certificate fields: scr_x509.c rides the archive ────────────── */ + + test("X509Certificate fields in a library", async () => { + const { archive, outDir } = await buildLibrary("x509", emission); + const probe = buildProbe("x509", archive, outDir); + const run = runProbe(probe); + expect(run.signal).toBeNull(); + expect(run.status).toBe(0); + expect(run.stdout).toBe( + "C=US\nST=California\nL=San Francisco\nO=Example Org\nOU=Dev Team\nCN=Example Root CA\nemailAddress=ca@example.com" + + "|0123456789ABCDEF|Sep 27 16:33:52 2126 GMT|true|" + + "7C:5F:CA:18:57:BF:BD:FC:67:AC:BD:21:1C:67:E8:C3:73:48:35:4E:4D:95:A8:2B:83:FF:E0:5A:E7:0A:97:A9\n", + ); + }); + /* ── cstring: NUL-terminated text in, out, and through callbacks ─────── */ test("cstring parameters, results, and callback arguments", async () => { diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index fd2077a48..ea5ddd51f 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -100,6 +100,13 @@ for (const [name, title] of [ "eventLoopUtilization and monitorEventLoopDelay follow @types/node's types", ], ["timeout-type", "NodeJS.Timeout fields, ref/unref/hasRef and clearInterval under @types/node"], + ["x509-methods", "X509Certificate's methods and the legacy object under @types/node"], + ["tls-peer-certificates", "TLS socket certificate accessors under @types/node"], + ["tls-client-certificates", "mutual TLS with client certificates under @types/node"], + [ + "tls-server-contexts", + "tls.Server addContext, setSecureContext and tlsClientError under @types/node", + ], ] as const) { test(`node-types: ${title}`, async () => { const outDir = outDirFor(name); diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c5476328e..90773c87f 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -52,6 +52,7 @@ const features: NativeLinkFeatures = { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, }; diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index 64ddd0213..859f820da 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -53,6 +53,7 @@ const base: NativeLinkFeatures = { dh: false, pki: false, ed25519: false, + x509: false, webcrypto: false, perfLoop: false, }; diff --git a/tests/harness/tls-socket-leaks.test.ts b/tests/harness/tls-socket-leaks.test.ts index 64edfac25..d657b0950 100644 --- a/tests/harness/tls-socket-leaks.test.ts +++ b/tests/harness/tls-socket-leaks.test.ts @@ -3,7 +3,9 @@ * every socket that closes, including one whose handshake fails, must drop * them. The sanitized lanes otherwise run with detect_leaks=0, so this runs * the TLSSocket programs with LeakSanitizer explicitly on. 7094's - * handshakes all fail. Linux only (Apple's ASan has no LeakSanitizer); + * handshakes all fail; 7095's detailed certificates link roots to + * themselves; 7097 and 7098 hold client certificates and the chains a ca + * completes. Linux only (Apple's ASan has no LeakSanitizer); * runs in the SCRIPTC_SAN=1 lane. */ import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; @@ -22,6 +24,12 @@ const programs = [ "tests/corpus/7087-tls-socket-wrap.ts", "tests/corpus/7088-tls-socket-connect-tunnel.ts", "tests/corpus/7094-tls-socket-failed-handshakes.ts", + "tests/corpus/7095-tls-peer-certificates.ts", + "tests/corpus/7097-tls-client-certificates.ts", + "tests/corpus/7098-tls-client-certificates-paths.ts", + "tests/corpus/7440-tls-server-add-context.ts", + "tests/corpus/7441-tls-client-error.ts", + "tests/corpus/7443-tls-final-flight.ts", ]; describe.skipIf(!sanitize || process.platform !== "linux")( diff --git a/tests/harness/tls-trust-store.test.ts b/tests/harness/tls-trust-store.test.ts new file mode 100644 index 000000000..714de9057 --- /dev/null +++ b/tests/harness/tls-trust-store.test.ts @@ -0,0 +1,42 @@ +// TLS trust stores that depend on the process's NODE_EXTRA_CA_CERTS: a +// server's ca option, where an empty Buffer is an empty store and an empty +// string is the default one. The corpus cannot set the environment, so the +// fixture runs here under Node and as a binary with the test CA added. +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test("a server's empty ca Buffer is an empty trust store, as in Node", async () => { + const entry = join(repoRoot, "tests/fixtures/tls/empty-ca.ts"); + const outDir = join(cacheDir, `tls-trust-store-${sanitize ? "san" : "plain"}`); + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { + outPath: join(outDir, "empty-ca"), + outDir, + sanitize, + backend: "llvm", + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const env = { + ...process.env, + NODE_EXTRA_CA_CERTS: join(repoRoot, "tests/fixtures/server/certs/ca.pem"), + }; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath, [], { cwd: repoRoot, env }), + execFileAsync(process.execPath, ["--experimental-strip-types", "--no-warnings", entry], { + cwd: repoRoot, + env, + }), + ]); + expect(native.stdout).toBe(node.stdout); + expect(node.stdout).toContain("empty Buffer false SELF_SIGNED_CERT_IN_CHAIN"); +}, 120_000); diff --git a/tests/library-mode/x509/lib.ts b/tests/library-mode/x509/lib.ts new file mode 100644 index 000000000..9b023a727 --- /dev/null +++ b/tests/library-mode/x509/lib.ts @@ -0,0 +1,41 @@ +// X509Certificate fields in a library archive: the field code lives in +// scr_x509.c, which the archive carries when the program reads a field. +import * as crypto from "node:crypto"; + +const CA = `-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIIASNFZ4mrze8wDQYJKoZIhvcNAQELBQAwgZwxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRQwEgYDVQQKDAtFeGFtcGxlIE9yZzERMA8GA1UECwwIRGV2IFRlYW0xGDAW +BgNVBAMMD0V4YW1wbGUgUm9vdCBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs +ZS5jb20wIBcNMjYxMDIxMTYzMzUyWhgPMjEyNjA5MjcxNjMzNTJaMIGcMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j +aXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBUZWFtMRgw +FgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1w +bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0YGcCr0pxR8d +6qDdLuILcoT7vhtAjyHwfkoChnH6N3TLWzDUugnBp9NbDb7gDLqbRT4uTDoNb6zS +tEJZ4Q7/hhd0jDvoPmOy0Y3Kyj9uNsdf25GdbvwwEnlddELJCZprTZ4OhEg6w2s0 +Yy4SlviLzDXLmsggTypxeS+K8JHT9onMipi5L4GFu8n1QZbzXvSS4BkRgMXEyrtr +FOE9qSo2qkubFyd/WNKRTRIpTSc5/GGsqJ0FKDDJnN5adotcUucbJFn4noNnFUWh +M8mhBEj7ENDdJsD2EVFsI6H958jA2SbNNu4ngzAOs2AKNBYBfQK02ZQAdRNOSL3K +5FkfbII5LwIDAQABo2MwYTAdBgNVHQ4EFgQUP+yQyItpNYEls26Jfv+NnciGaK8w +HwYDVR0jBBgwFoAUP+yQyItpNYEls26Jfv+NnciGaK8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBABDw3Va/C+RFc/Hs +TJnHtQJtxFL0ZhnHUqg4DcDA3m+JDdIDNL8sNpLEbtn/pr0VeEWgG+MImWAQlrUW +IAZkLj9/Zg1wSO0oZ86ZyZJZmp54lUYXgG17CNl4g+F7VHt3bBwUJHTbWye/FKM6 +5OVZetCYKVj7Z+5BYuf7cumzvRqcn32fXidB/kjRX14SJ4JDPn2yFZWCr2ygIqWQ +SOuEmWbNAbYGaVZkQr2Ma8U28DWY2jb0f6nP8jLt49t9o6N/e6Fwd6I5t/mx9Mfj +0403omkxmr9vqN2kBamzVnun+2mDkRZcKPCr72Q4sfbvmDi/W5Nf1Iu2cQW8VfDO +SqpyT54= +-----END CERTIFICATE----- +`; + +export function describe(): string { + const cert = new crypto.X509Certificate(CA); + return [ + cert.subject, + cert.serialNumber, + cert.validTo, + String(cert.ca), + cert.fingerprint256, + ].join("|"); +} diff --git a/tests/library-mode/x509/probe.c b/tests/library-mode/x509/probe.c new file mode 100644 index 000000000..d8065d039 --- /dev/null +++ b/tests/library-mode/x509/probe.c @@ -0,0 +1,23 @@ +/* X509Certificate fields read through a library export. */ +#include +#include + +extern void kx_init(void); +extern void kx_set_panic_sink(void (*fn)(void *, const uint8_t *, size_t, uint64_t), void *ctx); +extern void kx_collect(void); +extern void kx_describe(const uint8_t **out, size_t *out_len); + +static void sink(void *ctx, const uint8_t *msg, size_t len, uint64_t addr) { + (void)ctx; (void)addr; + printf("SINK %.*s\n", (int)len, msg); +} + +int main(void) { + kx_set_panic_sink(sink, NULL); + kx_init(); + const uint8_t *s; size_t n; + kx_describe(&s, &n); + printf("%.*s\n", (int)n, s); + kx_collect(); + return 0; +} diff --git a/tests/library-mode/x509/profile.json b/tests/library-mode/x509/profile.json new file mode 100644 index 000000000..fe9b4fb59 --- /dev/null +++ b/tests/library-mode/x509/profile.json @@ -0,0 +1,14 @@ +{ + "profile_format": 1, + "name": "conformance-x509", + "entry": "lib.ts", + "emission": "llvm", + "abi": { + "prefix": "kx_", + "init_symbol": "kx_init", + "sink_register_symbol": "kx_set_panic_sink", + "collect_symbol": "kx_collect", + "result_reset_symbol": null + }, + "exports": [{ "export": "describe", "symbol": "kx_describe", "params": [], "returns": "string" }] +} From b72e164e169d18ca6e23a09b715b3dc186226217 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 18:18:27 -0500 Subject: [PATCH 031/395] Track views through more flows and standard library calls --- .../compiler/src/frontend/lowering/lowerer.ts | 79 ++++++++++++++----- packages/runtime/src/scr_array.c | 3 + .../corpus/6067-view-holes-in-typed-arrays.ts | 58 ++++++++++++++ .../6068-foreign-elements-through-views.ts | 25 ++++++ 4 files changed, 145 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index b23ee147a..9fb65bd4c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6338,6 +6338,20 @@ export class Lowerer { let dynamicViews: Set | null = null; // Element ABIs of the native arrays that an `any` or `unknown` value is // converted to, including arrays nested in the converted value. + // Assignments, including the logical ones (`view ??= numbers`). + const assignmentOperators = new Set([ + ts.SyntaxKind.EqualsToken, + ts.SyntaxKind.QuestionQuestionEqualsToken, + ts.SyntaxKind.BarBarEqualsToken, + ts.SyntaxKind.AmpersandAmpersandEqualsToken, + ]); + // Initializers of class properties, parameter defaults and + // destructuring defaults, which convert to the binding's type. + const initializedBinding = ( + node: ts.Node, + ): node is ts.PropertyDeclaration | ts.ParameterDeclaration | ts.BindingElement => + (ts.isPropertyDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) && + node.initializer !== undefined; const collectDynamicViews = (): Set => { const keys = new Set(); const dynamicValue = (node: ts.Expression): boolean => { @@ -6388,14 +6402,18 @@ export class Lowerer { dynamicValue(node.initializer) ) target(typeOrNull(node.name)); + else if (initializedBinding(node) && dynamicValue(node.initializer!)) + target(typeOrNull(node.name ?? node)); else if ( ts.isBinaryExpression(node) && - node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + assignmentOperators.has(node.operatorToken.kind) && dynamicValue(node.right) ) target(typeOrNull(node.left)); else if (ts.isReturnStatement(node) && node.expression && dynamicValue(node.expression)) target(contextualOrNull(node.expression)); + else if (ts.isYieldExpression(node) && node.expression && dynamicValue(node.expression)) + target(contextualOrNull(node.expression)); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { for (const arg of node.arguments?.nodes ?? []) if (!ts.isSpreadElement(arg) && dynamicValue(arg)) target(contextualOrNull(arg)); @@ -6427,26 +6445,45 @@ export class Lowerer { // Element ABIs of the native arrays a value converted to a dynamic type // can reach: the conversion keeps each such array as a live view, so // stores through the dynamic value write the native storage. - // A standard library function only reads an array argument it takes as - // `any` (console.log, JSON.stringify, String), except the reflective - // writes: Object.assign and defineProperty, Reflect.set and the like, - // and Array.prototype methods through call or apply. - const reflectiveWriters = new Set([ - "assign", - "defineProperty", - "defineProperties", - "setPrototypeOf", - "set", - "apply", - "call", + // Standard library functions that only read the arguments they take as + // `any` or `unknown`: the console methods, String, Number, Boolean, + // Array.isArray, Object.is, JSON.stringify without a replacer function, + // and util's inspect and format. Any other function can keep an + // argument (Promise.resolve, Map.prototype.set, push onto an + // `unknown[]`) or pass it to a callback (a JSON.stringify replacer, an + // emitted event), which can write through it. + const readerGlobals = new Map | null>([ + ["console", null], + ["JSON", new Set(["stringify"])], + ["Array", new Set(["isArray"])], + ["Object", new Set(["is"])], ]); + const readerFunctions = new Set(["String", "Number", "Boolean"]); + const utilReaders = new Set(["inspect", "format"]); const standardLibraryReader = (node: ts.CallExpression | ts.NewExpression): boolean => panicSafe(() => { + if (!ts.isCallExpression(node)) return false; const callee = peel(node.expression); - if (ts.isPropertyAccessExpression(callee) && reflectiveWriters.has(callee.name.text)) - return false; + if (ts.isIdentifier(callee)) + return [...readerFunctions].some((name) => this.isStdlibGlobal(callee, name)); + if (!ts.isPropertyAccessExpression(callee)) return false; + const member = callee.name.text; + for (const [global, members] of readerGlobals) { + if (!this.isStdlibGlobal(callee.expression, global)) continue; + if (members !== null && !members.has(member)) return false; + if (global !== "JSON") return true; + const replacer = node.arguments.nodes[1]; + return ( + replacer === undefined || + ts.isArrayLiteralExpression(peel(replacer)) || + peel(replacer).kind === ts.SyntaxKind.NullKeyword + ); + } + if (!utilReaders.has(member)) return false; const declaration = this.checker.getResolvedSignature(node)?.declaration; - return declaration !== undefined && this.isStdlibFile(ts.getSourceFileOfNode(declaration)); + if (declaration === undefined) return false; + const file = ts.getSourceFileOfNode(declaration); + return this.isStdlibFile(file) && /[\\/]util\.d\.ts$/.test(file.fileName); }, false); const collectNativeViews = (): Set => { const keys = new Set(); @@ -6504,13 +6541,15 @@ export class Lowerer { flow(node.expression, typeOrNull(node)); else if (ts.isVariableDeclaration(node) && node.type && node.initializer) flow(node.initializer, typeOrNull(node.name)); - else if ( - ts.isBinaryExpression(node) && - node.operatorToken.kind === ts.SyntaxKind.EqualsToken - ) + else if (initializedBinding(node)) flow(node.initializer!, typeOrNull(node.name ?? node)); + else if (ts.isBinaryExpression(node) && assignmentOperators.has(node.operatorToken.kind)) flow(node.right, typeOrNull(node.left)); else if (ts.isReturnStatement(node) && node.expression) flow(node.expression, contextualOrNull(node.expression)); + else if (ts.isYieldExpression(node) && node.expression) + flow(node.expression, contextualOrNull(node.expression)); + // A thrown value reaches a catch binding, which is dynamic. + else if (ts.isThrowStatement(node)) flow(node.expression, null); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { if (!standardLibraryReader(node)) for (const arg of node.arguments?.nodes ?? []) diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 098a72c4f..a243c94b8 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -2195,6 +2195,8 @@ ScrArr *scr_arr_fill_undefined(ScrArr *a, double start, double end) { * pending when a conversion throws. +1 malloc'd buffer. */ static double *scr_arr_coerced_numbers(const ScrArr *a, size_t *count) { size_t n = a->len; + /* A sparse array's length can exceed what the buffers can count. */ + if (n > SIZE_MAX / sizeof(double)) scr_arr_oom(); double *out = malloc((n ? n : 1) * sizeof(*out)); ScrDyn **held = calloc(n ? n : 1, sizeof(*held)); if (!out || !held) scr_arr_oom(); @@ -3555,6 +3557,7 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { size_t length = a->len; size_t dense = a->len < a->cap ? a->len : a->cap; size_t cap = dense + SCR_AX(a)->sparse_len; + if (cap > SIZE_MAX / sizeof(uint64_t)) scr_arr_oom(); uint64_t *slots = cap ? malloc(cap * sizeof(*slots)) : NULL; uint8_t *states = cap ? malloc(cap) : NULL; ScrStr **keys = cap ? malloc(cap * sizeof(*keys)) : NULL; diff --git a/tests/corpus/6067-view-holes-in-typed-arrays.ts b/tests/corpus/6067-view-holes-in-typed-arrays.ts index b59437646..831b3be7a 100644 --- a/tests/corpus/6067-view-holes-in-typed-arrays.ts +++ b/tests/corpus/6067-view-holes-in-typed-arrays.ts @@ -55,3 +55,61 @@ const bag = named as unknown as Record; bag["label"] = "x"; for (const value of named) console.log("named", value); console.log(named.length, bag["label"]); + +// Views that reach dynamic code through class field initializers, +// parameter defaults, logical assignments, yield and throw, and through +// standard library calls that keep their argument or pass it to a +// callback: Promise.resolve, push onto an `unknown[]`, Map.prototype.set +// and a JSON.stringify replacer. +const fieldNumbers: number[] = [1, 2]; +class FieldView { + view: unknown[] = fieldNumbers; +} +new FieldView().view[3] = 4; +for (const value of fieldNumbers) console.log("field", value); +const defaultNumbers: number[] = [1]; +function extend(view: unknown[] = defaultNumbers): void { + view.length = 3; +} +extend(); +console.log(defaultNumbers.findIndex((value) => value === undefined), defaultNumbers.length); +let assigned: unknown[] | undefined; +const assignedNumbers: number[] = [5]; +assigned ??= assignedNumbers; +assigned[2] = 6; +for (const value of assignedNumbers) console.log("assigned", value); +const yieldedNumbers: number[] = [7]; +function* yieldViews(): Generator { + yield yieldedNumbers; +} +for (const view of yieldViews()) view[2] = 8; +console.log(yieldedNumbers.findLastIndex((value) => value === undefined), yieldedNumbers.length); +const thrownNumbers: number[] = [9]; +try { + throw thrownNumbers; +} catch (error) { + (error as unknown[])[2] = 10; +} +for (const value of thrownNumbers) console.log("thrown", value); +const retainedNumbers: number[] = [12]; +const retained: unknown[] = []; +retained.push(retainedNumbers); +(retained[0] as unknown[])[2] = 13; +for (const value of retainedNumbers) console.log("kept", value); +const mappedNumbers: number[] = [14]; +const byName = new Map(); +byName.set("a", mappedNumbers); +(byName.get("a") as unknown[])[2] = 15; +console.log(mappedNumbers.findIndex((value) => value === undefined), mappedNumbers.length); +const replacedNumbers: number[] = [16]; +JSON.stringify({ replacedNumbers: replacedNumbers }, (key, value: unknown) => { + if (key === "replacedNumbers") (value as unknown[])[2] = 17; + return value; +}); +for (const value of replacedNumbers) console.log("replaced", value); +const resolvedNumbers: number[] = [11]; +void Promise.resolve(resolvedNumbers).then((view) => { + (view as unknown[])[2] = 18; + for (const value of resolvedNumbers) console.log("resolved", value); +}); + diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 9a64ebc70..5181d7fe5 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -112,6 +112,31 @@ paddedView[1] = { }; padded.sort(); console.log(padded.length, 3 in padded, 4 in padded, padded[5]); +const spread: number[] = [4, 3]; +const spreadView: unknown[] = spread; +let spreadCalls = 0; +spreadView.push({ + toString(): string { + if (spreadCalls++ === 0) { + spreadView[1000] = 1; + for (let i = 0; i < 64; i++) spread.push(i); + } + return "2"; + }, +}); +console.log(spread.toSorted().length, spread.length); +spread.sort(); +console.log(spread.length, spread[0], spread[1], spread[3], 999 in spread, spread[1000]); +const grownMath: number[] = [1]; +const grownMathView: unknown[] = grownMath; +grownMathView[10] = { + valueOf(): number { + grownMathView[5000] = 7; + for (let i = 0; i < 256; i++) grownMath.push(i); + return 3; + }, +}; +console.log(Math.max(...grownMath), grownMath.length); // A conversion that removes the element it converts. const shrinking: number[] = [1]; From f0da37fa423971fcee011da2aa66137975c294c5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 19:52:48 -0500 Subject: [PATCH 032/395] Compile classes that extend Array A class declared `extends Array` (or `extends Array`) was refused with "extending classes not declared in the program ('Array')", which blocked lru-cache at module load. An instance is now an array of the class's element type: its struct begins with the runtime's array header, whose reference count it shares, and the class's own fields follow it. The class's TypeScript type maps to its element array type, so every array operation applies to an instance, and passing it where an array is expected passes the same object. The class's own fields, methods, accessors and statics read the array as the class object. super() follows the Array constructor: one number argument sets the length (a RangeError for an invalid one), other arguments become the elements, and the implicit constructor forwards its arguments the same way. The runtime finds the class through the array's ext (ScrArrClass: name, destroy, the own fields, the species constructor), so instanceof the class (or a binding that holds it) works on static, array-typed and dyn values, util.inspect prints `Name(length) [ ..., field: value ]`, Object.keys lists the own fields, and a release through any array path reaches the class's teardown. Instances whose elements or fields can form cycles carry the collector header and are traced. map, filter, slice, splice, concat, flat and flatMap construct their result through the class as ArraySpeciesCreate does: `new C(length)` with the specification's argument, then the elements, then the length where the specification sets it. A map to another element type retypes the new instance when its constructor added no elements. Array.of and Array.from called on the class construct through it. Programs that declare no Array subclass compile as before. A generic subclass, a subclass of such a class and `static [Symbol.species]` are refused at compile time; a species result the class's constructor cannot construct throws a TypeError. String() of an array with checked-dynamic elements now uses the runtime's String() instead of being refused. Corpus 7720-7726, diagnostics fixture array-subclass-fences.ts. --- docs/content/docs/limitations.mdx | 2 + .../array-subclasses.mdx | 3 + .../compiler/src/backend/cycle-analysis.ts | 4 + packages/compiler/src/backend/llvm/classes.ts | 135 +++- .../compiler/src/backend/llvm/expr-calls.ts | 12 + .../compiler/src/backend/llvm/lib-dispatch.ts | 48 ++ .../compiler/src/backend/llvm/lib-shared.ts | 5 + packages/compiler/src/backend/llvm/shapes.ts | 45 +- .../lowering/builtins/array-classes.ts | 610 ++++++++++++++++++ .../lowering/containers/array-construction.ts | 9 +- .../frontend/lowering/expressions/binary.ts | 6 + .../lowering/expressions/property-access.ts | 32 + .../lowering/expressions/relational.ts | 3 + .../src/frontend/lowering/lower-calls.ts | 18 + .../src/frontend/lowering/lower-classes.ts | 55 +- .../src/frontend/lowering/lower-exprs.ts | 42 +- .../src/frontend/lowering/lower-modules.ts | 6 + .../compiler/src/frontend/lowering/lowerer.ts | 41 +- packages/compiler/src/frontend/type-mapper.ts | 36 ++ .../compiler/src/ir/builtin-signatures.ts | 17 + .../src/ir/function-references.test.ts | 10 + .../compiler/src/ir/function-references.ts | 2 + packages/compiler/src/ir/ir.ts | 11 + packages/compiler/src/ir/traverse.ts | 1 + packages/compiler/src/ir/validate.ts | 43 ++ .../corpus/7720-array-subclass-basics.ts.json | 6 + .../7721-array-subclass-species.ts.json | 6 + .../7722-array-subclass-zero-array.js.json | 6 + .../7723-array-subclass-as-array.ts.json | 6 + .../corpus/7724-array-subclass-cycles.ts.json | 6 + .../7725-array-subclass-operations.ts.json | 6 + .../7726-array-subclass-expressions.js.json | 6 + .../diagnostics/array-subclass-fences.ts.json | 6 + packages/runtime/src/scr_array.c | 242 ++++++- packages/runtime/src/scr_array.h | 58 ++ packages/runtime/src/scr_inspect.c | 26 +- tests/corpus/7720-array-subclass-basics.ts | 44 ++ tests/corpus/7721-array-subclass-species.ts | 37 ++ .../corpus/7722-array-subclass-zero-array.js | 16 + tests/corpus/7723-array-subclass-as-array.ts | 21 + tests/corpus/7724-array-subclass-cycles.ts | 36 ++ .../corpus/7725-array-subclass-operations.ts | 46 ++ .../corpus/7726-array-subclass-expressions.js | 21 + tests/diagnostics/array-subclass-fences.ts | 21 + .../array-subclass-fences.ts.txt | 41 ++ .../7720-array-subclass-basics.ts.json | 1 + .../7721-array-subclass-species.ts.json | 1 + .../7722-array-subclass-zero-array.js.json | 1 + .../7723-array-subclass-as-array.ts.json | 1 + .../7724-array-subclass-cycles.ts.json | 1 + .../7725-array-subclass-operations.ts.json | 1 + .../7726-array-subclass-expressions.js.json | 1 + 52 files changed, 1818 insertions(+), 42 deletions(-) create mode 100644 docs/content/limitations/types-and-class-definitions/array-subclasses.mdx create mode 100644 packages/compiler/src/frontend/lowering/builtins/array-classes.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json create mode 100644 tests/corpus/7720-array-subclass-basics.ts create mode 100644 tests/corpus/7721-array-subclass-species.ts create mode 100644 tests/corpus/7722-array-subclass-zero-array.js create mode 100644 tests/corpus/7723-array-subclass-as-array.ts create mode 100644 tests/corpus/7724-array-subclass-cycles.ts create mode 100644 tests/corpus/7725-array-subclass-operations.ts create mode 100644 tests/corpus/7726-array-subclass-expressions.js create mode 100644 tests/diagnostics/array-subclass-fences.ts create mode 100644 tests/harness/__snapshots__/array-subclass-fences.ts.txt create mode 100644 tests/harness/corpus-costs/7720-array-subclass-basics.ts.json create mode 100644 tests/harness/corpus-costs/7721-array-subclass-species.ts.json create mode 100644 tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json create mode 100644 tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json create mode 100644 tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json create mode 100644 tests/harness/corpus-costs/7725-array-subclass-operations.ts.json create mode 100644 tests/harness/corpus-costs/7726-array-subclass-expressions.js.json diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index a86ed217e..862e49b12 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -52,6 +52,8 @@ title: "Limitations" ../limitations/types-and-class-definitions/static-field-updates.mdx +../limitations/types-and-class-definitions/array-subclasses.mdx + ../limitations/types-and-class-definitions/local-class-expressions.mdx ../limitations/types-and-class-definitions/constructor-function-inheritance.mdx diff --git a/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx b/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx new file mode 100644 index 000000000..ea76e7313 --- /dev/null +++ b/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx @@ -0,0 +1,3 @@ +- **Array subclasses:** A class that extends `Array` compiles: its instances are arrays, and `map`, `filter`, `slice` and the other methods that create arrays return instances of the subclass, as in Node.js. + A generic subclass (`class Bag extends Array`), a class that extends such a subclass, and a subclass that declares `Symbol.species` are unsupported. + A method whose result the subclass's constructor cannot construct throws a `TypeError`: the constructor's parameters must take the one length argument, and a `map` to another element type needs a constructor that adds no elements. diff --git a/packages/compiler/src/backend/cycle-analysis.ts b/packages/compiler/src/backend/cycle-analysis.ts index fbd96e854..b03979bd9 100644 --- a/packages/compiler/src/backend/cycle-analysis.ts +++ b/packages/compiler/src/backend/cycle-analysis.ts @@ -146,6 +146,10 @@ function analyzeCycles( ...(c.localCaptures !== undefined ? [{ name: "", type: { kind: "classval" as const, className: c.name } }] : []), + // An instance of a class that extends Array holds its elements. + ...(c.arrayElem !== undefined + ? [{ name: "", type: { kind: "array" as const, elem: c.arrayElem } }] + : []), ], })), ...(mod.records ?? []).map((r) => ({ diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index 82e34de90..d848b86fc 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -58,6 +58,7 @@ import { traceAdapter, type ShapeHost, youngEntryLines, + arrElemInit, } from "./shapes.js"; /** One virtual method slot of a hierarchy: the ROOT-MOST declaring class @@ -371,11 +372,34 @@ function runtimePrefixOf(meta: LlClassMeta): RuntimePrefix | null { * on classes rooted at a runtime base, then the stream-state slot on * stream-rooted ones. */ function headerEnd(meta: LlClassMeta): number { + if (meta.def.arrayElem !== undefined) return ARRAY_PREFIX.length + 1; if (!meta.hierarchy || meta.idInRc) return 1; if (streamRooted(meta)) return 5; return runtimePrefixOf(meta) ? 4 : 2; } +/** The array header a class declared `extends Array` begins with, after + * the size_t reference count: %ScrArr's len, cap, elem, cyc, spare, filled + * and data (scr_array.h). */ +const ARRAY_PREFIX = ["i32", "i32", "i8", "i8", "i16", "i32", "ptr"]; + +/** LLVM `c"..."` text of a string's UTF-8 bytes and a terminating NUL. */ +function nulTerminatedBytes(text: string): string { + let out = ""; + for (const byte of Buffer.from(text, "utf8")) + out += + byte >= 0x20 && byte < 0x7f && byte !== 0x22 && byte !== 0x5c + ? String.fromCharCode(byte) + : `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`; + return out + "\\00"; +} + +/** The class descriptor (ScrArrClass: name, destroy) of a class declared + * `extends Array`. */ +export function arrayClassDescriptor(className: string): string { + return `@${mangleClassStruct(className)}.arrcls`; +} + /** The GEP index of the ThreadSlots slot word (an i32) in a class whose * hierarchy holds ThreadSlots keys: right after the header words, so every * member of the hierarchy has it at the same offset. */ @@ -473,7 +497,11 @@ export interface ClassHost extends ShapeHost { * hierarchy folds away. The cycle analysis leaves the hierarchy untraced, so * no trace adapter ever visits an instance (as for every untraced class). */ export function programLifetimeClass(host: ClassHost, meta: LlClassMeta): boolean { - return host.programLifetime?.has(meta.root.def.name) === true && meta.root.def.runtime !== true; + return ( + host.programLifetime?.has(meta.root.def.name) === true && + meta.root.def.runtime !== true && + meta.root.def.arrayElem === undefined + ); } /** The reference count a program-lifetime instance is created with: the @@ -725,7 +753,12 @@ export function assignCompactLayouts( ): void { const isCompact = (className: string): boolean => { const target = metaMap.get(className); - return target !== undefined && target.def.runtime !== true && compact(target.root); + return ( + target !== undefined && + target.def.runtime !== true && + target.root.def.arrayElem === undefined && + compact(target.root) + ); }; const byPre = [...metaMap.values()].sort((a, b) => a.pre - b.pre); for (const meta of byPre) { @@ -900,13 +933,15 @@ export function emitClassShapes( // usual pointer reinterprets. const runtimePrefix = runtimePrefixOf(meta); const prefix = - meta.hierarchy && !meta.idInRc - ? streamRooted(meta) - ? ["ptr", "ptr", "ptr", "ptr"] - : runtimePrefix - ? ["ptr", "ptr", "ptr"] - : ["ptr"] - : []; + cls.arrayElem !== undefined + ? ARRAY_PREFIX + : meta.hierarchy && !meta.idInRc + ? streamRooted(meta) + ? ["ptr", "ptr", "ptr", "ptr"] + : runtimePrefix + ? ["ptr", "ptr", "ptr"] + : ["ptr"] + : []; const members = [...prefix, ...(meta.threadSlot ? ["i32"] : []), ...fieldTys]; typeDefs.push( `%${mangleClassStruct(cls.name)} = type { ${host.sizeType}${members.length ? ", " + members.join(", ") : ""} } ` + @@ -993,6 +1028,7 @@ export function emitClassShapes( const types = ownedFields(cls).map(({ field }) => storedFieldType(cls, field)); for (const t of types) fieldTypes.push(t); if (programLifetimeClass(host, meta)) continue; + if (cls.arrayElem !== undefined) continue; if (meta.hierarchy && (meta.children.length !== 0 || meta.root.def.runtime === true)) continue; if (!types.some((t) => isRefCounted(t) && needsBoundedRelease(t))) continue; const release = mangleClassRelease(cls.name); @@ -1009,6 +1045,12 @@ export function emitClassShapes( } const directTargets = usedDirectTargets(host, candidates, fieldTypes); + // The descriptor entries of a class declared `extends Array`: a helper + // the module defines, or null (a class that is never constructed has + // neither). + const defined = new Set(mod.functions.map((fn) => fn.name)); + const arrayClassFn = (name: string | undefined): string => + name !== undefined && defined.has(name) ? `@${mangleFunction(name)}` : "null"; for (const cls of emitted) { const meta = metaMap.get(cls.name)!; const struct = mangleClassStruct(cls.name); @@ -1090,6 +1132,7 @@ export function emitClassShapes( const destroy = `${meta.hierarchy ? mangleClassReleaseDirect(cls.name) : mangleClassRelease(cls.name)}_destroy`; const segmented = chained && + cls.arrayElem === undefined && !meta.compact && runtimePrefix === null && !isStreamRooted && @@ -1105,7 +1148,12 @@ export function emitClassShapes( // members). Runs at rc == 0. const teardown = (lines: string[]): void => { const release = (prefix: string, f: (typeof refFields)[number], t: number): string[] => { - const direct = bounded ? directDestroyTarget(host, f.type, directTargets) : null; + // An Array subclass instance's destroy releases its fields plainly + // (it takes no chain length). + const direct = + bounded && cls.arrayElem === undefined + ? directDestroyTarget(host, f.type, directTargets) + : null; if (direct === null) return [ ` call void ${releaseSym(host, f.type)}(ptr %v${t}) ; ${llvmCommentText(f.name)}`, @@ -1236,6 +1284,45 @@ export function emitClassShapes( reld.push(`done:`, ` ret void`, `}`, ``); defs.push(...reld); } + } else if (cls.arrayElem !== undefined) { + // An instance is an array: its release is the array's, which reaches + // the class's destroy (the descriptor's) when the count reaches zero. + host.declare(`declare void @scr_arr_release(ptr)`); + host.declare(`declare void @scr_arr_subclass_drop(ptr)`); + const destroy = `${mangleClassRelease(cls.name)}.arrdestroy`; + const destroyBody: string[] = [ + `define internal void @${destroy}(ptr %o) ${FN_ATTRS} { ; destroy ${cls.name} (an Array subclass instance)`, + `entry:`, + ]; + const fieldsTeardown: string[] = []; + teardown(fieldsTeardown); + const free = emitObjectFree(host, traced); + destroyBody.push( + ...fieldsTeardown.slice(0, fieldsTeardown.length - free.length), + ` call void @scr_arr_subclass_drop(ptr %o)`, + ...free, + ` ret void`, + `}`, + ``, + ); + const displayName = cls.name.includes(".") + ? cls.name.slice(cls.name.lastIndexOf(".") + 1) + : cls.name; + const jsName = cls.jsName ?? displayName; + defs.push( + ...destroyBody, + // The name is the class's own copy: its address identifies the + // class in dyn snapshots of an instance (scr_arr_copy_metadata). + `${arrayClassDescriptor(cls.name)}.name = private constant [${Buffer.byteLength(jsName, "utf8") + 1} x i8] c"${nulTerminatedBytes(jsName)}"`, + `${arrayClassDescriptor(cls.name)} = internal constant { ptr, ptr, ptr, ptr } { ptr ${arrayClassDescriptor(cls.name)}.name, ptr @${destroy}, ptr ${arrayClassFn(cls.arrayFields)}, ptr ${arrayClassFn(cls.arraySpecies)} }`, + ``, + `define internal void @${mangleClassRelease(cls.name)}(ptr %o) ${FN_ATTRS} { ; release ${cls.name} (the array's)`, + `entry:`, + ` call void @scr_arr_release(ptr %o)`, + ` ret void`, + `}`, + ``, + ); } else { const freeBody: string[] = []; teardown(freeBody); @@ -1323,6 +1410,13 @@ export function emitClassShapes( nw.push( ` store ${host.sizeType} ${lifetime ? programLifetimeRc(host.sizeType) : 1}, ptr %o`, ); + if (cls.arrayElem !== undefined) { + const init = arrElemInit(host, cls.arrayElem); + host.declare(`declare void @scr_arr_subclass_init(ptr, i8, ptr, ptr, i1 zeroext)`); + nw.push( + ` call void @scr_arr_subclass_init(ptr %o, i8 ${init.kind}, ptr ${init.ops}, ptr ${arrayClassDescriptor(cls.name)}, i1 ${traced})`, + ); + } if (lifetime && audit) { // --sanitize builds: the RC audit skips its leak report when the // program keeps program-lifetime objects (scr_console.c), and @@ -1359,7 +1453,12 @@ export function emitClassShapes( // Young instances: program-owned traced classes. Classes rooted at a // runtime base (errors, emitters, events, streams) carry runtime state // and stay with trial deletion, like every runtime family. - if (traced && !lifetime && (!meta.hierarchy || meta.root.def.runtime !== true)) + if ( + traced && + !lifetime && + (!meta.hierarchy || meta.root.def.runtime !== true) && + cls.arrayElem === undefined + ) nw.push(...youngEntryLines(host)); nw.push(` ret ptr ${meta.hierarchy ? "%o.vt" : "%o"}`, `}`, ``); defs.push(...nw); @@ -1382,6 +1481,14 @@ export function emitClassShapes( tracedFields.forEach((f, i) => { tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name))); }); + // An Array subclass instance's elements, when they are cycle-capable. + const tracedElements = + cls.arrayElem !== undefined && + traceAdapter(host, { kind: "array", elem: cls.arrayElem }) !== null; + if (tracedElements) { + host.declare(`declare void @scr_arr_trace_v(ptr, ptr, ptr)`); + tr.push(` call void @scr_arr_trace_v(ptr %o, ptr %visit, ptr %ctx) ; the elements`); + } tr.push(` ret void`, `}`, ``); defs.push(...tr); @@ -1399,6 +1506,12 @@ export function emitClassShapes( ); }); if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw")); + if (cls.arrayElem !== undefined) { + // The elements: traced ones are the collector's, the others are + // released with the storage. + host.declare(`declare void @scr_arr_subclass_gc_drop(ptr, i1 zeroext)`); + gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o, i1 ${tracedElements})`); + } gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``); defs.push(...gf); } diff --git a/packages/compiler/src/backend/llvm/expr-calls.ts b/packages/compiler/src/backend/llvm/expr-calls.ts index 6ff52619e..5a16dabf3 100644 --- a/packages/compiler/src/backend/llvm/expr-calls.ts +++ b/packages/compiler/src/backend/llvm/expr-calls.ts @@ -1,4 +1,5 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ +import { arrayClassDescriptor } from "./classes.js"; import { InternalCompilerError } from "../../errors.js"; import { newValueMayThrow } from "../../ir/analysis.js"; import { @@ -1154,6 +1155,17 @@ export function emitCallExpr( return host.own({ name: v.name, type: e.type }); } case "instanceOf": { + // An array is an instance of a class declared `extends Array` when + // its ext names that class (scr_arr_class_of). + if (e.value.type.kind === "array") { + const v = emitBorrowedInputs(host, [e.value])[0]!; + host.declare(`declare ptr @scr_arr_class_of(ptr)`); + const cls = host.B.tmp(); + const result = host.B.tmp(); + host.B.line(`${cls} = call ptr @scr_arr_class_of(ptr ${v.name})`); + host.B.line(`${result} = icmp eq ptr ${cls}, ${arrayClassDescriptor(e.className)}`); + return { name: result, type: e.type }; + } // O(1) preorder-interval test against the vtable the object // carries; the target's interval is a compile-time constant. if (e.value.type.kind !== "object") diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..06ac8aa0e 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1,3 +1,4 @@ +import { arrayClassDescriptor } from "./classes.js"; import { emitBigIntWrap } from "./bigint-wrap.js"; import { emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js"; import { borrowsIteratorInputs, borrowsJsonInputs } from "./checked-value-lifetimes.js"; @@ -276,6 +277,53 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll host.dyn.emitRecordSourceSpread(B, built.name, value.name, type.shapeId); return { name: "", type: e.type }; } + if (e.fn === "array.speciesAdopt") { + // The receiver is borrowed; the method's result moves into the call, + // which answers it or the species instance that replaces it. + const receiver = emitBorrowedInput(host, e.args[0]!); + const result = host.emitExpr(e.args[1]!); + host.toHeap(result); + const length = host.emitExpr(e.args[2]!); + const setLength = host.emitExpr(e.args[3]!); + host.declare(`declare ptr @scr_arr_species_adopt(ptr, ptr, double, i1 zeroext)`); + const out = B.tmp(); + B.line( + `${out} = call ptr @scr_arr_species_adopt(ptr ${receiver.name}, ptr ${result.name}, double ${length.name}, i1 zeroext ${setLength.name})`, + ); + const value = host.own({ name: out, type: e.type }); + host.emitPendingCheck(); + return value; + } + if (e.fn === "array.dynIsClass") { + const className = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null; + if (className === null) + throw new InternalCompilerError("llvm emitter bug: array.dynIsClass needs a class name"); + const value = emitBorrowedInput(host, e.args[0]!); + host.declare(`declare zeroext i1 @scr_dyn_is_array_class(ptr, ptr)`); + const out = B.tmp(); + B.line( + `${out} = call zeroext i1 @scr_dyn_is_array_class(ptr ${value.name}, ptr ${arrayClassDescriptor(className)})`, + ); + return { name: out, type: e.type }; + } + if (e.fn === "array.classConstructAdopt") { + const className = e.args[0]!.kind === "strLit" ? e.args[0]!.value : null; + if (className === null) + throw new InternalCompilerError( + "llvm emitter bug: array.classConstructAdopt needs a class name", + ); + const result = host.emitExpr(e.args[1]!); + host.toHeap(result); + const length = host.emitExpr(e.args[2]!); + host.declare(`declare ptr @scr_arr_class_construct_adopt(ptr, ptr, double)`); + const out = B.tmp(); + B.line( + `${out} = call ptr @scr_arr_class_construct_adopt(ptr ${arrayClassDescriptor(className)}, ptr ${result.name}, double ${length.name})`, + ); + const value = host.own({ name: out, type: e.type }); + host.emitPendingCheck(); + return value; + } if (e.fn === "dyn.typedRefIs" || e.fn === "dyn.typedRefKeyIs") { const literal = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null; if (e.fn === "dyn.typedRefKeyIs" && literal === null) diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index c9b125cd7..c706fb187 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1348,6 +1348,11 @@ export const LIB_FN_SYMS: Record = { "http.reqPipeSock": "scr_http_req_pipe_sock", "http.reqResume": "scr_http_req_resume", "http.reqPause": "scr_http_req_pause", + "array.subclassConstruct": "scr_arr_subclass_construct", + "array.subclassConstructDyn": "scr_arr_subclass_construct_dyn", + "array.speciesAdopt": "scr_arr_species_adopt", + "array.classConstructAdopt": "scr_arr_class_construct_adopt", + "array.dynIsClass": "scr_dyn_is_array_class", "http.reqDestroy": "scr_http_req_destroy", "http.reqSetEncoding": "scr_http_req_set_encoding", "http.resSetHeader": "scr_http_res_set_header", diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index b3f5084d3..08d0db9b4 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -1084,7 +1084,33 @@ export function arrNewCall( * live in its own block): capText must be "0". */ small = false, ): string { - const useRef = + const useRef = arrUsesRef(host, elem); + if (!useRef) { + if (small) { + host.declare(`declare ptr @scr_arr_new_small(i32)`); + return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`; + } + host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`); + return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`; + } + if (small) { + host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`); + return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`; + } + host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`); + return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`; +} + +/** The ScrElemKind and ops descriptor (or `null`) an array of `elem` + * starts with: the arrays arrNewCall constructs, for the instances of a + * class that extends Array (scr_arr_subclass_init). */ +export function arrElemInit(host: ShapeHost, elem: IrType): { kind: number; ops: string } { + if (!arrUsesRef(host, elem)) return { kind: elemKindNum(elem), ops: "null" }; + return { kind: host.compactArrayElem?.(elem) === true ? 6 : 5, ops: arrOps(host, elem) }; +} + +function arrUsesRef(host: ShapeHost, elem: IrType): boolean { + return ( elem.kind === "record" || elem.kind === "object" || elem.kind === "union" || @@ -1101,21 +1127,8 @@ export function arrNewCall( elem.kind === "dyn" || // native collection seeds/drains: traced REF elem.kind === "regex" || // RegExp values: scr_regex_* adapters, no trace (no refs inside) elem.kind === "date" || // Date objects: scr_date_* adapters, no trace (no refs inside) - (elem.kind === "array" && traceAdapter(host, elem) !== null); - if (!useRef) { - if (small) { - host.declare(`declare ptr @scr_arr_new_small(i32)`); - return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`; - } - host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`); - return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`; - } - if (small) { - host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`); - return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`; - } - host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`); - return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`; + (elem.kind === "array" && traceAdapter(host, elem) !== null) + ); } /** The %ScrArrOps descriptor (retain, release, trace, registry id) of a REF diff --git a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts new file mode 100644 index 000000000..0136eca08 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts @@ -0,0 +1,610 @@ +/* Classes declared `extends Array`. An instance is an array of the class's + * element type: its struct begins with the runtime's array header (the + * reference count is the array's) and the class's own fields follow it, so + * every array operation applies to the instance unchanged and passing it + * where a plain array is expected passes the same object. The class's + * TypeScript type maps to its element array type; the class's own members, + * construction and `instanceof` view the array as the class object + * (arrayClassView). */ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { stripTypeWrappers } from "../typed-arguments.js"; +import { dynUndefinedExpr, nodeThrowExpr } from "../../../ir/build.js"; +import { lowerArrayFromCall, lowerArrayOfCall } from "../containers/array-construction.js"; +import { storedClassValueType, type ClassInfo } from "../lower-classes.js"; +import { + BOOL, + DYN, + F64, + STRING, + UNDEFINED_T, + arrayOf, + type IrExpr, + type IrFunction, + type IrLocal, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../../ir/ir.js"; + +/** The element type of `class X extends Array` (`extends Array` in + * JavaScript reads `any[]`, whose elements are checked-dynamic). */ +export function arrayClassBase( + lowerer: Lowerer, + heritage: ts.ExpressionWithTypeArguments, +): { elem: IrType } { + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(heritage)); + // `extends Array` in JavaScript reads `any[]`: dyn elements. + if (isJsSourceFile(ts.getSourceFileOfNode(heritage)) && mapped?.kind !== "array") + return { elem: DYN }; + if (mapped?.kind !== "array") + lowerer.unsupported( + "SC1090", + heritage, + "extending Array with an element type that does not compile", + ); + return { elem: mapped!.kind === "array" ? mapped!.elem : DYN }; +} + +/** The class declared `extends Array` at the root of `info`'s chain, or null. */ +export function arrayRootOf(info: ClassInfo | null | undefined): ClassInfo | null { + for (let c = info ?? null; c; c = c.base) if (c.extendsArray) return c; + return null; +} + +/** The array a class instance is (a reinterpret: the same object). */ +export function arrayOfClassInstance(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr { + const root = arrayRootOf(info)!; + return { kind: "upcast", value, type: arrayOf(root.extendsArray!.elem), loc }; +} + +/** The class object an instance of `info` is, viewed from its array type + * (a reinterpret; the value is known to be an instance of the class). */ +export function arrayClassView(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "downcast", value, type: { kind: "object", className: info.def.name }, loc }; +} + +function thisArray(info: ClassInfo, thisLocal: IrLocal, loc: SrcLoc): IrExpr { + return arrayOfClassInstance( + info, + { + kind: "varRef", + localId: thisLocal.id, + type: { kind: "object", className: info.def.name }, + loc, + }, + loc, + ); +} + +/** The implicit constructor's super(...args): `pack` is its dyn argument + * array, applied with the Array constructor's length-or-elements rule. */ +export function arraySuperCall( + info: ClassInfo, + thisRef: IrExpr, + pack: IrExpr, + loc: SrcLoc, +): IrStmt { + const elem = arrayRootOf(info)!.extendsArray!.elem; + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "array.subclassConstructDyn", + args: [ + arrayOfClassInstance(info, thisRef, loc), + pack, + { kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc }, + ], + type: { kind: "void" }, + loc, + }, + loc, + }; +} + +/** An explicit super(...) in a class declared `extends Array`: one number + * argument sets the length; otherwise the arguments (spreads included) + * become the elements, with the same one-number rule at run time. */ +export function lowerArraySuperCall( + lowerer: Lowerer, + info: ClassInfo, + thisLocal: IrLocal, + superCall: ts.CallExpression, +): IrStmt[] { + const loc = locOf(superCall); + const elem = arrayRootOf(info)!.extendsArray!.elem; + const args = superCall.arguments.nodes; + if (args.length === 0) return []; + const only = args.length === 1 && !ts.isSpreadElement(args[0]!) ? args[0]! : null; + if (only !== null && (lowerer.typeOf(only).flags & ts.TypeFlags.NumberLike) !== 0) { + return [ + { + kind: "arraySetLength", + arr: thisArray(info, thisLocal, loc), + length: lowerer.lowerExprExpecting(only, { kind: "f64" }), + loc, + }, + ]; + } + const arrT = arrayOf(elem); + const elements: IrExpr[] = []; + const spreads: number[] = []; + for (const arg of args) { + if (ts.isSpreadElement(arg)) { + spreads.push(elements.length); + elements.push(lowerer.lowerExprExpecting(arg.expression, arrT)); + } else elements.push(lowerer.lowerExprExpecting(arg, elem)); + } + const pack: IrExpr = { + kind: "arrayLit", + elems: elements, + ...(spreads.length > 0 ? { spreads } : {}), + type: arrT, + loc, + } as IrExpr; + return [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "array.subclassConstruct", + args: [ + thisArray(info, thisLocal, loc), + pack, + { kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc }, + ], + type: { kind: "void" }, + loc, + }, + loc, + }, + ]; +} + +/** The ClassInfo of a class declared `extends Array`, by its declaration. */ +export function arrayClassOfDecl( + lowerer: Lowerer, + decl: ts.ClassLikeDeclaration, +): ClassInfo | null { + const symbol = decl.name ? lowerer.checker.getSymbolAtLocation(decl.name) : undefined; + let info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info) + for (const candidate of lowerer.classes.values()) if (candidate.decl === decl) info = candidate; + return info && arrayRootOf(info) ? info : null; +} + +/** The class that declares the member `access` names when that class is + * declared `extends Array` (its own fields, methods and accessors), or null + * for the Array members it inherits and every other access. */ +export function arrayClassMemberOwner( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): ClassInfo | null { + const symbol = lowerer.checker.getSymbolAtLocation(access.name); + const decl = symbol?.declarations?.[0]; + const owner = decl?.parent; + if (owner === undefined || !ts.isClassLikeDeclaration(owner)) return null; + const declarer = arrayClassOfDecl(lowerer, owner); + if (declarer === null) return null; + // The receiver's own class (a subclass member reached through a base + // spelling is the same instance). + const receiver = lowerer.typeOf(access.expression).getSymbol()?.valueDeclaration; + if (receiver !== undefined && ts.isClassLikeDeclaration(receiver)) + return arrayClassOfDecl(lowerer, receiver) ?? declarer; + return declarer; +} + +/** `x instanceof Array` on a static array (always true: every array value, + * subclass instances included, is an Array) and `x instanceof X` for a + * class X declared `extends Array`: the array's class, read from its ext + * at run time. Null for every other instanceof. */ +export function lowerArrayClassInstanceOf( + lowerer: Lowerer, + expr: ts.BinaryExpression, + loc: SrcLoc, +): IrExpr | null { + if (!ts.isIdentifier(expr.right)) return null; + const leftType = lowerer.mapTypeOf(lowerer.typeOf(expr.left)); + if (lowerer.isStdlibGlobal(expr.right, "Array")) { + // A checked-dynamic value: the Array.isArray test (one realm). + if (leftType?.kind === "dyn") + return { + kind: "dynTest", + test: "array", + value: lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN), + type: BOOL, + loc, + } as IrExpr; + if (leftType?.kind !== "array") return null; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(expr.left), loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + } + const symbol = lowerer.resolveValueSymbol(expr.right); + const decl = symbol?.valueDeclaration; + let info: ClassInfo | null = null; + if (decl !== undefined && ts.isClassLikeDeclaration(decl)) { + info = arrayClassOfDecl(lowerer, decl); + } else { + // A binding that holds a class value (`const C = class extends Array + // {}`): its static class, the only class it can hold while nothing + // extends that class. + const stored = storedClassValueType(lowerer, expr.right); + const held = stored?.kind === "classval" ? lowerer.classes.get(stored.className) : undefined; + if (held === undefined || arrayRootOf(held) === null) return null; + if (held.subclasses.length > 0) + lowerer.unsupported( + "SC1090", + expr, + "'instanceof' against a binding holding a class that extends Array and has subclasses", + ); + info = held; + } + if (info === null) return null; + const value = lowerer.lowerExpr(expr.left); + if (value.type.kind === "array") + return { kind: "instanceOf", value, className: info.def.name, type: BOOL, loc }; + if (value.type.kind === "dyn") + return { + kind: "libCall", + fn: "array.dynIsClass", + args: [value, { kind: "strLit", value: info.def.name, type: STRING, loc }], + type: BOOL, + loc, + }; + if (value.type.kind === "object" && value.type.className === info.def.name) + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + return null; +} + +/** The class declared `extends Array` that a value of `type` is an instance + * of, or null. */ +export function arrayClassOfType(lowerer: Lowerer, type: ts.Type): ClassInfo | null { + const decl = type.getSymbol()?.valueDeclaration; + return decl !== undefined && ts.isClassLikeDeclaration(decl) + ? arrayClassOfDecl(lowerer, decl) + : null; +} + +/** `x.constructor` on an instance of a class declared `extends Array`: the + * class (the class has no subclasses, so the static type names it). */ +export function lowerArrayClassConstructorRead( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + loc: SrcLoc, +): IrExpr | null { + if (access.name.text !== "constructor") return null; + const info = arrayClassOfType(lowerer, lowerer.typeOf(access.expression)); + if (info === null) return null; + const value = lowerer.lowerExpr(access.expression); + const cls = lowerer.classValueRef(info, access); + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: cls, + type: cls.type, + loc, + }; +} + +/** `%C.arrayFields(a)`: the own fields of an instance of a class declared + * `extends Array` as a dyn object, in declaration order (the descriptor's + * `fields`, which util.inspect and Object.keys read). Fields whose type + * has no dyn form are left out. Null when the class has no such field. */ +export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { + const elem = arrayRootOf(info)!.extendsArray!.elem; + const arrT = arrayOf(elem); + const loc = info.def.loc; + const fields = info.def.fields.filter( + (f) => !f.name.startsWith("%") && !f.name.startsWith("#") && lowerer.dynConvertible(f.type), + ); + if (fields.length === 0) return null; + const name = `%${info.def.name}.arrayFields`; + const self = (): IrExpr => + arrayClassView(info, { kind: "varRef", localId: "a.0", type: arrT, loc }, loc); + const value: IrExpr = { + kind: "dynObjLit", + fields: fields.map((f) => ({ + key: { kind: "strLit", value: f.name, type: STRING, loc }, + value: lowerer.coerceToExpected( + { + kind: "fieldGet", + obj: self(), + className: info.def.name, + field: f.name, + type: f.type, + loc, + }, + DYN, + ), + })), + type: DYN, + loc, + }; + return { + name, + params: [{ localId: "a.0", name: "a", type: arrT }], + returnType: DYN, + locals: [{ id: "a.0", name: "a", type: arrT, mutable: false }], + body: [{ kind: "return", value, loc }], + loc, + }; +} + +/** `%C.species(n, withLength)`: `new C(n)`, or `new C()` without + * `withLength`, as ArraySpeciesCreate, Array.of and Array.from call it (the + * descriptor's `species`). Null when the constructor's parameters cannot + * take those arguments: they must be none, a forwarding rest (dyn or + * number), or a number first parameter whose others may be omitted (whose + * no-argument form throws). */ +export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { + const elem = arrayRootOf(info)!.extendsArray!.elem; + const arrT = arrayOf(elem); + const loc = info.def.loc; + const n = (): IrExpr => ({ kind: "varRef", localId: "n.0", type: F64, loc }); + const params = info.ctorParams; + // The constructor arguments with the length and without it (null: that + // form throws a TypeError). + let withLength: IrExpr[] | null = null; + let without: IrExpr[] | null = null; + if (params.length === 0) { + withLength = []; + without = []; + } else if (params.length === 1 && params[0]!.mode === "dynRest") { + withLength = [ + { + kind: "dynArrLit", + elems: [{ kind: "dynFrom", value: n(), type: DYN, loc }], + type: DYN, + loc, + }, + ]; + without = [{ kind: "dynArrLit", elems: [], type: DYN, loc }]; + } else if ( + params.length === 1 && + params[0]!.mode === "rest" && + params[0]!.type.kind === "array" && + params[0]!.type.elem.kind === "f64" + ) { + withLength = [{ kind: "arrayLit", elems: [n()], type: params[0]!.type, loc } as IrExpr]; + without = [{ kind: "arrayLit", elems: [], type: params[0]!.type, loc } as IrExpr]; + } else if ( + params[0]!.mode === "required" && + (params[0]!.type.kind === "f64" || params[0]!.type.kind === "dyn") && + params.slice(1).every((p) => p.mode === "omittable") + ) { + const rest = params + .slice(1) + .map((p) => + lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, + p.type, + ), + ); + // A dyn first parameter (a JavaScript `constructor(size)`) takes the + // length as a number, and undefined in the no-argument form. + if (params[0]!.type.kind === "dyn") without = [dynUndefinedExpr(loc), ...rest.map((e) => e)]; + withLength = [ + params[0]!.type.kind === "dyn" ? { kind: "dynFrom", value: n(), type: DYN, loc } : n(), + ...params + .slice(1) + .map((p) => + lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, + p.type, + ), + ), + ]; + } + if (withLength === null) return null; + const construct = (args: IrExpr[]): IrStmt => ({ + kind: "return", + value: arrayOfClassInstance( + info, + { + kind: "new", + className: info.def.name, + args, + type: { kind: "object", className: info.def.name }, + loc, + }, + loc, + ), + loc, + }); + const unsupported: IrStmt = { + kind: "exprStmt", + expr: nodeThrowExpr( + 1, + "", + `constructing ${info.def.jsName ?? info.def.name} without arguments is not supported yet`, + DYN, + loc, + ), + loc, + }; + return { + name: `%${info.def.name}.species`, + params: [ + { localId: "n.0", name: "n", type: F64 }, + { localId: "w.0", name: "withLength", type: BOOL }, + ], + returnType: arrT, + locals: [ + { id: "n.0", name: "n", type: F64, mutable: false }, + { id: "w.0", name: "withLength", type: BOOL, mutable: false }, + ], + body: [ + { + kind: "if", + cond: { kind: "varRef", localId: "w.0", type: BOOL, loc }, + then: [construct(withLength)], + else_: [without !== null ? construct(without) : unsupported], + loc, + }, + ], + loc, + }; +} + +/** Whether the program declares a class that extends Array. Array methods + * consult species only then. */ +export function programDeclaresArrayClass(lowerer: Lowerer): boolean { + for (const info of lowerer.classes.values()) if (info.extendsArray) return true; + return false; +} + +/** The Array methods that create their result through ArraySpeciesCreate: + * whether the species constructor receives the result's length (map, + * slice, splice; the others pass 0) and whether the length is set after + * the elements are defined (slice, splice, concat). */ +const SPECIES_METHODS: ReadonlyMap = new Map( + [ + ["map", { resultLength: true, setLength: false }], + ["filter", { resultLength: false, setLength: false }], + ["slice", { resultLength: true, setLength: true }], + ["splice", { resultLength: true, setLength: true }], + ["concat", { resultLength: false, setLength: true }], + ["flat", { resultLength: false, setLength: false }], + ["flatMap", { resultLength: false, setLength: false }], + ], +); + +/** An Array method call whose result an Array subclass instance creates + * through its species (scr_arr_species_adopt): the receiver is evaluated + * once into a hidden local that the method's lowering reads, and the + * method's plain result passes through the species constructor when the + * receiver is a subclass instance. Programs that declare no Array subclass + * keep the plain lowering. */ +export function lowerWithArraySpecies( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + lower: () => IrExpr | null, +): IrExpr | null { + const mode = SPECIES_METHODS.get(access.name.text); + if (mode === undefined || !programDeclaresArrayClass(lowerer)) return lower(); + const receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (receiverType?.kind !== "array") return lower(); + const loc = locOf(call); + const receiver = lowerer.lowerExpr(access.expression); + if (receiver.type.kind !== "array") return lower(); + const local = lowerer.declareHiddenLocal("%speciesReceiver", receiver.type); + const key = stripTypeWrappers(access.expression); + lowerer.stagedArguments.set(key, { localId: local.id, type: receiver.type }); + let result: IrExpr | null; + try { + result = lower(); + } finally { + lowerer.stagedArguments.delete(key); + } + if (result === null || result.type.kind !== "array") return result; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }], + result: { + kind: "libCall", + fn: "array.speciesAdopt", + args: [ + { kind: "varRef", localId: local.id, type: receiver.type, loc }, + result, + { kind: "numLit", value: mode.resultLength ? -1 : 0, type: F64, loc }, + { kind: "boolLit", value: mode.setLength, type: BOOL, loc }, + ], + type: result.type, + loc, + }, + type: result.type, + loc, + }; +} + +/** `C.of(...items)` and `C.from(iterable)` on a class declared `extends + * Array` that declares neither: Array.of / Array.from's elements, in an + * instance the class constructs (`new C(items.length)` for of, `new C()` + * for an iterable's from), as Node's inherited statics do. */ +export function lowerArrayClassStaticCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + const name = access.name.text; + if (name !== "of" && name !== "from") return null; + if (!ts.isIdentifier(access.expression)) return null; + const symbol = lowerer.resolveValueSymbol(access.expression); + const decl = symbol?.valueDeclaration; + if (decl === undefined || !ts.isClassLikeDeclaration(decl)) return null; + const info = arrayClassOfDecl(lowerer, decl); + if (info === null || lowerer.findStaticOn(info, name)) return null; + const loc = locOf(call); + const args = call.arguments.nodes; + let ctorLength: number; + if (name === "of") { + if (args.some(ts.isSpreadElement)) + lowerer.unsupported("SC1090", call, "spreading arguments into Array.of on an Array subclass"); + ctorLength = args.length; + } else { + const source = args[0] ? lowerer.mapTypeOf(lowerer.typeOf(args[0])) : null; + if ( + source?.kind !== "array" && + source?.kind !== "string" && + source?.kind !== "set" && + source?.kind !== "map" + ) + lowerer.unsupported( + "SC1090", + call, + "Array.from on an Array subclass over a value that is not an array, string, Set or Map", + ); + ctorLength = -1; + } + const plain = + name === "of" + ? lowerArrayOfCall(lowerer, call, access, true) + : lowerArrayFromCall(lowerer, call, access, true); + if (plain === null || plain.type.kind !== "array") return plain; + return { + kind: "libCall", + fn: "array.classConstructAdopt", + args: [ + { kind: "strLit", value: info.def.name, type: STRING, loc }, + plain, + { kind: "numLit", value: ctorLength, type: F64, loc }, + ], + type: plain.type, + loc, + }; +} + +/** A class declared `extends Array` that defines `static [Symbol.species]` + * (Node would construct method results through that constructor). */ +export function refuseArrayClassSpecies(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): void { + for (const member of decl.members.nodes) { + const name = member.name; + if ( + name !== undefined && + ts.isComputedPropertyName(name) && + ts.isPropertyAccessExpression(name.expression) && + name.expression.name.text === "species" && + ts.isIdentifier(name.expression.expression) && + name.expression.expression.text === "Symbol" + ) + lowerer.unsupported( + "SC1090", + member, + "Symbol.species on a class that extends Array (its methods' results construct the class itself)", + ); + } +} diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 1f10640c0..284f87104 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -498,12 +498,15 @@ export function lowerArrayOfCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + /** The receiver is a class declared `extends Array` (array-classes.ts + * constructs the result through it). */ + arrayClassReceiver = false, ): IrExpr | null { if ( call.questionDotToken || access.questionDotToken || access.name.text !== "of" || - !lowerer.isStdlibGlobal(access.expression, "Array") + (!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array")) ) return null; let result = lowerer.mapTypeOf(lowerer.typeOf(call)); @@ -856,9 +859,11 @@ export function lowerArrayFromCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + /** The receiver is a class declared `extends Array` (array-classes.ts). */ + arrayClassReceiver = false, ): IrExpr | null { if (call.questionDotToken || access.questionDotToken) return null; - if (!lowerer.isStdlibGlobal(access.expression, "Array")) return null; + if (!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array")) return null; if (access.name.text !== "from") return null; const loc = locOf(call); const args = call.arguments.nodes; diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 00a23caf5..dbe89544e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -773,6 +773,12 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr isUnitType(a), ) ?? false); + // Checked-dynamic elements: the runtime's String() over the array's + // dyn form (its join, element by element). + if (elem.kind === "dyn") { + const boxed: IrExpr = { kind: "dynFrom", value: e, type: DYN, loc: e.loc }; + return { kind: "toString", operand: boxed, type: STRING, loc: e.loc }; + } if ( elem.kind === "f64" || elem.kind === "string" || diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 8148e85f5..9d76a0f17 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,4 +1,9 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ +import { + arrayClassMemberOwner, + arrayClassView, + lowerArrayClassConstructorRead, +} from "../builtins/array-classes.js"; import { keyedHelperName } from "../stable-names.js"; import { staticallySelected } from "../static-typeof.js"; import * as ts from "../../ts7/adapter.js"; @@ -290,6 +295,8 @@ export function lowerPropertyAccessExpr( expr: ts.PropertyAccessExpression, loc: SrcLoc, ): IrExpr { + const arrayClassConstructor = lowerArrayClassConstructorRead(lowerer, expr, loc); + if (arrayClassConstructor !== null) return arrayClassConstructor; // `value.#x` on a checked-dynamic value, or on a checked extraction of // one: the field of the brand-checked receiver (checkedPrivateValue). if (ts.isPrivateIdentifier(expr.name)) { @@ -3717,6 +3724,31 @@ export function fieldTarget( ); } } + // A field declared by a class that extends Array, on an instance (whose + // type maps to its array): the class object's field. + { + const owner = arrayClassMemberOwner(lowerer, access); + const name = access.name.text; + if ( + owner !== null && + (owner.fields.has(name) || + lowerer.findMethodOn(owner, `get:${name}`) || + lowerer.findMethodOn(owner, `set:${name}`)) + ) { + const value = lowerer.lowerExpr(access.expression); + const receiver = + value.type.kind === "array" + ? arrayClassView(owner, value, locOf(access.expression)) + : value; + return classFieldTarget( + lowerer, + access.expression, + { kind: "object", className: owner.def.name }, + access.name.text, + receiver, + ); + } + } const stored = ts.isIdentifier(access.expression) ? (lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type) : undefined; diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 1651bf0d9..08987e538 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1,4 +1,5 @@ /* instanceof and in, including private-name and runtime-key in. */ +import { lowerArrayClassInstanceOf } from "../builtins/array-classes.js"; import { dateRootOf } from "../builtins/date-classes.js"; import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; @@ -106,6 +107,8 @@ const NO_CLASS_INSTANCE_KINDS: ReadonlySet = new Set([ * Folding is limited to side-effect-free operands — dropping a computed * operand would skip its effects, so those are rejected instead. */ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { + const arrayClassTest = lowerArrayClassInstanceOf(lowerer, expr, loc); + if (arrayClassTest !== null) return arrayClassTest; const webAssembly = lowerWebAssemblyInstanceof(lowerer, expr); if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(lowerer, expr.right); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 8ac97fd32..2be259226 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,3 +1,8 @@ +import { + arrayClassMemberOwner, + arrayClassView, + lowerArrayClassStaticCall, +} from "./builtins/array-classes.js"; import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; @@ -1669,6 +1674,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerIteratorStaticFence(lowerer, expr, expr.expression) ?? lowerObjectStaticCall(lowerer, expr, expr.expression) ?? lowerObjectFromEntriesCall(lowerer, expr, expr.expression) ?? + lowerArrayClassStaticCall(lowerer, expr, expr.expression) ?? lowerArrayFromCall(lowerer, expr, expr.expression) ?? lowerArrayFromAsyncCall(lowerer, expr, expr.expression) ?? lowerArrayOfCall(lowerer, expr, expr.expression) ?? @@ -10075,6 +10081,12 @@ export function lowerObjectMethodCall( ? probe : null; if (specializedReceiver) mappedReceiver = specializedReceiver.type; + // A member declared by a class that extends Array, called on an instance + // (whose type maps to its array): the class's own method. + const arrayClassOwner = + mappedReceiver?.kind === "array" ? arrayClassMemberOwner(lowerer, access) : null; + if (arrayClassOwner !== null) + mappedReceiver = { kind: "object", className: arrayClassOwner.def.name }; if (mappedReceiver?.kind === "union") { const dispatched = lowerUnionObjectMethodCall(lowerer, call, access, mappedReceiver) ?? @@ -10108,6 +10120,12 @@ export function lowerObjectMethodCall( : null; if (receiverIr === null) return null; const lowerReceiver = (): IrExpr => { + if (arrayClassOwner !== null) { + const value = lowerer.lowerExpr(access.expression); + return value.type.kind === "array" + ? arrayClassView(arrayClassOwner, value, locOf(access.expression)) + : value; + } const receiver = specializedReceiver ?? lowerer.lowerExpr(access.expression); const optional = lowerer.runtimeOptionalPropertyReceiver( access.expression, diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 3e49304d2..dfc27dd86 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -7,6 +7,14 @@ import { lowerDateSuperMethodCall, } from "./builtins/date-classes.js"; import { predeclareForwardClass } from "./lower-stmts.js"; +import { + arrayClassBase, + arrayClassFieldsFn, + arrayClassSpeciesFn, + arraySuperCall, + refuseArrayClassSpecies, + lowerArraySuperCall, +} from "./builtins/array-classes.js"; import { lowerDateStaticCall } from "./builtins/dates.js"; import { keyedHelperName } from "./stable-names.js"; import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; @@ -590,6 +598,10 @@ export interface ClassInfo { * constructor's rules, and the Date methods it does not declare lower * over that value. Subclasses inherit the field. */ extendsDate?: true; + /** A class declared `extends Array` (builtins/array-classes.ts): its + * instances are arrays of `elem` whose own fields follow the array + * header, and its TypeScript type maps to `elem[]`. */ + extendsArray?: { elem: IrType }; /** The synthetic LifetimeRegion class (@scriptc/experimental, * lifetime-regions.ts): its constructor is synthesized with the class, * and its methods lower at their call sites. */ @@ -1978,6 +1990,7 @@ export function collectClassShapeInner( let genericBase: ts.ExpressionWithTypeArguments | undefined; let callableBase: ClassInfo["callableBase"]; let extendsDate = false; + let extendsArray: { elem: IrType } | undefined; let factoryBaseExpression: ts.Expression | undefined; const adoptCallableBase = (expression: ts.Expression): boolean => { if (familyMode || inst || mixin) return false; @@ -2178,6 +2191,13 @@ export function collectClassShapeInner( if (t && adoptCallableBase(t.expression)) continue; lowerer.unsupported("SC1090", clause, "extending computed expressions"); } + if ((familyMode || inst) && lowerer.isStdlibGlobal(t.expression, "Array")) + lowerer.unsupported("SC1090", t, "a generic class that extends Array"); + if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Array")) { + extendsArray = arrayClassBase(lowerer, t); + refuseArrayClassSpecies(lowerer, decl); + continue; + } if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Date")) { if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes"); extendsDate = true; @@ -2278,6 +2298,12 @@ export function collectClassShapeInner( continue; } if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes"); + if (named?.extendsArray) + lowerer.unsupported( + "SC1090", + t, + `extending '${t.expression.text}', a class that extends Array (one level of Array subclass compiles)`, + ); base = named; if (!base) { if (adoptCallableBase(t.expression)) continue; @@ -4384,7 +4410,7 @@ export function collectClassShapeInner( ctor && !mixinForwarding ? lowerer.paramShapes(ctor.parameters.nodes) : (base?.ctorParams ?? - (callableBase || extendsDate ? [{ type: DYN, mode: "dynRest" }] : [])); + (callableBase || extendsDate || extendsArray ? [{ type: DYN, mode: "dynRest" }] : [])); if ( ctor && !mixinForwarding && @@ -4439,6 +4465,13 @@ export function collectClassShapeInner( return am.length > 0 ? { abstractMethods: am } : {}; })(), ...(inst ? { genericOf: inst.family.def.name } : {}), + ...(extendsArray + ? { + arrayElem: extendsArray.elem, + arrayFields: `%${className}.arrayFields`, + arraySpecies: `%${className}.species`, + } + : {}), loc: locOf(decl), }, fields, @@ -4455,6 +4488,7 @@ export function collectClassShapeInner( ...(genericBase ? { genericBase } : {}), ...(callableBase ? { callableBase } : {}), ...(extendsDate ? { extendsDate: true as const } : {}), + ...(extendsArray ? { extendsArray } : {}), ...(factoryBaseExpression ? { factoryBaseExpression } : {}), subclasses: [], throwingSetters, @@ -7926,6 +7960,12 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction const diagsBefore = lowerer.diags.length; try { out.push(lowerer.lowerClassCtor(info)); + if (info.extendsArray) { + const fields = arrayClassFieldsFn(lowerer, info); + if (fields) out.push(fields); + const species = arrayClassSpeciesFn(lowerer, info); + if (species) out.push(species); + } } catch (e) { if (!(e instanceof PoisonError)) throw e; if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { @@ -7974,6 +8014,10 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction * the base's signature — forward every param to super(), then run own * field initializers. */ export function lowerClassCtor(lowerer: Lowerer, info: ClassInfo): IrFunction { + if (info.extendsArray) { + lowerer.noteEdge(`%${info.def.name}.arrayFields`); + lowerer.noteEdge(`%${info.def.name}.species`); + } const cached = info.localClass?.bodies.get(`%${info.def.name}.constructor`); if (cached) return cached; return withInstanceBindings(lowerer, info, () => lowerClassCtorInner(lowerer, info)); @@ -8041,7 +8085,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction { lowerer.ctx.argumentsLocal = local; } body.push(...declared.prologue); - if (!ctorBase && !info.callableBase && !info.extendsDate) { + if (!ctorBase && !info.callableBase && !info.extendsDate && !info.extendsArray) { // Node's base-class order: field initializers run at the start // of construction, the parameter-property assignments open the // constructor body (probed — a field initializer reading a @@ -8053,7 +8097,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction { body.push(...lowerer.lowerDerivedCtorBody(info, thisLocal)); } } else { - if (ctorBase || info.callableBase || info.extendsDate) { + if (ctorBase || info.callableBase || info.extendsDate || info.extendsArray) { // Synthetic forwarding params (the inherited ABI signature). // Nothing references them by symbol — only the super call below, // which forwards the already-completed values UNCHANGED (defaults @@ -8972,7 +9016,9 @@ export function lowerDerivedCtorBody( loc, }, ]; - if (info.callableBase || info.extendsDate) { + if (info.extendsArray) { + statements.push(...lowerArraySuperCall(lowerer, info, thisLocal, superCall)); + } else if (info.callableBase || info.extendsDate) { if (superCall.arguments.nodes.some(ts.isSpreadElement)) lowerer.unsupported( "SC1090", @@ -9165,6 +9211,7 @@ export function superCallStmt( loc, }; if (info.extendsDate) return dateSuperCall(thisRef, info, args[0]!, loc); + if (info.extendsArray) return arraySuperCall(info, thisRef, args[0]!, loc); if (info.callableBase) return { kind: "exprStmt", diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index c790581d8..e178de2eb 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1,3 +1,8 @@ +import { + arrayClassMemberOwner, + arrayOfClassInstance, + arrayRootOf, +} from "./builtins/array-classes.js"; import { keyedHelperName } from "./stable-names.js"; import { isNewTarget, @@ -499,7 +504,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // noImplicitThis, so the generic scope walk here is preflight-safe. const local = lowerer.resolveThis(expr); if (local) { - const value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; + let value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; + // `this` in a class declared `extends Array` is the array, except as + // the receiver of a member the class declares. + if (local.type.kind === "object") { + const arrayClass = lowerer.classes.get(local.type.className); + const parent = expr.parent; + if ( + arrayClass && + arrayRootOf(arrayClass) && + !( + ts.isPropertyAccessExpression(parent) && + parent.expression === expr && + arrayClassMemberOwner(lowerer, parent) !== null + ) + ) + value = arrayOfClassInstance(arrayClass, value, loc); + } // `this.#x` in a function-local class's prototype method, whose `this` // is the call-site value: the field lives on the native instance of // this evaluation (checkedPrivateReceiver). @@ -575,7 +596,24 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // any other super position (field reads, bare references) stays out. lowerer.unsupported("SC1090", expr, "'super' outside super() and super.method() calls"); } - if (ts.isNewExpression(expr)) return lowerer.lowerNew(expr); + if (ts.isNewExpression(expr)) { + const value = lowerer.lowerNew(expr); + // `new X()` for a class declared `extends Array` is the array, except + // as the receiver of a member the class declares (as `this` above). + if (value.type.kind !== "object") return value; + const arrayClass = lowerer.classes.get(value.type.className); + if (!arrayClass || !arrayRootOf(arrayClass)) return value; + let operand: ts.Expression = expr; + while (ts.isParenthesizedExpression(operand.parent)) operand = operand.parent; + const parent = operand.parent; + if ( + ts.isPropertyAccessExpression(parent) && + parent.expression === operand && + arrayClassMemberOwner(lowerer, parent) !== null + ) + return value; + return arrayOfClassInstance(arrayClass, value, loc); + } if (ts.isAwaitExpression(expr)) { if (!lowerer.ctx.isAsync) { // A tsc-clean occurrence here is outside both an async function diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index b0139aba9..b69805adf 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -949,6 +949,12 @@ export function moduleArtifacts( visitClass("%Readable"); visitClass("%Writable"); } + // A class declared `extends Array` named by its instanceof test + // or by X.of / X.from. + if (node.fn === "array.dynIsClass" && node.args[1]?.kind === "strLit") + visitClass(node.args[1].value); + if (node.fn === "array.classConstructAdopt" && node.args[0]?.kind === "strLit") + visitClass(node.args[0].value); break; case "dynFrom": if (boxedChild(node.value.type)) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 14af97de7..0845826d2 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,10 @@ +import { + arrayClassFieldsFn, + arrayClassSpeciesFn, + arrayOfClassInstance, + arrayRootOf, + lowerWithArraySpecies, +} from "./builtins/array-classes.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject } from "./module-namespace-elements.js"; @@ -8129,6 +8136,20 @@ export class Lowerer { lower: () => info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info), }); + // The descriptor helpers of a class that extends Array, reached + // from its constructor. + if (info.extendsArray) { + units.set(`%${cName}.arrayFields`, { + order: unitOrder++, + roots: [], + lower: () => arrayClassFieldsFn(this, info), + }); + units.set(`%${cName}.species`, { + order: unitOrder++, + roots: [], + lower: () => arrayClassSpeciesFn(this, info), + }); + } for (const { mName, member } of this.classMethodMembers(info)) { units.set(`%${cName}.${mName}`, { order: unitOrder++, @@ -8244,6 +8265,10 @@ export class Lowerer { register(`%${cName}.constructor`, classCtorRoots(info), () => info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info), ); + if (info.extendsArray) { + register(`%${cName}.arrayFields`, [], () => arrayClassFieldsFn(this, info)); + register(`%${cName}.species`, [], () => arrayClassSpeciesFn(this, info)); + } for (const { mName, member } of this.classMethodMembers(info)) { register(`%${cName}.${mName}`, functionRoots(member), () => info.localClass && !info.localClass.ready @@ -9608,6 +9633,18 @@ export class Lowerer { const result = this.coerceToExpected(expr.result, expected); return { ...expr, result, type: result.type }; } + // An instance of a class declared `extends Array` is its array (the + // same object); an array slot of another element type converts it as + // an array. + if (expr.type.kind === "object" && expected.kind !== "object") { + const arrayClass = this.classes.get(expr.type.className); + if (arrayClass && arrayRootOf(arrayClass)) { + const asArray = arrayOfClassInstance(arrayClass, expr, expr.loc); + return typeEquals(asArray.type, expected) + ? asArray + : this.coerceToExpected(asArray, expected); + } + } // A destination that holds undefined takes an unassigned deferred-init // field's slot as is (Node passes the undefined along). if (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) { @@ -13315,7 +13352,9 @@ export class Lowerer { call: ts.CallExpression, access: ts.PropertyAccessExpression, ): IrExpr | null { - return lowerArrayMethodCall(this, call, access); + return lowerWithArraySpecies(this, call, access, () => + lowerArrayMethodCall(this, call, access), + ); } lowerMapMethodCall(call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index fc159116f..4273eace0 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1779,6 +1779,36 @@ export function isFetchHandleType( return handle; } +/** The array type a class declared `extends Array` maps to (its base type, + * `T[]`), or null for every other class. */ +function arrayClassBaseType( + classDecl: ts.ClassLikeDeclaration, + instance: ts.Type, + ctx: TypeMapperCtx, +): IrType | null { + const heritage = classDecl.heritageClauses?.nodes.find( + (clause) => clause.token === ts.SyntaxKind.ExtendsKeyword, + )?.types?.nodes[0]; + if (heritage === undefined || !ts.isIdentifier(heritage.expression)) return null; + if (heritage.expression.text !== "Array") return null; + // The polymorphic `this` type of the class names the class's own type. + const symbol = instance.getSymbol(); + const declared = + (instance.flags & ts.TypeFlags.TypeParameter) !== 0 && symbol !== undefined + ? ctx.checker.getDeclaredTypeOfSymbol(symbol) + : instance; + if ((declared.flags & ts.TypeFlags.Object) === 0) return null; + const target = declared.isTypeReference() ? (declared.getTarget() ?? declared) : declared; + const bases = ctx.checker.getBaseTypes(target as ts.InterfaceType); + const base = bases[0]; + if (base === undefined || !ctx.checker.isArrayType(base)) return null; + const mapped = mapType(base, ctx); + if (mapped?.kind === "array") return mapped; + // `extends Array` in JavaScript reads `any[]`: dyn elements (the class's + // arrayClassBase). + return isJsSourceFile(ts.getSourceFileOfNode(classDecl)) ? { kind: "array", elem: DYN } : null; +} + export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (mapTypeDepth >= MAP_TYPE_MAX_DEPTH) return null; // These intrinsic domains cannot depend on generic bindings, declaration @@ -2546,6 +2576,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return viaMixin; } } + // A class declared `extends Array` (frontend/lowering/builtins/ + // array-classes.ts): its instances are arrays of the base's elements. + { + const arrayBase = arrayClassBaseType(classDecl, widened, ctx); + if (arrayBase !== null) return arrayBase; + } if (classExprNeedsContext(classDecl)) { const local = ctx.localClassInstance?.(classDecl, widened); if (local) contextResolutions++; diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index ac7413c6a..db7dc81c3 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3891,6 +3891,23 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ // The string keys `for...in` visits on an array (args[0], any element // type) after its indices. Borrowed; never throws. "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, + /** super(...) in a class declared `extends Array`: the instance, the + * arguments (an array of its element type, or the implicit constructor's + * dyn argument array) and whether the elements are dyn values. */ + "array.subclassConstruct": { argTypes: [null, null, BOOL], result: VOID }, + "array.subclassConstructDyn": { argTypes: [null, DYN, BOOL], result: VOID }, + /** ArraySpeciesCreate over an Array method's plain result: the receiver + * (borrowed), the result (moved), the species constructor's length + * argument (-1: the result's length) and whether to set the length. The + * result has the second argument's type. */ + "array.speciesAdopt": { argTypes: [null, null, F64, BOOL], result: VOID }, + /** Array.of / Array.from on a class declared `extends Array`: the class + * (its name, a literal), the plain result (moved) and the constructor's + * length argument (negative: no argument). */ + "array.classConstructAdopt": { argTypes: [STRING, null, F64], result: VOID }, + /** `value instanceof C` for a dyn value and a class that extends Array + * (its name, a literal). */ + "array.dynIsClass": { argTypes: [DYN, STRING], result: BOOL }, // Whether the array (args[0]) still has the key (args[1]) from its // namedKeys list. Borrowed; never throws. "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index 91e1d5f05..33d68c621 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -157,6 +157,8 @@ const MODULE_FIELDS: Record>, Role> = { "classes[].errorNameAfter": "other", "classes[].prototypeDataHelper": "reference", "classes[].instancePrototypeHelper": "reference", + "classes[].arrayFields": "reference", + "classes[].arraySpecies": "reference", "classes[].localCaptures[].name": "other", "classes[].localCaptures[].localId": "other", "classes[].symbolFields[].field": "other", @@ -281,6 +283,14 @@ const REFERENCE_FIXTURES: Record IrModule> = { ...module(), classes: [{ name: "%m0.Cell", fields: [], instancePrototypeHelper: TARGET, loc }], }), + "classes[].arrayFields": () => ({ + ...module(), + classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arrayFields: TARGET, loc }], + }), + "classes[].arraySpecies": () => ({ + ...module(), + classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arraySpecies: TARGET, loc }], + }), "lib.exports[].fnName": () => ({ ...module(), lib: { diff --git a/packages/compiler/src/ir/function-references.ts b/packages/compiler/src/ir/function-references.ts index db474e9bb..da05e854c 100644 --- a/packages/compiler/src/ir/function-references.ts +++ b/packages/compiler/src/ir/function-references.ts @@ -24,6 +24,8 @@ export function moduleFunctionReferences(mod: IrModule): string[] { for (const cls of mod.classes ?? []) { if (cls.prototypeDataHelper !== undefined) names.push(cls.prototypeDataHelper); if (cls.instancePrototypeHelper !== undefined) names.push(cls.instancePrototypeHelper); + if (cls.arrayFields !== undefined) names.push(cls.arrayFields); + if (cls.arraySpecies !== undefined) names.push(cls.arraySpecies); } return names; } diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 550333edd..b9849152c 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1508,6 +1508,17 @@ export interface IrClassDef { * RUNTIME_ERROR_CLASSES). User subclasses are ordinary emitted classes * whose layout prefix embeds ScrError's fields. */ runtime?: true; + /** A class declared `extends Array`: its instances are arrays of this + * element type. The struct begins with the runtime's array header (the + * reference count is the array's, field 0) and the class's fields follow + * it; the runtime finds the class through the array's ext. */ + arrayElem?: IrType; + /** The function (array) => dyn listing such a class's own fields + * (ScrArrClass.fields); absent when it declares none. */ + arrayFields?: string; + /** The function (length) => array constructing such a class for + * ArraySpeciesCreate (ScrArrClass.species). */ + arraySpecies?: string; /** Base class name (single inheritance, `extends`). A class is IN A * HIERARCHY when it has a base or is some class's base; hierarchy classes * carry a vtable word after `rc` (backends), standalone classes are laid diff --git a/packages/compiler/src/ir/traverse.ts b/packages/compiler/src/ir/traverse.ts index 80425a056..e83bf5c42 100644 --- a/packages/compiler/src/ir/traverse.ts +++ b/packages/compiler/src/ir/traverse.ts @@ -140,6 +140,7 @@ export function everyModuleNode(mod: IrModule, visitor: IrModuleVisitor): boolea for (const cls of mod.classes ?? []) { for (const capture of cls.localCaptures ?? []) if (!type(capture.type, cls.loc)) return false; for (const field of cls.fields) if (!type(field.type, cls.loc)) return false; + if (cls.arrayElem !== undefined && !type(cls.arrayElem, cls.loc)) return false; } for (const global of mod.globals ?? []) if (!type(global.type, global.source?.loc ?? entryLoc)) return false; diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index d7eee18bf..0dea09d1e 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -2993,6 +2993,18 @@ function validateFunction( } break; } + // An instance of a class declared `extends Array` and its array + // are the same object: upcast to the array, downcast back. + { + const instance = e.kind === "upcast" ? e.value.type : e.type; + const array = e.kind === "upcast" ? e.type : e.value.type; + if (instance.kind === "object" && array.kind === "array") { + const elem = classes.get(instance.className)?.arrayElem; + if (elem === undefined || !typeEquals(elem, array.elem)) + err(`${e.kind}: "${instance.className}" is not an array of that element type`, e.loc); + break; + } + } if (e.type.kind !== "object" || e.value.type.kind !== "object") { err(`${e.kind} between non-class types`, e.loc); break; @@ -3100,6 +3112,15 @@ function validateFunction( case "instanceOf": { checkExpr(e.value); if (e.type.kind !== "bool") err("instanceOf must be bool", e.loc); + // An array against a class declared `extends Array` (its ext). + if (e.value.type.kind === "array") { + if (classes.get(e.className)?.arrayElem === undefined) + err( + `instanceOf of an array against "${e.className}", which does not extend Array`, + e.loc, + ); + break; + } if (!hierarchy.has(e.className)) { err(`instanceOf against non-hierarchy class "${e.className}"`, e.loc); } @@ -4190,6 +4211,28 @@ function validateFunction( } function checkLibValueCall(e: IrExpr & { kind: "libCall" }): boolean { + // ArraySpeciesCreate over an Array method's result, and Array.of / + // Array.from on a class that extends Array: the result has the plain + // result's array type. + if (e.fn === "array.dynIsClass") { + const cls = e.args[1]?.kind === "strLit" ? classes.get(e.args[1].value) : undefined; + if (cls?.arrayElem === undefined) + err("libCall array.dynIsClass must name a class that extends Array", e.loc); + return false; + } + if (e.fn === "array.speciesAdopt" || e.fn === "array.classConstructAdopt") { + const plain = e.args[1]; + if (plain?.type.kind !== "array" || !typeEquals(e.type, plain.type)) + err(`libCall ${e.fn} must have its array argument's type`, e.loc); + if (e.fn === "array.speciesAdopt" && e.args[0]?.type.kind !== "array") + err("libCall array.speciesAdopt receiver must be an array", e.loc); + if (e.fn === "array.classConstructAdopt") { + const cls = e.args[0]?.kind === "strLit" ? classes.get(e.args[0].value) : undefined; + if (cls?.arrayElem === undefined) + err("libCall array.classConstructAdopt must name a class that extends Array", e.loc); + } + return true; + } const cryptoPromise = CRYPTO_PROMISE_VALUES[e.fn]; if (cryptoPromise) { const inner = e.type.kind === "promise" ? e.type.inner : undefined; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json new file mode 100644 index 000000000..11b1d8d51 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7720-array-subclass-basics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json new file mode 100644 index 000000000..41bd59eb2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7721-array-subclass-species.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json new file mode 100644 index 000000000..6cbc69c47 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7722-array-subclass-zero-array.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json new file mode 100644 index 000000000..4671e20ea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7723-array-subclass-as-array.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json new file mode 100644 index 000000000..7956c64c5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7724-array-subclass-cycles.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json new file mode 100644 index 000000000..5d9c82a12 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7725-array-subclass-operations.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json new file mode 100644 index 000000000..7d49ce0ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7726-array-subclass-expressions.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json new file mode 100644 index 000000000..659372fdb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/array-subclass-fences.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 4c7b84d98..aed1b5c0c 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1590,8 +1590,8 @@ static inline __attribute__((always_inline)) void scr_arr_free(ScrArr *a) { scr_mem_free(a); } -static void scr_arr_destroy(void *object) { - ScrArr *a = object; +/* Releases an array's elements and ext contents (destroy's first half). */ +static void scr_arr_release_contents(ScrArr *a) { if (scr_arr_ext_of(a)) { scr_dyn_release(scr_arr_ext_of(a)->metadata); scr_arr_ext_of(a)->metadata = NULL; @@ -1657,10 +1657,209 @@ static void scr_arr_destroy(void *object) { } } } +} + +static void scr_arr_destroy(void *object) { + ScrArr *a = object; + scr_arr_release_contents(a); if (a->cyc) scr_arr_gc_free(a); else scr_arr_free(a); } +void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls, + bool cyc) { + a->len = 0; + a->cap = 0; + a->elem = elem; + a->cyc = cyc ? 1 : 0; + uint32_t id = ops ? scr_arr_ops_id(ops) : 0; + a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT); + a->filled = 0; + a->data = NULL; + ScrArrExt *x = scr_arr_ext(a); + if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops; + x->cls = cls; +#ifdef SCR_RC_AUDIT + scr_live_arrays++; +#endif +} + +/* super(...args) in a class that extends Array, as the Array constructor: + * one number argument sets the length (a RangeError for an invalid length), + * otherwise the arguments become the elements. `args` has the instance's + * element type and is borrowed; `dyn_elems` says its elements are dyn + * values, whose number test reads the value. */ +void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems) { + if (args->len == 1) { + if (args->elem == SCR_ELEM_F64 || args->elem == SCR_ELEM_I32) { + scr_arr_set_len(a, scr_arr_get_number(args, 0)); + return; + } + if (dyn_elems) { + const ScrDyn *d = scr_arr_borrow_ref(args, 0); + if (d != NULL && d->kind == SCR_DYN_NUM) { + scr_arr_set_len(a, d->v.num); + return; + } + } + } + if (args->len > 0) (void)scr_arr_push_spread(a, args); +} + +/* The implicit constructor of a class that extends Array forwards its + * arguments (a dyn array) to super: the same rule, each argument converted + * to the element type. Element types other than numbers, booleans, strings + * and dyn values take only the length form. */ +void scr_arr_subclass_construct_dyn(ScrArr *a, const ScrDyn *args, bool dyn_elems) { + if (args == NULL || args->kind != SCR_DYN_ARR) return; + size_t n = args->v.arr.len; + ScrDyn *const *items = args->v.arr.items; + if (n == 1 && items[0] != NULL && items[0]->kind == SCR_DYN_NUM) { + scr_arr_set_len(a, items[0]->v.num); + return; + } + for (size_t i = 0; i < n; i++) { + ScrDyn *d = items[i]; + if (dyn_elems) { + (void)scr_arr_push_ref(a, scr_dyn_retain(d)); + continue; + } + if ((a->elem == SCR_ELEM_F64 || a->elem == SCR_ELEM_I32) && d && d->kind == SCR_DYN_NUM) { + (void)scr_arr_push_f64(a, d->v.num); + } else if (a->elem == SCR_ELEM_BOOL && d && d->kind == SCR_DYN_BOOL) { + (void)scr_arr_push_bool(a, d->v.b); + } else if (a->elem == SCR_ELEM_STR && d && d->kind == SCR_DYN_STR) { + (void)scr_arr_push_ref(a, scr_str_retain(d->v.str)); + } else { + static const char msg[] = + "constructing this Array subclass with element arguments is not supported yet"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return; + } + } +} + +static void scr_arr_set_slot(ScrArr *a, double i, uint64_t slot); + +static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length, + bool with_length, bool set_length); + +/* Gives an Array subclass instance holding no elements (only holes) another + * element type: its storage is dropped and its length kept. */ +static void scr_arr_retype_empty(ScrArr *a, uint8_t elem, const ScrArrOps *ops) { + uint32_t len = a->len; + ScrArrExt *x = scr_arr_ext_of(a); + const ScrArrClass *cls = x->cls; + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->elem = elem; + uint32_t id = ops ? scr_arr_ops_id(ops) : 0; + a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT); + x = scr_arr_ext(a); + if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops; + x->cls = cls; + scr_arr_set_len(a, (double)len); +} + +ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length) { + const ScrArrClass *cls = scr_arr_class_of(recv); + if (cls == NULL) return result; + if (ctor_length < 0) ctor_length = (double)result->len; + return scr_arr_species_fill(cls, result, ctor_length, true, set_length); +} + +ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length) { + return scr_arr_species_fill(cls, result, ctor_length < 0 ? 0 : ctor_length, ctor_length >= 0, true); +} + +static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length, + bool with_length, bool set_length) { + if (cls->species == NULL) { + static const char msg[] = + "this Array subclass's constructor cannot construct a species result (Symbol.species)"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return result; + } + ScrArr *species = cls->species(ctor_length, with_length); + if (scr_exc_pending()) { + scr_arr_release(species); + return result; + } + if (species->elem != result->elem || scr_arr_ops(species) != scr_arr_ops(result)) { + /* A result of another element type (map, flatMap): an instance the + * constructor left without elements takes the result's element type; + * its length stays. An instance whose collector header the elements + * would need, or that already holds elements, cannot. */ + const ScrArrOps *ops = scr_arr_ops(result); + bool traced = ops != NULL && ops->trace != NULL; + bool empty = true; + for (size_t i = 0; empty && i < species->len; i++) { + uint64_t slot; + empty = scr_arr_state_at(species, i, &slot) == SCR_ARR_HOLE; + } + if (!empty || (traced && !species->cyc)) { + static const char msg[] = + "an Array subclass's species result of another element type is not supported yet"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + scr_arr_release(species); + return result; + } + scr_arr_retype_empty(species, result->elem, ops); + } + for (size_t i = 0; i < result->len; i++) { + uint64_t slot; + uint8_t state = scr_arr_state_at(result, i, &slot); + if (state == SCR_ARR_VALUE) scr_arr_set_slot(species, (double)i, scr_elem_retain_slot(result, slot)); + else if (state == SCR_ARR_UNDEFINED) scr_arr_set_undefined(species, (double)i); + } + if (set_length) scr_arr_set_len(species, (double)result->len); + scr_arr_release(result); + return species; +} + +bool scr_dyn_is_array_class(const ScrDyn *d, const ScrArrClass *cls) { + if (d == NULL) return false; + /* A live view of the native instance. */ + if (d->kind == SCR_DYN_TYPED_REF && d->v.typed_ref.array != NULL && !d->v.typed_ref.record && + !d->v.typed_ref.klass && !d->v.typed_ref.bag_class) + return scr_arr_class_of(d->v.typed_ref.ptr) == cls; + /* A snapshot: scr_arr_copy_metadata names the class by its name's + * address, which each class owns. */ + if (d->kind == SCR_DYN_ARR) { + const ScrDyn *props = d->v.arr.properties; + return props != NULL && props->v.obj.class_name == cls->name; + } + return false; +} + +const ScrArrClass *scr_arr_class_of(const ScrArr *a) { + if (!(a->spare & SCR_ARR_HAS_EXT)) return NULL; + return scr_arr_ext_of((ScrArr *)a)->cls; +} + +void scr_arr_subclass_gc_drop(ScrArr *a, bool traced) { + if (!traced) scr_arr_release_contents(a); + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->spare &= (uint16_t)~SCR_ARR_HAS_EXT; +#ifdef SCR_RC_AUDIT + scr_live_arrays--; +#endif +} + +void scr_arr_subclass_drop(ScrArr *a) { + scr_arr_release_contents(a); + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->spare &= (uint16_t)~SCR_ARR_HAS_EXT; +#ifdef SCR_RC_AUDIT + scr_live_arrays--; +#endif +} + /* Whether destroying `a`, an acyclic array, releases nothing: it has no ext * and its elements are numbers, booleans or compact references, or * references whose release does nothing (SCR_ARR_OPS_RELEASE_NOTHING). */ @@ -1674,6 +1873,12 @@ static inline bool scr_arr_releases_nothing(const ScrArr *a) { void scr_arr_release(ScrArr *a) { if (!a || a->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */ if (--a->rc == 0) { + /* An Array subclass instance: its class tears it down. */ + if ((a->spare & SCR_ARR_HAS_EXT) && scr_arr_ext_of(a)->cls) { + if (a->cyc) scr_cyc_dead(a); + scr_arr_ext_of(a)->cls->destroy(a); + return; + } /* Nothing to tear down: free the storage without scr_rc_destroy's * queue (no release can recurse) or the element loop. */ if (!a->cyc && scr_arr_releases_nothing(a)) { @@ -5350,10 +5555,32 @@ bool scr_arr_numeric_key(const char *key, size_t len, double *out) { bool scr_arr_has_properties(const ScrArr *a) { const ScrArrExt *x = scr_arr_ext_of(a); - return x && (x->prop_len || x->metadata); + return x && (x->prop_len || x->metadata || x->cls); +} + +/* The own fields of an Array subclass instance (ScrArrClass.fields), a + * fresh dyn object, or NULL. */ +static ScrDyn *scr_arr_class_fields(ScrArr *a) { + const ScrArrClass *cls = scr_arr_class_of(a); + if (!cls || !cls->fields) return NULL; + return cls->fields(scr_arr_retain(a)); } void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) { + const ScrArrClass *cls = scr_arr_class_of(a); + if (cls) { + /* An Array subclass instance: the class name util.inspect prints before + * the elements, then the class's own fields as properties. */ + if (!target->v.arr.properties) target->v.arr.properties = scr_dyn_new_obj(); + target->v.arr.properties->v.obj.class_name = cls->name; + ScrDyn *fields = scr_arr_class_fields(a); + if (fields) { + for (size_t i = 0; i < fields->v.obj.len; i++) + scr_dyn_copy_entry(target, fields, &fields->v.obj.entries[i]); + scr_dyn_release(fields); + } + return; + } const ScrDyn *metadata = SCR_AX(a)->metadata; if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE && scr_arr_match_copy(a, target)) @@ -5372,6 +5599,15 @@ ScrArr *scr_arr_named_keys(ScrArr *a) { ScrArr *keys = scr_arr_new(SCR_ELEM_STR, 0); for (size_t i = 0; i < x->prop_len; i++) scr_arr_push_ref(keys, scr_str_new(x->props[i].key, x->props[i].key_len)); + ScrDyn *fields = scr_arr_class_fields(a); + if (fields) { + for (size_t i = 0; i < fields->v.obj.len; i++) { + const ScrDynEntry *entry = &fields->v.obj.entries[i]; + if (entry->enumerable) scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len)); + } + scr_dyn_release(fields); + return keys; + } const ScrDyn *metadata = x->metadata; if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE) { static const char *const names[] = {"index", "input", "groups"}; diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index 23390a183..de51e2938 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -171,7 +171,31 @@ typedef struct ScrArrExt { /* The element ops of an array whose ops have no registry id * (SCR_ARR_OPS_OVERFLOW). */ const ScrArrOps *ops; + /* The class of an instance of a program class that extends Array + * (scr_arr_subclass_init); NULL for every other array. */ + const struct ScrArrClass *cls; } ScrArrExt; + +/* A program class that extends Array. Its instances are emitted structs + * that begin with a ScrArr (the reference count is the array's) and keep + * the class's own fields after it; they always have an ext, whose `cls` + * names the class. Every array entry point accepts them as arrays. */ +typedef struct ScrArrClass { + /* The class's name, as constructor.name reads it. */ + const char *name; + /* Destroys an instance whose count reached zero: releases the class's own + * fields, then scr_arr_subclass_drop, then frees the instance. */ + void (*destroy)(ScrArr *a); + /* The class's own fields as a dyn object in declaration order (the array + * is passed owned, +1), or NULL when the class declares none. util.inspect + * and Object.keys list them after the elements. */ + struct ScrDyn *(*fields)(ScrArr *a); + /* `new C(length)` (`new C()` without `with_length`): the instance + * ArraySpeciesCreate makes for map, filter, slice, splice, concat, flat + * and flatMap, and Array.of / Array.from called on the class. NULL when + * the class's constructor cannot take that argument. */ + ScrArr *(*species)(double length, bool with_length); +} ScrArrClass; /* The LLVM backend mirrors ScrArr as %ScrArr: its inline retain/release read * `cyc` (field 4, i8) to decide mark-live / candidate buffering exactly like * scr_arr_retain and scr_arr_release; its inline element access uses len and @@ -471,6 +495,40 @@ static inline ScrArr *scr_arr_retain(ScrArr *a) { * NULL-tolerant (uninitialized locals). Cycle-capable arrays (`cyc`) feed * the candidate-root buffer like every headered type. */ void scr_arr_release(ScrArr *a); +/* Initializes the ScrArr prefix of a zeroed, freshly allocated instance of + * an Array subclass (count already 1): no elements, the element kind and + * ops, and an ext naming the class. `cyc` says the instance carries the + * collector header (its class is cycle-capable). */ +void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls, + bool cyc); +/* The collector's teardown of an Array subclass instance's storage: the + * elements are released unless they are the collector's (`traced`). */ +void scr_arr_subclass_gc_drop(ScrArr *a, bool traced); +/* super(...args) in a class that extends Array (the Array constructor's + * length-or-elements rule); `args` is borrowed. */ +void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems); +/* The implicit constructor's super(...args) over its dyn argument array. */ +void scr_arr_subclass_construct_dyn(ScrArr *a, const struct ScrDyn *args, bool dyn_elems); +/* ArraySpeciesCreate over the result `result` (owned) of an Array method + * called on `recv` (borrowed): a plain receiver keeps the result; a + * subclass instance's result is `new C(ctor_length)`, whose indices then + * receive the result's present elements, and whose length is set to the + * result's when `set_length` (slice, splice and concat set it; map, + * filter, flat and flatMap do not). A negative `ctor_length` passes the + * result's length. Result owned. */ +ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length); +/* Array.of / Array.from called on an Array subclass: `new C(ctor_length)` + * (`new C()` when ctor_length is negative), then the elements of `result` + * (owned) and its length, as scr_arr_species_adopt. */ +ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length); +/* `value instanceof C` for a dyn value and a class that extends Array: a + * live view of an instance of C, or a snapshot of one. */ +bool scr_dyn_is_array_class(const struct ScrDyn *d, const ScrArrClass *cls); +/* The class of an Array subclass instance, or NULL for a plain array. */ +const ScrArrClass *scr_arr_class_of(const ScrArr *a); +/* Releases the elements of an Array subclass instance and frees its + * storage and ext, leaving the instance itself to its class's destroy. */ +void scr_arr_subclass_drop(ScrArr *a); void scr_arr_copy_metadata(ScrArr *a, struct ScrDyn *target); /* The commit of a live array view calls this on `a`, the array converted * from `view`, before `target` takes its storage: `a` takes the view's named diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 79b2564ff..3ad34cb95 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1513,8 +1513,25 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { const ScrDyn *props = d->v.arr.properties; size_t nprops = 0; for (size_t i = 0; props && i < props->v.obj.len; i++) nprops += props->v.obj.entries[i].enumerable; - if (len == 0 && nprops == 0) return scr_str_new("[]", 2); - if (recurse > depth) return scr_str_new("[Array]", 7); + /* An instance of a class that extends Array: Node's `Name(length) ` + * prefix (scr_arr_copy_metadata names the class). */ + const char *cls = props ? props->v.obj.class_name : NULL; + char prefix[160] = ""; + if (cls) snprintf(prefix, sizeof prefix, "%.120s(%zu) ", cls, len); + if (len == 0 && nprops == 0) { + InspBuf eb = {0}; + ib_cstr(&eb, prefix); + ib_cstr(&eb, "[]"); + return ib_take(&eb); + } + if (recurse > depth) { + if (!cls) return scr_str_new("[Array]", 7); + InspBuf eb = {0}; + ib_char(&eb, '['); + ib_cstr(&eb, cls); + ib_char(&eb, ']'); + return ib_take(&eb); + } scr_insp_begin(recurse + 1); /* Node's maxArrayLength (100) counts output entries; a run of holes * is one `` entry (formatSpecialArray). */ @@ -1568,7 +1585,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { * array has no properties). */ bool last_out = entries > 100; ScrStr *base = scr_str_new("", 0); - ScrStr *b0 = scr_str_new("[", 1); + InspBuf ob = {0}; + ib_cstr(&ob, prefix); + ib_char(&ob, '['); + ScrStr *b0 = ib_take(&ob); ScrStr *b1 = scr_str_new("]", 1); ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, last_out); scr_str_release(base); diff --git a/tests/corpus/7720-array-subclass-basics.ts b/tests/corpus/7720-array-subclass-basics.ts new file mode 100644 index 000000000..1e5130f9d --- /dev/null +++ b/tests/corpus/7720-array-subclass-basics.ts @@ -0,0 +1,44 @@ +// A class that extends Array: construction (one number argument sets the +// length, other arguments become the elements), length and index writes, +// Array.isArray, instanceof, constructor, inherited methods, the subclass's +// own fields and methods, and how console.log and JSON show the instance. +class Stack extends Array { + pushes = 0; + + top(): number | undefined { + return this[this.length - 1]; + } + + add(value: number): this { + this.push(value); + this.pushes++; + return this; + } +} + +const s = new Stack(); +s.add(1).add(2).add(3); +console.log(s.length, s.top(), s.pushes); +console.log(Array.isArray(s), s instanceof Stack, s instanceof Array, s.constructor === Stack); +console.log(s); +console.log(JSON.stringify(s)); +s[5] = 9; +console.log(s.length, s[4], s); +s.length = 2; +console.log(s, s.indexOf(2), s.includes(3), s.join("-")); +s.reverse(); +console.log(s.top(), s); + +const sized = new Stack(3); +console.log(sized.length, sized, 0 in sized); +const listed = new Stack(4, 5); +console.log(listed.length, listed); + +try { + new Stack(-1); +} catch (e) { + console.log((e as Error).name, (e as Error).message); +} + +for (const v of listed) console.log("item", v); +console.log([...listed, 6], Object.keys(listed)); diff --git a/tests/corpus/7721-array-subclass-species.ts b/tests/corpus/7721-array-subclass-species.ts new file mode 100644 index 000000000..03e0d19ab --- /dev/null +++ b/tests/corpus/7721-array-subclass-species.ts @@ -0,0 +1,37 @@ +// Array.prototype methods that create a new array construct it through the +// subclass (ArraySpeciesCreate): map, filter, slice, splice, concat, flat +// and flatMap return an instance of the subclass, built by calling its +// constructor with the length the specification passes. Methods that +// return the receiver keep it, and Array.of / Array.from inherited as +// statics construct the subclass too. +class Logged extends Array { + constructor(...args: number[]) { + super(...args); + console.log("construct", args.length === 1 ? `length ${args[0]}` : `items ${args.join(",")}`); + } + + sum(): number { + let total = 0; + for (const v of this) total += v; + return total; + } +} + +const base = Logged.of(1, 2, 3, 4); +console.log(base instanceof Logged, base); +const doubled = base.map((v) => v * 2); +console.log(doubled instanceof Logged, (doubled as Logged).sum(), doubled); +const even = base.filter((v) => v % 2 === 0); +console.log(even instanceof Logged, even); +const part = base.slice(1, 3); +console.log(part instanceof Logged, part); +const removed = base.splice(0, 1); +console.log(removed instanceof Logged, removed, base); +const joined = base.concat([7, 8]); +console.log(joined instanceof Logged, joined.length, joined); +const flat = base.flatMap((v) => [v, v]); +console.log(flat instanceof Logged, flat.length); +const filled = base.fill(0, 0, 1); +console.log(filled === base, base); +const from = Logged.from([5, 6]); +console.log(from instanceof Logged, (from as Logged).sum()); diff --git a/tests/corpus/7722-array-subclass-zero-array.js b/tests/corpus/7722-array-subclass-zero-array.js new file mode 100644 index 000000000..1ed52d386 --- /dev/null +++ b/tests/corpus/7722-array-subclass-zero-array.js @@ -0,0 +1,16 @@ +// lru-cache's ZeroArray: a JavaScript class extending Array whose +// constructor fills the new array with zeros, constructed directly and +// through map (species construction runs the constructor again). +class ZeroArray extends Array { + constructor(size) { + super(size); + this.fill(0); + } +} + +const z = new ZeroArray(4); +z[1] = 7; +console.log(z, z.length, z instanceof ZeroArray, Array.isArray(z)); +const m = z.map((v) => v + 1); +console.log(m, m instanceof ZeroArray); +console.log(z.slice(1) instanceof ZeroArray, String(z)); diff --git a/tests/corpus/7723-array-subclass-as-array.ts b/tests/corpus/7723-array-subclass-as-array.ts new file mode 100644 index 000000000..5faf5b13f --- /dev/null +++ b/tests/corpus/7723-array-subclass-as-array.ts @@ -0,0 +1,21 @@ +// A subclass instance passed where a plain array is expected is the same +// object: writes through either view are seen through the other, and +// species construction still applies in code typed with the plain array. +class Path extends Array { + label = "path"; +} + +function addSegment(parts: string[], segment: string): string[] { + parts.push(segment); + return parts.filter((part) => part.length > 0); +} + +const p = new Path(); +p.push("a", "", "b"); +const filtered = addSegment(p, "c"); +console.log(p.length, filtered.length, filtered instanceof Path, (filtered as Path).label); +const view: string[] = p; +view[0] = "z"; +console.log(p[0], view === p, p.label); +console.log(p.map((s) => s.toUpperCase())); +console.log(Object.keys(p)); diff --git a/tests/corpus/7724-array-subclass-cycles.ts b/tests/corpus/7724-array-subclass-cycles.ts new file mode 100644 index 000000000..9a1a7b813 --- /dev/null +++ b/tests/corpus/7724-array-subclass-cycles.ts @@ -0,0 +1,36 @@ +// An Array subclass whose elements and fields refer back to the instance: +// the cycles are collected once nothing outside them is reachable, and +// the elements and fields read back as Node reads them. +class Member { + name: string; + group: Group | undefined; + constructor(name: string, group: Group | undefined) { + this.name = name; + this.group = group; + } +} + +class Group extends Array { + owner: Member | undefined = undefined; + + add(name: string): Member { + const member = new Member(name, this); + this.push(member); + return member; + } +} + +function build(round: number): number { + const g = new Group(); + const lead = g.add(`lead${round}`); + g.add(`member${round}`); + g.owner = lead; + return g.length + (g[0]!.group === g ? 1 : 0) + (g.owner === lead ? 1 : 0); +} + +let total = 0; +for (let i = 0; i < 200; i++) total += build(i); +console.log(total); +const kept = new Group(); +kept.add("a").group?.add("b"); +console.log(kept.length, kept.map((m) => m.name).join(","), kept[1]!.group === kept); diff --git a/tests/corpus/7725-array-subclass-operations.ts b/tests/corpus/7725-array-subclass-operations.ts new file mode 100644 index 000000000..94cfa8b04 --- /dev/null +++ b/tests/corpus/7725-array-subclass-operations.ts @@ -0,0 +1,46 @@ +// Array operations on an Array subclass instance: iteration, spread, +// destructuring, the mutators that return the instance itself, searches, +// length writes, Array.isArray through unknown, instanceof through unknown, +// and JSON. +class Row extends Array { + tag = "row"; + #reads = 0; + + static of2(a: string, b: string): Row { + const row = new Row(); + row.push(a, b); + return row; + } + + get size(): number { + this.#reads++; + return this.length; + } + + get reads(): number { + return this.#reads; + } + + label(): string { + return `${this.tag}:${this.join("|")}`; + } +} + +const r = new Row(); +console.log(r.push("b", "a", "c"), r.length, r.label()); +const [first, ...rest] = r; +console.log(first, rest, [...r].length); +for (const [i, v] of r.entries()) console.log(i, v); +console.log(r.sort() === r, r.label(), r.reverse() === r, r.label()); +console.log(r.fill("x", 1) === r, r.label(), r.indexOf("x"), r.lastIndexOf("x"), r.at(-1)); +console.log(r.pop(), r.shift(), r.length, r.unshift("z"), r.label()); +r.length = 0; +console.log(r.length, r.label(), JSON.stringify({ r })); +const u: unknown = r; +console.log(Array.isArray(u), u instanceof Row, u instanceof Array); +const rows: Row[] = [new Row(), new Row()]; +rows[1]!.push("q"); +console.log(rows.map((row) => row.label()).join(" ")); +console.log({ nested: rows[1] }); +const pair = Row.of2("m", "n"); +console.log(pair.size, pair.size, pair.reads, pair.label(), pair instanceof Row); diff --git a/tests/corpus/7726-array-subclass-expressions.js b/tests/corpus/7726-array-subclass-expressions.js new file mode 100644 index 000000000..d2e5d2a04 --- /dev/null +++ b/tests/corpus/7726-array-subclass-expressions.js @@ -0,0 +1,21 @@ +// Classes extending Array reached through expressions rather than +// declarations and locals: a construction used in place, a class +// expression held by a binding, and an instanceof test whose class the +// program never constructs. +class Sized extends Array { + describe() { + return `sized ${this.length}`; + } +} + +console.log(new Sized(3).length, new Sized(2).describe(), new Sized(1, 2).join("-")); +console.log(new Sized(4).fill(1), (new Sized(2)).length); + +const Holder = class extends Array {}; +const held = new Holder(); +held.push("a", "b"); +console.log(held instanceof Holder, held instanceof Array, [] instanceof Holder, held.length); + +class Unbuilt extends Array {} +const values = [[1, 2], "text", new Sized(1)]; +for (const value of values) console.log(value instanceof Unbuilt, Array.isArray(value)); diff --git a/tests/diagnostics/array-subclass-fences.ts b/tests/diagnostics/array-subclass-fences.ts new file mode 100644 index 000000000..cc2966201 --- /dev/null +++ b/tests/diagnostics/array-subclass-fences.ts @@ -0,0 +1,21 @@ +// Array subclasses that do not compile yet, each refused where it is +// declared: a generic subclass, a subclass of a class that extends Array, +// and a class that declares its own Symbol.species. +class Bag extends Array { + firstOr(fallback: T): T { + return this.length > 0 ? this[0]! : fallback; + } +} + +class Base extends Array {} +class Derived extends Base {} + +class Speciated extends Array { + static get [Symbol.species](): ArrayConstructor { + return Array; + } +} + +console.log(new Bag().firstOr(1)); +console.log(new Derived().length); +console.log(new Speciated().length); diff --git a/tests/harness/__snapshots__/array-subclass-fences.ts.txt b/tests/harness/__snapshots__/array-subclass-fences.ts.txt new file mode 100644 index 000000000..3d81a58a9 --- /dev/null +++ b/tests/harness/__snapshots__/array-subclass-fences.ts.txt @@ -0,0 +1,41 @@ +array-subclass-fences.ts:4:22 - error SC1090: a generic class that extends Array is not supported yet + + 3 | // and a class that declares its own Symbol.species. + 4 | class Bag extends Array { + | ^~~~~~~~ + 5 | firstOr(fallback: T): T { + +array-subclass-fences.ts:11:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet + + 10 | class Base extends Array {} + 11 | class Derived extends Base {} + | ^~~~ + 12 | + +array-subclass-fences.ts:14:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet + + 13 | class Speciated extends Array { + 14 | static get [Symbol.species](): ArrayConstructor { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 15 | return Array; + +array-subclass-fences.ts:19:13 - error SC1090: constructing values other than classes declared in the program is not supported yet + + 18 | + 19 | console.log(new Bag().firstOr(1)); + | ^~~~~~~~~~~~~~~~~ + 20 | console.log(new Derived().length); + +array-subclass-fences.ts:20:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + + 19 | console.log(new Bag().firstOr(1)); + 20 | console.log(new Derived().length); + | ^~~~~~~~~~~~~ + 21 | console.log(new Speciated().length); + +array-subclass-fences.ts:21:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + + 20 | console.log(new Derived().length); + 21 | console.log(new Speciated().length); + | ^~~~~~~~~~~~~~~ + 22 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json b/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7721-array-subclass-species.ts.json b/tests/harness/corpus-costs/7721-array-subclass-species.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7721-array-subclass-species.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json b/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json b/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json b/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json b/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json b/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } From c0d6c34f78df6c6a852a703c9c1f6b505ec589e3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 20:02:25 -0500 Subject: [PATCH 033/395] Compare integer values without converting them to doubles Two kinds of number compares still went through doubles when the integers were already at hand. Integer typed-array elements and `charCodeAt` results are integers or NaN (past the end). A compare of two such values, or of one and an exact integer, all within int32 (or all within uint32), now compares their 32-bit views, which hold ToUint32 of the value, as signed (or unsigned) integers, and folds in an ordered test of each double: NaN makes `<`, `<=`, `>`, `>=` and `===` false and `!==` true, as before. A sum, difference or product of two operands that always equal their doubles, such as `2 * i + 1` of a speculated counter, has a double that is its integer rounded once, even past 2^53. Rounding is monotonic, and the neighbors of a safe integer are doubles, so such a value compares with an exact safe integer as its integer does: the compare uses the i64s. A product by a power of two never rounds, so its wide view always equals its double. --- .../compiler/src/backend/llvm/expr-context.ts | 3 + .../src/backend/llvm/expr-primitives.ts | 76 ++++++++- .../src/backend/llvm/integer-values.ts | 49 +++++- .../test/integer-view-compares.test.ts | 16 +- .../corpus/7983-integer-compares.ts.json | 6 + tests/corpus/7983-integer-compares.ts | 150 ++++++++++++++++++ .../7983-integer-compares.ts.json | 1 + 7 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7983-integer-compares.ts.json create mode 100644 tests/corpus/7983-integer-compares.ts create mode 100644 tests/harness/corpus-costs/7983-integer-compares.ts.json diff --git a/packages/compiler/src/backend/llvm/expr-context.ts b/packages/compiler/src/backend/llvm/expr-context.ts index c6dc506f4..ac5262571 100644 --- a/packages/compiler/src/backend/llvm/expr-context.ts +++ b/packages/compiler/src/backend/llvm/expr-context.ts @@ -30,6 +30,9 @@ export interface WideInteger { name: string; below: number; bound: number; + /** The double is the integer rounded once: the result of one operation on + * operands that always equal their doubles. */ + single?: true; } export type ExprOf = Extract; diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 04af0fcef..370056ec4 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -13,13 +13,14 @@ import { emitStringInputs } from "./string-lifetimes.js"; import { emitStringParts, numberPart, stringParts } from "./string-construction.js"; import { emitBorrowedInputs } from "./borrowed-inputs.js"; import { - alwaysExactInteger, + exactCompareOperands, exactInteger, int32Number, integerNumber, narrowInteger, wideArithmetic, widenInteger, + type LlInteger, } from "./integer-values.js"; import { bitwiseIdentityOperand, @@ -45,7 +46,7 @@ import { slotWidth, type ArrayAccess, } from "./dense-array-access.js"; -import { emitWrappingSum, markWrappingOperands } from "./wrapping-sums.js"; +import { emitWrappingSum, integerOrNaNRange, markWrappingOperands } from "./wrapping-sums.js"; /** The signed integer predicates of the number compares. */ const INTEGER_COMPARES: Record = { @@ -57,6 +58,63 @@ const INTEGER_COMPARES: Record = { "!==": "ne", }; +const UNSIGNED_COMPARES: Record = { + "<": "ult", + "<=": "ule", + ">": "ugt", + ">=": "uge", + "===": "eq", + "!==": "ne", +}; + +/** A number compare of operands that are each an exact integer or an + * integer-or-NaN value (integerOrNaNRange: integer typed-array elements, + * `charCodeAt` results, sums of those), all within int32 or all within + * uint32: their 32-bit views compared, and NaN comparing false (unequal for + * `!==`). The 32-bit view is the value's ToUint32, which holds its bits + * whenever it is not NaN. Null when an operand has no such view. */ +function emitIntegerOrNaNCompare( + host: LlvmEmitterContext, + e: ExprOf<"bin">, + l: LlValue, + li: LlInteger | null, + r: LlValue, + ri: LlInteger | null, +): string | null { + const lr = li?.range ?? integerOrNaNRange(host, e.left), + rr = ri?.range ?? integerOrNaNRange(host, e.right); + if (!lr || !rr) return null; + const signed = withinInt32(lr) && withinInt32(rr); + if (!signed && !(withinUint32(lr) && withinUint32(rr))) return null; + const bits = (value: LlValue, integer: LlInteger | null): string | undefined => + integer ? narrowInteger(host, integer) : value.uint32; + const left = bits(l, li), + right = bits(r, ri); + if (left === undefined || right === undefined) return null; + const B = host.B; + const compare = B.tmp(); + B.line( + `${compare} = icmp ${(signed ? INTEGER_COMPARES : UNSIGNED_COMPARES)[e.op]} i32 ${left}, ${right}`, + ); + let result = compare; + for (const [value, integer] of [ + [l, li], + [r, ri], + ] as const) { + if (integer) continue; + const ordered = B.tmp(), + combined = B.tmp(); + B.line(`${ordered} = fcmp ord double ${value.name}, 0.0`); + if (e.op === "!==") { + const unordered = B.tmp(); + B.line(`${unordered} = xor i1 ${ordered}, true`); + B.line(`${combined} = or i1 ${result}, ${unordered}`); + } else B.line(`${combined} = and i1 ${result}, ${ordered}`); + result = combined; + } + return result; +} + export function emitLiteralExpr( host: LlvmEmitterContext, e: ExprOf<"numLit" | "boolLit" | "strLit" | "moduleNsRef" | "unitLit" | "varRef">, @@ -360,14 +418,16 @@ export function emitOperatorExpr( return value; } } else { - // An operand with only a wide view, such as `i + 1` of a speculated - // counter: compare the integers when both always equal the doubles. - const left = alwaysExactInteger(host, l, li), - right = left === null ? null : alwaysExactInteger(host, r, ri); - if (left !== null && right !== null) { - B.line(`${t} = icmp ${INTEGER_COMPARES[e.op]} i64 ${left}, ${right}`); + // An operand with only a wide view, such as `i + 1` or `2 * i + 1` + // of a speculated counter: compare the integers when that gives the + // doubles' result. + const operands = exactCompareOperands(host, l, li, r, ri); + if (operands) { + B.line(`${t} = icmp ${INTEGER_COMPARES[e.op]} i64 ${operands[0]}, ${operands[1]}`); return { name: t, type: e.type }; } + const nanCoded = emitIntegerOrNaNCompare(host, e, l, li, r, ri); + if (nanCoded) return { name: nanCoded, type: e.type }; B.line(`${t} = fcmp ${cmp[e.op]} double ${l.name}, ${r.name}`); } } else if (bit[e.op] !== undefined) { diff --git a/packages/compiler/src/backend/llvm/integer-values.ts b/packages/compiler/src/backend/llvm/integer-values.ts index 90954f4c6..497eb5b0d 100644 --- a/packages/compiler/src/backend/llvm/integer-values.ts +++ b/packages/compiler/src/backend/llvm/integer-values.ts @@ -111,13 +111,17 @@ export function wideArithmetic( rv = view(r, ri); if (!lv || !rv) return null; let below: number, bound: number; + let single = false; if (op === "*") { if (lv.below !== Infinity || rv.below !== Infinity) return null; - below = EXACT_DOUBLES; bound = productBound(lv.bound, rv.bound); + // Scaling by a power of two never rounds a double that stays finite. + below = powerOfTwo(li) || powerOfTwo(ri) ? Infinity : EXACT_DOUBLES; + single = true; } else if (lv.below === Infinity && rv.below === Infinity) { below = EXACT_DOUBLES; bound = sumBound(lv.bound, rv.bound); + single = true; } else if (lv.below === Infinity || rv.below === Infinity) { const [exact, wide] = lv.below === Infinity ? [lv, rv] : [rv, lv]; below = Math.min(wide.below - exact.bound, EXACT_DOUBLES); @@ -132,7 +136,48 @@ export function wideArithmetic( host.B.line( `${name} = ${op === "+" ? "add" : op === "-" ? "sub" : "mul"} nsw i64 ${left}, ${right}`, ); - return { name, below, bound }; + return single ? { name, below, bound, single } : { name, below, bound }; +} + +/** Whether an exact integer is the constant 2^k or -2^k. */ +function powerOfTwo(integer: LlInteger | null): boolean { + if (integer === null || integer.range.min !== integer.range.max) return false; + const value = Math.abs(integer.range.min); + return value >= 1 && value <= 2 ** 52 && Number.isInteger(Math.log2(value)); +} + +/** The i64 operands of a number compare that gives the doubles' result: + * both equal their doubles wherever they can be, or one is an exact integer + * of magnitude at most 2^53 - 1 and the other's double is its integer + * rounded once. Rounding is monotonic, and b - 1, b and b + 1 are doubles + * for a safe integer b, so an integer rounded once compares with b as the + * integer does. Null for any other pair. */ +export function exactCompareOperands( + host: LlvmEmitterContext, + l: LlValue, + li: LlInteger | null, + r: LlValue, + ri: LlInteger | null, +): [string, string] | null { + const describe = (value: LlValue, integer: LlInteger | null) => { + if (integer) return { exact: true, single: true, bound: magnitude(integer.range) }; + const wide = value.wideInteger; + if (!wide) return null; + return { exact: wide.bound <= wide.below, single: wide.single === true, bound: wide.bound }; + }; + const a = describe(l, li), + b = describe(r, ri); + if (!a || !b) return null; + const safe = (d: { exact: boolean; bound: number }): boolean => + d.exact && d.bound <= Number.MAX_SAFE_INTEGER; + const ok = + (a.exact && b.exact) || + (safe(a) && (b.exact || b.single)) || + (safe(b) && (a.exact || a.single)); + if (!ok) return null; + const operand = (value: LlValue, integer: LlInteger | null): string => + integer ? widenInteger(host, integer) : value.wideInteger!.name; + return [operand(l, li), operand(r, ri)]; } /** An i64 index for a consumer that compares it, unsigned, with a length or diff --git a/packages/compiler/test/integer-view-compares.test.ts b/packages/compiler/test/integer-view-compares.test.ts index e988d48bd..4e814368d 100644 --- a/packages/compiler/test/integer-view-compares.test.ts +++ b/packages/compiler/test/integer-view-compares.test.ts @@ -22,7 +22,7 @@ siftDown(a, 0, Number(process.argv[2] ?? "5")); console.log(Array.from(a).join()); `; -test("integer sums of speculated locals compare and index as integers", async () => { +test("integer values compare and index as integers", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-integer-views-")); try { const entry = join(dir, "sift.ts"); @@ -43,9 +43,17 @@ test("integer sums of speculated locals compare and index as integers", async () expect(uses("icmp slt").length).toBeGreaterThan(0); // and `a[child + 1]` bounds the integer index without a double round trip. expect(uses("icmp ult").length).toBeGreaterThan(0); - // `2 * root + 1 < end` may pass 2^53, so it keeps the double compare. - expect(body).toMatch(/fmul double 0x4000000000000000, %t\d+\n\s+%t\d+ = mul nsw i64 2/); - expect(body).toContain("fcmp olt double"); + // `2 * root + 1` may pass 2^53, but its double is its integer rounded + // once (doubling is exact) and `end` is a safe integer, so the integers + // compare as the doubles do. + const doubled = /(%t\d+) = mul nsw i64 2, %t\d+\n/.exec(body)![1]!; + const plusOne = new RegExp(`(%t\\d+) = add nsw i64 \\${doubled}, 1\\n`).exec(body)![1]!; + expect(body).toMatch(new RegExp(`= icmp slt i64 \\${plusOne}, %t\\d+\\n`)); + // `a[child]! < a[child + 1]!` compares the elements' int32 bits, and is + // false when either read is out of range (NaN). + const ordered = String.raw`\s+%t\d+ = fcmp ord double %t\d+, 0\.0\n\s+%t\d+ = and i1 %t\d+, %t\d+\n`; + expect(body).toMatch(new RegExp(String.raw`icmp slt i32 %t\d+, %t\d+\n(${ordered}){2}`)); + expect(body).toMatch(new RegExp(String.raw`icmp sge i32 %t\d+, %t\d+\n(${ordered}){2}`)); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7983-integer-compares.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7983-integer-compares.ts.json new file mode 100644 index 000000000..f7c61e778 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7983-integer-compares.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7983-integer-compares.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7983-integer-compares.ts b/tests/corpus/7983-integer-compares.ts new file mode 100644 index 000000000..1114df8a8 --- /dev/null +++ b/tests/corpus/7983-integer-compares.ts @@ -0,0 +1,150 @@ +// Compares of integer values that are not doubles in the generated code: +// typed-array elements and char codes, which read as NaN past the end, and +// sums such as `2 * i + 1` whose double is the integer rounded once, near and +// past 2^53. + +function countPairs(a: Int32Array): string { + // `j` runs one past the end, where `a[j]` is NaN and every order compare + // is false. + let lt = 0; + let ge = 0; + let eq = 0; + let ne = 0; + for (let i = 0; i < a.length; i++) { + for (let j = 0; j <= a.length; j++) { + if (a[i]! < a[j]!) lt++; + if (a[i]! >= a[j]!) ge++; + if (a[i]! === a[j]!) eq++; + if (a[i]! !== a[j]!) ne++; + } + } + return `${lt} ${ge} ${eq} ${ne}`; +} + +function pastTheEnd(a: Int32Array, b: Uint8Array, c: Uint32Array): string { + const n = a.length; + const out: boolean[] = []; + out.push(a[n]! < a[0]!, a[n]! > a[0]!, a[n]! <= a[n]!, a[n]! === a[0]!, a[0]! !== a[n]!); + out.push(a[n]! < 0, a[n]! >= -1, a[-1]! < a[0]!, b[n]! <= b[0]!, b[0]! !== b[n]!); + out.push(c[n]! < c[0]!, c[n]! >= c[n]!, c[0]! === c[0]!, c[n]! !== c[0]!); + out.push(a[0]! < b[n]!, b[0]! >= a[n]!, a[0]! < c[n]!); + return out.map((v) => (v ? 1 : 0)).join(""); +} + +function unsigned(c: Uint32Array): string { + // Elements above 2^31 compare as unsigned values. + let below = 0; + let above = 0; + let same = 0; + for (let i = 0; i < c.length; i++) { + for (let j = 0; j < c.length; j++) { + if (c[i]! < c[j]!) below++; + if (c[i]! > c[j]!) above++; + if (c[i]! === c[j]!) same++; + } + } + return `${below} ${above} ${same}`; +} + +function mixed(a: Int32Array, b: Uint8Array, c: Uint32Array, d: Int16Array): string { + let count = 0; + for (let i = 0; i < a.length; i++) { + if (a[i]! < b[i]!) count += 1; + if (a[i]! < c[i]!) count += 10; + if (c[i]! > d[i]!) count += 100; + if (b[i]! >= d[i]!) count += 1000; + if (a[i]! === c[i]!) count += 10000; + } + return String(count); +} + +function charCodes(s: string): string { + // `s.charCodeAt(s.length)` is NaN. + let rising = 0; + let falls = 0; + let equal = 0; + let differ = 0; + for (let i = 0; i < s.length; i++) { + if (s.charCodeAt(i) < s.charCodeAt(i + 1)) rising++; + if (s.charCodeAt(i) >= s.charCodeAt(i + 1)) falls++; + if (s.charCodeAt(i) === s.charCodeAt(i + 1)) equal++; + if (s.charCodeAt(i) !== s.charCodeAt(i + 1)) differ++; + } + return `${rising} ${falls} ${equal} ${differ}`; +} + +function heapChildren(start: number, end: number, steps: number): string { + // `2 * i + 1` passes 2^53 for `i` at or above 2^52, where its double + // rounds; `end` stays a safe integer. + const out: string[] = []; + for (let i = start; i < start + steps; i++) { + const child = 2 * i + 1; + out.push( + [2 * i + 1 < end, 2 * i + 1 <= end, 2 * i + 1 > end, 2 * i + 1 === end, 2 * i + 1 !== end] + .map((v) => (v ? 1 : 0)) + .join("") + ` ${child}`, + ); + } + return out.join(" "); +} + +function countNear(start: number, end: number, steps: number): string { + // The same compares in a loop over integer locals only, stopping early if + // `i` reaches `end`. + let lt = 0; + let le = 0; + let gt = 0; + let eq = 0; + let ne = 0; + for (let i = start; i < start + steps && i !== end; i++) { + if (2 * i + 1 < end) lt++; + if (2 * i + 1 <= end) le++; + if (2 * i + 1 > end) gt++; + if (2 * i + 1 === end) eq++; + if (2 * i + 1 !== end) ne++; + } + return `${lt} ${le} ${gt} ${eq} ${ne}`; +} + +function descend(root: number, end: number): number { + // A sift-down's index walk: doubles `root` until `2 * root + 1` reaches `end`. + let steps = 0; + while (2 * root + 1 < end) { + root = 2 * root + 1; + steps++; + } + return steps * 1000 + (root % 1000); +} + +function scaled(i: number, end: number): string { + // Products by a power of two never round: 4 * i and 1024 * i compare exactly. + return [4 * i < end, 4 * i === end, 1024 * i - 1 < end, 3 * i + 1 < end, 2 * i - 1 > -end] + .map((v) => (v ? 1 : 0)) + .join(""); +} + +const a = new Int32Array([5, -3, 2147483647, -2147483648, 5, 0]); +const b = new Uint8Array([0, 255, 7, 128, 5, 0]); +const c = new Uint32Array([4294967295, 2147483648, 2147483647, 0, 1, 4294967295]); +const d = new Int16Array([-32768, 32767, -1, 1, 5, 0]); +console.log(countPairs(a)); +console.log(countPairs(new Int32Array(0))); +console.log(pastTheEnd(a, b, c)); +console.log(unsigned(c)); +console.log(mixed(a, b, c, d)); +console.log(charCodes("abcddcbaÿ￿\u0000")); +console.log(charCodes("")); +const p52 = 2 ** 52; +const safe = Number.MAX_SAFE_INTEGER; +console.log(heapChildren(p52 - 2, safe, 5)); +console.log(heapChildren(p52 - 2, safe - 1, 5)); +console.log(heapChildren(p52 + 100, safe, 3)); +console.log(heapChildren(-p52 - 2, -safe, 5)); +console.log(heapChildren(0, 4, 4)); +console.log(countNear(p52 - 3, safe, 6), countNear(p52 - 3, safe - 2, 6), countNear(p52, safe, 4)); +console.log(countNear(-p52 - 3, -safe, 6), countNear(-p52 + 1, -safe + 4, 4), countNear(0, 7, 6)); +console.log(descend(0, safe), descend(1, safe), descend(p52 - 1, safe), descend(p52, safe)); +console.log(descend(3, 100), descend(0, 1), descend(5, 0)); +for (const i of [0, 1, p52 / 2 - 1, p52 / 2, p52 / 1024, p52 / 2 + 1, p52, -p52]) { + console.log(scaled(i, safe), scaled(i, safe - 1), scaled(i, 2 ** 51), scaled(i, 4)); +} diff --git a/tests/harness/corpus-costs/7983-integer-compares.ts.json b/tests/harness/corpus-costs/7983-integer-compares.ts.json new file mode 100644 index 000000000..8bfb69ce2 --- /dev/null +++ b/tests/harness/corpus-costs/7983-integer-compares.ts.json @@ -0,0 +1 @@ +{ "plain": 2000, "san": 2100 } From 7bce848e4d69c044e7d5595a2f59f19b8e33eb84 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 20:13:09 -0500 Subject: [PATCH 034/395] Freeze class instances held in untyped values Object.freeze, Object.seal and Object.preventExtensions of a class instance held in an untyped value threw SC2020 at run time. Node changes the instance itself: writes through its typed references then throw the read-only TypeError in strict mode code, adding properties through any reference fails, and Object.isFrozen, isSealed and isExtensible report the state through either reference. - In a program that freezes, seals or prevents extensions of an untyped value that is not a fresh literal, every class hierarchy that can be held there gets the integrity word that static Object.freeze already uses, and with it the existing write checks. - The runtime applies the operation to the instance's checked view and commits the view. The commit of such a hierarchy stores the view's integrity state in the word (scr_dyn_class_view_integrity) and makes the instance's property bag not extensible with it. A hierarchy without the word still throws SC2020. - These commits replace and remove the bag's field entries whatever their attributes, so a sealed or frozen view does not leave stale field values in the bag. - The analysis behind records and arrays now starts from these calls only, rather than from every untyped freeze the lowering emits, such as the frozen strings array of a tagged template. - Programs without such calls compile as before. The runtime ABI moves to v38. Corpus 7407 covers freeze, seal and preventExtensions through unknown of classes and subclasses, writes through typed and untyped references and through methods, properties added before and after the operation, and the integrity queries, and matches Node. Refs #1374 Co-authored-by: blurname <50226652+blurname@users.noreply.github.com> --- .../builtin-apis/object-freezing.mdx | 3 +- packages/cli/test/native-link-info.test.ts | 4 +- packages/compiler/src/backend/llvm/emitter.ts | 10 +- .../src/backend/llvm/expr-stream-bridges.ts | 28 +++- packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/class-attributes.ts | 24 ++- .../lowering/expressions/property-access.ts | 2 + .../src/frontend/lowering/frozen-records.ts | 20 ++- .../src/frontend/lowering/lower-calls.ts | 7 +- .../compiler/src/frontend/lowering/lowerer.ts | 7 +- packages/compiler/src/ir/ir.ts | 5 + packages/compiler/src/ir/validate.ts | 2 + .../compiler/test/class-attributes.test.ts | 30 ++++ .../test/native-codegen-integration.test.ts | 4 +- .../7407-frozen-class-instances.ts.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_dyn.h | 9 ++ packages/runtime/src/scr_json.c | 96 +++++++++--- tests/corpus/7407-frozen-class-instances.ts | 143 ++++++++++++++++++ .../7407-frozen-class-instances.ts.json | 1 + .../self-hosting-native-toolchain.test.ts | 2 +- 26 files changed, 372 insertions(+), 47 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7407-frozen-class-instances.ts.json create mode 100644 tests/corpus/7407-frozen-class-instances.ts create mode 100644 tests/harness/corpus-costs/7407-frozen-class-instances.ts.json diff --git a/docs/content/limitations/builtin-apis/object-freezing.mdx b/docs/content/limitations/builtin-apis/object-freezing.mdx index 99b78ded5..030b03b4b 100644 --- a/docs/content/limitations/builtin-apis/object-freezing.mdx +++ b/docs/content/limitations/builtin-apis/object-freezing.mdx @@ -4,7 +4,8 @@ Class instances support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible` when the static type of the argument names the class. A write to a read-only field throws Node's `TypeError` in strict mode code and leaves the field unchanged in sloppy mode code. `Object.defineProperty` with a string-literal key changes the value, `writable`, `enumerable`, and `configurable` attributes of a declared field; an object-literal accessor descriptor (`get` or `set`) for a declared field is refused at compile time with `SC2020`, and another accessor descriptor for a declared field throws `SC2020` when the call runs. - Freezing, sealing, or preventing extensions of a class instance held in an untyped value throws `SC2020`. + `Object.freeze`, `Object.seal`, and `Object.preventExtensions` of a class instance held in an untyped value change the instance itself, so writes through its typed references are checked the same way. + The `TypeError` for a write to a read-only property that was added to a class instance through an untyped value names `#` where Node names the class. Functions support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible`; the operations apply to the function's own properties (`name`, `length`, `prototype`, and the ones a program assigns). Class constructors are not supported. A write to a read-only property of a checked dictionary or a function throws a `TypeError` whose message omits Node's `'x' of object '#'` (or `of function '…'`) suffix. diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index 8ac579449..f85b6908f 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v37"); + expect(error?.stderr).toContain("scr_runtime_abi_v38"); }); }); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 8106a6478..3bf1c34d7 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1387,6 +1387,10 @@ export class LlEmitter { * commit back into their objects. Library builds keep their commits: * their values cross a native boundary. */ readonly liveViewsReadOnly: boolean; + /** The program freezes, seals or prevents extensions of untyped values + * (IrModule.untypedIntegrity): the commits of class views store the + * view's integrity state in the instance's integrity word. */ + readonly untypedIntegrity: boolean; /** Functions whose direct calls use i32 parameters or results. */ readonly int32Abis: ReadonlyMap; /** Pure number functions with speculative integer clones. */ @@ -1599,8 +1603,12 @@ export class LlEmitter { initializerBindings: this.initializerBindings, nodes: this.nodeIndex, }); + this.untypedIntegrity = mod.untypedIntegrity === true; this.liveViewsReadOnly = - mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0; + mod.lib === undefined && + !this.untypedIntegrity && + !dynamicStores.all && + dynamicStores.names.size === 0; this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map(); this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []); this.programLifetime = new Set(mod.contracts?.programLifetime ?? []); diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 1acc75cec..757e4349f 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -437,10 +437,14 @@ export function streamTypedRefCommitAdapter( host.declare(`declare ptr @scr_str_new(ptr, ${host.sizeType})`); host.declare(`declare void @scr_str_release(ptr)`); host.declare(`declare void @scr_dyn_key_delete(ptr, ptr, i1 zeroext)`); + // The view of a sealed or frozen instance holds its fields as + // non-configurable entries, which the bag copies. + const withAttributes = meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES); + const placeholderSet = withAttributes ? "scr_dyn_obj_set_field" : "scr_dyn_obj_set"; if (meta.def.tracksOwnFields) { host.declare(`declare ptr @scr_dyn_retain_v(ptr)`); - host.declare(`declare void @scr_dyn_obj_set(ptr, ptr, ${host.sizeType}, ptr)`); - } + host.declare(`declare void @${placeholderSet}(ptr, ptr, ${host.sizeType}, ptr)`); + } else if (withAttributes) host.declare(`declare void @scr_dyn_obj_delete_field(ptr, ptr)`); lines.push( ` %bag = call ptr @scr_dyn_new_obj()`, ` %bag_copy = call ptr @scr_dyn_copy_property_descriptors(ptr %bag, ptr %d)`, @@ -477,14 +481,16 @@ export function streamTypedRefCommitAdapter( `bag_field${index}_present:`, ` %bag_undefined${index} = call ptr @scr_dyn_undefined()`, ` %bag_placeholder${index} = call ptr @scr_dyn_retain_v(ptr %bag_undefined${index})`, - ` call void @scr_dyn_obj_set(ptr %bag, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr %bag_placeholder${index})`, + ` call void @${placeholderSet}(ptr %bag, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr %bag_placeholder${index})`, ` br label %bag_field${index}_after`, `bag_field${index}_after:`, ); else lines.push( ` %bag_key${index} = call ptr @scr_str_new(ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")})`, - ` call void @scr_dyn_key_delete(ptr %bag, ptr %bag_key${index}, i1 zeroext false)`, + withAttributes + ? ` call void @scr_dyn_obj_delete_field(ptr %bag, ptr %bag_key${index})` + : ` call void @scr_dyn_key_delete(ptr %bag, ptr %bag_key${index}, i1 zeroext false)`, ` call void @scr_str_release(ptr %bag_key${index})`, ); }); @@ -516,6 +522,20 @@ export function streamTypedRefCommitAdapter( host.declare(`declare void @scr_error_commit_dyn(ptr, ptr)`); lines.push(` call void @scr_error_commit_dyn(ptr %target, ptr %d)`); } + // Untyped Object.freeze, seal and preventExtensions apply to the view; + // the instance's integrity word records them. + if (host.untypedIntegrity && meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES)) { + const bag = meta.def.fields.some((field) => field.name === DYN_CLASS_PROPERTIES) + ? "%bag" + : "null"; + host.declare(`declare double @scr_dyn_class_view_integrity(ptr, double, ptr)`); + lines.push( + ` %word_ptr = getelementptr inbounds %${classStructSym(t.className)}, ptr %target, i64 0, i32 ${classFieldIndex(meta, CLASS_ATTRIBUTES).index}`, + ` %word = load double, ptr %word_ptr`, + ` %word_next = call double @scr_dyn_class_view_integrity(ptr %d, double %word, ptr ${bag})`, + ` store double %word_next, ptr %word_ptr`, + ); + } lines.push(` br label %done`, `done:`, ` ret void`, `}`, ``); host.resolveThunkDefs.push(...lines); return `@${commit}`; diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index 11bde6c2b..612cd2b2e 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 37 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v37" as const; +export const RUNTIME_ABI_VERSION = 38 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v38" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 9846e8d89..5227f6062 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index e2b24f8ca..e401d5ed2 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -128,7 +128,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/class-attributes.ts b/packages/compiler/src/frontend/lowering/class-attributes.ts index 4e7bbe801..de7cc8ca7 100644 --- a/packages/compiler/src/frontend/lowering/class-attributes.ts +++ b/packages/compiler/src/frontend/lowering/class-attributes.ts @@ -39,8 +39,14 @@ import { runtimeRooted } from "./class-descriptors.js"; type ClassObjectType = Extract; function attributeClass(lowerer: Lowerer, target: IrExpr): ClassInfo | null { - if (!isDynTypedRefType(target.type)) return null; - const info = lowerer.classes.get(target.type.className); + return isDynTypedRefType(target.type) + ? attributeClassNamed(lowerer, target.type.className) + : null; +} + +/** The compiled class `className`, when its instances can carry attributes. */ +function attributeClassNamed(lowerer: Lowerer, className: string): ClassInfo | null { + const info = lowerer.classes.get(className); if (!info || runtimeRooted(info) || info.decl == null) return null; return info; } @@ -62,6 +68,20 @@ function addHiddenField(info: ClassInfo, name: string): void { add(root); } +/** In a program that freezes, seals or prevents extensions of untyped + * values, gives every hierarchy whose instances can be held there the + * integrity word. The runtime applies the operation to the instance's + * checked view, whose commit stores the view's integrity state in the word + * (backend streamTypedRefCommitAdapter), and the write checks below read + * it. */ +export function addUntypedIntegrityWords(lowerer: Lowerer): void { + if (!lowerer.untypedIntegrity) return; + for (const [className, info] of lowerer.classes) { + if (isDynTypedRefType({ kind: "object", className }) && attributeClassNamed(lowerer, className)) + addHiddenField(info, CLASS_ATTRIBUTES); + } +} + function hiddenFieldGet(receiver: IrExpr, className: string, field: string, loc: SrcLoc): IrExpr { return { kind: "fieldGet", obj: receiver, className, field, type: F64, loc }; } diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 1a4883e5a..4ef003f81 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -499,6 +499,8 @@ export function lowerPropertyAccessExpr( }; } if (lowerer.stdlibGlobalMember(expr, "Object") === "freeze") { + // Object.freeze as a value can freeze any untyped value it is passed. + lowerer.untypedIntegrity = true; const name = "%builtin.Object.freeze"; if (!lowerer.liftedFns.some((fn) => fn.name === name)) lowerer.liftedFns.push({ diff --git a/packages/compiler/src/frontend/lowering/frozen-records.ts b/packages/compiler/src/frontend/lowering/frozen-records.ts index 30eb9e455..fc770a0cb 100644 --- a/packages/compiler/src/frontend/lowering/frozen-records.ts +++ b/packages/compiler/src/frontend/lowering/frozen-records.ts @@ -24,7 +24,8 @@ import { isStrictCode } from "./strict-mode.js"; * (scr_array.h SCR_ARR_FROZEN), which the backend's array write guards test * for the array types the module lists (IrModule.freezableArrays). A type is * freezable when a static Object.freeze names it, or, in a program that - * freezes untyped values, when a value of it or of a type reaching it can be + * freezes, seals or prevents extensions of untyped values + * (noteUntypedIntegrity), when a value of it or of a type reaching it can be * held as a live untyped reference. */ type RecordType = Extract; @@ -59,6 +60,17 @@ export function lowerNativeFreeze( }; } +/** Records that the program freezes, seals or prevents extensions of the + * untyped value `node` lowers to. Unless it is a fresh object or array + * literal, the value can be any record, array or class instance the program + * holds as an untyped reference, so those types become freezable. */ +export function noteUntypedIntegrity(lowerer: Lowerer, node: ts.Expression): void { + let inner = node; + while (ts.isParenthesizedExpression(inner) || ts.isAsExpression(inner)) inner = inner.expression; + if (!ts.isObjectLiteralExpression(inner) && !ts.isArrayLiteralExpression(inner)) + lowerer.untypedIntegrity = true; +} + function isRecordWrite(s: IrStmt): s is RecordWrite { return s.kind === "recordSet" || s.kind === "recordKeySet" || s.kind === "recordKeyDelete"; } @@ -109,7 +121,6 @@ function freezableTypes( const shapes = new Set(); const arrays = new Set(); const live: IrType[] = []; - let dynFreeze = false; for (const fn of functions) everyStmtList(fn.body, { expr: (e) => { @@ -119,13 +130,12 @@ function freezableTypes( } else if (e.kind === "libCall" && e.fn === "array.freeze") { const target = e.args[0]; if (target && target.type.kind === "array") arrays.add(typeKey(target.type)); - } else if (e.kind === "libCall" && e.fn === "dyn.freeze") dynFreeze = true; - else if (e.kind === "dynFrom") live.push(e.value.type); + } else if (e.kind === "dynFrom") live.push(e.value.type); return true; }, stmt: () => true, }); - if (!dynFreeze) return { shapes, arrays }; + if (!lowerer.untypedIntegrity) return { shapes, arrays }; // An untyped reference to a class instance, or to a record or array // boxed live, reaches the values its fields and elements hold as further // live references. Class instances also cross into untyped code through diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 8ac97fd32..0db330077 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -249,7 +249,7 @@ import { import { fenceSymbolFieldCopy } from "./symbol-fields.js"; import { lowerClassDataDescriptor, lowerClassDescriptorRead } from "./class-descriptors.js"; import { lowerClassFieldDefine, lowerClassIntegrity } from "./class-attributes.js"; -import { freezableRecordType, lowerNativeFreeze } from "./frozen-records.js"; +import { freezableRecordType, lowerNativeFreeze, noteUntypedIntegrity } from "./frozen-records.js"; import { classStaticDataFor } from "./class-static-data.js"; import { lowerUrlFactory } from "./lower-url.js"; import { nullishValueUnitOf } from "./binding-analysis.js"; @@ -8544,6 +8544,7 @@ function lowerObjectStaticCall( isSealed: "dyn.isSealed", } as const; const fn = operation[member as keyof typeof operation]; + if (!member.startsWith("is")) noteUntypedIntegrity(lowerer, call.arguments.nodes[0]!); return { kind: "libCall", fn, @@ -8610,8 +8611,10 @@ function lowerObjectStaticCall( (ts.isObjectLiteralExpression(inner) || ts.isArrayLiteralExpression(inner)) ? lowerer.lowerExprExpecting(argNode, DYN) : lowerer.lowerExpr(argNode); - if (value.type.kind === "dyn") + if (value.type.kind === "dyn") { + noteUntypedIntegrity(lowerer, argNode); return { kind: "libCall", fn: "dyn.freeze", args: [value], type: DYN, loc: locOf(call) }; + } if (ts.isObjectLiteralExpression(inner) || ts.isArrayLiteralExpression(inner)) { return value; // fresh — freeze is identity here, honestly } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 3582e8e8a..f14f20265 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -151,7 +151,7 @@ import type { FsPathPlan } from "./builtins/fs-paths.js"; import { defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js"; import { type ProtocolDispatch } from "./class-protocol-dispatch.js"; import { ClassDynamicDispatch, trackClassFieldCreation } from "./class-dynamic-dispatch.js"; -import { guardClassAttributeWrites } from "./class-attributes.js"; +import { addUntypedIntegrityWords, guardClassAttributeWrites } from "./class-attributes.js"; import { guardFrozenWrites, markSloppyRecordWrites } from "./frozen-records.js"; import { applyHandleProperties } from "./handle-properties.js"; import { installBufferValue } from "./containers/bytes.js"; @@ -3296,6 +3296,9 @@ export class Lowerer { readonly recordFieldDeletes = new Set(); /** typeKeys of the array types the program may freeze (IrModule.freezableArrays). */ readonly freezableArrays = new Set(); + /** Whether the program freezes, seals or prevents extensions of untyped + * values other than fresh literals (noteUntypedIntegrity). */ + untypedIntegrity = false; readonly liftedFns: IrFunction[] = []; /** The first lifted function of each name (liftedFnNamed), for the * liftedFns indexed so far. liftedFns only grows. */ @@ -7767,6 +7770,7 @@ export class Lowerer { finalizeClassMethodValues(this, functions); finalizePrivateBrands(this, functions); trackClassFieldCreation(this, functions); + addUntypedIntegrityWords(this); guardClassAttributeWrites(this, functions); guardFrozenWrites(this, functions); applyHandleProperties(this, functions); @@ -7881,6 +7885,7 @@ export class Lowerer { ...(this.freezableArrays.size > 0 ? { freezableArrays: [...this.freezableArrays].sort() } : {}), + ...(this.untypedIntegrity ? { untypedIntegrity: true as const } : {}), ...(isNodeEsmFile(this.entry, this.program) ? {} : { commonJsEntry: true }), ...(this.ffiImports.length > 0 ? { ffiImports: [...this.ffiImports] } : {}), }; diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 5916a5166..636634dab 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1073,6 +1073,11 @@ export interface IrModule { * (frontend frozen-records.ts): their writes test the array's frozen bit * (SCR_ARR_FROZEN) and throw Node's TypeError when it is set. */ freezableArrays?: string[]; + /** The program freezes, seals or prevents extensions of untyped values + * (frontend frozen-records.ts noteUntypedIntegrity). Class hierarchies + * that can be held there carry an integrity word, which the runtime sets + * through the commit of the instance's checked view. */ + untypedIntegrity?: true; /** The entry module is CommonJS under Node's classification. Node runs a * CommonJS main synchronously and drains process.nextTick callbacks * before promise jobs at the first checkpoint; an ES-module main's diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index a694e8d2c..8152775d1 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -123,6 +123,8 @@ export function validateModule(mod: IrModule): IrValidationError[] { errors.push({ message: "worker execution mode must be a boolean", loc: moduleLoc }); if (mod.declBoundary !== undefined && mod.declBoundary !== true) errors.push({ message: "declBoundary must be true when present", loc: moduleLoc }); + if (mod.untypedIntegrity !== undefined && mod.untypedIntegrity !== true) + errors.push({ message: "untypedIntegrity must be true when present", loc: moduleLoc }); if (mod.lifetimeRegions !== undefined && mod.lifetimeRegions !== true) errors.push({ message: "lifetimeRegions must be true when present", loc: moduleLoc }); if (mod.workers && mod.lib !== undefined) diff --git a/packages/compiler/test/class-attributes.test.ts b/packages/compiler/test/class-attributes.test.ts index 1a6fea654..a0e6efa62 100644 --- a/packages/compiler/test/class-attributes.test.ts +++ b/packages/compiler/test/class-attributes.test.ts @@ -82,3 +82,33 @@ b.x = 2; expect(throwing.length).toBeGreaterThan(0); expect(throwing.every((name) => name.includes("constructor"))).toBe(true); }); + +test("an untyped freeze gives every class hierarchy attribute state", async () => { + const mod = await lowerModule(` +export {}; +class Point { x = 1; } +class Labeled extends Point { label = "a"; } +function freezeUnknown(value: unknown): unknown { return Object.freeze(value); } +function move(p: Point): void { p.x = 2; } +freezeUnknown(new Labeled()); +move(new Point()); +`); + expect(mod.untypedIntegrity).toBe(true); + expect(fieldsOf(mod, "Point")).toContain("%attributes"); + expect(fieldsOf(mod, "Labeled")).toContain("%attributes"); + expect(functionsCalling(mod, "class.throwReadOnly")).toContain("move"); +}); + +test("untyped freezes of fresh literals and tagged templates leave classes unchanged", async () => { + const mod = await lowerModule( + ` +class Point { constructor() { this.x = 1; } } +const tag = (strings) => strings.length; +const frozen = Object.freeze({ a: 1 }); +console.log(tag\`a\${1}b\`, frozen.a, new Point().x); +`, + "main.js", + ); + expect(mod.untypedIntegrity).toBeUndefined(); + expect(fieldsOf(mod, "Point")).not.toContain("%attributes"); +}); diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index cecebb49c..2a0f8371e 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v37"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v38"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v37"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v38"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7407-frozen-class-instances.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7407-frozen-class-instances.ts.json new file mode 100644 index 000000000..da868130c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7407-frozen-class-instances.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7407-frozen-class-instances.ts" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index e492c36b4..951883c07 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -340,7 +340,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index e22f2a37c..988c899cf 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -437,7 +437,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 086340014..819121c3f 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v37(void) {} +void scr_runtime_abi_v38(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index 3707b8d50..20c591cbb 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v37(void); +void scr_runtime_abi_v38(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 5b1598a8b..bf3b8d426 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1182,6 +1182,12 @@ void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor); void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits); void scr_dyn_class_view_attributes(ScrDyn *view, double bits); +/* The integrity state of a class instance's checked view as CLASS_* bits, + * added to `bits`. The commits of hierarchies with an integrity word store + * it in the word, in programs that freeze, seal or prevent extensions of + * untyped values. `bag`, the instance's new property bag or NULL, stops + * being extensible with the view. */ +double scr_dyn_class_view_integrity(const ScrDyn *view, double bits, ScrDyn *bag); ScrDyn *scr_class_define_field(ScrDyn *current, double field_bits, double object_bits, ScrStr *name, ScrDyn *descriptor); double scr_class_defined_bits(ScrDyn *holder, ScrStr *name); @@ -1211,6 +1217,9 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value); void scr_dyn_arr_named_put(ScrDyn *arr, const char *key, size_t key_len, ScrDyn *value); void scr_dyn_key_set_computed(ScrDyn *recv, ScrDyn *key, ScrDyn *value); void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict); +/* Removes the own property `key` of an ordinary object whatever its + * attributes, as scr_dyn_obj_set_field replaces values. */ +void scr_dyn_obj_delete_field(ScrDyn *obj, const ScrStr *key); /* This thread's global object box (borrowed). Every spelling of the global * object answers it; its symbol-keyed properties are the global object's * (scr_node_builtin.c). */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 40fd73508..2f8e4b6f8 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2389,9 +2389,74 @@ void scr_record_throw_frozen(ScrStr *key, ScrStr *owner, double operation) { scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); } -/* A class instance's integrity state lives in its native layout, which only - * statically typed calls can reach (the compiler adds the state to the - * hierarchies they name). Through an untyped value the operation throws. */ +/* ── class instance attributes ─────────────────────────────────────────── + * A hierarchy passed to Object.freeze, Object.seal, Object.preventExtensions + * or Object.defineProperty keeps an integrity state per instance and an + * attribute word per redefined field (CLASS_* and FIELD_* bits in the + * compiler's ir.ts). The checked view of an instance carries them, so + * reflection over the view answers as Node does. */ +enum { + SCR_CLASS_NOT_EXTENSIBLE = 1, + SCR_CLASS_SEALED = 2, + SCR_CLASS_FROZEN = 4, + SCR_FIELD_NOT_WRITABLE = 1, + SCR_FIELD_NOT_ENUMERABLE = 2, + SCR_FIELD_NOT_CONFIGURABLE = 4, +}; + +/* Set by scr_dyn_class_view_integrity, which only the commits of + * hierarchies with an integrity word call. */ +static SCR_TL bool scr_class_integrity_committed; + +double scr_dyn_class_view_integrity(const ScrDyn *view, double bits, ScrDyn *bag) { + scr_class_integrity_committed = true; + unsigned state = (unsigned)bits; + if (view->kind != SCR_DYN_OBJ || !view->non_extensible) return state; + state |= SCR_CLASS_NOT_EXTENSIBLE; + if (bag && bag->kind == SCR_DYN_OBJ) bag->non_extensible = true; + bool sealed = true, frozen = true; + for (size_t i = 0; i < view->v.obj.len; i++) { + const ScrDynEntry *entry = &view->v.obj.entries[i]; + if (entry->configurable) sealed = false; + if (!entry->accessor && entry->writable) frozen = false; + } + if (sealed) state |= SCR_CLASS_SEALED; + if (sealed && frozen) state |= SCR_CLASS_FROZEN; + return state; +} + +/* Object.freeze, Object.seal or Object.preventExtensions (`operation`) of + * the class instance a capsule refers to. In a program that applies them to + * untyped values, every hierarchy that can be held there keeps an + * integrity word (class-attributes.ts), and its commit stores the + * integrity state of the instance's checked view in the word + * (scr_dyn_class_view_integrity). The operation applies to the view, and + * the commit records it. A first commit of the unchanged view finds out + * whether the hierarchy has the word. Answers false for other values and + * for instances whose hierarchy has no word, whose state stays as it was, + * and true when the operation applied or threw. */ +static bool scr_dyn_class_integrity(ScrDyn *object, ScrDyn *(*operation)(ScrDyn *)) { + if (object->kind != SCR_DYN_TYPED_REF || !object->v.typed_ref.commit || + object->v.typed_ref.type_key_len < 7 || memcmp(object->v.typed_ref.type_key, "object:", 7) != 0) + return false; + ScrDyn *view = scr_dyn_typed_ref_materialize(object); + bool word = false; + if (!scr_exc_pending()) { + scr_class_integrity_committed = false; + scr_dyn_typed_ref_commit(object); + word = scr_class_integrity_committed; + } + if (word && !scr_exc_pending()) { + scr_dyn_release(operation(view)); + if (!scr_exc_pending()) scr_dyn_typed_ref_commit(object); + } + scr_dyn_release(view); + return word || scr_exc_pending(); +} + +/* A class instance's integrity state lives in its native layout. Through an + * untyped value whose hierarchy has no integrity word, the operation + * throws. */ static bool scr_dyn_class_integrity_fence(const ScrDyn *object, const char *operation) { if (object->kind != SCR_DYN_TYPED_REF) return false; ScrJsonBuf buffer; @@ -2456,6 +2521,8 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { scr_map_prevent_extensions(collection); return scr_dyn_retain(object); } + if (scr_dyn_class_integrity(object, scr_dyn_prevent_extensions)) + return scr_exc_pending() ? NULL : scr_dyn_retain(object); if (scr_dyn_class_integrity_fence(object, "Object.preventExtensions")) return NULL; ScrDyn *table = scr_dyn_fn_integrity_table(object); if (table) { @@ -2494,6 +2561,8 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) { scr_map_prevent_extensions(collection); return scr_dyn_retain(object); } + if (scr_dyn_class_integrity(object, scr_dyn_seal)) + return scr_exc_pending() ? NULL : scr_dyn_retain(object); if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL; if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties)); ScrDyn *table = scr_dyn_fn_integrity_table(object); @@ -12118,6 +12187,10 @@ static void scr_json_delete_member(ScrDyn *object, const ScrStr *key) { } } +void scr_dyn_obj_delete_field(ScrDyn *obj, const ScrStr *key) { + if (obj->kind == SCR_DYN_OBJ) scr_json_delete_member(obj, key); +} + /* Ordinary native objects carry removable own data properties. Dense * arrays, typed references and handles keep their explicit boundary; * deleting from a materialized snapshot would lose the mutation. */ @@ -16724,6 +16797,8 @@ ScrDyn *scr_dyn_freeze(ScrDyn *value) { if (scr_dyn_class_reflection_fence(value) || scr_dyn_error_prototype_fence(value)) return NULL; ScrDyn *native = scr_dyn_freeze_native(value); if (native) return native; + if (scr_dyn_class_integrity(value, scr_dyn_freeze_data)) + return scr_exc_pending() ? NULL : scr_dyn_retain(value); if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL; ScrDyn *table = scr_dyn_fn_integrity_table(value); if (!table) return scr_dyn_freeze_data(value); @@ -16829,21 +16904,6 @@ ScrDyn *scr_caught_to_dyn(const ScrCaught *c) { } } -/* ── class instance attributes ─────────────────────────────────────────── - * A hierarchy passed to Object.freeze, Object.seal, Object.preventExtensions - * or Object.defineProperty keeps an integrity state per instance and an - * attribute word per redefined field (CLASS_* and FIELD_* bits in the - * compiler's ir.ts). The checked view of an instance carries them, so - * reflection over the view answers as Node does. */ -enum { - SCR_CLASS_NOT_EXTENSIBLE = 1, - SCR_CLASS_SEALED = 2, - SCR_CLASS_FROZEN = 4, - SCR_FIELD_NOT_WRITABLE = 1, - SCR_FIELD_NOT_ENUMERABLE = 2, - SCR_FIELD_NOT_CONFIGURABLE = 4, -}; - void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits) { unsigned b = (unsigned)bits; if (b == 0 || view->kind != SCR_DYN_OBJ) return; diff --git a/tests/corpus/7407-frozen-class-instances.ts b/tests/corpus/7407-frozen-class-instances.ts new file mode 100644 index 000000000..0732b8c35 --- /dev/null +++ b/tests/corpus/7407-frozen-class-instances.ts @@ -0,0 +1,143 @@ +// Object.freeze, Object.seal and Object.preventExtensions of class instances +// held in untyped values. The operation changes the instance itself: writes +// through its typed references, through untyped references and through its +// methods then throw Node's TypeError (this module is strict mode code), and +// Object.isFrozen, isSealed and isExtensible report the state through either +// reference. +export {}; + +function report(e: unknown): void { + console.log((e as Error).name + ": " + (e as Error).message); +} + +function freezeUnknown(value: unknown): unknown { + return Object.freeze(value); +} + +function sealUnknown(value: unknown): unknown { + return Object.seal(value); +} + +function preventUnknown(value: unknown): unknown { + return Object.preventExtensions(value); +} + +function integrity(value: unknown): string { + return [Object.isFrozen(value), Object.isSealed(value), Object.isExtensible(value)].join(" "); +} + +function writeAny(value: any, key: string, next: unknown): void { + value[key] = next; +} + +function deleteAny(value: any, key: string): void { + delete value[key]; +} + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + move(dx: number): void { + this.x += dx; + } +} + +class Labeled extends Point { + label: string; + constructor(x: number, y: number, label: string) { + super(x, y); + this.label = label; + } + rename(label: string): void { + this.label = label; + } +} + +class Counter { + count = 0; + tags: string[] = []; + bump(): number { + return ++this.count; + } +} + +function frozen(): void { + const p = new Point(1, 2); + console.log(integrity(p)); + const same = freezeUnknown(p); + console.log(same === p, integrity(p), Object.isFrozen(p)); + try { p.x = 5; } catch (e) { report(e); } + try { p.move(1); } catch (e) { report(e); } + try { writeAny(p, "y", 9); } catch (e) { report(e); } + try { writeAny(p, "z", 1); } catch (e) { report(e); } + console.log(p.x, p.y, JSON.stringify(p)); + + const l = new Labeled(3, 4, "a"); + const base: Point = l; + freezeUnknown(base); + console.log(integrity(l), integrity(base)); + try { l.rename("b"); } catch (e) { report(e); } + try { base.x = 0; } catch (e) { report(e); } + try { writeAny(l, "label", "c"); } catch (e) { report(e); } + console.log(l.x, l.label, l instanceof Labeled); + freezeUnknown(l); + console.log(integrity(l)); +} + +function sealed(): void { + const c = new Counter(); + sealUnknown(c); + console.log(integrity(c)); + console.log(c.bump(), c.bump()); + c.tags.push("kept"); + try { writeAny(c, "extra", 1); } catch (e) { report(e); } + writeAny(c, "count", 10); + console.log(c.count, c.tags.join(), JSON.stringify(c)); + freezeUnknown(c); + console.log(integrity(c)); + try { c.bump(); } catch (e) { report(e); } + c.tags.push("still"); + console.log(c.count, c.tags.join()); +} + +function notExtensible(): void { + const l = new Labeled(0, 0, "n"); + preventUnknown(l); + console.log(integrity(l)); + l.move(2); + l.rename("m"); + try { writeAny(l, "extra", true); } catch (e) { report(e); } + console.log(l.x, l.label, Object.keys(l).join()); + sealUnknown(l); + console.log(integrity(l)); + writeAny(l, "y", 7); + console.log(l.y); +} + +function untouched(): void { + const p = new Point(5, 6); + const q = new Point(7, 8); + freezeUnknown(q); + p.x = 50; + writeAny(p, "y", 60); + console.log(p.x, p.y, integrity(p), integrity(q)); +} + +function added(): void { + const p = new Point(1, 1); + writeAny(p, "extra", 1); + freezeUnknown(p); + console.log(integrity(p), Object.keys(p).join()); + try { deleteAny(p, "extra"); } catch (e) { report(e); } + console.log((p as any).extra, JSON.stringify(p)); +} + +frozen(); +sealed(); +notExtensible(); +untouched(); +added(); diff --git a/tests/harness/corpus-costs/7407-frozen-class-instances.ts.json b/tests/harness/corpus-costs/7407-frozen-class-instances.ts.json new file mode 100644 index 000000000..34061c3eb --- /dev/null +++ b/tests/harness/corpus-costs/7407-frozen-class-instances.ts.json @@ -0,0 +1 @@ +{ "plain": 3923, "san": 3742 } diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c5476328e..030408deb 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -109,7 +109,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 38, marker: "scr_runtime_abi_v38" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From 15cc7310aca7924abc26f6d2ef91c0543ab945ef Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 23 Oct 2026 15:15:34 -0700 Subject: [PATCH 035/395] Keep record values that subtype reduction folds into an inferred type TypeScript infers a function's return type as the subtype-reduced union of the values it returns: returning `{ status }` in one branch and `{ status, value }` (or a call returning either) in another infers just `{ status }`. Records have fixed layouts, so that type cannot hold the second value: the return failed its union wrap at run time ('... is not representable in the target union'), and values that reached a binding of the reduced type failed a checked narrowing. - A function with an inferred return type and no contextual signature returns the union of the record types it actually returns, including through calls of other such functions. Only value types the checker's reduction dropped from the inferred union are added, so functions whose inferred type already names every returned type keep their layout. Declared return types keep their meaning. - Reading such a union through the reduced type (a binding, a member read, JSON.stringify) uses a dedicated checked extraction that converts the wider record arms with the ordinary width coercion; other narrowings are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/builtins/json.ts | 10 +- .../src/frontend/lowering/coercions/state.ts | 9 +- .../src/frontend/lowering/coercions/unions.ts | 71 ++++++++- .../lowering/expressions/property-access.ts | 20 ++- .../src/frontend/lowering/function-returns.ts | 143 ++++++++++++++++++ .../compiler/src/frontend/lowering/lowerer.ts | 10 +- .../src/frontend/lowering/union-narrow.ts | 17 +++ ...nferred-return-overlapping-records.ts.json | 6 + ...990-inferred-return-overlapping-records.ts | 59 ++++++++ ...nferred-return-overlapping-records.ts.json | 1 + 10 files changed, 340 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json create mode 100644 tests/corpus/7990-inferred-return-overlapping-records.ts create mode 100644 tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index 64f829865..5aad5077f 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -1,3 +1,4 @@ +import { widensReducedRecords } from "../function-returns.js"; import { keyedHelperName } from "../stable-names.js"; import { isDateJsonSource } from "./dates.js"; import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; @@ -96,7 +97,14 @@ export function lowerJsonMethodCall( !isDateJsonSource(lowerer, argNode) ) { const narrowed = lowerer.mapTypeOf(lowerer.typeOf(argNode)); - if (narrowed && !isUnitType(narrowed) && narrowed.kind !== "void") { + // A union of record types the checker's subtype reduction spelled as + // one of them serializes each value by its own layout. + if ( + narrowed && + !isUnitType(narrowed) && + narrowed.kind !== "void" && + !widensReducedRecords(lowerer, narrowed, value.type) + ) { const helper = narrowed.kind === "union" ? lowerer.narrowedRetagHelper(argNode, value.type.unionId, narrowed.unionId, loc) diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index ad1d7c6ea..3c2ecf7bd 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -12,7 +12,14 @@ export class CoercionState { // Provenance for checked extraction; consumers must not infer it from names. readonly checkedNarrows = new Set(); // The extracted arm of each checked (non-defaulting) extraction helper. - readonly checkedNarrowArms = new Map(); + readonly checkedNarrowArms = new Map< + string, + { unionId: string; tag: number; target: IrType; widens?: true } + >(); readonly retags = new Map(); readonly narrows = new Map(); + readonly widthNarrows = new Map(); + /** Unions widenReducedReturn produced: only these read through the + * width-converting extraction (widthNarrowHelper). */ + readonly widenedReturns = new Set(); } diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 434e687a5..776892487 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -421,6 +421,46 @@ export function narrowedArmHelper( return name; } +/** Interned `%union.narrow.width.(u)`: the checked extraction of a + * record arm from a union that also holds records whose fields strictly + * include the arm's (a function's widened inferred return, see + * widenReducedReturn). Those arms convert like any record width coercion; + * every other arm throws like narrowedArmHelper's. */ +export function widthNarrowHelper( + lowerer: Lowerer, + fromId: string, + target: IrType, + loc: SrcLoc, +): string | null { + const from = lowerer.unions.get(fromId); + if (!from || target.kind !== "record") return null; + const tag = lowerer.armTag(fromId, target); + if (tag < 0) return null; + const key = `${fromId}:${tag}`; + const existing = lowerer.coercions.widthNarrows.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%union.narrow.width", key); + lowerer.coercions.widthNarrows.set(key, name); + const fn = buildUnionNarrow( + name, + from, + target, + loc, + (type) => lowerer.fmt(type), + null, + (armTag, payload) => widthSubtypeConversion(lowerer, from.arms[armTag]!, target, payload, loc), + ); + if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); + lowerer.coercions.checkedNarrowArms.set(name, { + unionId: fromId, + tag, + target: from.arms[tag]!, + widens: true, + }); + lowerer.liftedFns.push(fn); + return name; +} + /** A checked extraction at its use. A plain read tests the stored tag in * place and projects the payload directly, so member reads can borrow it; * every other arm throws the helper's TypeError with the same message. @@ -442,7 +482,8 @@ export function checkedNarrowCall( value.type.kind !== "union" || value.type.unionId !== arm.unionId || !typeEquals(type, arm.target) || - !isSafeToRepeat(value) + !isSafeToRepeat(value) || + arm.widens === true ) return call; const unionId = arm.unionId; @@ -599,3 +640,31 @@ export function deferredReadHelper( lowerer.liftedFns.push(fn); return name; } + +/** Whether `arm` is a record whose fields strictly include those of the + * record `target`: a value of the target type under TypeScript's structural + * typing, which the checker's subtype reduction may have spelled as the + * target (`{ status } | { status, value }` infers `{ status }`). */ +export function isWidthSubtypeRecord(lowerer: Lowerer, arm: IrType, target: IrType): boolean { + if (arm.kind !== "record" || target.kind !== "record") return false; + const own = lowerer.shapes.get(arm.shapeId); + const want = lowerer.shapes.get(target.shapeId); + if (!own || !want || own.tuple || want.tuple || own.indexValue || want.indexValue) return false; + if (want.fields.length >= own.fields.length) return false; + const names = new Set(own.fields.map((f) => f.name)); + return want.fields.every((f) => names.has(f.name)); +} + +/** A checked narrow that meets a width-subtype record arm converts it like + * any record width coercion instead of reporting a lying narrowing. */ +function widthSubtypeConversion( + lowerer: Lowerer, + arm: IrType, + target: IrType, + payload: IrExpr, + loc: SrcLoc, +): IrExpr | null { + if (!isWidthSubtypeRecord(lowerer, arm, target)) return null; + const lift = lowerer.widthLiftPlan(arm, target); + return lift === null ? null : lowerer.applyWidthLift(lift, payload, target, loc); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 9894cfb71..bac0d0daa 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,3 +1,5 @@ +import { widensReducedRecords } from "../function-returns.js"; +import { widthNarrowHelper } from "../coercions/unions.js"; /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ import { keyedHelperName } from "../stable-names.js"; import { staticallySelected } from "../static-typeof.js"; @@ -1293,7 +1295,23 @@ export function lowerPropertyAccessExpr( // honest one, not a generic recitation about the outer dot. A // receiver that lowers cleanly means the MEMBER is the gap — name // the property and the receiver's type instead of "property access". - const recvLowered = lowerer.lowerExpr(expr.expression); + let recvLowered = lowerer.lowerExpr(expr.expression); + // A receiver whose value union holds record types the checker's subtype + // reduction folded into its type (`{ status } | { status, value }` typed + // `{ status }`, a call of a function with an inferred return type): read + // through the checker's type, converting the wider arms. + if (recvLowered.type.kind === "union") { + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); + if ( + declared !== null && + declared.kind === "record" && + widensReducedRecords(lowerer, declared, recvLowered.type) + ) { + const helper = widthNarrowHelper(lowerer, recvLowered.type.unionId, declared, loc); + if (helper !== null) + recvLowered = lowerer.checkedNarrowCall(helper, recvLowered, declared, loc); + } + } // A dyn receiver — JSON.parse's `any`, or `unknown` the checker // narrowed to `object` — reads through the dyn keyed read: member // or undefined (JS's own-property answer), throwing JS's TypeError diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index dde109ea7..b1132a8b9 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -11,12 +11,14 @@ import { VOID, isUnitType, typeEquals, + typeKey, } from "../../ir/ir.js"; import { isJsSourceFile } from "../program.js"; import { PoisonError, dynFallbackType } from "./lowerer.js"; import { functionCanReturnUndefined } from "../function-completion.js"; import { jsBindingAppendsOnly, jsBindingHasOpenWrites } from "./lower-stmts.js"; import type { FnCtx } from "./function-context.js"; +import { constituentTypes } from "../ts7/checker.js"; /** Keep the conservative completion rule shared by return construction * and inference. Nested control flow may still make completion unreachable. */ @@ -175,6 +177,8 @@ export function declaredReturnType( // (`.action(() => { throw ... })`). `never` VALUES stay unmapped. if (retTsType.flags & ts.TypeFlags.Never) return VOID; let mappedReturn = lowerer.mapTypeOf(retTsType); + if (mappedReturn !== null) + mappedReturn = widenReducedReturn(lowerer, decl, retTsType, mappedReturn); // A generator declared to return Iterable returns a generator over T. if (mappedReturn && decl.asteriskToken) mappedReturn = iterableGeneratorArm(lowerer, mappedReturn); @@ -522,3 +526,142 @@ export function blockBodyOf(decl: ts.FunctionLikeDeclaration): ts.Block | null { if (ts.isBlock(body)) return body; return ts.forEachChild(decl, (c) => (ts.isBlock(c) ? c : undefined)) ?? null; } + +const widening = new Set(); + +/** An inferred return type is the checker's SUBTYPE-REDUCED union of the + * returned values' types: `return aborted` (`{ status }`) beside `return + * { status, value }` infers just `{ status }`, because the second is a + * subtype of the first. Records have fixed layouts, so that type cannot + * hold the second value: the return would drop `value` or fail the union + * wrap. A function whose return type is inferred therefore returns the + * union of the record types it actually returns. Declared return types keep + * their meaning. */ +function widenReducedReturn( + lowerer: Lowerer, + decl: ts.SignatureDeclaration, + retTsType: ts.Type, + mapped: IrType, +): IrType { + if (decl.type !== undefined || hasExplicitJsDocReturn(decl)) return mapped; + const body = (decl as { body?: ts.Node }).body; + if (body === undefined || decl.asteriskToken !== undefined) return mapped; + if (decl.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) return mapped; + // A function expression with a contextual signature returns that + // signature's type; only free-standing inference is widened. + if ( + (ts.isFunctionExpression(decl) || ts.isArrowFunction(decl)) && + lowerer.checker.getContextualType(decl) !== undefined + ) + return mapped; + const arms = + mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; + const recordArms = arms.filter((a) => a.kind === "record"); + if (recordArms.length === 0 || widening.has(decl)) return mapped; + const checkerArms = new Set( + retTsType.flags & ts.TypeFlags.Union ? constituentTypes(retTsType) : [retTsType], + ); + const fieldsOf = (t: IrType): readonly string[] | null => { + if (t.kind !== "record") return null; + const shape = lowerer.shapes.get(t.shapeId); + return shape === undefined || shape.tuple || shape.indexValue !== undefined + ? null + : shape.fields.map((f) => f.name); + }; + const narrower = (part: IrType): boolean => { + const own = fieldsOf(part); + if (own === null || arms.some((a) => typeKey(a) === typeKey(part))) return false; + return recordArms.some((arm) => { + const fields = fieldsOf(arm); + return fields !== null && fields.length < own.length && fields.every((f) => own.includes(f)); + }); + }; + const extra = new Map(); + const returned = (expr: ts.Expression): void => { + // A call of another inferred function returns ITS widened type. + const callee = ts.isCallExpression(expr) + ? lowerer.checker.getResolvedSignature(expr)?.declaration + : undefined; + if ( + callee !== undefined && + ts.isFunctionLike(callee) && + (callee as { body?: ts.Node }).body !== undefined && + callee.type === undefined + ) { + let t: IrType | null = null; + widening.add(decl); + try { + t = declaredReturnType(lowerer, callee, expr); + } catch { + t = null; + } finally { + widening.delete(decl); + } + if (t === null) return; + const parts = t.kind === "union" ? (lowerer.unions.get(t.unionId)?.arms ?? []) : [t]; + for (const part of parts) if (narrower(part)) extra.set(typeKey(part), part); + return; + } + // Only a value type the checker's reduction dropped from the inferred + // union is a candidate; a constituent of the inferred type is already + // represented by it. + const valueType = lowerer.typeOf(expr); + const valueArms = + valueType.flags & ts.TypeFlags.Union ? constituentTypes(valueType) : [valueType]; + for (const part of valueArms) { + if (checkerArms.has(part) || !(part.flags & ts.TypeFlags.Object)) continue; + if (!lowerer.checker.isTypeAssignableTo(part, retTsType)) continue; + const t = lowerer.mapTypeOf(part); + if (t !== null && narrower(t)) extra.set(typeKey(t), t); + } + }; + const scan = (node: ts.Node): void => { + if (node !== decl && ts.isFunctionLike(node)) return; + if (ts.isReturnStatement(node) && node.expression !== undefined) returned(node.expression); + ts.forEachChild(node, scan); + }; + if (ts.isArrowFunction(decl) && !ts.isBlock(body)) returned(body as ts.Expression); + else scan(body); + if (extra.size === 0) return mapped; + const joined = new Map(); + for (const a of [...arms, ...extra.values()]) joined.set(typeKey(a), a); + const sorted = [...joined.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + const unionId = lowerer.unions.intern(sorted); + lowerer.coercions.widenedReturns.add(unionId); + return { kind: "union", unionId }; +} + +/** Whether `wide` is a widened inferred return that holds `narrow`'s arms plus records that are width + * subtypes of `narrow`'s record arms: the shape a subtype-reduced inferred + * type takes next to the values it was inferred from. */ +export function widensReducedRecords(lowerer: Lowerer, narrow: IrType, wide: IrType): boolean { + if (wide.kind !== "union" || typeKey(narrow) === typeKey(wide)) return false; + if (!lowerer.coercions.widenedReturns.has(wide.unionId)) return false; + const narrowArms = + narrow.kind === "union" ? (lowerer.unions.get(narrow.unionId)?.arms ?? []) : [narrow]; + const wideArms = lowerer.unions.get(wide.unionId)?.arms ?? []; + const keys = new Set(wideArms.map(typeKey)); + if (!narrowArms.every((a) => keys.has(typeKey(a)))) return false; + const fieldsOf = (t: IrType): readonly string[] | null => { + if (t.kind !== "record") return null; + const shape = lowerer.shapes.get(t.shapeId); + return shape === undefined || shape.tuple || shape.indexValue !== undefined + ? null + : shape.fields.map((f) => f.name); + }; + const narrowKeys = new Set(narrowArms.map(typeKey)); + const extras = wideArms.filter((a) => !narrowKeys.has(typeKey(a))); + return ( + extras.length > 0 && + extras.every((extra) => { + const own = fieldsOf(extra); + return ( + own !== null && + narrowArms.some((arm) => { + const f = fieldsOf(arm); + return f !== null && f.length < own.length && f.every((x) => own.includes(x)); + }) + ); + }) + ); +} diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 7aad166e9..4bf938027 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -115,6 +115,7 @@ import { checkedNarrowCall, checkedNarrowOperand, deferredReadHelper, + widthNarrowHelper, } from "./coercions/unions.js"; import { lowerUtilTypeValue, @@ -443,7 +444,7 @@ import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import { completeArgs, wrappedUndefined, undefinedArgFor } from "./call-arguments.js"; -import { bodyReturnType, declaredReturnType } from "./function-returns.js"; +import { bodyReturnType, declaredReturnType, widensReducedRecords } from "./function-returns.js"; import { lowerCall, lowerPromiseMethodCall, lowerFilterNarrowCall } from "./lower-calls.js"; import { lowerFfiCall, validateFfiImports } from "./native-ffi.js"; import { @@ -3464,6 +3465,7 @@ export class Lowerer { this.internTableHelpers, this.coercions.retags, this.coercions.narrows, + this.coercions.widthNarrows, ]) for (const name of table.values()) interned.add(name); for (const fn of [...this.liftedFns, ...this.implicitFns]) @@ -10013,7 +10015,11 @@ export class Lowerer { expected.kind !== "void" && this.armTag(expr.type.unionId, expected) >= 0 ) { - const helper = this.narrowedArmHelper(expr.type.unionId, expected, expr.loc); + // A function's widened inferred return (widenReducedReturn) also + // holds records with more fields; those convert instead of throwing. + const helper = widensReducedRecords(this, expected, expr.type) + ? widthNarrowHelper(this, expr.type.unionId, expected, expr.loc) + : this.narrowedArmHelper(expr.type.unionId, expected, expr.loc); if (helper) { return this.checkedNarrowCall(helper, expr, expected, expr.loc); } diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 3bb09cb26..7ae036fad 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -28,6 +28,9 @@ export function buildUnionNarrow( loc: SrcLoc, formatType: (type: IrType) => string, undefinedDefault: IrExpr | null = null, + /** Converts another arm's payload into the target (a record arm that is + * a width subtype of a record target), or null to throw. */ + convert: ((tag: number, payload: IrExpr) => IrExpr | null) | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -43,6 +46,7 @@ export function buildUnionNarrow( const inputType: IrType = { kind: "union", unionId: from.id }; const input: IrExpr = { kind: "varRef", localId: "u.0", type: inputType, loc }; const body: IrStmt[] = []; + let converted: IrExpr | null = null; for (let tag = 0; tag < from.arms.length; tag++) { const arm = from.arms[tag]!; let branch: IrStmt[]; @@ -56,6 +60,19 @@ export function buildUnionNarrow( ]; } else if (arm.kind === "undefinedT" && undefinedDefault !== null) { branch = [{ kind: "return", value: undefinedDefault, loc }]; + } else if ( + convert !== null && + !isUnitType(arm) && + (converted = convert(tag, { + kind: "unionNarrow", + unionId: from.id, + tag, + value: input, + type: arm, + loc, + })) !== null + ) { + branch = [{ kind: "return", value: converted, loc }]; } else { branch = [ { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json new file mode 100644 index 000000000..6a55f1f2e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7990-inferred-return-overlapping-records.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7990-inferred-return-overlapping-records.ts b/tests/corpus/7990-inferred-return-overlapping-records.ts new file mode 100644 index 000000000..c2d1c9ea0 --- /dev/null +++ b/tests/corpus/7990-inferred-return-overlapping-records.ts @@ -0,0 +1,59 @@ +// Functions whose inferred return type is a union of overlapping records. +// TypeScript's subtype reduction infers `{ status }` for a function that +// returns `{ status }` in one branch and `{ status, value }` in another, so +// the inferred type is narrower than the values. Returned values keep their +// fields, chained returns keep them, and bindings typed by the reduced type +// read their declared fields. +type Aborted = { status: string }; +const ABORTED: Aborted = { status: "aborted" }; + +function inner(ok: boolean, value: unknown) { + if (!ok) return ABORTED; + return { status: "valid", value }; +} + +// Returns inner's union through a call: inferred as `{ status }` alone. +function outer(ok: boolean, value: unknown) { + if (value === "stop") return ABORTED; + return inner(ok, value); +} + +function merge(entries: [string, number][]) { + const result: Record = {}; + for (const [key, n] of entries) { + if (key === "__proto__") return ABORTED; + result[key] = n; + } + return { status: "valid", value: result }; +} + +function parse(entries: [string, number][]) { + if (entries.length === 0) return ABORTED; + return merge(entries); +} + +console.log(JSON.stringify(outer(true, [1, 2]))); +console.log(JSON.stringify(outer(false, 1))); +console.log(JSON.stringify(outer(true, "stop"))); +console.log(JSON.stringify(parse([["a", 1], ["b", 2]]))); +console.log(JSON.stringify(parse([]))); + +// Into untyped positions: the value keeps every field. +const seen: unknown[] = []; +seen.push(outer(true, { nested: true })); +seen.push(parse([["c", 3]])); +console.log(JSON.stringify(seen)); +const loose: unknown = outer(true, "kept"); +console.log(JSON.stringify(loose)); + +// Bindings typed by the reduced type: declared fields read as in Node. +const r = outer(true, 42); +console.log(r.status); +let status = ""; +for (const ok of [true, false]) status += outer(ok, 0).status + " "; +console.log(status.trim()); +function statusOf(ok: boolean): string { + const local = outer(ok, null); + return local.status; +} +console.log(statusOf(true), statusOf(false)); diff --git a/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json b/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json new file mode 100644 index 000000000..7da9b7545 --- /dev/null +++ b/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json @@ -0,0 +1 @@ +{ "plain": 3700 } From c9ce961458c83e81d798e181cbb313a1b79e6df2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 20:54:08 -0500 Subject: [PATCH 036/395] Restore the inspection context when a custom inspector throws --- .../src/frontend/lowering/lower-inspect.ts | 33 ++++++++++++------ .../compiler/src/ir/builtin-signatures.ts | 7 ++-- .../corpus/8125-inspect-custom-throws.ts.json | 6 ++++ .../8126-inspect-custom-throws.cjs.json | 6 ++++ packages/runtime/src/scr_inspect.c | 34 +++++++++++++++---- packages/runtime/src/scr_inspect.h | 8 ++--- tests/corpus/8125-inspect-custom-throws.ts | 32 +++++++++++++++++ tests/corpus/8126-inspect-custom-throws.cjs | 30 ++++++++++++++++ .../8125-inspect-custom-throws.ts.json | 1 + .../8126-inspect-custom-throws.cjs.json | 1 + 10 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json create mode 100644 tests/corpus/8125-inspect-custom-throws.ts create mode 100644 tests/corpus/8126-inspect-custom-throws.cjs create mode 100644 tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json create mode 100644 tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 86255ca56..334c1dcac 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1277,21 +1277,34 @@ function customInspectHelper( const resultT = found.sig.ret; const call = accessorCall(lowerer, t.className, INSPECT_CUSTOM_MEMBER, v(), args, resultT, loc); // The method runs in a fresh inspect context (insp.customCallBegin); the - // walk's own context returns before the result renders. - const callStmt = (fn: "insp.customCallBegin" | "insp.customCallEnd"): IrStmt => ({ + // walk's own context returns before the result renders. The token lets + // an enclosing end restore this walk's context when the method throws. + locals.push({ id: "tok.0", name: "tok", type: F64, mutable: false }); + const end: IrStmt = { kind: "exprStmt", - expr: { kind: "libCall", fn, args: [], type: VOID, loc }, + expr: { + kind: "libCall", + fn: "insp.customCallEnd", + args: [varRef("tok.0", F64, loc)], + type: VOID, + loc, + }, loc, - }); - const render: IrStmt[] = [...argDecls, callStmt("insp.customCallBegin")]; + }; + const render: IrStmt[] = [ + ...argDecls, + { + kind: "varDecl", + localId: "tok.0", + init: { kind: "libCall", fn: "insp.customCallBegin", args: [], type: F64, loc }, + loc, + }, + ]; if (resultT.kind === "void") { - render.push({ kind: "exprStmt", expr: call, loc }, callStmt("insp.customCallEnd")); + render.push({ kind: "exprStmt", expr: call, loc }, end); } else { locals.push({ id: "res.0", name: "res", type: resultT, mutable: false }); - render.push( - { kind: "varDecl", localId: "res.0", init: call, loc }, - callStmt("insp.customCallEnd"), - ); + render.push({ kind: "varDecl", localId: "res.0", init: call, loc }, end); } const res = (): IrExpr => varRef("res.0", resultT, loc); if (resultT.kind === "string") { diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index d8f150605..4730f283d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4492,9 +4492,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.customIsSelf": { argTypes: [DYN, DYN], result: BOOL }, "insp.customResult": { argTypes: [DYN, F64, F64], result: STRING }, /** Around the method's call: the inspect and format calls it makes run - * in a fresh inspect context with the default options. */ - "insp.customCallBegin": { argTypes: [], result: VOID }, - "insp.customCallEnd": { argTypes: [], result: VOID }, + * in a fresh inspect context with the default options. begin answers the + * token end takes; end also discards what a call that threw left. */ + "insp.customCallBegin": { argTypes: [], result: F64 }, + "insp.customCallEnd": { argTypes: [F64], result: VOID }, /** Whether a runtime format string has a '%' before its last character. */ "insp.formatSpecifies": { argTypes: [STRING], result: BOOL }, "insp.formatSpecifiesDyn": { argTypes: [DYN], result: BOOL }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json new file mode 100644 index 000000000..1dc598fd4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8125-inspect-custom-throws.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json new file mode 100644 index 000000000..374de8db3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8126-inspect-custom-throws.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 9905518ab..9cfe3a3ef 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1706,6 +1706,8 @@ typedef struct { double break_length; double compact; const ScrDyn *user_options; + size_t nsaved; /* the nested-context stack's depth before the call */ + size_t nframes; /* the frame stack's depth before the call */ } InspCallSaved; static SCR_TL InspCallSaved *g_call_saved; @@ -1721,8 +1723,13 @@ static void insp_call_saved_cleanup(void) { /* A custom inspect function's call: Node gives each util.inspect and * util.format call its own ctx, so the calls the function makes start * with no indentation, no seen objects, no colors and the default layout - * and options. end restores the walk's context. */ -void scr_insp_custom_call_begin(void) { + * and options. begin answers a token; end(token) restores the walk's + * context as it was at that begin. A call that threw skips its end, and + * an enclosing custom function's end (or the walk's, when the function + * caught the error) discards what the throw left: the contexts of calls + * that never ended and the frames of renders that never finished. */ +double scr_insp_custom_call_begin(void) { + size_t nsaved = g_nsaved; scr_insp_nested_begin(); if (g_ncall_saved == g_call_saved_cap) { if (g_call_saved_cap == 0) scr_atexit(insp_call_saved_cleanup); @@ -1730,7 +1737,10 @@ void scr_insp_custom_call_begin(void) { g_call_saved = realloc(g_call_saved, g_call_saved_cap * sizeof(InspCallSaved)); if (!g_call_saved) insp_oom(); } + double token = (double)g_ncall_saved; InspCallSaved *sv = &g_call_saved[g_ncall_saved++]; + sv->nsaved = nsaved; + sv->nframes = g_nframes; sv->colors = g_colors; sv->custom_inspect = g_custom_inspect; sv->sorted = g_sorted; @@ -1747,10 +1757,20 @@ void scr_insp_custom_call_begin(void) { g_depth_number = 0; g_user_options = NULL; scr_insp_layout(80, 3, false); + return token; } -void scr_insp_custom_call_end(void) { - InspCallSaved *sv = &g_call_saved[--g_ncall_saved]; +void scr_insp_custom_call_end(double token) { + size_t depth = (size_t)token; + if (depth >= g_ncall_saved) return; /* an enclosing end already restored it */ + InspCallSaved *sv = &g_call_saved[depth]; + g_ncall_saved = depth; + while (g_nframes > sv->nframes) { + InspFrame *f = &g_frames[--g_nframes]; + for (size_t i = 0; i < f->len; i++) scr_str_release(f->items[i]); + free(f->items); + } + g_pending_seen = NULL; g_colors = sv->colors; g_custom_inspect = sv->custom_inspect; g_no_break = sv->no_break; @@ -1758,7 +1778,7 @@ void scr_insp_custom_call_end(void) { g_depth_number = sv->depth_number; g_user_options = sv->user_options; scr_insp_layout(sv->break_length, sv->compact, sv->sorted); - scr_insp_nested_restore(); + while (g_nsaved > sv->nsaved) scr_insp_nested_restore(); } /* The style inspect.styles maps a styleType to, as [open, close] codes: @@ -2024,7 +2044,7 @@ static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown } args[2] = insp_inspect_value(); ScrDyn *ret; - scr_insp_custom_call_begin(); + double token = scr_insp_custom_call_begin(); if (compiled) { ScrDyn *call[4] = {d, args[0], args[1], args[2]}; ret = scr_dyn_call(fn, call, 4, "[nodejs.util.inspect.custom]"); @@ -2033,7 +2053,7 @@ static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown ret = scr_dyn_call(fn, args, 3, "[nodejs.util.inspect.custom]"); scr_dyn_this_pop(); } - scr_insp_custom_call_end(); + scr_insp_custom_call_end(token); for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); scr_dyn_release(fn); if (!ret) { diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index d56f17bba..9619f0dee 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -127,10 +127,10 @@ void scr_insp_nested_restore(void); * value for the caller to restore. */ bool scr_insp_custom_inspect(bool enabled); /* Around a custom inspect function's call: begin gives the calls it makes - * a fresh inspect context with the default options; end restores the - * caller's. */ -void scr_insp_custom_call_begin(void); -void scr_insp_custom_call_end(void); + * a fresh inspect context with the default options and answers a token; + * end(token) restores the caller's, also after calls that threw. */ +double scr_insp_custom_call_begin(void); +void scr_insp_custom_call_end(double token); /* How the unbounded depth of the renders that follow was given, for custom * inspect functions: 0 Infinity, 1 null, 2 undefined. Answers the * previous kind for the caller to restore. */ diff --git a/tests/corpus/8125-inspect-custom-throws.ts b/tests/corpus/8125-inspect-custom-throws.ts new file mode 100644 index 000000000..33077a32b --- /dev/null +++ b/tests/corpus/8125-inspect-custom-throws.ts @@ -0,0 +1,32 @@ +// A [util.inspect.custom] method that throws inside an outer inspect: an +// enclosing custom method that catches the error, and the walk around it, +// continue with their own indentation, circular references and colors. +import * as util from "node:util"; +class Throws { + [util.inspect.custom](): string { + throw new Error("inner"); + } +} +class Catches { + [util.inspect.custom](): string { + let note = ""; + try { + note = util.inspect({ a: { b: [new Throws()] } }); + } catch (e) { + note = "caught " + (e as Error).message; + } + return note + " then " + util.inspect({ x: { y: [1, 2] } }); + } +} +const shared = { k: 1 }; +type Outer = { first: { deep: Catches }; list: unknown[]; s: object; t: object; me?: Outer }; +const outer: Outer = { first: { deep: new Catches() }, list: [new Catches(), { z: 3 }], s: shared, t: shared }; +outer.me = outer; +console.log(util.inspect(outer, { breakLength: 50 })); +console.log(JSON.stringify(util.inspect([new Catches(), "s"], { colors: true }))); +try { + console.log(util.inspect({ a: { b: new Throws() } })); +} catch (e) { + console.log("outer caught", (e as Error).message); +} +console.log({ after: { deep: { x: 1 } }, list: [1, 2, 3] }); diff --git a/tests/corpus/8126-inspect-custom-throws.cjs b/tests/corpus/8126-inspect-custom-throws.cjs new file mode 100644 index 000000000..8d4e97608 --- /dev/null +++ b/tests/corpus/8126-inspect-custom-throws.cjs @@ -0,0 +1,30 @@ +// A [util.inspect.custom] method that throws inside an outer inspect: an +// enclosing custom method that catches the error, and the walk around it, +// continue with their own indentation and colors. +const util = require("node:util"); +class Throws { + [util.inspect.custom]() { + throw new Error("inner"); + } +} +class Catches { + [util.inspect.custom]() { + let note = ""; + try { + note = util.inspect({ a: { b: [new Throws()] } }); + } catch (e) { + note = "caught " + e.message; + } + return note + " then " + util.inspect({ x: { y: [1, 2] } }); + } +} +const shared = { k: 1 }; +const outer = { first: { deep: new Catches() }, list: [new Catches(), { z: 3 }], s: shared, t: shared }; +console.log(util.inspect(outer, { breakLength: 50 })); +console.log(JSON.stringify(util.inspect([new Catches(), "s"], { colors: true }))); +try { + console.log(util.inspect({ a: { b: new Throws() } })); +} catch (e) { + console.log("outer caught", e.message); +} +console.log({ after: { deep: { x: 1 } }, list: [1, 2, 3] }); diff --git a/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json b/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json new file mode 100644 index 000000000..7d8f08f56 --- /dev/null +++ b/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json @@ -0,0 +1 @@ +{ "plain": 3090, "san": 5295 } diff --git a/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json b/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json new file mode 100644 index 000000000..49ea4c6d4 --- /dev/null +++ b/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json @@ -0,0 +1 @@ +{ "plain": 2826, "san": 4308 } From 01e88d0b4862fb9d3b5c86e1e0b7a5f372e774b8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 21:30:21 -0500 Subject: [PATCH 037/395] Support named properties and Symbol.species on typed arrays held as untyped values --- packages/cli/test/native-link-info.test.ts | 4 +- packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-calls.ts | 5 +- .../compiler/test/llvm-runtime-abi.test.ts | 7 +- .../test/native-codegen-integration.test.ts | 4 +- .../7753-typed-array-named-properties.js.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_bytes.c | 13 +- packages/runtime/src/scr_bytes.h | 8 + packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_dyn.h | 6 + packages/runtime/src/scr_dyn_invoke.c | 14 + packages/runtime/src/scr_inspect.c | 79 ++- packages/runtime/src/scr_json.c | 497 ++++++++++++++++-- packages/runtime/src/scr_symbol.c | 10 + .../7752-typed-array-dynamic-methods.js | 12 + .../7753-typed-array-named-properties.js | 143 +++++ .../7753-typed-array-named-properties.js.json | 1 + .../typed-array-property-refusals.test.ts | 64 +++ 23 files changed, 836 insertions(+), 53 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json create mode 100644 tests/corpus/7753-typed-array-named-properties.js create mode 100644 tests/harness/corpus-costs/7753-typed-array-named-properties.js.json create mode 100644 tests/harness/typed-array-property-refusals.test.ts diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index 8ac579449..187dbc0d5 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v37"); + expect(error?.stderr).toContain("scr_runtime_abi_v40"); }); }); diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index 11bde6c2b..229d8ec33 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 37 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v37" as const; +export const RUNTIME_ABI_VERSION = 40 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v40" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 9846e8d89..6e7a09fb3 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index e2b24f8ca..b2f5995fb 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -128,7 +128,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 073ebed91..9eaf9535b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1087,7 +1087,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (!argNode || (ts.isIdentifier(argNode) && argNode.text === "undefined")) { return { kind: "libCall", fn: "sym.newAnon", args: [], type: SYMBOL_T, loc }; } - const desc = lowerer.lowerExpr(argNode); + let desc = lowerer.lowerExpr(argNode); + // A number or boolean description is its ToString. + if (desc.type.kind === "f64" || desc.type.kind === "bool") + desc = lowerer.ensureString(desc, argNode); if (desc.type.kind !== "string") { lowerer.noLowering( `Symbol with a '${lowerer.fmt(desc.type)}' description`, diff --git a/packages/compiler/test/llvm-runtime-abi.test.ts b/packages/compiler/test/llvm-runtime-abi.test.ts index 6cfd862b7..a457feb72 100644 --- a/packages/compiler/test/llvm-runtime-abi.test.ts +++ b/packages/compiler/test/llvm-runtime-abi.test.ts @@ -355,12 +355,13 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => { #include "scr_runtime.h" /* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, i8, ptr }. - * Keep every ABI fact used by its field-index GEPs explicit here. */ + * Keep every ABI fact used by its field-index GEPs explicit here. The C + * struct continues with fields only the runtime reads. */ _Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t"); _Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers"); _Static_assert(sizeof(ScrBytesElem) == 4, "LLVM ScrBytes elem field is i32"); _Static_assert(_Alignof(ScrBytes) == 8, "LLVM ScrBytes alignment changed"); -_Static_assert(sizeof(ScrBytes) == 56, "LLVM ScrBytes size changed"); +_Static_assert(sizeof(ScrBytes) == 64, "ScrBytes size changed"); _Static_assert(offsetof(ScrBytes, rc) == 0, "LLVM ScrBytes.rc offset changed"); _Static_assert(offsetof(ScrBytes, len) == 8, "LLVM ScrBytes.len offset changed"); _Static_assert(offsetof(ScrBytes, elem) == 16, "LLVM ScrBytes.elem offset changed"); @@ -370,7 +371,9 @@ _Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer off _Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed"); _Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed"); _Static_assert(offsetof(ScrBytes, untransferable) == 43, "LLVM ScrBytes.untransferable offset changed"); +_Static_assert(offsetof(ScrBytes, properties_traced) == 44, "ScrBytes.properties_traced must stay in padding"); _Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed"); +_Static_assert(offsetof(ScrBytes, properties) == 56, "ScrBytes.properties must follow the LLVM prefix"); `, ); const driver = resolveCc(); diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index cecebb49c..d35cbfefd 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v37"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v40"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v37"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v40"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json new file mode 100644 index 000000000..74a4316d9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7753-typed-array-named-properties.js" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index e492c36b4..27cafe022 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -340,7 +340,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index e22f2a37c..318880a54 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -437,7 +437,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 3926bf83e..e761641c2 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -190,6 +190,8 @@ ScrBytes *scr_bytes_take_data(uint8_t *data, size_t len) { b->external = false; b->untransferable = false; b->shared = NULL; + b->properties = NULL; + b->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -304,6 +306,7 @@ void scr_bytes_release(ScrBytes *b) { if (!b || b->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */ if (--b->rc == 0) { scr_weak_dispose_inline(b); + if (b->properties) scr_dyn_release(b->properties); if (b->backing) { scr_bytes_release(b->backing); /* a view: data points into the owner */ } else if (b->shared) { @@ -740,6 +743,8 @@ ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) { v->external = false; v->untransferable = false; v->shared = owner->shared; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -866,7 +871,9 @@ ScrBytes *scr_bytes_from_dyn(ScrBytesElem elem, const ScrDyn *value, bool from) if (value->kind == SCR_DYN_BYTES) return scr_bytes_convert(elem, value->v.bytes); if (!from && scr_buffer_storage_is(value)) return scr_array_buffer_view(elem, value, scr_dyn_undefined(), scr_dyn_undefined()); if (!from && (value->kind == SCR_DYN_NUM || value->kind == SCR_DYN_BOOL || - value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF)) { + value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF || + value->kind == SCR_DYN_SYMBOL || value->kind == SCR_DYN_BIGINT)) { + /* ToIndex: a Symbol or BigInt length throws ToNumber's TypeError. */ double number; if (!scr_dyn_number_coerce_js(value, &number)) return NULL; return scr_bytes_new(elem, number); @@ -992,6 +999,8 @@ ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double v->external = false; v->untransferable = false; v->shared = owner->shared; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -2883,6 +2892,8 @@ static ScrBytes *scr_pool_take(size_t len) { v->external = false; v->untransferable = false; v->shared = NULL; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index 7c0a935cc..64e3e299a 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -65,7 +65,15 @@ typedef struct ScrBytes { bool is_data_view; /* DataView is distinct from a numeric typed array. */ bool external; /* Root aliases foreign memory; releasing it never frees data. */ bool untransferable; /* A Buffer pool slab: its ArrayBuffer cannot be transferred. */ + /* The cycle collector's last trace of `properties` from the one checked + * value that held this view (see scr_dyn_trace_v). In padding, so the + * offsets compiled code uses hold. */ + bool properties_traced; ScrSharedBytes *shared; /* Neutral backing, borrowed by views from their local owner. */ + /* Named properties a program adds to the view (`ta.foo = 1`, + * Object.defineProperty): a checked object, NULL until the first one. + * Runtime-only and last, so compiled code's field indices hold. */ + struct ScrDyn *properties; } ScrBytes; /* Copies out of and into shared storage, made of relaxed atomic accesses diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 086340014..06cc7ccd7 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v37(void) {} +void scr_runtime_abi_v40(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index 3707b8d50..bdb9942fe 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v37(void); +void scr_runtime_abi_v40(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index bd76c12e2..b4955220a 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -917,6 +917,9 @@ void scr_dyn_install_iterator_symbol(ScrDyn *key); void scr_dyn_install_has_instance_symbol(ScrDyn *key); void scr_dyn_install_async_iterator_symbol(ScrDyn *key); void scr_dyn_install_async_dispose_symbol(ScrDyn *key); +void scr_dyn_install_species_symbol(ScrDyn *key); +/* Records each well-known symbol the symbol unit creates. */ +void scr_dyn_install_well_known_symbol(ScrDyn *key); /* True when key is Symbol.asyncDispose (registered by the symbol unit when * the program names it); handle symbol_get ops outside that unit use it. */ bool scr_dyn_is_async_dispose_symbol(const ScrDyn *key); @@ -1003,6 +1006,9 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS extern SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result); ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key); +/* A typed array's, Buffer's or DataView's own named property (+1), or NULL + * without an exception when it has none. */ +ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len); /* The Buffer-flavored twin (stream chunks): string coercion/toString * decode utf8 instead of joining elements. */ ScrDyn *scr_dyn_new_buffer(const ScrBytes *b); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index b76fe38d2..96035cccc 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1602,6 +1602,20 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_BYTES) { ScrBytes *bytes = recv->v.bytes; + /* An own property of the view shadows the prototype's method. */ + ScrDyn *member = bytes->properties ? scr_bytes_named_get(recv, method, strlen(method)) : NULL; + if (!member && scr_exc_pending()) return NULL; + if (member) { + ScrDyn *result = NULL; + if (!scr_dyn_is_callable(member)) dyn_throw_not_fn(what); + else { + scr_dyn_this_push_dyn(recv); + result = scr_dyn_call(member, args, argc, what); + scr_dyn_this_pop(); + } + scr_dyn_release(member); + return result; + } if (bytes->is_data_view) { static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64", "Float16" }; const bool get = !strncmp(method, "get", 3); diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 011a1f582..ce85c00ff 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -743,6 +743,52 @@ ScrStr *scr_insp_regex(ScrRegex *re) { return ib_take(&out); } +/* Named properties of a typed array, Buffer or DataView (its + * ScrBytes.properties table): slot k is the k-th string-keyed entry, then + * the k-th symbol key. */ +static size_t insp_view_property_slots(const ScrDyn *props) { + if (!props) return 0; + return props->v.obj.len + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0); +} + +static ScrStr *insp_accessor_tag(const ScrDynEntry *ent); +static void insp_key_into(InspBuf *b, const char *key, size_t len); + +/* Slot k's `key: value` entry when it is enumerable, else NULL. */ +static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, double depth) { + const ScrDynEntry *ent; + ScrStr *key = NULL; + if (k < props->v.obj.len) { + ent = &props->v.obj.entries[k]; + } else { + ScrDyn *symbol = props->symbol_keys->v.arr.items[k - props->v.obj.len]; + ent = scr_dyn_own_symbol_entry(props, symbol); + if (ent && ent->enumerable) key = scr_insp_dyn(symbol, recurse, depth); + } + if (!ent || !ent->enumerable) return NULL; + ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse, depth); + InspBuf eb = {0}; + if (key) ib_bytes(&eb, key->data, key->len); + else insp_key_into(&eb, ent->key, ent->key_len); + ib_cstr(&eb, ": "); + ib_bytes(&eb, val->data, val->len); + scr_str_release(key); + scr_str_release(val); + return ib_take(&eb); +} + +static size_t insp_view_property_count(ScrDyn *props) { + size_t count = 0; + for (size_t k = 0; k < insp_view_property_slots(props); k++) { + if (k < props->v.obj.len) count += props->v.obj.entries[k].enumerable; + else { + const ScrDynEntry *ent = scr_dyn_own_symbol_entry(props, props->symbol_keys->v.arr.items[k - props->v.obj.len]); + count += ent && ent->enumerable; + } + } + return count; +} + /* Buffer: the custom-inspect hex form ``, capped at * INSPECT_MAX_BYTES (50); renders fully at any depth (custom inspection * runs before the depth check in Node). u8 element kind only (the @@ -765,6 +811,17 @@ ScrStr *scr_insp_buffer(ScrBytes *b) { remaining > 1 ? "s" : ""); ib_bytes(&out, more, (size_t)n); } + /* Named properties follow as `, key: value`, rendered as the members of + * an object at depth 0 (Buffer's custom inspect). */ + bool first = true; + for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) { + ScrStr *entry = insp_view_property(b->properties, k, 1, 2); + if (!entry) continue; + if (!first || b->len) ib_cstr(&out, ", "); + first = false; + ib_bytes(&out, entry->data, entry->len); + scr_str_release(entry); + } ib_char(&out, '>'); return ib_take(&out); } @@ -848,6 +905,12 @@ static ScrStr *scr_insp_data_view(const ScrBytes *b, double recurse, double dept entry = ib_take(&eb); scr_insp_entry(entry, false); scr_str_release(entry); + for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) { + entry = insp_view_property(b->properties, k, recurse + 1, depth); + if (!entry) continue; + scr_insp_entry(entry, false); + scr_str_release(entry); + } ScrStr *base = scr_str_new("", 0); ScrStr *b0 = scr_str_new("DataView {", 10); ScrStr *b1 = scr_str_new("}", 1); @@ -1455,7 +1518,10 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { char prefix[48]; int pn = snprintf(prefix, sizeof prefix, "%s(%zu) [", scr_bytes_elem_name(b->elem), b->len); - if (b->len == 0) { + /* Named properties follow the elements as `key: value` entries. */ + ScrDyn *props = b->properties; + size_t nprops = insp_view_property_count(props); + if (b->len == 0 && nprops == 0) { InspBuf out = {0}; ib_bytes(&out, prefix, (size_t)pn); ib_char(&out, ']'); @@ -1479,10 +1545,19 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { scr_insp_entry(m, true); scr_str_release(m); } + size_t entries = shown + (more ? 1 : 0); + for (size_t k = 0; nprops && k < insp_view_property_slots(props); k++) { + ScrStr *entry = insp_view_property(props, k, recurse + 1, depth); + if (!entry) continue; + /* Node's grouping pads by the element at the entry's output position. */ + scr_insp_entry(entry, entries < b->len); + scr_str_release(entry); + entries++; + } ScrStr *base = scr_str_new("", 0); ScrStr *b0 = scr_str_new(prefix, (size_t)pn); ScrStr *b1 = scr_str_new("]", 1); - ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, more); + ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, nprops ? entries > 100 : more); scr_str_release(base); scr_str_release(b0); scr_str_release(b1); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 4af8c63b5..928d43490 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -202,6 +202,33 @@ void scr_dyn_install_has_instance_symbol(ScrDyn *key) { scr_atexit(scr_has_instance_symbol_cleanup); } +/* Symbol.species, installed when the program first evaluates it: no + * constructor names a species before then. */ +static SCR_TL ScrDyn *scr_species_symbol; +static void scr_species_symbol_cleanup(void) { + scr_dyn_release(scr_species_symbol); + scr_species_symbol = NULL; +} +void scr_dyn_install_species_symbol(ScrDyn *key) { + if (scr_species_symbol) return; + scr_species_symbol = scr_dyn_retain(key); + scr_atexit(scr_species_symbol_cleanup); +} + +/* The well-known symbols the program has created. They live until exit, + * so their identities are compared without a reference. */ +static SCR_TL const ScrSym *scr_well_known_symbols[24]; +static SCR_TL size_t scr_well_known_count; +void scr_dyn_install_well_known_symbol(ScrDyn *key) { + if (scr_well_known_count < sizeof scr_well_known_symbols / sizeof scr_well_known_symbols[0]) + scr_well_known_symbols[scr_well_known_count++] = key->v.symbol.value; +} +static bool scr_dyn_well_known_symbol(const ScrDyn *key) { + for (size_t i = 0; i < scr_well_known_count; i++) + if (scr_well_known_symbols[i] == key->v.symbol.value) return true; + return false; +} + void scr_dyn_install_iterator_symbol(ScrDyn *key) { if (scr_iterator_symbol) return; scr_iterator_symbol = scr_dyn_retain(key); @@ -880,6 +907,17 @@ void scr_dyn_trace_v(void *ptr, ScrTraceVisit visit, void *ctx) { } break; case SCR_DYN_FUNC: visit(d->v.fn.clo, ctx); break; + case SCR_DYN_BYTES: { + /* A view's named properties belong to its ScrBytes. Only a view this + * value alone holds owns that edge; a shared view's table stays out of + * trial deletion. Teardown reads the snapshot, not the count. */ + ScrBytes *bytes = d->v.bytes; + if (bytes->properties) { + bytes->properties_traced = bytes->rc == 1; + if (bytes->properties_traced) visit(bytes->properties, ctx); + } + break; + } case SCR_DYN_PROXY: visit(d->v.proxy.target, ctx); visit(d->v.proxy.handler, ctx); @@ -1144,6 +1182,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { scr_str_release(d->v.str); break; case SCR_DYN_BYTES: + /* The collector frees a traced table itself. */ + if (collected && d->v.bytes->properties && d->v.bytes->properties_traced) d->v.bytes->properties = NULL; scr_bytes_release(d->v.bytes); break; case SCR_DYN_ARR: @@ -2417,10 +2457,27 @@ static bool scr_dyn_typed_ref_view_answer(ScrDyn *object, bool (*query)(const Sc return true; } +static ScrDyn *scr_bytes_properties(const ScrDyn *v); static bool scr_dyn_is_extensible_view(const ScrDyn *view) { + if (view->kind == SCR_DYN_BYTES) return !view->v.bytes->properties || !view->v.bytes->properties->non_extensible; return (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR) && !view->non_extensible; } +/* A view's integrity state lives on its named-property table. A typed array + * with elements cannot be frozen, and cannot be sealed while extensible: as + * in V8, the named properties are sealed or frozen and the view made + * non-extensible before the TypeError. */ +static bool scr_bytes_integrity_fence(const ScrDyn *view, const char *verb) { + if (view->v.bytes->is_data_view || view->v.bytes->len == 0) return false; + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot "); + scr_jb_puts(&message, verb); + scr_jb_puts(&message, " array buffer views with elements"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return true; +} + /* A plain function's own properties (name, length, prototype, and the ones * a program adds) live in its property table, which carries the function's * integrity state too: freezing or sealing the function freezes or seals @@ -2467,6 +2524,7 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { if (object->kind == SCR_DYN_ARR && object->v.arr.properties) object->v.arr.properties->non_extensible = true; } + if (object->kind == SCR_DYN_BYTES) scr_bytes_properties(object)->non_extensible = true; return scr_dyn_retain(object); } @@ -2495,6 +2553,12 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) { return scr_dyn_retain(object); } if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL; + if (object->kind == SCR_DYN_BYTES) { + bool extensible = scr_dyn_is_extensible_view(object); + scr_dyn_release(scr_dyn_seal(scr_bytes_properties(object))); + if (extensible && scr_bytes_integrity_fence(object, "seal")) return NULL; + return scr_dyn_retain(object); + } if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties)); ScrDyn *table = scr_dyn_fn_integrity_table(object); if (table) { @@ -2530,6 +2594,8 @@ bool scr_dyn_is_sealed(const ScrDyn *object) { if (object->kind == SCR_DYN_ARR) return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) && (!object->v.arr.properties || scr_dyn_is_sealed(object->v.arr.properties)); + /* V8 answers from a typed array's named properties alone. */ + if (object->kind == SCR_DYN_BYTES) return object->v.bytes->properties && scr_dyn_is_sealed(object->v.bytes->properties); if (object->kind != SCR_DYN_OBJ) return true; if (!object->non_extensible) return false; for (size_t i = 0; i < object->v.obj.len; i++) if (object->v.obj.entries[i].configurable) return false; @@ -2736,6 +2802,65 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) { return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); } +/* A canonical numeric string ("1", "-0", "1.5", "Infinity"): typed arrays + * answer those keys as element indices, never as named properties. */ +static bool scr_bytes_numeric_key(const ScrStr *key, double *index) { + if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) { *index = -0.0; return true; } + *index = scr_string_to_number(key); + char spelling[32]; + size_t length = scr_f64_to_str(*index, spelling); + return length == key->len && memcmp(spelling, key->data, length) == 0; +} + +/* Whether the view has an own named property `key`. */ +static bool scr_bytes_has_named(const ScrDyn *v, const ScrStr *key) { + const ScrDyn *properties = v->v.bytes->properties; + return properties && scr_dyn_obj_get(properties, key->data, key->len); +} + +/* The view's own named property `key` read with the view as a getter's + * `this` (+1), or NULL without an exception when the view has none. Only + * reads the table, so callers do not link the typed array methods. */ +ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len) { + const ScrDyn *properties = view->v.bytes->properties; + if (!properties || !scr_dyn_obj_get(properties, key, key_len)) return NULL; + return scr_dyn_obj_read_receiver(properties, key, key_len, view); +} + +/* The view's named-property table, created on first use. Borrowed. The + * table belongs to the ScrBytes, which several checked values can wrap. */ +static ScrDyn *scr_bytes_properties(const ScrDyn *v) { + ScrBytes *bytes = v->v.bytes; + if (!bytes->properties) bytes->properties = scr_dyn_new_obj_null_proto(); + return bytes->properties; +} + +/* Accessors without a setter on the view's prototype chain: writing one + * that the view does not own throws V8's strict-mode TypeError. */ +static bool scr_bytes_getter_name(const ScrDyn *v, const ScrStr *key) { + static const char *const names[] = { "byteLength", "byteOffset", "buffer", "length", "parent", "offset" }; + size_t count = v->v.bytes->is_data_view ? 3 : v->buffer ? 6 : 4; + return scr_dyn_name_in(key, names, count); +} + +/* Keys that the runtime's conversions, iteration and printing of a view + * read from its prototype without consulting its own properties. */ +static bool scr_bytes_protocol_name(const ScrStr *key) { + static const char *const names[] = { "toString", "valueOf", "toLocaleString", "toJSON", "join" }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +static void scr_bytes_protocol_fence(const char *key, size_t key_len) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "defining "); + scr_jb_write(&message, key, key_len); + scr_jb_puts(&message, " on a typed array, Buffer or DataView is not supported yet"); + ScrStr *text = scr_jb_finish(&message); + scr_throw_error_msg_code(SCR_ERR_ERROR, text->data, text->len, "SC2020"); + scr_str_release(text); +} + /* `in` on a typed array, Buffer or DataView. On a typed array, a canonical * numeric key (one that ToString(ToNumber(key)) reproduces, or "-0") * answers by bounds alone: an integral index, not -0, below the length. @@ -2743,6 +2868,7 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) { * Buffer, or Object.prototype. A DataView has no indexed elements: its keys * name members of DataView.prototype or Object.prototype. */ static bool scr_dyn_bytes_in(const ScrDyn *v, const ScrStr *key) { + if (scr_bytes_has_named(v, key)) return true; if (v->v.bytes->is_data_view) return scr_dyn_data_view_proto_name(key) || scr_dyn_object_proto_name(key); if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) return false; double index = scr_string_to_number(key); @@ -2858,6 +2984,19 @@ ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) { scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false)); return out; } + if (value->kind == SCR_DYN_BYTES && scr_bytes_has_named(value, key)) + return scr_dyn_own_descriptor(value->v.bytes->properties, key); + if (value->kind == SCR_DYN_BYTES && !value->v.bytes->is_data_view) { + if (!scr_dyn_canonical_own_index(key, value->v.bytes->len)) return scr_dyn_retain(scr_dyn_undefined()); + size_t index = 0; + for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0'); + ScrDyn *out = scr_dyn_new_obj(); + scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num(scr_bytes_get(value->v.bytes, (double)index))); + scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(true)); + scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(true)); + scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(true)); + return out; + } if (value->kind == SCR_DYN_ARR) { if (value->v.arr.properties && scr_dyn_obj_get(value->v.arr.properties, key->data, key->len)) @@ -4847,6 +4986,55 @@ static double *dyn_bytes_values(const ScrBytes *b, size_t n) { return values; } +/* SpeciesConstructor of a typed array: NULL (and no exception) for the + * default constructor of its kind, else the species constructor (+1). + * Only a `constructor` the program set on the view can differ. */ +static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { + *failed = false; + ScrStr *name = scr_str_new("constructor", 11); + bool own = scr_bytes_has_named(recv, name); + ScrDyn *constructor = own ? scr_dyn_obj_read_receiver(recv->v.bytes->properties, name->data, name->len, recv) : NULL; + scr_str_release(name); + if (!own) return NULL; + if (!constructor) { *failed = true; return NULL; } + if (constructor->kind == SCR_DYN_UNDEF) { scr_dyn_release(constructor); return NULL; } + if (constructor->kind != SCR_DYN_OBJ && constructor->kind != SCR_DYN_FUNC) { + scr_dyn_release(constructor); + static const char message[] = "object.constructor is not an object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + *failed = true; + return NULL; + } + ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + scr_dyn_release(constructor); + if (scr_exc_pending()) { scr_dyn_release(species); *failed = true; return NULL; } + if (!species || species->kind == SCR_DYN_UNDEF || species->kind == SCR_DYN_NULL || + scr_bytes_ctor_elem(species) == (int)recv->v.bytes->elem) { + scr_dyn_release(species); + return NULL; + } + if (!scr_dyn_is_callable(species)) { + scr_dyn_release(species); + static const char message[] = "object.constructor[Symbol.species] is not a constructor"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + *failed = true; + return NULL; + } + return species; +} + +/* TypedArraySpeciesCreate(recv, [n]): a new array of the receiver's kind + * (and Buffer flavor), or the species constructor's checked result. */ +static ScrDyn *scr_bytes_species_create(ScrDyn *recv, size_t n) { + bool failed; + ScrDyn *species = scr_bytes_species(recv, &failed); + if (failed) return NULL; + if (!species) return dyn_bytes_wrap(recv, scr_bytes_new(recv->v.bytes->elem, (double)n)); + ScrDyn *result = scr_bytes_static_create(species, n); + scr_dyn_release(species); + return result; +} + ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, bool *handled) { ScrBytes *bytes = recv->v.bytes; size_t len = bytes->len; @@ -4866,11 +5054,41 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar if (scr_exc_pending()) return NULL; double endD = scr_dyn_bytes_index_arg(args, argc, 1, (double)blen); if (scr_exc_pending()) return NULL; - ScrBytes *out = recv->buffer || scr_dyn_bytes_named(method, "subarray") - ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); - ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); + const bool subarray = recv->buffer || scr_dyn_bytes_named(method, "subarray"); + bool failed; + ScrDyn *species = recv->buffer ? NULL : scr_bytes_species(recv, &failed); + if (!recv->buffer && failed) return NULL; + ScrBytes *out = subarray ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); + if (!species) { + ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); + scr_bytes_release(out); + return d; + } + ScrDyn *result; + if (subarray) { + /* The species constructor views the same buffer. */ + ScrDyn *ctor_args = scr_dyn_new_arr(); + scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); + ScrStr *what = scr_str_new("TypedArray species", 18); + result = scr_dyn_construct(species, ctor_args, what); + scr_str_release(what); + scr_dyn_release(ctor_args); + if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { + scr_dyn_release(result); + result = NULL; + static const char message[] = "this is not a typed array."; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + } else { + result = scr_bytes_static_create(species, out->len); + for (size_t i = 0; result && i < out->len; i++) + scr_bytes_set(result->v.bytes, (double)i, scr_bytes_get(out, (double)i)); + } + scr_dyn_release(species); scr_bytes_release(out); - return d; + return result; } if (scr_dyn_bytes_named(method, "set")) { double offset = scr_dyn_bytes_index_arg(args, argc, 1, 0); @@ -4908,6 +5126,22 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar scr_str_release(joined); return out; } + if (scr_dyn_bytes_named(method, "toLocaleString")) { + /* Each element's Number.prototype.toLocaleString, joined by ",". */ + ScrJsonBuf b; + scr_jb_init(&b); + for (size_t i = 0; i < len; i++) { + if (i > 0) scr_jb_putc(&b, ','); + if (i >= bytes->len) continue; + ScrStr *text = scr_intl_num_format_en_us(scr_bytes_get(bytes, (double)i)); + scr_jb_write(&b, text->data, text->len); + scr_str_release(text); + } + ScrStr *joined = scr_jb_finish(&b); + ScrDyn *out = scr_dyn_new_str(joined); + scr_str_release(joined); + return out; + } if (scr_dyn_bytes_named(method, "keys") || scr_dyn_bytes_named(method, "values") || scr_dyn_bytes_named(method, "entries")) return scr_dyn_native_handle_iterator(recv, scr_dyn_bytes_named(method, "keys") ? 1 : scr_dyn_bytes_named(method, "entries") ? 2 : 0); if (scr_dyn_bytes_named(method, "indexOf") || scr_dyn_bytes_named(method, "lastIndexOf") || scr_dyn_bytes_named(method, "includes")) { @@ -4946,7 +5180,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar if (!scr_dyn_bytes_cb_check(args, argc)) return NULL; ScrDyn *cb = args[0], *this_arg = argc > 1 ? args[1] : scr_dyn_undefined(); const bool backward = scr_dyn_bytes_named(method, "findLast") || scr_dyn_bytes_named(method, "findLastIndex"); - ScrBytes *mapped = scr_dyn_bytes_named(method, "map") ? scr_bytes_new(bytes->elem, (double)len) : NULL; + ScrDyn *mapped = NULL; + if (scr_dyn_bytes_named(method, "map") && !(mapped = scr_bytes_species_create(recv, len))) return NULL; double *kept = scr_dyn_bytes_named(method, "filter") ? malloc((len ? len : 1) * sizeof *kept) : NULL; size_t nkept = 0; for (size_t step = 0; step < len; step++) { @@ -4957,7 +5192,7 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar scr_dyn_release(index); if (!r) { scr_dyn_release(item); - if (mapped) scr_bytes_release(mapped); + scr_dyn_release(mapped); free(kept); return NULL; } @@ -4966,8 +5201,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar bool converted = scr_dyn_number_coerce_js(r, &v); scr_dyn_release(r); scr_dyn_release(item); - if (!converted) { scr_bytes_release(mapped); return NULL; } - if (k < mapped->len) scr_bytes_set(mapped, (double)k, v); + if (!converted) { scr_dyn_release(mapped); return NULL; } + if (k < mapped->v.bytes->len) scr_bytes_set(mapped->v.bytes, (double)k, v); continue; } bool truthy = scr_dyn_truthy(r); @@ -4988,12 +5223,12 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar } scr_dyn_release(item); } - if (mapped) return dyn_bytes_wrap(recv, mapped); + if (mapped) return mapped; if (kept) { - ScrBytes *out = scr_bytes_new(bytes->elem, (double)nkept); - for (size_t i = 0; i < nkept; i++) scr_bytes_set(out, (double)i, kept[i]); + ScrDyn *out = scr_bytes_species_create(recv, nkept); + for (size_t i = 0; out && i < nkept; i++) scr_bytes_set(out->v.bytes, (double)i, kept[i]); free(kept); - return dyn_bytes_wrap(recv, out); + return out; } if (scr_dyn_bytes_named(method, "some")) return scr_dyn_new_bool(false); if (scr_dyn_bytes_named(method, "every")) return scr_dyn_new_bool(true); @@ -5120,6 +5355,8 @@ static ScrDyn *scr_dyn_bytes_method_value(const ScrStr *name, size_t arity) { /* Shared view metadata and indexed reads for both compiler backends. */ ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) { ScrBytes *bytes = value->v.bytes; + /* Own named properties first; a getter sees the view as `this`. */ + if (scr_bytes_has_named(value, key)) return scr_bytes_named_get(value, key->data, key->len); if (key->len == 10 && memcmp(key->data, "byteLength", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_len(bytes)); if (key->len == 10 && memcmp(key->data, "byteOffset", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_offset(bytes)); if (key->len == 6 && memcmp(key->data, "buffer", 6) == 0) return scr_array_buffer_from_bytes(bytes); @@ -6385,6 +6622,10 @@ static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value) { if (value->kind == SCR_DYN_FUNC) return scr_dyn_fn_properties(value); if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_HANDLE) return scr_dyn_retain((ScrDyn *)value); + /* A view keeps its symbol properties on its named-property table, which + * writes create first (scr_bytes_symbol_write). */ + if (value->kind == SCR_DYN_BYTES) + return value->v.bytes->properties ? scr_dyn_retain(value->v.bytes->properties) : NULL; if (value->kind == SCR_DYN_PROXY) { static const char message[] = "Symbol properties on this native value have no lowering"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); @@ -7798,8 +8039,46 @@ void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key) { entry->value = scr_dyn_retain(scr_dyn_undefined()); } +/* Symbol keys that the runtime reads from a view's prototype without + * consulting its own properties: the well-known symbols and + * util.inspect.custom. */ +static bool scr_bytes_protocol_symbol(const ScrDyn *key) { + if (scr_dyn_well_known_symbol(key)) return true; + ScrStr *description = key->v.symbol.description(key->v.symbol.value); + bool custom = description && description->len == 26 && !memcmp(description->data, "nodejs.util.inspect.custom", 26); + scr_str_release(description); + return custom; +} + +/* Before a symbol-keyed write or definition on a view: false with an + * exception pending for a key the view cannot own, else true with the + * view's table created. Symbol.toStringTag is an accessor without a setter + * on %TypedArray%.prototype, so assigning it throws V8's TypeError. */ +static bool scr_bytes_symbol_write(ScrDyn *value, const ScrDyn *key, bool assign) { + ScrDyn *receiver = scr_dyn_symbol_receiver(value); + bool own = receiver && scr_dyn_symbol_entry(receiver, key, true); + scr_dyn_release(receiver); + if (own) return true; + ScrStr *description = key->v.symbol.description(key->v.symbol.value); + bool tag = assign && !value->v.bytes->is_data_view && scr_dyn_well_known_symbol(key) && description && + description->len == 18 && !memcmp(description->data, "Symbol.toStringTag", 18); + if (tag) { + static const char message[] = "Cannot set property Symbol(Symbol.toStringTag) of [object Object] which has only a getter"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } else if (scr_bytes_protocol_symbol(key)) { + ScrStr *name = key->v.symbol.render(key->v.symbol.value); + scr_bytes_protocol_fence(name->data, name->len); + scr_str_release(name); + } + scr_str_release(description); + if (scr_exc_pending()) return false; + scr_bytes_properties(value); + return true; +} + void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { if (scr_dyn_error_prototype_fence(value)) return; + if (value->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(value, key, true)) return; ScrDyn *receiver = scr_dyn_symbol_receiver(value); if (!receiver) { if (!scr_exc_pending()) { @@ -7822,6 +8101,18 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } else { bool present = scr_dyn_symbol_entry(receiver, key, true) != NULL; + if (!present && receiver->non_extensible) { + ScrStr *rendered = key->v.symbol.render(key->v.symbol.value); + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot add property "); + scr_jb_write(&message, rendered->data, rendered->len); + scr_jb_puts(&message, ", object is not extensible"); + scr_str_release(rendered); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + scr_dyn_release(receiver); + return; + } scr_dyn_symbol_table(receiver); ScrStr *name = scr_dyn_symbol_storage_key(key); scr_dyn_key_set(receiver->symbol_properties, name, stored); @@ -7832,6 +8123,7 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { } static ScrDyn *scr_dyn_define_symbol(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) { + if (target->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(target, key, false)) return NULL; ScrDyn *receiver = scr_dyn_symbol_receiver(target); if (!receiver) { if (!scr_exc_pending()) { @@ -8026,6 +8318,53 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) scr_dyn_release(defined); return scr_dyn_retain(target); } + if (target->kind == SCR_DYN_BYTES) { + ScrStr *name = scr_dyn_property_key(key); + if (!name) return NULL; + double index; + bool numeric = !target->v.bytes->is_data_view && scr_bytes_numeric_key(name, &index); + if (numeric) { + /* [[DefineOwnProperty]] of an element: a valid index and a data + * descriptor that keeps it writable, enumerable and configurable. */ + bool valid = !signbit(index) && trunc(index) == index && index < (double)target->v.bytes->len; + const ScrDyn *configurable = scr_dyn_obj_get(descriptor, "configurable", 12); + const ScrDyn *enumerable = scr_dyn_obj_get(descriptor, "enumerable", 10); + const ScrDyn *writable = scr_dyn_obj_get(descriptor, "writable", 8); + bool accessor = scr_dyn_obj_get(descriptor, "get", 3) || scr_dyn_obj_get(descriptor, "set", 3); + if (!valid) { + scr_str_release(name); + static const char message[] = "Invalid typed array index"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; + } + if (accessor || (configurable && !scr_dyn_truthy(configurable)) || + (enumerable && !scr_dyn_truthy(enumerable)) || (writable && !scr_dyn_truthy(writable))) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot redefine property: "); + scr_jb_write(&message, name->data, name->len); + scr_str_release(name); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return NULL; + } + scr_str_release(name); + const ScrDyn *value = scr_dyn_obj_get(descriptor, "value", 5); + double number; + if (value) { + if (!scr_dyn_number_coerce_js(value, &number)) return NULL; + scr_bytes_set(target->v.bytes, index, number); + } + return scr_dyn_retain(target); + } + bool fenced = scr_bytes_protocol_name(name) && !scr_bytes_has_named(target, name); + if (fenced) scr_bytes_protocol_fence(name->data, name->len); + scr_str_release(name); + if (fenced) return NULL; + ScrDyn *defined = scr_dyn_define_property(scr_bytes_properties(target), key, descriptor); + if (!defined) return NULL; + scr_dyn_release(defined); + return scr_dyn_retain(target); + } if (target->kind != SCR_DYN_OBJ) { static const char msg[] = "Object.defineProperty called on non-object"; scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); @@ -8173,7 +8512,7 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key) { scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); return NULL; } - if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF) { + if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF && target->kind != SCR_DYN_BYTES) { static const char msg[] = "Object.getOwnPropertyDescriptor on this value is not supported yet"; scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1); return NULL; @@ -8477,7 +8816,8 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) { const ScrBytes *bytes = d->v.bytes; if (d->buffer) scr_jb_puts(b, "{\"type\":\"Buffer\",\"data\":["); else scr_jb_putc(b, '{'); - for (size_t i = 0; i < bytes->len; i++) { + size_t count = bytes->is_data_view ? 0 : bytes->len; + for (size_t i = 0; i < count; i++) { if (i > 0) scr_jb_putc(b, ','); if (!d->buffer) { char idx[32]; @@ -8486,6 +8826,32 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) { } scr_jb_put_f64(b, scr_bytes_get(bytes, (double)i)); } + /* Named properties follow the elements; a Buffer's toJSON leaves them + * out. */ + ScrDyn *named = d->buffer || !bytes->properties ? NULL : scr_dyn_obj_keys(bytes->properties); + bool first = count == 0; + for (size_t i = 0; named && i < named->v.arr.len; i++) { + const ScrStr *key = named->v.arr.items[i]->v.str; + ScrDyn *value = scr_dyn_obj_read_receiver(bytes->properties, key->data, key->len, d); + if (!value) { scr_dyn_release(named); return false; } + ScrJsonBuf probe; + scr_jb_init(&probe); + bool present = scr_dyn_json_write(&probe, value); + scr_dyn_release(value); + if (!present) { + scr_jb_dispose(&probe); + if (scr_exc_pending()) { scr_dyn_release(named); return false; } + continue; + } + if (!first) scr_jb_putc(b, ','); + first = false; + scr_jb_put_json_str(b, key); + scr_jb_putc(b, ':'); + ScrStr *body = scr_jb_finish(&probe); + scr_jb_write(b, body->data, body->len); + scr_str_release(body); + } + scr_dyn_release(named); scr_jb_puts(b, d->buffer ? "]}" : "}"); return true; } @@ -10221,6 +10587,7 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) { } if (v->kind == SCR_DYN_STR) return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str)); + if (v->kind == SCR_DYN_BYTES) return scr_dyn_bytes_in(v, key); return false; } @@ -10283,7 +10650,13 @@ static void scr_dyn_throw_property(const ScrDyn *object, const char *key, size_t scr_jb_puts(&buffer, deleting ? "Cannot delete property '" : "Cannot assign to read only property '"); scr_jb_write(&buffer, key, key_len); scr_jb_puts(&buffer, deleting ? "' of " : "' of object '"); - if (object->null_proto) scr_jb_puts(&buffer, "[object Object]"); + if (object->kind == SCR_DYN_BYTES) { + /* V8 names a typed array by its tag and a DataView by its class. */ + const char *kind = object->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(object->v.bytes->elem); + scr_jb_puts(&buffer, object->v.bytes->is_data_view ? "#<" : "[object "); + scr_jb_puts(&buffer, kind); + scr_jb_puts(&buffer, object->v.bytes->is_data_view ? ">" : "]"); + } else if (object->null_proto) scr_jb_puts(&buffer, "[object Object]"); else { scr_jb_puts(&buffer, "#<"); scr_jb_puts(&buffer, name ? name : "Object"); @@ -10497,19 +10870,36 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { } if (recv->kind == SCR_DYN_BYTES) { /* Integer-indexed exotic writes coerce even for an invalid canonical - * numeric index, then ignore an out-of-range index without an error. */ - double index = scr_string_to_number(key); - char spelling[32]; - size_t length = scr_f64_to_str(index, spelling); - bool negative_zero = key->len == 2 && memcmp(key->data, "-0", 2) == 0; - if (negative_zero || (length == key->len && memcmp(spelling, key->data, length) == 0)) { + * numeric index, then ignore an out-of-range index without an error. + * A DataView has no elements: its numeric keys are named properties. */ + double index; + if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) { double number; if (!scr_dyn_number_coerce_js(value, &number)) return; - if (!negative_zero && index >= 0 && index < (double)recv->v.bytes->len && trunc(index) == index) { + if (!signbit(index) && index < (double)recv->v.bytes->len && trunc(index) == index) { scr_bytes_set(recv->v.bytes, index, number); } return; } + /* Any other key is a named property of the view. */ + if (!scr_bytes_has_named(recv, key)) { + if (scr_bytes_getter_name(recv, key)) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot set property "); + scr_jb_write(&message, key->data, key->len); + scr_jb_puts(&message, recv->v.bytes->is_data_view ? " of # which has only a getter" + : " of [object Object] which has only a getter"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return; + } + if (scr_bytes_protocol_name(key)) { + scr_bytes_protocol_fence(key->data, key->len); + return; + } + } + scr_dyn_object_key_set(scr_bytes_properties(recv), key, value, recv); + return; } ScrJsonBuf b; @@ -12181,6 +12571,11 @@ static ScrDyn *scr_json_member(const ScrDyn *holder, const ScrStr *key) { scr_dyn_release(view); return member; } + /* A view's named properties, getters included, read through the view. */ + if (holder->kind == SCR_DYN_BYTES && (holder->v.bytes->is_data_view || scr_bytes_has_named(holder, key))) { + ScrDyn *named = scr_bytes_named_get(holder, key->data, key->len); + return named || scr_exc_pending() ? named : scr_dyn_retain(scr_dyn_undefined()); + } if (holder->kind == SCR_DYN_ARR || holder->kind == SCR_DYN_BYTES) { size_t index = 0; for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0'); @@ -12324,6 +12719,23 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) { scr_dyn_key_delete(recv->v.arr.properties, key, strict); return; } + if (recv->kind == SCR_DYN_BYTES) { + /* A typed array's elements cannot be deleted; other numeric keys name + * no property. Named properties follow their configurability. */ + double index; + if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) { + if (strict && !signbit(index) && trunc(index) == index && index < (double)recv->v.bytes->len) + scr_dyn_throw_property(recv, key->data, key->len, true); + return; + } + ScrDynEntry *entry = recv->v.bytes->properties ? scr_dyn_entry(recv->v.bytes->properties, key) : NULL; + if (entry && !entry->configurable) { + if (strict) scr_dyn_throw_property(recv, key->data, key->len, true); + return; + } + if (entry) scr_json_delete_member(recv->v.bytes->properties, key); + return; + } if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) { const char *message = "Cannot convert undefined or null to object"; scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); @@ -13982,7 +14394,7 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) { ScrDyn *keys = scr_dyn_new_arr(); size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len : - value->kind == SCR_DYN_BYTES ? value->v.bytes->len : + value->kind == SCR_DYN_BYTES ? (value->v.bytes->is_data_view ? 0 : value->v.bytes->len) : (size_t)scr_str_utf16_len(value->v.str); for (size_t i = 0; i < length; i++) { if (value->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(value, i)) continue; @@ -13992,8 +14404,10 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { } if (value->kind != SCR_DYN_BYTES) scr_dyn_arr_push(keys, scr_dyn_objwalk_key("length", 6)); - if (value->kind == SCR_DYN_ARR && value->v.arr.properties) { - ScrDyn *properties = scr_dyn_obj_own_keys(value->v.arr.properties); + ScrDyn *named = value->kind == SCR_DYN_ARR ? value->v.arr.properties + : value->kind == SCR_DYN_BYTES ? value->v.bytes->properties : NULL; + if (named) { + ScrDyn *properties = scr_dyn_obj_own_keys(named); for (size_t i = 0; i < properties->v.arr.len; i++) scr_dyn_arr_push(keys, scr_dyn_retain(properties->v.arr.items[i])); scr_dyn_release(properties); @@ -14258,7 +14672,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) { return out; } if (v->kind == SCR_DYN_ARR || v->kind == SCR_DYN_BYTES) { - size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->len; + size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->is_data_view ? 0 : v->v.bytes->len; for (size_t i = 0; i < n; i++) { if (v->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(v, i)) continue; char key[24]; @@ -14279,10 +14693,11 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) { scr_dyn_arr_push(out, pair); } } - if (v->kind == SCR_DYN_ARR && v->v.arr.properties) { - ScrDyn *names = scr_dyn_obj_own_keys(v->v.arr.properties); + ScrDyn *named = v->kind == SCR_DYN_ARR ? v->v.arr.properties : v->v.bytes->properties; + if (named) { + ScrDyn *names = scr_dyn_obj_own_keys(named); for (size_t i = 0; i < names->v.arr.len; i++) { - if (!scr_dyn_objwalk_entry(out, v->v.arr.properties, mode, names->v.arr.items[i]->v.str, v)) { + if (!scr_dyn_objwalk_entry(out, named, mode, names->v.arr.items[i]->v.str, v)) { scr_dyn_release(names); scr_dyn_release(out); return NULL; @@ -14386,6 +14801,7 @@ ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v) { return keys; } case SCR_DYN_ARR: + case SCR_DYN_BYTES: return scr_dyn_obj_keys(v); case SCR_DYN_STR: { ScrDyn *keys = scr_dyn_new_arr(); @@ -14639,7 +15055,7 @@ static void scr_dyn_assign_strings_from(ScrDyn *target, const ScrDyn *src, bool scr_dyn_proxy_unsupported("Object.assign"); return; } - if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC) return; + if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_BYTES) return; if (src->kind == SCR_DYN_TYPED_REF) { ScrDyn *materialized = scr_dyn_typed_ref_materialize(src); if (materialized) scr_dyn_assign_strings_from(target, materialized, define); @@ -14702,13 +15118,15 @@ static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src, bool define) return; } ScrDyn *view = src->kind == SCR_DYN_FUNC ? scr_dyn_fn_properties(src) : scr_dyn_retain((ScrDyn *)src); + /* A view's symbol properties live on its named-property table. */ + const ScrDyn *holder = src->kind == SCR_DYN_BYTES ? src->v.bytes->properties : view; ScrDyn *symbols = scr_dyn_new_arr(); - if (view->symbol_keys) for (size_t i = 0; i < view->symbol_keys->v.arr.len; i++) - scr_dyn_arr_push(symbols, scr_dyn_retain(view->symbol_keys->v.arr.items[i])); + if (holder && holder->symbol_keys) for (size_t i = 0; i < holder->symbol_keys->v.arr.len; i++) + scr_dyn_arr_push(symbols, scr_dyn_retain(holder->symbol_keys->v.arr.items[i])); scr_dyn_assign_strings_from(target, view, define); for (size_t i = 0; i < symbols->v.arr.len && !scr_exc_pending(); i++) { ScrDyn *key = symbols->v.arr.items[i]; - ScrDynEntry *entry = scr_dyn_symbol_entry(view, key, true); + ScrDynEntry *entry = scr_dyn_symbol_entry(holder, key, true); if (!entry || !entry->enumerable) continue; ScrDyn *value = scr_dyn_symbol_key_get(src, key, false); if (!value) break; @@ -15093,7 +15511,7 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str)); } if (v->kind == SCR_DYN_BYTES) { - return scr_dyn_canonical_own_index(key, v->v.bytes->len); + return (!v->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, v->v.bytes->len)) || scr_bytes_has_named(v, key); } if (v->kind == SCR_DYN_FUNC) { ScrDyn *desc = scr_dyn_own_descriptor(v, key); @@ -15130,7 +15548,8 @@ bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) { return (scr_dyn_canonical_own_index(key, value->v.arr.len) && scr_dyn_has_own(value, key)) || (value->v.arr.properties && scr_dyn_obj_enumerable(value->v.arr.properties, key->data, key->len)); if (value->kind == SCR_DYN_BYTES) - return scr_dyn_canonical_own_index(key, value->v.bytes->len); + return (!value->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, value->v.bytes->len)) || + (value->v.bytes->properties && scr_dyn_obj_enumerable(value->v.bytes->properties, key->data, key->len)); return false; } ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); } @@ -16834,6 +17253,11 @@ ScrDyn *scr_dyn_freeze_data(ScrDyn *value) { ScrDyn *native = scr_dyn_freeze_native(value); if (native) return native; if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL; + if (value->kind == SCR_DYN_BYTES) { + scr_dyn_release(scr_dyn_freeze_data(scr_bytes_properties(value))); + if (scr_bytes_integrity_fence(value, "freeze")) return NULL; + return scr_dyn_retain(value); + } if (value->symbol_properties) scr_dyn_release(scr_dyn_freeze_data(value->symbol_properties)); if (value->kind == SCR_DYN_ARR) { value->non_extensible = true; @@ -16875,6 +17299,9 @@ bool scr_dyn_is_frozen(const ScrDyn *value) { if (value->kind == SCR_DYN_ARR) return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) && (!value->v.arr.properties || scr_dyn_is_frozen(value->v.arr.properties)); + if (value->kind == SCR_DYN_BYTES) + return (value->v.bytes->is_data_view || value->v.bytes->len == 0) && value->v.bytes->properties && + scr_dyn_is_frozen(value->v.bytes->properties); if (value->kind == SCR_DYN_OBJ && !value->copied_from_native) { if (!value->non_extensible) return false; for (size_t i = 0; i < value->v.obj.len; i++) { diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 647c31dc3..fb0b6cb0b 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -80,6 +80,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_str_release(description); symbol->reg_next = g_sym_well_known; g_sym_well_known = scr_sym_retain(symbol); + { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_well_known_symbol(key); + scr_dyn_release(key); + } if (name->len == 8 && !memcmp(name->data, "iterator", 8)) { ScrDyn *key = scr_dyn_new_symbol(symbol); scr_dyn_install_iterator_symbol(key); @@ -90,6 +95,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_dyn_install_has_instance_symbol(key); scr_dyn_release(key); } + if (name->len == 7 && !memcmp(name->data, "species", 7)) { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_species_symbol(key); + scr_dyn_release(key); + } if (name->len == 13 && !memcmp(name->data, "asyncIterator", 13)) { ScrDyn *key = scr_dyn_new_symbol(symbol); scr_dyn_install_async_iterator_symbol(key); diff --git a/tests/corpus/7752-typed-array-dynamic-methods.js b/tests/corpus/7752-typed-array-dynamic-methods.js index a728e3ce9..d4f077b49 100644 --- a/tests/corpus/7752-typed-array-dynamic-methods.js +++ b/tests/corpus/7752-typed-array-dynamic-methods.js @@ -123,3 +123,15 @@ function plainIterator(TA) { console.log(new TA(source), TA.from(source, (v) => v * 2), gets, nexts); } plainIterator(Int16Array); +// ToIndex of a Symbol or BigInt length, toLocaleString, and Symbol descriptions +// that are not strings. +function small(TA) { + for (const bad of [Symbol("s"), 1n]) { + try { new TA(bad); console.log("no throw"); } catch (e) { console.log(e.constructor.name); } + } + console.log(new TA([1, 1234.5, -0, 0]).toLocaleString(), new TA(0).toLocaleString(), typeof new TA(1).toLocaleString); +} +small(Float64Array); +small(Uint16Array); +const s = Symbol(1), t = Symbol(true), u = Symbol(2.5); +console.log(s.description, t.description, u.toString(), typeof s.description); diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js new file mode 100644 index 000000000..b49f100b5 --- /dev/null +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -0,0 +1,143 @@ +// Named properties on typed arrays, Buffers and DataViews (assignment, +// Object.defineProperty, reflection, deletion, integrity levels, JSON and +// inspection), and the Symbol.species constructor of map, filter, slice and +// subarray. +function box(value) { + const holder = {}; + holder.value = value; + return holder.value; +} + +function expandos(TA) { + const a = new TA([1, 2]); + a.foo = 1; + a.bar = "x"; + a[5] = 9; + a["1.5"] = 7; + console.log(a, a.foo, "foo" in a, a.hasOwnProperty("bar"), Object.keys(a), Object.entries(a).length); + console.log(Object.getOwnPropertyNames(a), JSON.stringify(Object.getOwnPropertyDescriptor(a, "foo")), JSON.stringify(Object.getOwnPropertyDescriptor(a, "0"))); + Object.defineProperty(a, "hidden", { value: 3, enumerable: false }); + Object.defineProperty(a, "0", { value: 8 }); + console.log(a, a.hidden, Object.keys(a)); + try { Object.defineProperty(a, "1", { value: 1, configurable: false }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "7", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "-0", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "0", { get() { return 1; } }); } catch (e) { console.log(e.constructor.name, e.message); } + let reads = 0; + Object.defineProperty(a, "g", { get() { reads++; return this.length; }, enumerable: true }); + console.log(a.g, reads, new TA(0)); + const e = new TA(0); + e.k = { deep: [1] }; + console.log(e); +} + +function species(TA, Other) { + const s = new TA([1, 2, 3, 4]); + const calls = []; + s.constructor = {}; + s.constructor[Symbol.species] = function (n) { calls.push(n); return new Other(n); }; + const m = s.map((v) => v * 1.5), f = s.filter((v) => v > 1), sl = s.slice(1, 3); + console.log(m, f, sl, calls); + s.constructor[Symbol.species] = function (buffer, offset, length) { calls.push([offset, length]); return new TA(buffer, offset, length); }; + const sub = s.subarray(1, 3); + sub[0] = 50; + console.log(sub, s[1], calls.length); + s.constructor[Symbol.species] = function () { return {}; }; + try { s.map((v) => v); } catch (err) { console.log(err.constructor.name); } + s.constructor[Symbol.species] = function () { return new TA(1); }; + try { s.slice(); } catch (err) { console.log(err.constructor.name); } + s.constructor = 5; + try { s.filter(() => true); } catch (err) { console.log(err.constructor.name); } + s.constructor = undefined; + console.log(s.map((v) => v + 1)); + const t = new TA([3, 1]); + Object.defineProperty(t, "constructor", { get() { calls.push("get"); return undefined; } }); + console.log(t.slice(), calls[calls.length - 1]); + const u = new TA([1, 2, 3]); + u.constructor = { [Symbol.species]: Uint16Array }; + console.log(u.filter(() => true), u.subarray(1), u.subarray(1).constructor === Uint16Array); +} + +function reflection(TA) { + const a = new TA([1, 2]); + a.foo = 1; + a[Symbol.for("s")] = 2; + const keys = []; + for (const k in a) keys.push(k); + console.log(keys, { ...a }, JSON.stringify(a), Object.values(a)); + console.log(a.propertyIsEnumerable("foo"), Object.hasOwn(a, "foo"), Object.getOwnPropertySymbols(a), a[Symbol.for("s")]); + console.log(Object.assign(new TA(2), { x: 1, 0: 9 }), Object.assign({}, a)); + delete a.foo; + delete a[5]; + delete a.length; + console.log(a, "foo" in a, a.length); + try { delete a[0]; } catch (err) { console.log(err.message); } + Object.defineProperty(a, "fixed", { value: 1 }); + try { delete a.fixed; } catch (err) { console.log(err.message); } + try { a.fixed = 2; } catch (err) { console.log(err.message); } + console.log(a.subarray(0, 1).fixed, a.slice().fixed); + let seen; + Object.defineProperty(a, "s", { set(v) { seen = [v, this === a]; } }); + a.s = 7; + console.log(seen); + a.map = 5; + try { a.map((v) => v); } catch (err) { console.log(err.message); } + for (const key of ["length", "byteLength", "byteOffset", "buffer"]) { + try { a[key] = 1; } catch (err) { console.log(err.message); } + } + try { a[Symbol.toStringTag] = "x"; } catch (err) { console.log(err.message); } + // Cycles through a view's own properties are collected. + const loop = new TA(1); + loop.self = loop; + loop.size = () => loop.length; + console.log(loop.self === loop, loop.size()); +} + +function integrity(TA) { + const results = []; + const attempt = (f) => { try { f(); results.push("ok"); } catch (err) { results.push(err.message); } }; + const sealed = new TA(1); + sealed.x = 1; + attempt(() => Object.seal(sealed)); + console.log(results, Object.isExtensible(sealed), Object.isSealed(sealed), JSON.stringify(Object.getOwnPropertyDescriptor(sealed, "x"))); + attempt(() => Object.seal(sealed)); + const frozen = new TA(1); + frozen.x = 1; + attempt(() => Object.freeze(frozen)); + attempt(() => Object.freeze(frozen)); + console.log(results, Object.isFrozen(frozen), JSON.stringify(Object.getOwnPropertyDescriptor(frozen, "x"))); + const closed = new TA(1); + Object.defineProperty(closed, "q", { value: 1 }); + Object.preventExtensions(closed); + try { closed.z = 1; } catch (err) { console.log(err.message); } + try { closed[Symbol("n")] = 1; } catch (err) { console.log(err.message); } + console.log(Object.isExtensible(closed), Object.isSealed(closed), Object.isFrozen(closed)); + const empty = new TA(0); + empty.k = 1; + Object.freeze(empty); + try { empty.k = 2; } catch (err) { console.log(err.message); } + console.log(Object.isFrozen(empty), Object.isSealed(empty), empty.k); +} + +expandos(Uint8Array); +expandos(Float64Array); +species(Int16Array, Float32Array); +reflection(Uint8Array); +reflection(Int32Array); +integrity(Uint16Array); + +const view = box(new DataView(new ArrayBuffer(2))); +view[0] = 5; +view.x = 1; +console.log(view, Object.keys(view), view[0], JSON.stringify(view), "0" in view); +try { view.byteLength = 1; } catch (err) { console.log(err.message); } +const buffer = box(Buffer.from([1, 2])); +buffer.foo = 1; +buffer[Symbol.for("k")] = 2; +console.log(buffer, Object.keys(buffer), JSON.stringify(buffer)); +for (const key of ["parent", "offset", "length"]) { + try { buffer[key] = 1; } catch (err) { console.log(err.message); } +} +const empty = box(Buffer.alloc(0)); +empty.foo = { a: [1, { b: 2 }] }; +console.log(empty); diff --git a/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json b/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json new file mode 100644 index 000000000..9ed866538 --- /dev/null +++ b/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json @@ -0,0 +1 @@ +{ "plain": 3900, "san": 4400 } diff --git a/tests/harness/typed-array-property-refusals.test.ts b/tests/harness/typed-array-property-refusals.test.ts new file mode 100644 index 000000000..b2d6d498a --- /dev/null +++ b/tests/harness/typed-array-property-refusals.test.ts @@ -0,0 +1,64 @@ +import { execFile } from "node:child_process"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); + +// The runtime's conversions, iteration and printing of a typed array, Buffer +// or DataView read these members from the prototype, so defining one on the +// view refuses with a catchable error instead of being ignored. +test("typed array members the runtime reads from the prototype refuse as own properties", async () => { + const outDir = mkdtempSync(join(tmpdir(), "scriptc-typed-array-refusals-")); + const entry = join(outDir, "main.mjs"); + writeFileSync( + entry, + ` +function attempt(define) { + try { define(); console.log("defined"); } catch (error) { console.log(error.name, error.message); } +} +function run(TA, view) { + const a = new TA(2); + attempt(() => { a.toString = () => "x"; }); + attempt(() => { a.valueOf = () => 1; }); + attempt(() => Object.defineProperty(a, "toJSON", { value: () => 1 })); + attempt(() => { a.join = () => ""; }); + attempt(() => { a[Symbol.iterator] = function* () {}; }); + attempt(() => { a[Symbol.for("nodejs.util.inspect.custom")] = () => "x"; }); + attempt(() => { view.toLocaleString = () => ""; }); + attempt(() => { a.label = "kept"; }); + console.log(a.label, String(a)); +} +run(Uint8Array, new DataView(new ArrayBuffer(1))); +`, + ); + const result = await compile(entry, { + backend: "llvm", + sanitize: process.env["SCRIPTC_SAN"] === "1", + outDir, + outPath: join(outDir, "program"), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + if (!result.ok) return; + const { stdout, stderr } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" }); + const refused = (key: string): string => + `Error defining ${key} on a typed array, Buffer or DataView is not supported yet`; + expect(stdout).toBe( + [ + refused("toString"), + refused("valueOf"), + refused("toJSON"), + refused("join"), + refused("Symbol(Symbol.iterator)"), + refused("Symbol(nodejs.util.inspect.custom)"), + refused("toLocaleString"), + "defined", + "kept 0,0", + "", + ].join("\n"), + ); + expect(stderr).toBe(""); +}); From 2a98dbfa329c8b9e176179257429edb8d2f6a5e6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 21:57:25 -0500 Subject: [PATCH 038/395] Answer constructor reads on instances without a property bag The constructor read asked dyn.hasOwn on the instance's property bag, which is undefined until the instance has one, and on the bag's prototype, so it threw "Cannot convert undefined or null to object" for an instance without own properties (for example `new ParseFailure('p').constructor` for a class that extends SyntaxError). It now goes through a lifted helper that answers false before the instance has a bag and when the bag has no prototype, as the dyn.hasKey read did. --- .../lowering/class-dynamic-dispatch.ts | 96 ++++++++++++++++++- .../7621-class-methods-foreign-receivers.mjs | 39 ++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index a1bc41782..5132e487f 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -3966,6 +3966,94 @@ export class ClassDynamicDispatch { return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; } + /** `%dyn.class.bagHasOwn[OrPrototype]`: whether an instance's property bag + * holds `key` as its own property, or, with `prototype`, whether the bag's + * prototype object does. False before the instance has a bag. */ + private readBagHasOwn( + lowerer: Lowerer, + bag: IrExpr, + key: IrExpr, + prototype: boolean, + loc: SrcLoc, + ): IrExpr { + const name = prototype ? "%dyn.class.bagHasOwnOrPrototype" : "%dyn.class.bagHasOwn"; + let helper = lowerer.liftedFnNamed(name); + if (!helper) { + const value = varRef("p.0", DYN, loc); + const keyRef = varRef("p.1", STRING, loc); + const proto = varRef("proto", DYN, loc); + const hasOwn = (target: IrExpr): IrExpr => ({ + kind: "libCall", + fn: "dyn.hasOwn", + args: [target, keyRef], + type: BOOL, + loc, + }); + const returnFalse: IrStmt = { + kind: "return", + value: { kind: "boolLit", value: false, type: BOOL, loc }, + loc, + }; + helper = { + name, + params: [ + { localId: "p.0", name: "bag", type: DYN }, + { localId: "p.1", name: "key", type: STRING }, + ], + returnType: BOOL, + locals: [ + { id: "p.0", name: "bag", type: DYN, mutable: false }, + { id: "p.1", name: "key", type: STRING, mutable: false }, + ...(prototype ? [{ id: "proto", name: "proto", type: DYN, mutable: false }] : []), + ], + body: [ + { + kind: "if", + cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc }, + then: [returnFalse], + else_: null, + loc, + }, + ...(prototype + ? ([ + { + kind: "if", + cond: hasOwn(value), + then: [ + { + kind: "return", + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, + ], + else_: null, + loc, + }, + { + kind: "varDecl", + localId: "proto", + init: { kind: "libCall", fn: "dyn.getPrototype", args: [value], type: DYN, loc }, + loc, + }, + { + kind: "if", + cond: { kind: "dynTest", test: "nullish", value: proto, type: BOOL, loc }, + then: [returnFalse], + else_: null, + loc, + }, + { kind: "return", value: hasOwn(proto), loc }, + ] satisfies IrStmt[]) + : [{ kind: "return", value: hasOwn(value), loc } satisfies IrStmt]), + ], + loc, + }; + lowerer.liftedFns.push(helper); + } + this.generated.add(helper); + return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; + } + /** The hints for which some boxed class declares a conversion method. A * string conversion calls toString first, so without a declared toString * or Symbol.toPrimitive it answers Object.prototype.toString's @@ -5633,12 +5721,18 @@ export class ClassDynamicDispatch { const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN); const bag = this.branchPropertyBag(lowerer, info, receiver, false, loc); const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + // `constructor` is an own property of every class prototype, so an + // ancestor's prototype never answers for an instance of this class: + // the instance's own property answers, then one this class's + // prototype object defines (a reflected prototype, or a write to + // `C.prototype.constructor`), and otherwise the class itself. + const answered = this.readBagHasOwn(lowerer, bag, key, hasClassPrototypeData(info), loc); return [ { kind: "return", value: { kind: "ternary", - cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + cond: answered, then: { kind: "libCall", fn: "dyn.bagGet", diff --git a/tests/corpus/7621-class-methods-foreign-receivers.mjs b/tests/corpus/7621-class-methods-foreign-receivers.mjs index beb046ea4..8e4feedbd 100644 --- a/tests/corpus/7621-class-methods-foreign-receivers.mjs +++ b/tests/corpus/7621-class-methods-foreign-receivers.mjs @@ -128,3 +128,42 @@ report("square both", () => Square.prototype.both.call(foreign)); report("tiny describe", () => Tiny.prototype.describe.call(foreign)); report("tiny tag", () => Tiny.prototype.tag.call(foreign)); report("square describe on tiny", () => Square.prototype.describe.call(tiny)); + +// A base method that builds `new this.constructor()` on a subclass instance +// builds the subclass, also once the base prototype is complete (a super call +// above reads it) and `copy` is read through its prototype slot. +class Node3 { + constructor() { + this.a = 0; + } + copy(source) { + this.a = source.a; + return this; + } + clone() { + return new this.constructor().copy(this); + } +} +class Mesh3 extends Node3 { + constructor(geometry) { + super(); + this.geometry = geometry; + } + copy(source) { + super.copy(source); + this.geometry = source.geometry; + return this; + } +} +const copyValue = Node3.prototype.copy; +const mesh = new Mesh3({ id: 1 }); +mesh.a = 5; +const meshClone = mesh.clone(); +console.log( + meshClone instanceof Mesh3, + meshClone.constructor === Mesh3, + mesh.constructor === Mesh3, + meshClone.a, + JSON.stringify(meshClone.geometry), + typeof copyValue, +); From 27593e366eeb4c4b270b1c809ea5d7512bfed15e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 22:08:02 -0500 Subject: [PATCH 039/395] Make Dates mutable objects with Node's setters and conversions --- benchmarks/runtime/README.md | 1 + benchmarks/runtime/workloads/date-math.json | 6 + benchmarks/runtime/workloads/date-math.ts | 52 +++++ .../content/limitations/builtin-apis/date.mdx | 9 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 9 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../compiler/src/diagnostics/diagnostic.ts | 2 +- .../frontend/lowering/abstract-equality.ts | 38 ++++ .../lowering/builtins/date-classes.ts | 22 +- .../src/frontend/lowering/builtins/dates.ts | 192 +++++++++++++++++- .../src/frontend/lowering/date-values.ts | 60 ++++-- .../frontend/lowering/expressions/binary.ts | 13 ++ .../lowering/expressions/property-access.ts | 6 +- .../lowering/expressions/relational.ts | 10 + .../frontend/lowering/expressions/typeof.ts | 1 + .../frontend/lowering/expressions/updates.ts | 31 ++- .../src/frontend/lowering/lower-calls.ts | 6 + .../src/frontend/lowering/lower-classes.ts | 23 +++ .../src/frontend/lowering/lower-exprs.ts | 1 + .../src/frontend/lowering/lower-generators.ts | 10 +- .../src/frontend/lowering/lower-stmts.ts | 27 ++- .../src/frontend/lowering/lowerer.test.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 3 + .../src/frontend/lowering/surfaces.ts | 7 + .../compiler/src/ir/builtin-signatures.ts | 9 + packages/compiler/src/ir/ir.ts | 13 +- packages/compiler/src/ir/validate.ts | 19 +- packages/compiler/surface-manifest.json | 7 + packages/compiler/test/ir.test.ts | 10 +- .../tests/corpus/8020-date-setters.ts.json | 6 + .../tests/corpus/8021-date-coercions.ts.json | 6 + .../tests/corpus/8022-date-unions.ts.json | 6 + .../corpus/8023-date-clone-throw.ts.json | 6 + .../corpus/8024-date-setters-dynamic.mjs.json | 6 + .../corpus/8025-date-subclass-setters.ts.json | 6 + ...026-date-subclass-setters-dynamic.mjs.json | 6 + packages/runtime/src/scr_lib.c | 126 +++++++++++- packages/runtime/src/scr_number.h | 17 ++ tests/corpus/8020-date-setters.ts | 115 +++++++++++ tests/corpus/8021-date-coercions.ts | 47 +++++ tests/corpus/8022-date-unions.ts | 68 +++++++ tests/corpus/8023-date-clone-throw.ts | 32 +++ tests/corpus/8024-date-setters-dynamic.mjs | 24 +++ tests/corpus/8025-date-subclass-setters.ts | 30 +++ .../8026-date-subclass-setters-dynamic.mjs | 17 ++ tests/diagnostics/stdlib-fence.ts | 10 - .../__snapshots__/assert-class-fences.ts.txt | 2 +- .../class-interface-refinements.ts.txt | 4 +- .../__snapshots__/date-formatters.ts.txt | 4 +- .../dynamic-representable-types.ts.txt | 2 +- tests/harness/__snapshots__/generics.ts.txt | 2 +- .../harness/__snapshots__/maps-surface.ts.txt | 2 +- .../regex-match-destructuring-fences.ts.txt | 2 +- ...engine-2103-optional-tuple-elements.ts.txt | 10 +- .../harness/__snapshots__/stdlib-fence.ts.txt | 81 +++----- tests/harness/__snapshots__/tuples.ts.txt | 4 +- .../union-clone-override-width.ts.txt | 2 +- .../corpus-costs/8020-date-setters.ts.json | 1 + .../corpus-costs/8021-date-coercions.ts.json | 1 + .../corpus-costs/8022-date-unions.ts.json | 1 + .../8023-date-clone-throw.ts.json | 1 + .../8024-date-setters-dynamic.mjs.json | 1 + .../8025-date-subclass-setters.ts.json | 1 + ...026-date-subclass-setters-dynamic.mjs.json | 1 + 64 files changed, 1061 insertions(+), 180 deletions(-) create mode 100644 benchmarks/runtime/workloads/date-math.json create mode 100644 benchmarks/runtime/workloads/date-math.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8020-date-setters.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8021-date-coercions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8022-date-unions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8023-date-clone-throw.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8024-date-setters-dynamic.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8025-date-subclass-setters.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8026-date-subclass-setters-dynamic.mjs.json create mode 100644 tests/corpus/8020-date-setters.ts create mode 100644 tests/corpus/8021-date-coercions.ts create mode 100644 tests/corpus/8022-date-unions.ts create mode 100644 tests/corpus/8023-date-clone-throw.ts create mode 100644 tests/corpus/8024-date-setters-dynamic.mjs create mode 100644 tests/corpus/8025-date-subclass-setters.ts create mode 100644 tests/corpus/8026-date-subclass-setters-dynamic.mjs create mode 100644 tests/harness/corpus-costs/8020-date-setters.ts.json create mode 100644 tests/harness/corpus-costs/8021-date-coercions.ts.json create mode 100644 tests/harness/corpus-costs/8022-date-unions.ts.json create mode 100644 tests/harness/corpus-costs/8023-date-clone-throw.ts.json create mode 100644 tests/harness/corpus-costs/8024-date-setters-dynamic.mjs.json create mode 100644 tests/harness/corpus-costs/8025-date-subclass-setters.ts.json create mode 100644 tests/harness/corpus-costs/8026-date-subclass-setters-dynamic.mjs.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 4f152e4bb..11c049140 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -10,6 +10,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `atomics-contended` | cpu | four worker threads updating `SharedArrayBuffer` counters with `Atomics`: one shared counter, one counter per worker, a `compareExchange` loop, and `Atomics.wait`/`notify` handoffs | | `channel-messages` | cpu | 200,000 MessageChannel messages in one thread, half numbers and half small objects, all posted before the port delivers any | | `csv-numbers` | cpu | `split`, `Number`, `parseInt`, `toFixed` | +| `date-math` | cpu | schedules built with Date setters (local and UTC), weekday and month-end rules through the getters, Dates sorted by time value | | `cyclic-graphs` | cpu | object graphs with back references built and dropped: trees with parent and sibling links, rings of records sharing a long-lived registry, closures capturing their owner | | `dictionary-walks` | cpu | `Record` dictionaries walked with for-in and `Object.keys`/`values`/`entries` while entries are added, updated and deleted | | `dynamic-methods` | cpu | untyped JavaScript calling builtin methods on values it does not type: string `slice`/`indexOf`/`toUpperCase`/`padStart`, array `push`/`pop`/`includes`, `Map` and `Set`, and an object's own methods | diff --git a/benchmarks/runtime/workloads/date-math.json b/benchmarks/runtime/workloads/date-math.json new file mode 100644 index 000000000..b43078582 --- /dev/null +++ b/benchmarks/runtime/workloads/date-math.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/date-math.ts", + "args": [ + "40" + ] +} diff --git a/benchmarks/runtime/workloads/date-math.ts b/benchmarks/runtime/workloads/date-math.ts new file mode 100644 index 000000000..56ec1066e --- /dev/null +++ b/benchmarks/runtime/workloads/date-math.ts @@ -0,0 +1,52 @@ +// Calendar arithmetic on Dates: schedules built by adjusting Dates with the +// UTC and local setters, weekday and month-end rules read back through the +// getters, Dates compared and sorted by time value, and ISO text for a +// sample. Argument: the number of rounds. +process.env.TZ = "America/New_York"; + +const rounds = Number(process.argv[2] ?? "40"); +let seed = 2026; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} + +/** The next business day at 09:00 local time on or after `from`. */ +function nextBusinessDay(from: Date): Date { + const day = new Date(from.getTime()); + day.setHours(9, 0, 0, 0); + if (day < from) day.setDate(day.getDate() + 1); + while (day.getDay() === 0 || day.getDay() === 6) day.setDate(day.getDate() + 1); + return day; +} + +/** The last day of `date`'s month, in UTC. */ +function monthEnd(date: Date): number { + const end = new Date(date.getTime()); + end.setUTCDate(1); + end.setUTCMonth(end.getUTCMonth() + 1); + end.setUTCDate(0); + return end.getUTCDate(); +} + +let checksum = 0; +let sample = ""; +for (let round = 0; round < rounds; round++) { + const due: Date[] = []; + const cursor = new Date(Date.UTC(2020 + (round % 7), 0, 1, 12)); + for (let i = 0; i < 4000; i++) { + cursor.setUTCMinutes(cursor.getUTCMinutes() + Math.floor(random() * 4000)); + const next = nextBusinessDay(cursor); + checksum += next.getDate() + monthEnd(next) + next.getHours(); + if (i % 3 === 0) next.setMonth(next.getMonth() + 1, 15); + due.push(next); + } + due.sort((a, b) => +a - +b); + let previous = due[0]!; + for (const date of due) { + checksum += (+date - +previous) / 3600000; + previous = date; + } + sample = due[due.length >> 1]!.toISOString(); +} +console.log(Math.round(checksum), sample); diff --git a/docs/content/limitations/builtin-apis/date.mdx b/docs/content/limitations/builtin-apis/date.mdx index 89266b745..d783984fa 100644 --- a/docs/content/limitations/builtin-apis/date.mdx +++ b/docs/content/limitations/builtin-apis/date.mdx @@ -1,6 +1,7 @@ -- **Date:** Spread constructor arguments, setters, thrown Date values, and locale formatters are unsupported. - Dates are objects: `===` compares identity, `new Date(date)` makes a new Date, and Dates compile in arrays (`includes`, `indexOf`, `lastIndexOf`, `join`, and default `sort` by `toString()` text), in `Date | undefined` and `Date | null` slots, as Map and Set keys and values, and in records, tuples, class fields, closures, promises and generators. - Unions of a Date with other non-nullish types are unsupported. - Zero-argument, single number/string, and local-time year/month field constructors, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, relational comparison, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. +- **Date:** Spread constructor arguments and locale formatters are unsupported. + Dates are objects with identity: `===` and `==` compare identity, `new Date(date)` makes a new Date, setters change the one Date every reference holds, and Dates compile in arrays (`includes`, `indexOf`, `lastIndexOf`, `join`, and default `sort` by `toString()` text), in unions with other types, as Map and Set keys and values, and in records, tuples, class fields, closures, promises and generators. + Thrown and rejected Dates keep their identity; `structuredClone` and `postMessage` copy them. + Zero-argument, single number/string, and local-time year/month field constructors, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, the local and UTC calendar getters and setters, `setTime`, `getTimezoneOffset`, and single-string `Date.parse` compile. + Unary `+`, `-` and `Number(date)` read the time value; `String(date)`, template literals and `+` with a string read the `toString()` text; `date[Symbol.toPrimitive](hint)` compiles with a literal hint. A Date converted to `unknown` is the same Date object. See [Dates and timezones](#dates-and-timezones) for timezone data. diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..d8ca5add8 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -649,6 +649,15 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll if (MAY_THROW_LIB_FNS.has(e.fn) && LIB_FN_SYMS[e.fn] === undefined) { throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); } + // A setter's write: the time value into the Date object (ScrDate.ms). + if (e.fn === "date.store") { + const date = emitBorrowedInput(host, e.args[0]!).name; + const ms = host.emitExpr(e.args[1]!).name; + const field = B.tmp(); + B.line(`${field} = getelementptr inbounds i8, ptr ${date}, i64 8 ; ->ms`); + B.line(`store double ${ms}, ptr ${field}`); + return { name: ms, type: e.type }; + } // queueMicrotask's checked-dynamic form carries the AsyncLocalStorage // context like the static one (lib-process.ts). const sym = diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 21f32431c..69d028539 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -334,6 +334,8 @@ export const LIB_FN_SYMS: Record = { "date.getTime": "scr_date_get_time", "date.valueOf": "scr_date_get_time", "date.fromTime": "scr_date_get_time", + "date.setFields": "scr_date_set_fields", + "date.timeClip": "scr_date_new_ms", "date.parseGetTime": "scr_date_parse", "date.utc": "scr_date_utc", "date.newLocal": "scr_date_new_local", diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 2fd8b6fb0..55c3e9047 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -622,7 +622,7 @@ export function unsupportedTypeDiag(typeText: string, loc: SrcLoc): ScrDiagnosti code: "SC2001", message: `values of type '${typeText}' cannot be compiled yet ` + - `(supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown')`, + `(supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown')`, loc, milestone: "M2", }; diff --git a/packages/compiler/src/frontend/lowering/abstract-equality.ts b/packages/compiler/src/frontend/lowering/abstract-equality.ts index 6c74b3546..634ff6f22 100644 --- a/packages/compiler/src/frontend/lowering/abstract-equality.ts +++ b/packages/compiler/src/frontend/lowering/abstract-equality.ts @@ -5,6 +5,7 @@ import { DYN, DYN_HANDLE_KINDS, F64, + STRING, type IrExpr, type IrStmt, type IrType, @@ -285,6 +286,43 @@ export function lowerAbstractEquality( }; return negated ? { kind: "unary", op: "!", operand: result, type: BOOL, loc } : result; } + // Two Dates are equal when they are the same object, as with ===. + if (left.type.kind === "date" && right.type.kind === "date") + return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc }; + // A Date and a string or number compare through the Date's default + // primitive, its toString() text. + if (left.type.kind === "date" || right.type.kind === "date") { + const otherType = left.type.kind === "date" ? right.type : left.type; + if (otherType.kind === "string" || otherType.kind === "f64") { + // Both operands evaluate before the Date converts. + const l = lowerer.declareHiddenLocal("%looseA", left.type); + const r = lowerer.declareHiddenLocal("%looseB", right.type); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: l.id, init: left, loc }, + { kind: "varDecl", localId: r.id, init: right, loc }, + ]; + const primitive = (value: IrExpr): IrExpr => { + if (value.type.kind !== "date") return value; + const text: IrExpr = { + kind: "libCall", + fn: "date.toString", + args: [value], + type: STRING, + loc, + }; + return otherType.kind === "string" + ? text + : { kind: "libCall", fn: "num.fromString", args: [text], type: F64, loc }; + }; + const a = primitive(varRef(l.id, left.type, loc)); + const b = primitive(varRef(r.id, right.type, loc)); + const result: IrExpr = + otherType.kind === "string" + ? { kind: "strEq", negated, left: a, right: b, type: BOOL, loc } + : { kind: "bin", op: negated ? "!==" : "===", left: a, right: b, type: BOOL, loc }; + return { kind: "seqExpr", stmts, result, type: BOOL, loc }; + } + } if (!supportsPair(lowerer, left.type, right.type)) return null; if (left.kind === "strLit" && right.kind === "strLit") { return { kind: "boolLit", value: (left.value === right.value) !== negated, type: BOOL, loc }; diff --git a/packages/compiler/src/frontend/lowering/builtins/date-classes.ts b/packages/compiler/src/frontend/lowering/builtins/date-classes.ts index a2b0fc20a..c8aab8687 100644 --- a/packages/compiler/src/frontend/lowering/builtins/date-classes.ts +++ b/packages/compiler/src/frontend/lowering/builtins/date-classes.ts @@ -2,7 +2,13 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; import { type ClassInfo } from "../lower-classes.js"; -import { DATE_METHOD_HINT, dateJsonType, dateValueMethod } from "./dates.js"; +import { + DATE_METHOD_HINT, + dateJsonType, + dateSetterTime, + dateValueMethod, + isDateSetter, +} from "./dates.js"; import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; import { BOOL, @@ -248,6 +254,20 @@ export function lowerInheritedDateMethodCall( type: result.type, loc, }); + if (isDateSetter(name) && name !== "setTime") { + // The field setters change the Date the class's time slot holds. + const date = lowerer.declareHiddenLocal("%dateTime", DATE_T); + stmts.push({ + kind: "varDecl", + localId: date.id, + init: timeOf(lowerer, value, root, loc), + loc, + }); + const current = varRef(date.id, DATE_T, loc); + const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [current], type: F64, loc }; + const next = dateSetterTime(lowerer, call, name, stmts, time); + return done({ kind: "libCall", fn: "date.store", args: [current, next], type: F64, loc }); + } if (name === "setTime") { // Every argument is evaluated before setTime converts the first one. let ms: IrExpr = { kind: "numLit", value: Number.NaN, type: F64, loc }; diff --git a/packages/compiler/src/frontend/lowering/builtins/dates.ts b/packages/compiler/src/frontend/lowering/builtins/dates.ts index 0204386ab..79696d1ba 100644 --- a/packages/compiler/src/frontend/lowering/builtins/dates.ts +++ b/packages/compiler/src/frontend/lowering/builtins/dates.ts @@ -2,12 +2,15 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer, own } from "../lowerer.js"; import { locOf } from "../../program.js"; import { tryLowerExpression } from "../expressions/try-lower-expression.js"; +import { lowerToNumberArguments } from "../lower-exprs.js"; import { programDeclaresDateClass } from "./date-classes.js"; import { BOOL, + DATE_T, F64, type IrExpr, type IrLibFn, + type IrLocal, type IrStmt, type IrType, NULL_T, @@ -36,6 +39,32 @@ const DATE_GETTER_FNS: Readonly> = { getTimezoneOffset: "date.getTimezoneOffset", }; +/** The setters that change calendar fields: the field code + * scr_date_set_fields takes (scr_number.h SCR_DATE_SET_*; 8 marks the UTC + * forms) and the most arguments each takes. */ +const DATE_FIELD_SETTERS: ReadonlyMap = new Map([ + ["setMilliseconds", { code: 0, max: 1 }], + ["setSeconds", { code: 1, max: 2 }], + ["setMinutes", { code: 2, max: 3 }], + ["setHours", { code: 3, max: 4 }], + ["setDate", { code: 4, max: 1 }], + ["setMonth", { code: 5, max: 2 }], + ["setFullYear", { code: 6, max: 3 }], + ["setUTCMilliseconds", { code: 8, max: 1 }], + ["setUTCSeconds", { code: 9, max: 2 }], + ["setUTCMinutes", { code: 10, max: 3 }], + ["setUTCHours", { code: 11, max: 4 }], + ["setUTCDate", { code: 12, max: 1 }], + ["setUTCMonth", { code: 13, max: 2 }], + ["setUTCFullYear", { code: 14, max: 3 }], +]); + +/** Whether `name` is a Date setter scriptc lowers: setTime or a field + * setter. */ +export function isDateSetter(name: string): boolean { + return name === "setTime" || DATE_FIELD_SETTERS.has(name); +} + const DATE_TEXT_FNS: ReadonlyMap = new Map([ ["toString", "date.toString"], ["toDateString", "date.toDateString"], @@ -43,11 +72,11 @@ const DATE_TEXT_FNS: ReadonlyMap = new Map([ ]); export const DATE_METHOD_HINT = - "getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering"; + "getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering"; -/** The Date slice: statics plus read-only Date values backed by one - * TimeClip'd millisecond scalar. Construction lives in lowerNew; - * identity and mutating methods remain fenced. */ +/** Date statics and methods on Date objects (a TimeClip'd millisecond + * time value with identity): getters, text forms and setters. + * Construction lives in lowerNew. */ export function lowerDateCall( lowerer: Lowerer, call: ts.CallExpression, @@ -67,16 +96,18 @@ export function lowerDateCall( if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "date") return null; if (!lowerer.isStdlibMember(access)) return null; const name = access.name.text; - // A Date a JavaScript file constructs is a mutable object; setTime on - // it takes the dynamic method call. So does every Date method on a + // A Date a JavaScript file constructs is a checked-dynamic value; a + // setter on it takes the dynamic method call, which changes the Date the + // handle holds. So does every Date method on a // checked-dynamic receiver when the program declares a class that extends // Date: the value can be an instance, whose class may declare the method. if ( - (name === "setTime" || programDeclaresDateClass(lowerer)) && + (isDateSetter(name) || programDeclaresDateClass(lowerer)) && tryLowerExpression(lowerer, access.expression)?.type.kind === "dyn" ) return null; if (name === "toJSON") return lowerDateToJson(lowerer, call, access); + if (isDateSetter(name)) return lowerDateSetter(lowerer, call, access, name); if (call.arguments.nodes.length !== 0) { lowerer.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT); } @@ -253,3 +284,150 @@ export function isDateJsonSource(lowerer: Lowerer, node: ts.Node): boolean { lowerer.isStdlibMember(access) ); } + +/** `d.setTime(t)` and the field setters (`d.setHours(h, m?, s?, ms?)` ...) + * on a Date object: the receiver, then the arguments in order, then the + * new time value (scr_date_set_fields over the time value the Date holds + * once the arguments have run) stored into the Date and answered. Missing + * optional arguments keep their fields; arguments past a setter's last + * one are evaluated and ignored. A Date local that holds its time value + * (date-values.ts) takes the new time value itself. */ +function lowerDateSetter( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + name: string, +): IrExpr { + const loc = locOf(call); + const nodes = call.arguments.nodes; + if (nodes.some((node) => ts.isSpreadElement(node))) + lowerer.noLowering(`Date.prototype.${name} with spread arguments`, call, DATE_METHOD_HINT); + const timeLocal = timeValueLocalOf(lowerer, access.expression); + const stmts: IrStmt[] = []; + let receiver: IrExpr | null = null; + if (timeLocal === null) { + // A checked-dynamic receiver takes the dynamic method call + // (lowerDateCall), so the value here is a Date object. + const value = lowerer.lowerExpr(access.expression); + if (value.type.kind !== "date") + lowerer.badType(access.expression, lowerer.typeOf(access.expression)); + const date = lowerer.declareHiddenLocal("%date", DATE_T); + stmts.push({ kind: "varDecl", localId: date.id, init: value, loc }); + receiver = { kind: "varRef", localId: date.id, type: DATE_T, loc }; + } + const time: IrExpr = + receiver === null + ? { kind: "varRef", localId: timeLocal!.id, type: F64, loc } + : { kind: "libCall", fn: "date.getTime", args: [receiver], type: F64, loc }; + const next = dateSetterTime(lowerer, call, name, stmts, time); + if (receiver === null) { + stmts.push({ kind: "assign", localId: timeLocal!.id, value: next, loc }); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: timeLocal!.id, type: F64, loc }, + type: F64, + loc, + }; + } + return { + kind: "seqExpr", + stmts, + result: { kind: "libCall", fn: "date.store", args: [receiver, next], type: F64, loc }, + type: F64, + loc, + }; +} + +/** The new time value of setter `name` called with `call`'s arguments on a + * Date whose time value `time` reads: the arguments evaluate into + * temporaries appended to `stmts` (ToNumber of each, an undefined one is + * NaN), and the result reads `time` after them. Missing optional arguments + * keep their fields; arguments past a setter's last one are evaluated and + * ignored. */ +export function dateSetterTime( + lowerer: Lowerer, + call: ts.CallExpression, + name: string, + stmts: IrStmt[], + time: IrExpr, +): IrExpr { + const loc = locOf(call); + const converted = lowerToNumberArguments( + lowerer, + call.arguments.nodes as readonly ts.Expression[], + ); + stmts.push(...converted.stmts); + const args: IrExpr[] = converted.values.map((value) => { + if (value.kind === "numLit") return value; + const arg = lowerer.declareHiddenLocal("%dateArg", F64); + stmts.push({ kind: "varDecl", localId: arg.id, init: value, loc }); + return { kind: "varRef", localId: arg.id, type: F64, loc }; + }); + const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); + if (name === "setTime") + return { + kind: "libCall", + fn: "date.timeClip", + args: [args[0] ?? number(Number.NaN)], + type: F64, + loc, + }; + const setter = DATE_FIELD_SETTERS.get(name)!; + const used = args.slice(0, setter.max); + const operands = [...used]; + while (operands.length < 4) operands.push(number(0)); + return { + kind: "libCall", + fn: "date.setFields", + args: [time, number(setter.code), number(used.length), ...operands], + type: F64, + loc, + }; +} + +/** The local a Date binding that holds its time value lowers to, for an + * identifier `node` that names one (date-values.ts); null otherwise. */ +function timeValueLocalOf(lowerer: Lowerer, node: ts.Expression): IrLocal | null { + while (ts.isParenthesizedExpression(node)) node = node.expression; + if (!ts.isIdentifier(node)) return null; + const local = lowerer.resolveLocal(node); + return local !== undefined && local !== null && lowerer.timeValueDateLocals.has(local) + ? local + : null; +} + +/** `d[Symbol.toPrimitive](hint)` on a Date with a literal hint: "number" + * answers the time value, "string" and "default" the toString() text (a + * Date's default hint is a string). Null for other calls. */ +export function lowerDateToPrimitiveCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.ElementAccessExpression, +): IrExpr | null { + const key = access.argumentExpression; + if ( + !ts.isPropertyAccessExpression(key) || + key.name.text !== "toPrimitive" || + !ts.isIdentifier(key.expression) || + !lowerer.isStdlibGlobal(key.expression, "Symbol") || + lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "date" + ) + return null; + const loc = locOf(call); + const hint = call.arguments.nodes.length === 1 ? call.arguments.nodes[0]! : null; + const text = hint !== null && ts.isStringLiteralLike(hint) ? hint.text : null; + if (text !== "number" && text !== "string" && text !== "default") + return lowerer.noLowering( + 'Date.prototype[Symbol.toPrimitive] without a "number", "string" or "default" literal hint', + call, + ); + const raw = lowerer.lowerExpr(access.expression); + const date: IrExpr = + raw.type.kind === "dyn" + ? { kind: "libCall", fn: "date.checkedValue", args: [raw], type: DATE_T, loc } + : raw; + return text === "number" + ? { kind: "libCall", fn: "date.valueOf", args: [date], type: F64, loc } + : { kind: "libCall", fn: "date.toString", args: [date], type: STRING, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/date-values.ts b/packages/compiler/src/frontend/lowering/date-values.ts index ef5eafebe..669bc1dbc 100644 --- a/packages/compiler/src/frontend/lowering/date-values.ts +++ b/packages/compiler/src/frontend/lowering/date-values.ts @@ -1,5 +1,6 @@ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; +import { isDateSetter } from "./builtins/dates.js"; /** Date methods whose receiver is only read for its time value: a Date * local used as their receiver can store the time value instead of a Date @@ -41,12 +42,14 @@ const RELATIONAL = new Set([ /** True when a local `const` (or a `let` that is never assigned again) * initialized with `new Date(...)` can keep the Date's time value instead - * of a Date object: no use observes the Date's identity. Every use reads the time value as - * a Date method receiver, a String() or template operand, a relational or - * string `+` operand, or `new Date(d)`'s argument, inside the declaring - * function, or inside the block or loop that scopes a module-level - * binding (no closure captures it). Top-level module bindings keep the - * object. Other uses box the time value into a new Date at the use. */ + * of a Date object: no use observes the Date's identity. Every use reads + * the time value as a Date method receiver (a setter replaces it), a + * String(), Number() or template operand, a unary + or - operand, a + * relational or string `+` operand, or `new Date(d)`'s argument, inside + * the declaring function, or inside the block or loop that scopes a + * module-level binding (no closure captures it). Top-level module bindings + * keep the object. Other uses box the time value into a new Date at the + * use. */ export function isTimeValueDateDeclaration( lowerer: Lowerer, decl: ts.VariableDeclaration, @@ -94,17 +97,21 @@ function decideTimeValueDate(lowerer: Lowerer, decl: ts.VariableDeclaration): bo const symbol = lowerer.checker.getSymbolAtLocation(decl.name); if (!symbol) return false; let ok = true; + let sets = false; const visit = (node: ts.Node): void => { if (!ok) return; if (ts.isIdentifier(node) && node !== decl.name && node.text === decl.name.text) { if (lowerer.checker.getSymbolAtLocation(node) === symbol) { - if (enclosingFunction(node) !== fn || !readsTimeValue(lowerer, node)) ok = false; + const use = enclosingFunction(node) === fn ? timeValueUse(lowerer, node) : null; + if (use === null) ok = false; + else if (use === "set") sets = true; } return; } ts.forEachChild(node, visit); }; visit(scope); + if (ok && sets) lowerer.timeValueDateWrites.add(decl); return ok; } @@ -164,29 +171,46 @@ function enclosingFunction(node: ts.Node): ts.FunctionLikeDeclaration | null { return null; } -/** A use of the binding that reads only its time value. */ -function readsTimeValue(lowerer: Lowerer, id: ts.Identifier): boolean { +/** How a use of the binding uses its time value: "read" for a use that + * reads only the time value, "set" for a setter call that replaces it, or + * null for any other use. */ +function timeValueUse(lowerer: Lowerer, id: ts.Identifier): "read" | "set" | null { let node: ts.Node = id; while (ts.isParenthesizedExpression(node.parent)) node = node.parent; const parent = node.parent; + if (parent === undefined) return null; if (ts.isPropertyAccessExpression(parent) && parent.expression === node) { const call = parent.parent; - return ( - !parent.questionDotToken && - ts.isCallExpression(call) && - call.expression === parent && - !call.questionDotToken && - TIME_VALUE_METHODS.has(parent.name.text) && - lowerer.isStdlibMember(parent) - ); + if ( + parent.questionDotToken || + !ts.isCallExpression(call) || + call.expression !== parent || + call.questionDotToken || + !lowerer.isStdlibMember(parent) + ) + return null; + if (isDateSetter(parent.name.text)) return "set"; + return TIME_VALUE_METHODS.has(parent.name.text) ? "read" : null; } + return readsTimeValue(lowerer, node, parent) ? "read" : null; +} + +/** A use of the binding, other than a method call, that reads only its + * time value. */ +function readsTimeValue(lowerer: Lowerer, node: ts.Node, parent: ts.Node): boolean { if (ts.isTemplateSpan(parent)) return parent.expression === node; + // Unary + and -, the number hint. + if (ts.isPrefixUnaryExpression(parent)) + return ( + parent.operator === ts.SyntaxKind.PlusToken || parent.operator === ts.SyntaxKind.MinusToken + ); if (ts.isCallExpression(parent)) { return ( parent.arguments.nodes.length === 1 && parent.arguments.nodes[0] === node && ts.isIdentifier(parent.expression) && - lowerer.isStdlibGlobal(parent.expression, "String") + (lowerer.isStdlibGlobal(parent.expression, "String") || + lowerer.isStdlibGlobal(parent.expression, "Number")) ); } if (ts.isNewExpression(parent)) { diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 00a23caf5..de6648836 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -1600,6 +1600,19 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr loc, }; } + // A Date against an untyped value: the same Date object, whose + // checked-dynamic handle the untyped value would hold. + if (dynSide.type.kind === "dyn" && scalarSide.type.kind === "date") { + const boxed = lowerer.coerceToExpected(scalarSide, DYN); + return { + kind: "dynScalarEq", + left: scalarSide === left ? boxed : left, + right: scalarSide === right ? boxed : right, + ...(negated ? { negated: true as const } : {}), + type: BOOL, + loc, + }; + } if ( dynSide.type.kind === "dyn" && (scalarSide.type.kind === "f64" || diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 1a4883e5a..874c0c6cc 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -112,6 +112,7 @@ import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../. import { typedArrayLacksMember } from "../builtins/typed-array-members.js"; import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; import { tryLowerExpression } from "./try-lower-expression.js"; +import { DATE_METHOD_HINT } from "../builtins/dates.js"; import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, @@ -2055,6 +2056,9 @@ export function lowerIntrinsicProperty( ); } if (kind === "date") { + // A Date held as a checked-dynamic value (a Date a JavaScript file + // constructs) answers its method values at run time. + if (tryLowerExpression(lowerer, expr.expression)?.type.kind === "dyn") return null; const methods = new Set([ "getTime", "valueOf", @@ -2083,7 +2087,7 @@ export function lowerIntrinsicProperty( lowerer.noLowering( `Date.prototype.${name}`, expr, - "getTime(), valueOf(), toISOString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering", + DATE_METHOD_HINT, lowerer.checker.getSymbolAtLocation(expr.name), ); } diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 1651bf0d9..2903e4e14 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -288,6 +288,16 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc if (!target) { const date = lowerer.isStdlibGlobal(expr.right, "Date"); const url = lowerer.isStdlibGlobal(expr.right, "URL"); + // A catch binding holds a thrown Date as its checked-dynamic handle. + const caughtDate = date ? lowerer.caughtLocalOf(expr.left) : null; + if (caughtDate) + return { + kind: "libCall", + fn: "dyn.nativeDateIs", + args: [{ kind: "caughtToDyn", value: varRef(caughtDate.id, CAUGHT, loc), type: DYN, loc }], + type: BOOL, + loc, + }; if ((date || url) && !lowerer.caughtLocalOf(expr.left)) { const value = lowerer.lowerExpr(expr.left); if (value.type.kind === "dyn") diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index fbbc068b9..0676f3cf5 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -281,6 +281,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc promise: "object", bytes: "object", regex: "object", + date: "object", generator: "object", classval: "function", moduleNs: "object", diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index bdcd94a44..35d5d1a2e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -120,6 +120,11 @@ function coercedUnion(lowerer: Lowerer, type: IrType): boolean { return arms.some((arm) => arm.kind !== "f64" && !isUnitType(arm)); } +/** ToNumber of a Date: its time value (valueOf, the number hint). */ +function dateTimeValue(date: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "date.valueOf", args: [date], type: F64, loc }; +} + /** ToNumber of a boolean: 1 for true, 0 for false. */ function booleanToNumber(value: IrExpr, loc: SrcLoc): IrExpr { return { @@ -145,23 +150,25 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio const operand = raw.type.kind === "bool" ? booleanToNumber(raw, loc) - : coercedUnion(lowerer, raw.type) - ? ({ - kind: "libCall", - fn: "dyn.toNumberCoerce", - args: [{ kind: "dynFrom", value: raw, type: DYN, loc }], - type: F64, - loc, - } satisfies IrExpr) - : raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) + : raw.type.kind === "date" + ? dateTimeValue(raw, loc) + : coercedUnion(lowerer, raw.type) ? ({ kind: "libCall", fn: "dyn.toNumberCoerce", - args: [raw], + args: [{ kind: "dynFrom", value: raw, type: DYN, loc }], type: F64, loc, } satisfies IrExpr) - : lowerOptionalNumber(lowerer, raw, loc, expr.operand); + : raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) + ? ({ + kind: "libCall", + fn: "dyn.toNumberCoerce", + args: [raw], + type: F64, + loc, + } satisfies IrExpr) + : lowerOptionalNumber(lowerer, raw, loc, expr.operand); if (operand.type.kind !== "f64") operatorUnsupported(lowerer, expr, "-", [raw.type]); if (operand.kind === "numLit") return { ...operand, value: -operand.value, loc }; return { kind: "unary", op: "-", operand, type: F64, loc }; @@ -185,6 +192,8 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio return { kind: "libCall", fn: "num.fromString", args: [raw], type: F64, loc }; } if (raw.type.kind === "bool") return booleanToNumber(raw, loc); + // A Date's number hint answers its time value. + if (raw.type.kind === "date") return dateTimeValue(raw, loc); const classNumber = classToNumberConversion(lowerer, raw, expr.operand, "unary plus"); if (classNumber) return classNumber; const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 073ebed91..6ca6b7016 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,5 +1,6 @@ import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; +import { lowerDateToPrimitiveCall } from "./builtins/dates.js"; import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; import { lowerDynStreamMemberCall } from "./dyn-streams.js"; @@ -1816,6 +1817,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // The element spelling of an unsupported native Web method must fence // before lowering the callee into a checked-dynamic keyed read. if (ts.isElementAccessExpression(expr.expression)) { + const datePrimitive = lowerDateToPrimitiveCall(lowerer, expr, expr.expression); + if (datePrimitive) return datePrimitive; const timersDispose = lowerMockTimersElementCall(lowerer, expr, expr.expression); if (timersDispose) return timersDispose; const symbolMethod = lowerClassSymbolMethodCall(lowerer, expr); @@ -2456,6 +2459,9 @@ function lowerNumberConstructorValue( }; const optionalNumber = lowerOptionalStringNumber(lowerer, arg, loc); if (optionalNumber) return optionalNumber; + // A Date's number hint answers its time value. + if (arg.type.kind === "date") + return { kind: "libCall", fn: "date.valueOf", args: [arg], type: F64, loc }; // A program class instance converts through its own methods. const classNumber = classToNumberConversion(lowerer, arg, argNode); if (classNumber) return classNumber; diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 3e49304d2..7655fcea7 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -10426,6 +10426,29 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [arg], type: F64, loc }; return { kind: "libCall", fn: "date.newMs", args: [time], type: DATE_T, loc }; } + // A union (`Date | string | number`) or checked-dynamic value: the + // runtime's constructor reads a Date's time value, parses a string + // and converts anything else to a primitive and a number. + if ( + (arg.type.kind === "union" || arg.type.kind === "dyn") && + lowerer.dynConvertible(arg.type) + ) { + const value: IrExpr = { + kind: "libCall", + fn: "date.nativeNewValue", + args: [ + { + kind: "dynArrLit", + elems: [lowerer.coerceToExpected(arg, DYN)], + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }; + return { kind: "libCall", fn: "date.checkedValue", args: [value], type: DATE_T, loc }; + } lowerer.noLowering( `new Date of '${lowerer.fmt(arg.type)}' values`, args[0]!, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index f2b601619..88e9ddb12 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -2320,6 +2320,7 @@ export function typeofAnswer(arm: IrType): string | null { case "set": case "generator": case "regex": + case "date": case "bytes": case "url": case "searchParams": diff --git a/packages/compiler/src/frontend/lowering/lower-generators.ts b/packages/compiler/src/frontend/lowering/lower-generators.ts index f837cc0af..ccfa4ad39 100644 --- a/packages/compiler/src/frontend/lowering/lower-generators.ts +++ b/packages/compiler/src/frontend/lowering/lower-generators.ts @@ -183,13 +183,9 @@ export function lowerGenMethodCall( ); } arg = lowerer.lowerExpr(argNode); - if (arg.type.kind === "date") { - lowerer.unsupported( - "SC1090", - argNode, - ".throw() with a Date value (the exception channel cannot preserve Date's object kind; throw an Error or primitive instead)", - ); - } + // A Date throws as its checked-dynamic handle, the same Date object + // (the throw statement's rule). + if (arg.type.kind === "date") arg = lowerer.coerceToExpected(arg, DYN); if (arg.type.kind === "void" || arg.type.kind === "caught" || isUnitType(arg.type)) { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index c50676863..9e9e75794 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1624,6 +1624,7 @@ const THROWN_AS_DYN: ReadonlySet = new Set([ "bytes", "func", "regex", + "date", ]); export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null { @@ -1828,9 +1829,7 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt return { kind: "rethrow", localId: caughtLocal.id, loc: locOf(stmt) }; } // Exception-representable values move into the runtime's exception - // cell. `throw f()` of a void call has no value; Date has numeric - // storage internally, but cannot preserve its object kind through the - // untyped catch boundary. Reject both rather than changing JS identity. + // cell. `throw f()` of a void call has no value. let value = lowerer.lowerExpr(stmt.expression); if (value.type.kind === "void") lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression)); @@ -1842,19 +1841,12 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt } else if (value.type.kind === "record" && lowerer.dynConvertible(value.type)) { value = { kind: "dynFrom", value, liveRef: true, type: DYN, loc: locOf(stmt.expression) }; } else if (THROWN_AS_DYN.has(value.type.kind) && lowerer.dynConvertible(value.type)) { - // Arrays, Maps, Sets, typed arrays, Buffers, functions, regexes and - // unions over them keep their identity and contents the same way: - // the catch binding, `typeof`, `instanceof`, `Array.isArray`, - // inspection and typed views read the thrown object. + // Arrays, Maps, Sets, typed arrays, Buffers, functions, regexes, + // Dates and unions over them keep their identity and contents the + // same way: the catch binding, `typeof`, `instanceof`, + // `Array.isArray`, inspection and typed views read the thrown object. value = lowerer.coerceInto(stmt.expression, value, DYN); } - if (value.type.kind === "date") { - lowerer.unsupported( - "SC1090", - stmt.expression, - "throwing Date values (catch bindings cannot preserve Date's object kind; throw an Error or primitive instead)", - ); - } // A dyn throw rides the REF cell arm (scr_throw_ref over the checked-dynamic tree // node — identity preserved through catch bindings, caughtToDyn // passes it back by reference). The JS-lane traced-call shape: @@ -5450,7 +5442,12 @@ export function lowerVarDecl( if (isTimeValueDateDeclaration(lowerer, decl)) { const loc = locOf(decl); const date = lowerer.lowerExprExpecting(decl.initializer!, DATE_T); - const local = lowerer.declareLocal(decl.name, decl.name.text, F64, isLet); + const local = lowerer.declareLocal( + decl.name, + decl.name.text, + F64, + isLet || lowerer.timeValueDateWrites.has(decl), + ); lowerer.timeValueDateLocals.add(local); const init: IrExpr = { kind: "libCall", fn: "date.getTime", args: [date], type: F64, loc }; return { kind: "varDecl", localId: local.id, init, loc }; diff --git a/packages/compiler/src/frontend/lowering/lowerer.test.ts b/packages/compiler/src/frontend/lowering/lowerer.test.ts index 7fbb9661f..f36695234 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.test.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.test.ts @@ -239,7 +239,7 @@ test("optional union widening keeps refused and missing contracts current", () = unionId: lowerer.unions.recursiveRef(recursive, () => "Recursive"), }; expect(lowerer.withUndefinedArmOf(pending)).not.toBeNull(); - lowerer.unions.finalizeRecursive(recursive, [{ kind: "date" }, STRING]); + lowerer.unions.finalizeRecursive(recursive, [{ kind: "promise", inner: F64 }, STRING]); expect(lowerer.withUndefinedArmOf(pending)).toBeNull(); expect(lowerer.withUndefinedArmOf(pending)).toBeNull(); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 2fcfc063b..bdb7ce6c1 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2362,6 +2362,9 @@ export class Lowerer { * Date's time value instead of a Date object (date-values.ts). */ readonly timeValueDateDecisions = new Map(); readonly timeValueDateLocals = new Set(); + /** Declarations of those locals that a setter call writes + * (date-values.ts). */ + readonly timeValueDateWrites = new Set(); /** Record bindings whose flow class reaches storage outside the class: * they keep the declared layout, so an initializer from unchecked reads * converts once instead of the binding adopting the widened variant. */ diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index d133eeb43..a2ca2cd1c 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2329,6 +2329,13 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ ], note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", }, + { + id: "stdlib.date.setters", + kind: "stdlib", + name: "Date.prototype setters", + fns: ["date.setFields", "date.timeClip", "date.store"], + note: "setTime and the local and UTC field setters, with their optional arguments", + }, { id: "stdlib.date.UTC", kind: "stdlib", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 37d9769e0..aaa58c088 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4858,6 +4858,15 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * lib-call argument passes the time value itself, and any other use * creates the Date. */ "date.fromTime": { argTypes: [F64], result: DATE_T }, + /** Date.prototype's field setters over a time value + * (scr_date_set_fields): time value, setter code, argument count and four + * arguments; answers the new time value. */ + "date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** TimeClip of a number (setTime's argument). */ + "date.timeClip": { argTypes: [F64], result: F64 }, + /** Stores a time value into a Date object and answers it (a setter's + * write). */ + "date.store": { argTypes: [DATE_T, F64], result: F64 }, /** Date.prototype.toString / toDateString / toTimeString and String(date): * V8's local-time formats with the host zone's long name, "Invalid Date" * for an invalid date. Owned (+1) string; never throws. */ diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index d910374fb..b2511f8a8 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -855,9 +855,8 @@ export function funcOf(params: IrType[], ret: IrType): IrType { * promises nullable-only; declared maybe-promise unions are admitted by * maybePromiseUnionArmsOk. */ export function unionContainerArmsOk(arms: IrType[]): boolean { - return ( - arms.every((a, i) => a.kind !== "promise" || arms.every((b, j) => j === i || isUnitType(b))) && - dateUnionArmsOk(arms) + return arms.every( + (a, i) => a.kind !== "promise" || arms.every((b, j) => j === i || isUnitType(b)), ); } @@ -868,13 +867,7 @@ export function unionContainerArmsOk(arms: IrType[]): boolean { * non-thenable path). Data arms must not be thenable; the type mapper * checks that against the checker types before minting such a union. */ export function maybePromiseUnionArmsOk(arms: IrType[]): boolean { - return arms.filter((a) => a.kind === "promise").length <= 1 && dateUnionArmsOk(arms); -} - -/** A Date arm has a union home beside undefined and null arms only: no - * narrowing contract tells it from other data arms yet. */ -function dateUnionArmsOk(arms: IrType[]): boolean { - return arms.every((a, i) => a.kind !== "date" || arms.every((b, j) => j === i || isUnitType(b))); + return arms.filter((a) => a.kind === "promise").length <= 1; } /** Canonical, injective text form of an IrType — the building block of diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index a694e8d2c..b1c571db2 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -55,7 +55,6 @@ import { typeEquals, typeKey, maybePromiseUnionArmsOk, - unionContainerArmsOk, VOID, } from "./ir.js"; import { DYN_CLASS_PROPERTIES, callSiteReturnType, classValueConstructor } from "./ir.js"; @@ -814,9 +813,6 @@ export function validateModule(mod: IrModule): IrValidationError[] { loc: noLoc, }); } - if (arm.kind === "date" && !unionContainerArmsOk(u.arms)) { - errors.push({ message: `union ${u.id}: date arm ${i} beside non-unit arms`, loc: noLoc }); - } if (arm.kind === "record" && !recordsById.has(arm.shapeId)) { errors.push({ message: `union ${u.id}: arm ${i} references undeclared shape "${arm.shapeId}"`, @@ -6334,15 +6330,10 @@ function validateFunction( } } else { // throw: any exception-representable payload (the throw - // statement's rule). Date cannot retain its object kind in the - // untyped exception cell. + // statement's rule). if (e.arg === null) { err("genResume throw with no payload", e.loc); - } else if ( - e.arg.type.kind === "void" || - e.arg.type.kind === "caught" || - e.arg.type.kind === "date" - ) { + } else if (e.arg.type.kind === "void" || e.arg.type.kind === "caught") { err(`genResume throw of a ${e.arg.type.kind} value`, e.loc); } } @@ -6783,11 +6774,7 @@ function validateFunction( checkExpr(s.value); // dyn throws are allowed: the dyn node rides the REF cell arm by // reference (the JS-lane `throw err` of a dyn argument). - if ( - s.value.type.kind === "void" || - s.value.type.kind === "caught" || - s.value.type.kind === "date" - ) { + if (s.value.type.kind === "void" || s.value.type.kind === "caught") { err(`throw of a ${s.value.type.kind} value`, s.loc); } break; diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 75541b8e5..2a98957b2 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -4251,6 +4251,13 @@ "status": "static", "note": "one date string, using V8's date parser shared with new Date(dateString)" }, + { + "id": "stdlib.date.setters", + "kind": "stdlib", + "name": "Date.prototype setters", + "status": "static", + "note": "setTime and the local and UTC field setters, with their optional arguments" + }, { "id": "stdlib.date.toDateString", "kind": "stdlib", diff --git a/packages/compiler/test/ir.test.ts b/packages/compiler/test/ir.test.ts index 7f5e9f3c9..5d835ef8a 100644 --- a/packages/compiler/test/ir.test.ts +++ b/packages/compiler/test/ir.test.ts @@ -8,12 +8,10 @@ test("hand-built fib module validates", () => { expect(validateModule(fibModule)).toEqual([]); }); -test("validator rejects Date union arms beside other data arms", () => { - const bad = structuredClone(fibModule); - bad.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "string" }] }]; - expect(validateModule(bad).map((e) => e.message)).toContain( - "union u-date: date arm 0 beside non-unit arms", - ); +test("validator accepts Date union arms beside other arms", () => { + const mixed = structuredClone(fibModule); + mixed.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "string" }] }]; + expect(validateModule(mixed)).toEqual([]); const nullable = structuredClone(fibModule); nullable.unions = [{ id: "u-date", arms: [{ kind: "date" }, { kind: "undefinedT" }] }]; expect(validateModule(nullable)).toEqual([]); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8020-date-setters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8020-date-setters.ts.json new file mode 100644 index 000000000..5b23d8ecd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8020-date-setters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8020-date-setters.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8021-date-coercions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8021-date-coercions.ts.json new file mode 100644 index 000000000..176788025 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8021-date-coercions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8021-date-coercions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8022-date-unions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8022-date-unions.ts.json new file mode 100644 index 000000000..ab8c55b7b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8022-date-unions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8022-date-unions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8023-date-clone-throw.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8023-date-clone-throw.ts.json new file mode 100644 index 000000000..934d260d8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8023-date-clone-throw.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8023-date-clone-throw.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8024-date-setters-dynamic.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8024-date-setters-dynamic.mjs.json new file mode 100644 index 000000000..505f6305d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8024-date-setters-dynamic.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8024-date-setters-dynamic.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8025-date-subclass-setters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8025-date-subclass-setters.ts.json new file mode 100644 index 000000000..f1c4d20e0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8025-date-subclass-setters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8025-date-subclass-setters.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8026-date-subclass-setters-dynamic.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8026-date-subclass-setters-dynamic.mjs.json new file mode 100644 index 000000000..8a0fe9b5a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8026-date-subclass-setters-dynamic.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8026-date-subclass-setters-dynamic.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index fb3524c81..69c19e1b7 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -11660,6 +11660,56 @@ double scr_date_new_local(double y, double mo, double d, return scr_date_from_local(scr_date_v8_make_day(y, mo, d), scr_date_v8_make_time(h, mi, s, ms)); } +/* Date.prototype's setters over a time value, as V8's builtins-date.cc + * computes them: the fields of `t` (local fields for the local setters) + * with the first `argc` of them replaced, in the order the setter names + * them, by `a`..`d`, through MakeDay/MakeTime/MakeDate, then UTC(t) for + * the local setters and TimeClip. An invalid `t` stays invalid, except + * that setFullYear and setUTCFullYear start from +0: January 1 of the + * epoch year at midnight (local midnight for setFullYear). Returns the new + * time value; the caller stores it into the Date. Never throws. */ +double scr_date_set_fields(double t, double setter_code, double argc_count, + double a, double b, double c, double d) { + int32_t setter = (int32_t)setter_code, argc = (int32_t)argc_count; + bool utc = (setter & SCR_DATE_SET_UTC) != 0; + int32_t field = setter & ~SCR_DATE_SET_UTC; + ScrDateParts p; + if (isnan(t)) { + if (field != SCR_DATE_SET_FULL_YEAR) return NAN; + p = (ScrDateParts){1970, 0, 1, 4, 0, 0, 0, 0, 0}; + } else if (utc) { + if (!scr_date_utc_parts(t, &p)) return NAN; + } else if (!scr_date_local_parts(t, &p)) { + return NAN; + } + double y = (double)p.year, mo = p.month, dt = p.date; + double h = p.hours, mi = p.minutes, s = p.seconds, ms = p.milliseconds; + double args[4] = {a, b, c, d}; + double *slots[7] = {&y, &mo, &dt, &h, &mi, &s, &ms}; + /* The first field each setter names, in slots' order. */ + int first; + switch (field) { + case SCR_DATE_SET_MILLISECONDS: first = 6; break; + case SCR_DATE_SET_SECONDS: first = 5; break; + case SCR_DATE_SET_MINUTES: first = 4; break; + case SCR_DATE_SET_HOURS: first = 3; break; + case SCR_DATE_SET_DATE: first = 2; break; + case SCR_DATE_SET_MONTH: first = 1; break; + case SCR_DATE_SET_FULL_YEAR: first = 0; break; + default: return NAN; + } + /* Each setter takes at most the fields from its own down to the next + * larger unit's end: setHours up to milliseconds, setMonth up to the + * date, setFullYear up to the date. */ + int last = first <= 2 ? 2 : 6; + for (int i = 0; i < argc && first + i <= last && i < 4; i++) *slots[first + i] = args[i]; + double day = scr_date_v8_make_day(y, mo, dt); + double time = scr_date_v8_make_time(h, mi, s, ms); + if (!utc) return scr_date_from_local(day, time); + if (!isfinite(day) || !isfinite(time)) return NAN; + return scr_date_new_ms(day * 86400000.0 + time); +} + /* ── V8's date-string parser, the fallback ───────────────────────────── * scr_date_parse answers the bounded grammars directly and asks * this parser only when they produce NaN. It follows V8's DateParser @@ -13203,6 +13253,27 @@ ScrDate *scr_dyn_native_date_check(const ScrDyn *value, const ScrDynPath *path) static const char scr_date_to_date_string_name[] = "toDateString"; static const char scr_date_to_time_string_name[] = "toTimeString"; +/* The field setters a checked-dynamic Date answers (scr_date_set_fields). */ +static const struct { + const char *name; + int code, max; +} scr_native_date_setters[] = { + {"setMilliseconds", SCR_DATE_SET_MILLISECONDS, 1}, + {"setSeconds", SCR_DATE_SET_SECONDS, 2}, + {"setMinutes", SCR_DATE_SET_MINUTES, 3}, + {"setHours", SCR_DATE_SET_HOURS, 4}, + {"setDate", SCR_DATE_SET_DATE, 1}, + {"setMonth", SCR_DATE_SET_MONTH, 2}, + {"setFullYear", SCR_DATE_SET_FULL_YEAR, 3}, + {"setUTCMilliseconds", SCR_DATE_SET_UTC | SCR_DATE_SET_MILLISECONDS, 1}, + {"setUTCSeconds", SCR_DATE_SET_UTC | SCR_DATE_SET_SECONDS, 2}, + {"setUTCMinutes", SCR_DATE_SET_UTC | SCR_DATE_SET_MINUTES, 3}, + {"setUTCHours", SCR_DATE_SET_UTC | SCR_DATE_SET_HOURS, 4}, + {"setUTCDate", SCR_DATE_SET_UTC | SCR_DATE_SET_DATE, 1}, + {"setUTCMonth", SCR_DATE_SET_UTC | SCR_DATE_SET_MONTH, 2}, + {"setUTCFullYear", SCR_DATE_SET_UTC | SCR_DATE_SET_FULL_YEAR, 3}, +}; + static ScrDyn *scr_native_date_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { (void)self; (void)what; @@ -13232,6 +13303,25 @@ static ScrDyn *scr_native_date_invoke(void *ptr, ScrDyn *self, const char *metho ((ScrDate *)ptr)->ms = scr_date_new_ms(time); return scr_dyn_new_num(((ScrDate *)ptr)->ms); } + for (size_t i = 0; i < sizeof scr_native_date_setters / sizeof *scr_native_date_setters; i++) { + if (strcmp(method, scr_native_date_setters[i].name)) continue; + /* V8's order: ToNumber of the first argument, then the time value, + * then (unless the date is invalid) ToNumber of the optional ones the + * setter takes. */ + int code = scr_native_date_setters[i].code, max = scr_native_date_setters[i].max; + double values[4] = {NAN, 0, 0, 0}; + if (argc > 0 && !scr_dyn_number_coerce_js(args[0], &values[0])) return NULL; + double t = ((ScrDate *)ptr)->ms; + int used = argc < (size_t)max ? (int)argc : max; + if (used < 1) used = 1; + if (!isnan(t) || (code & ~SCR_DATE_SET_UTC) == SCR_DATE_SET_FULL_YEAR) { + for (int k = 1; k < used; k++) + if (!scr_dyn_number_coerce_js(args[k], &values[k])) return NULL; + } + double next = scr_date_set_fields(t, code, used, values[0], values[1], values[2], values[3]); + ((ScrDate *)ptr)->ms = next; + return scr_dyn_new_num(next); + } #define DATE_GET(name, fn) if (!strcmp(method, name)) return scr_dyn_new_num(fn(ms)) DATE_GET("getFullYear", scr_date_get_full_year_local); DATE_GET("getUTCFullYear", scr_date_get_full_year_utc); @@ -13311,7 +13401,9 @@ static const char *const scr_native_date_methods[] = { "getFullYear", "getUTCFullYear", "getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds", "getUTCSeconds", "getMilliseconds", - "getUTCMilliseconds", "getTimezoneOffset", "setTime", + "getUTCMilliseconds", "getTimezoneOffset", "setTime", "setMilliseconds", "setSeconds", + "setMinutes", "setHours", "setDate", "setMonth", "setFullYear", "setUTCMilliseconds", + "setUTCSeconds", "setUTCMinutes", "setUTCHours", "setUTCDate", "setUTCMonth", "setUTCFullYear", }; #define SCR_NATIVE_DATE_METHODS (sizeof scr_native_date_methods / sizeof scr_native_date_methods[0]) static SCR_TL ScrDyn *scr_native_date_method_values[SCR_NATIVE_DATE_METHODS]; @@ -13397,12 +13489,40 @@ static ScrDyn *scr_native_date_get(void *ptr, const char *key, size_t key_len) { return NULL; } +/* structuredClone of a Date: a new Date with the same time value. */ +static ScrDyn *scr_native_date_clone(void *h) { + return scr_dyn_native_date_from_ms(((ScrDate *)h)->ms); +} + +/* A Date crossing threads (postMessage, workerData) carries its time value + * in a plain allocation any thread may read and free. */ +static void *scr_native_date_share(void *h) { + double *ms = malloc(sizeof *ms); + if (!ms) scr_trap("scriptc: out of memory\n"); + *ms = ((ScrDate *)h)->ms; + return ms; +} + +static ScrDyn *scr_native_date_adopt(void *payload) { + return scr_dyn_native_date_from_ms(*(double *)payload); +} + +static void scr_native_date_drop(void *payload) { free(payload); } + +static const ScrDynHandleTransfer scr_native_date_transfer = { + .share = scr_native_date_share, .adopt = scr_native_date_adopt, .drop = scr_native_date_drop}; + /* A checked-dynamic handle for the Date object `date` (borrowed; the * handle retains it), so the value stays the same object. */ ScrDyn *scr_dyn_native_date(ScrDate *date) { static const ScrDynHandleOps ops = { - "Date", &scr_date_retain_v, &scr_date_release_v, &scr_native_date_invoke, - &scr_native_date_get, NULL, NULL, NULL, + .cls = "Date", + .retain = &scr_date_retain_v, + .release = &scr_date_release_v, + .invoke = &scr_native_date_invoke, + .get = &scr_native_date_get, + .clone = &scr_native_date_clone, + .transfer = &scr_native_date_transfer, }; scr_dyn_handle_install(SCR_DYNH_DATE, &ops); return scr_dyn_new_handle(date, SCR_DYNH_DATE); diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index a71b0ccfd..39564c061 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -111,6 +111,23 @@ double scr_date_now(void); /* integer ms since epoch, like Node */ extern SCR_TL const double *scr_date_mock_now; double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */ double scr_date_get_time(double ms); +/* Date.prototype's setters (setMilliseconds ... setFullYear and their UTC + * forms) over a time value: the new time value for `argc` arguments + * `a`..`d` (scr_lib.c). `setter` is an SCR_DATE_SET_* code; it and `argc` + * are whole numbers passed as doubles, as the compiler's lib calls pass + * numbers. */ +enum { + SCR_DATE_SET_MILLISECONDS = 0, + SCR_DATE_SET_SECONDS = 1, + SCR_DATE_SET_MINUTES = 2, + SCR_DATE_SET_HOURS = 3, + SCR_DATE_SET_DATE = 4, + SCR_DATE_SET_MONTH = 5, + SCR_DATE_SET_FULL_YEAR = 6, + SCR_DATE_SET_UTC = 8, +}; +double scr_date_set_fields(double t, double setter, double argc, + double a, double b, double c, double d); /* A Date object: the TimeClip'd time value in milliseconds (NaN for an * invalid date). Dates are immutable; their identity is the pointer. * Retain/release follow the runtime's RC conventions (SIZE_MAX immortal). */ diff --git a/tests/corpus/8020-date-setters.ts b/tests/corpus/8020-date-setters.ts new file mode 100644 index 000000000..6bfeff5c8 --- /dev/null +++ b/tests/corpus/8020-date-setters.ts @@ -0,0 +1,115 @@ +// Date setters: every local and UTC setter with each argument count, the +// time values they return, normalization of out-of-range fields, local +// setters across a daylight saving transition, setters on invalid dates, +// explicit undefined arguments, fractional arguments, and the TimeClip +// range. A setter changes the one Date object every alias holds. +export {}; +process.env.TZ = "America/Chicago"; + +const show = (label: string, d: Date): void => { + console.log(label, d.getTime(), Number.isNaN(d.getTime()) ? "invalid" : d.toISOString()); +}; + +const d = new Date(Date.UTC(2026, 0, 31, 10, 20, 30, 400)); +console.log(d.setMilliseconds(999), d.getMilliseconds()); +console.log(d.setSeconds(5), d.setSeconds(6, 7)); +console.log(d.setMinutes(1), d.setMinutes(2, 3), d.setMinutes(4, 5, 6)); +console.log(d.setHours(7), d.setHours(8, 9), d.setHours(10, 11, 12), d.setHours(13, 14, 15, 16)); +console.log(d.setDate(15), d.setMonth(1), d.setMonth(2, 3)); +console.log(d.setFullYear(2025), d.setFullYear(2024, 1), d.setFullYear(2023, 4, 6)); +show("local", d); + +const u = new Date(Date.UTC(2026, 0, 31)); +console.log(u.setUTCMilliseconds(1), u.setUTCSeconds(2, 3), u.setUTCMinutes(4, 5, 6)); +console.log(u.setUTCHours(7, 8, 9, 10), u.setUTCDate(1), u.setUTCMonth(5, 30)); +console.log(u.setUTCFullYear(1999), u.setUTCFullYear(2000, 1, 29)); +show("utc", u); + +// Fields out of range carry into the next larger field, both ways. +const n = new Date(Date.UTC(2026, 0, 31, 12)); +n.setUTCMonth(1); +show("Jan 31 + month", n); +n.setUTCDate(0); +show("day 0", n); +n.setUTCMonth(13); +show("month 13", n); +n.setUTCHours(-1); +show("hour -1", n); +n.setUTCMinutes(60 * 24 + 1); +show("minutes past a day", n); +n.setUTCSeconds(-0.5); +show("fractional seconds", n); +n.setUTCMilliseconds(1.9); +show("fractional ms", n); + +// Local setters in a zone with daylight saving time: 2026-03-08 02:30 +// does not exist in Chicago and 2026-11-01 01:30 happens twice. +const spring = new Date(2026, 2, 8, 0, 0); +spring.setHours(2, 30); +show("spring gap", spring); +console.log(spring.getHours(), spring.getMinutes(), spring.getTimezoneOffset()); +const fall = new Date(2026, 10, 1, 0, 0); +fall.setHours(1, 30); +show("fall overlap", fall); +fall.setMinutes(fall.getMinutes() + 60); +show("fall overlap + 60 min", fall); +const walk = new Date(2026, 2, 7, 12); +for (let i = 0; i < 3; i++) { + walk.setDate(walk.getDate() + 1); + console.log("walk", walk.getDate(), walk.getHours(), walk.getTimezoneOffset()); +} + +// Invalid dates: setFullYear starts from +0 local time, the others keep NaN. +const bad = new Date(NaN); +console.log(bad.setHours(1), bad.setDate(2), bad.setUTCMonth(3), bad.setMilliseconds(4)); +console.log(bad.setFullYear(2020)); +show("after setFullYear", bad); +const badUtc = new Date("not a date"); +console.log(badUtc.setUTCFullYear(2021, 6)); +show("after setUTCFullYear", badUtc); + +// Explicit undefined and NaN arguments make the date invalid; a missing +// optional argument keeps the current field. +const opt = new Date(Date.UTC(2026, 5, 15, 9)); +console.log(opt.setUTCHours(10, undefined), Number.isNaN(opt.getTime())); +const opt2 = new Date(Date.UTC(2026, 5, 15, 9)); +console.log(opt2.setUTCMonth(Number.NaN), opt2.setUTCMonth(2)); + +// setTime and the TimeClip range. +const t = new Date(0); +console.log(t.setTime(8.64e15), t.setTime(8.64e15 + 1), t.setTime(-8.64e15), t.setTime(1.5)); +console.log(t.setTime(-0), Object.is(t.getTime(), 0), t.setTime(Number.NaN)); +const far = new Date(Date.UTC(275760, 8, 13)); +console.log(far.getTime(), far.setUTCMilliseconds(1)); + +// Every alias sees the change: a second binding, an array slot, a Map +// value, a record field, a closure and a parameter. +const shared = new Date(Date.UTC(2026, 6, 4)); +const alias = shared; +const list = [shared, new Date(Date.UTC(2000, 0, 1))]; +const byName = new Map([["shared", shared]]); +const holder = { when: shared }; +const bump = (): number => shared.setUTCDate(shared.getUTCDate() + 1); +const nextYear = (date: Date): void => { + date.setUTCFullYear(date.getUTCFullYear() + 1); +}; +alias.setUTCHours(6); +list[0]!.setUTCMinutes(30); +byName.get("shared")!.setUTCSeconds(15); +holder.when.setUTCMilliseconds(250); +bump(); +nextYear(shared); +show("shared", shared); +console.log(alias === shared, list[0] === shared, byName.get("shared") === holder.when); +console.log(list.map((x) => x.toISOString()).join(" ")); + +// A Date a loop adjusts in place. +const cursor = new Date(Date.UTC(2026, 0, 1)); +let weekdays = 0; +while (cursor.getUTCFullYear() === 2026) { + const day = cursor.getUTCDay(); + if (day !== 0 && day !== 6) weekdays++; + cursor.setUTCDate(cursor.getUTCDate() + 1); +} +console.log("weekdays in 2026", weekdays); +show("cursor", cursor); diff --git a/tests/corpus/8021-date-coercions.ts b/tests/corpus/8021-date-coercions.ts new file mode 100644 index 000000000..2410853a4 --- /dev/null +++ b/tests/corpus/8021-date-coercions.ts @@ -0,0 +1,47 @@ +// Dates converted to primitives as Node converts them: unary +, Number() +// and relational operators read the time value (the number hint), while + +// with a string, template literals and String() read the text (a Date's +// default hint is a string). Symbol.toPrimitive, valueOf and toString answer +// the same values; == compares two Dates by identity and a Date with a +// primitive through its default primitive. typeof, truthiness and +// instanceof treat a Date as an object, invalid ones included. +export {}; +process.env.TZ = "UTC"; + +const a = new Date(Date.UTC(2026, 9, 23, 8, 30)); +const b = new Date(Date.UTC(2026, 9, 24, 8, 30)); +const bad = new Date(Number.NaN); + +console.log(+a, -a, +b - +a, (+b - +a) / 3600000, Number(a), Math.max(+a, +b) === b.getTime()); +console.log(a + "!"); +console.log(`at ${a}`); +console.log(String(b)); +console.log("" + a === a.toString(), a + "1" === a.toString() + "1"); +console.log(a[Symbol.toPrimitive]("number"), a[Symbol.toPrimitive]("string") === a.toString()); +console.log(a[Symbol.toPrimitive]("default") === a.toString(), a.valueOf() === a.getTime()); +console.log(a < b, a <= b, a > b, a >= b, +b > a.getTime() + 1); + +const same = a; +const twin = new Date(a.getTime()); +console.log(a == same, a == twin, a === twin, a != twin, a.getTime() === twin.getTime()); +console.log(a == (a.toString() as unknown), a == (a.getTime() as unknown)); + +console.log(typeof a, typeof bad, a instanceof Date, bad instanceof Date); +console.log(Object.prototype.toString.call(a), !a, !!bad, bad ? "truthy" : "falsy"); + +// Invalid dates. +console.log(+bad, +bad - +a, `${bad}`, String(bad), bad + "", Number.isNaN(bad.getTime())); +console.log(bad.toJSON(), JSON.stringify({ when: bad, at: a })); +try { + bad.toISOString(); +} catch (error) { + console.log((error as Error).name, (error as Error).message); +} + +// Sorting and summing through the number hint. +const days = [b, a, new Date(Date.UTC(2026, 0, 1))]; +days.sort((x, y) => +x - +y); +console.log(days.map((x) => x.getUTCDate()).join(",")); +let span = 0; +for (let i = 1; i < days.length; i++) span += +days[i]! - +days[i - 1]!; +console.log(span / 86400000); diff --git a/tests/corpus/8022-date-unions.ts b/tests/corpus/8022-date-unions.ts new file mode 100644 index 000000000..dedcd8bf1 --- /dev/null +++ b/tests/corpus/8022-date-unions.ts @@ -0,0 +1,68 @@ +// Dates in unions with other arms: Date | string, Date | number and +// Date | a record, narrowed with instanceof and typeof, in arrays, Map +// values, optional fields and function results. A Date arm keeps its +// identity, so a setter through a union-typed alias changes the original. +export {}; +process.env.TZ = "UTC"; + +type When = Date | string | number; + +function describe(when: When): string { + if (when instanceof Date) return `date ${when.toISOString()}`; + if (typeof when === "string") return `text ${when}`; + return `ms ${when}`; +} + +function normalize(when: When): Date { + return when instanceof Date ? when : new Date(when); +} + +const fixed = new Date(Date.UTC(2026, 9, 23)); +const values: When[] = [fixed, "2026-01-02T03:04:05Z", 0, new Date(Number.NaN)]; +for (const value of values) { + const text = value instanceof Date && Number.isNaN(value.getTime()) ? "invalid" : describe(value); + console.log(text); +} +console.log(normalize(fixed) === fixed, normalize("2000-01-01").getUTCFullYear(), normalize(1e12).getUTCFullYear()); + +const held: When = fixed; +if (held instanceof Date) held.setUTCHours(12); +console.log(fixed.getUTCHours(), held === fixed); + +type Stamp = Date | { label: string; at: number }; +const stamps: Stamp[] = [fixed, { label: "start", at: 5 }]; +for (const stamp of stamps) { + console.log(stamp instanceof Date ? stamp.getUTCDate() : `${stamp.label}@${stamp.at}`); +} + +const byKey = new Map(); +byKey.set("a", fixed); +byKey.set("b", "later"); +for (const [key, value] of byKey) console.log(key, value instanceof Date ? value.getTime() : value); + +interface Task { + name: string; + due?: Date; + done: Date | null; +} +const tasks: Task[] = [ + { name: "write", due: fixed, done: null }, + { name: "ship", done: new Date(Date.UTC(2026, 11, 1)) }, +]; +for (const task of tasks) { + const due = task.due ? task.due.toISOString().slice(0, 10) : "none"; + const done = task.done === null ? "open" : task.done.toISOString().slice(0, 10); + console.log(task.name, due, done); +} +tasks[0]!.due!.setUTCDate(30); +console.log(fixed.getUTCDate(), tasks[0]!.due === fixed); + +function latest(dates: (Date | undefined)[]): Date | string { + let best: Date | undefined; + for (const date of dates) if (date && (best === undefined || date > best)) best = date; + return best ?? "none"; +} +const found = latest([undefined, fixed, new Date(0)]); +console.log(found === fixed, latest([])); + +console.log(JSON.stringify({ values, stamps })); diff --git a/tests/corpus/8023-date-clone-throw.ts b/tests/corpus/8023-date-clone-throw.ts new file mode 100644 index 000000000..b9d569d00 --- /dev/null +++ b/tests/corpus/8023-date-clone-throw.ts @@ -0,0 +1,32 @@ +// Dates through structuredClone and MessageChannel, which copy them as +// new Dates with the same time value, and Dates thrown and rejected as +// values, which keep their identity. +import { MessageChannel } from "node:worker_threads"; +process.env.TZ = "UTC"; + +const at = new Date(Date.UTC(2026, 9, 23, 7)); +const copy = structuredClone(at); +console.log(copy instanceof Date, copy === at, copy.getTime() === at.getTime()); +copy.setUTCFullYear(2000); +console.log(at.getUTCFullYear(), copy.getUTCFullYear()); + +const nested = structuredClone({ at, list: [at, at], invalid: new Date(Number.NaN) }); +console.log(nested.list[0] instanceof Date, nested.at === at, nested.list.length); +console.log(nested.at.toISOString(), Number.isNaN(nested.invalid.getTime())); + +try { + throw at; +} catch (error) { + console.log(error instanceof Date, error === at); +} + +const { port1, port2 } = new MessageChannel(); +port2.on("message", (data: { when: Date; again: Date }) => { + const { when, again } = data; + console.log("message", when instanceof Date, when === at, again instanceof Date, when.toISOString()); + port2.close(); + Promise.reject(at).catch((reason: unknown) => { + console.log("rejected", reason instanceof Date, reason === at); + }); +}); +port1.postMessage({ when: at, again: at }); diff --git a/tests/corpus/8024-date-setters-dynamic.mjs b/tests/corpus/8024-date-setters-dynamic.mjs new file mode 100644 index 000000000..9b6392b60 --- /dev/null +++ b/tests/corpus/8024-date-setters-dynamic.mjs @@ -0,0 +1,24 @@ +// Date setters in JavaScript, on Dates held as untyped values: each setter +// converts its arguments with ToNumber (valueOf runs, undefined is NaN) and +// changes the one Date object every alias holds. +process.env.TZ = "Europe/Berlin"; + +const d = new Date(Date.UTC(2026, 2, 28, 12)); +const alias = d; +const box = { d }; +console.log(d.setHours(1, 30), d.getHours(), d.getTimezoneOffset()); +console.log(alias.setDate(29), box.d.getHours(), d.getTimezoneOffset()); +console.log(d.setMinutes("15"), d.setSeconds(true), d.setMilliseconds(null)); +console.log(d.setUTCFullYear(2030, 11, 31), d.toISOString()); +console.log(d.setMonth(undefined), Number.isNaN(d.getTime())); +console.log(d.setFullYear(2001), d.toISOString()); + +let calls = 0; +const later = { valueOf() { calls++; return 45; } }; +console.log(d.setUTCMinutes(later, later), calls, d.toISOString()); + +const other = new Date(0); +console.log(other.setHours(5), other.getHours(), typeof d.setMonth); +const list = [new Date(0), new Date(86400000)]; +list.forEach((x, i) => x.setUTCHours(i + 1)); +console.log(list.map((x) => x.toISOString()).join(" ")); diff --git a/tests/corpus/8025-date-subclass-setters.ts b/tests/corpus/8025-date-subclass-setters.ts new file mode 100644 index 000000000..a6414a047 --- /dev/null +++ b/tests/corpus/8025-date-subclass-setters.ts @@ -0,0 +1,30 @@ +// Setters on instances of a class declared `extends Date`: each changes +// the instance's time value, which the inherited getters, the subclass's +// own methods and toISOString then read; invalid instances follow the same +// rules as Dates. +export {}; +process.env.TZ = "Asia/Kolkata"; + +class Deadline extends Date { + label: string; + constructor(label: string, ms: number) { + super(ms); + this.label = label; + } + extend(days: number): this { + this.setDate(this.getDate() + days); + return this; + } + describe(): string { + return `${this.label} ${this.toISOString()}`; + } +} + +const due = new Deadline("report", Date.UTC(2026, 9, 23, 18, 30)); +console.log(due.describe()); +console.log(due.setHours(9, 15), due.getHours(), due.getMinutes()); +console.log(due.extend(10).describe()); +console.log(due.setUTCMonth(0, 31), due.setMonth(1), due.getDate(), due.getMonth()); +console.log(due.setFullYear(2024, 1, 29), due.describe()); +console.log(due.setTime(Number.NaN), Number.isNaN(due.getTime()), due.setUTCFullYear(2030)); +console.log(due.describe(), due instanceof Date, due instanceof Deadline); diff --git a/tests/corpus/8026-date-subclass-setters-dynamic.mjs b/tests/corpus/8026-date-subclass-setters-dynamic.mjs new file mode 100644 index 000000000..8b89d6ec8 --- /dev/null +++ b/tests/corpus/8026-date-subclass-setters-dynamic.mjs @@ -0,0 +1,17 @@ +// Setters on instances of a JavaScript class declared `extends Date`, held +// as untyped values: the inherited setters change the instance's time value. +process.env.TZ = "America/Sao_Paulo"; + +class Shift extends Date { + start() { + this.setHours(8, 0, 0, 0); + return this; + } +} + +const shift = new Shift(Date.UTC(2026, 4, 5, 15)); +console.log(shift.start().toISOString(), shift.getHours()); +console.log(shift.setDate(31), shift.getMonth(), shift.setUTCHours(0), shift.toISOString()); +const values = [shift, new Shift(0)]; +for (const value of values) value.setUTCMinutes(45); +console.log(values.map((value) => value.toISOString()).join(" "), values[0] === shift); diff --git a/tests/diagnostics/stdlib-fence.ts b/tests/diagnostics/stdlib-fence.ts index d28333178..0952436e1 100644 --- a/tests/diagnostics/stdlib-fence.ts +++ b/tests/diagnostics/stdlib-fence.ts @@ -21,8 +21,6 @@ const fmt = new Intl.NumberFormat(); // Constructors. const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced -const mutableDate = new Date(0); -mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice const wr = new WeakRef({}); const px = new Proxy({ a: 1 }, {}); const buf = new ArrayBuffer(8); @@ -73,14 +71,6 @@ function pick(a: string | number, b: number | boolean): boolean { } const picked = pick(1, 2); -function throwDate(): void { - throw new Date(0); // the exception cell cannot preserve Date's object kind -} -throwDate(); -function* dateGenerator(): Generator { - yield 1; -} -dateGenerator().throw(new Date(0)); // Related classes may store one object under different union tags. A // structural arm beside them leaves no identity-preserving comparison. class FenceBase { diff --git a/tests/harness/__snapshots__/assert-class-fences.ts.txt b/tests/harness/__snapshots__/assert-class-fences.ts.txt index 29d2e4668..f70683e22 100644 --- a/tests/harness/__snapshots__/assert-class-fences.ts.txt +++ b/tests/harness/__snapshots__/assert-class-fences.ts.txt @@ -16,7 +16,7 @@ assert-class-fences.ts:12:16 - error SC2020: 'assert.Assert instances used as va hint: bind `new Assert(...)` with literal options to a const and only call methods on it (`const a = new Assert({ diff: 'full' }); a.strictEqual(x, y)`) -assert-class-fences.ts:13:22 - error SC2001: values of type 'object | Assert' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +assert-class-fences.ts:13:22 - error SC2001: values of type 'object | Assert' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 12 | const passed = new Assert(); 13 | function useInstance(a: Assert | object): void { diff --git a/tests/harness/__snapshots__/class-interface-refinements.ts.txt b/tests/harness/__snapshots__/class-interface-refinements.ts.txt index 9d7d7b87f..89f3ead6d 100644 --- a/tests/harness/__snapshots__/class-interface-refinements.ts.txt +++ b/tests/harness/__snapshots__/class-interface-refinements.ts.txt @@ -7,7 +7,7 @@ class-interface-refinements.ts:8:26 - error SC2002: record shapes must match exa 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 -class-interface-refinements.ts:12:16 - error SC2001: values of type 'AddedStorage' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +class-interface-refinements.ts:12:16 - error SC2001: values of type 'AddedStorage' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 11 | interface AddedStorage extends Box { extra: string; } 12 | function added(view: AddedStorage): string { return view.extra; } @@ -23,7 +23,7 @@ class-interface-refinements.ts:13:13 - error SC2004: uses of 'added' inherit the hint: the declaration of 'added' did not compile — fix the diagnostic reported there and these sites clear with it -class-interface-refinements.ts:16:18 - error SC2001: values of type 'ChangedMethod' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +class-interface-refinements.ts:16:18 - error SC2001: values of type 'ChangedMethod' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 15 | interface ChangedMethod extends Box { read(): number; } 16 | function changed(view: ChangedMethod): number { return view.read(); } diff --git a/tests/harness/__snapshots__/date-formatters.ts.txt b/tests/harness/__snapshots__/date-formatters.ts.txt index 75e15760b..d6dd27c5a 100644 --- a/tests/harness/__snapshots__/date-formatters.ts.txt +++ b/tests/harness/__snapshots__/date-formatters.ts.txt @@ -5,7 +5,7 @@ date-formatters.ts:4:1 - error SC2020: 'Date.prototype.toLocaleTimeString' is pa | ^~~~~~~~~~~~~~~~~~~~~~~~~ 5 | date.toLocaleString(); - hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering + hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part of the standard library types but has no scriptc lowering yet @@ -14,4 +14,4 @@ date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part o | ^~~~~~~~~~~~~~~~~~~~~ 6 | - hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering \ No newline at end of file + hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering \ No newline at end of file diff --git a/tests/harness/__snapshots__/dynamic-representable-types.ts.txt b/tests/harness/__snapshots__/dynamic-representable-types.ts.txt index 21402b597..6241894f1 100644 --- a/tests/harness/__snapshots__/dynamic-representable-types.ts.txt +++ b/tests/harness/__snapshots__/dynamic-representable-types.ts.txt @@ -1,4 +1,4 @@ -dynamic-representable-types.ts:7:7 - error SC2001: values of type '[number, (string | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +dynamic-representable-types.ts:7:7 - error SC2001: values of type '[number, (string | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 6 | const bare: any = 41; 7 | const optTuple: [number, string?] = [1]; diff --git a/tests/harness/__snapshots__/generics.ts.txt b/tests/harness/__snapshots__/generics.ts.txt index 4dfc8019f..86dd94bb9 100644 --- a/tests/harness/__snapshots__/generics.ts.txt +++ b/tests/harness/__snapshots__/generics.ts.txt @@ -12,7 +12,7 @@ generics.ts:30:10 - error SC1090: '.join()' on arrays of this element type (numb | ^~~~~~~~~~~ 31 | } -generics.ts:44:7 - error SC2001: values of type 'typeof Box' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +generics.ts:44:7 - error SC2001: values of type 'typeof Box' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 43 | // The uninstantiated family as a value: no thunk, no single ctor ABI. 44 | const BoxAlias = Box; diff --git a/tests/harness/__snapshots__/maps-surface.ts.txt b/tests/harness/__snapshots__/maps-surface.ts.txt index c1db0576d..ed4ebb79f 100644 --- a/tests/harness/__snapshots__/maps-surface.ts.txt +++ b/tests/harness/__snapshots__/maps-surface.ts.txt @@ -1,4 +1,4 @@ -maps-surface.ts:21:11 - error SC2001: values of type '(v: number, k: string, theMap: Map) => void' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +maps-surface.ts:21:11 - error SC2001: values of type '(v: number, k: string, theMap: Map) => void' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 20 | // The forEach callback receives (value, key) — no third map parameter. 21 | m.forEach((v, k, theMap) => console.log(k, v)); diff --git a/tests/harness/__snapshots__/regex-match-destructuring-fences.ts.txt b/tests/harness/__snapshots__/regex-match-destructuring-fences.ts.txt index d4eb59168..43d4eff77 100644 --- a/tests/harness/__snapshots__/regex-match-destructuring-fences.ts.txt +++ b/tests/harness/__snapshots__/regex-match-destructuring-fences.ts.txt @@ -5,7 +5,7 @@ regex-match-destructuring-fences.ts:6:16 - error SC1031: rest bindings over rege | ^~~~~~~ 7 | const { input = "none" } = m; -regex-match-destructuring-fences.ts:6:19 - error SC2001: values of type '{ [n: number]: string; input: string; 0: string; length: number; toString(): string; toLocaleString(): string; toLocaleString(locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; ... 39 more ...; with(index: number, value: string): string[]; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +regex-match-destructuring-fences.ts:6:19 - error SC2001: values of type '{ [n: number]: string; input: string; 0: string; length: number; toString(): string; toLocaleString(): string; toLocaleString(locales: string | string[], options?: (NumberFormatOptions & DateTimeFormatOptions) | undefined): string; ... 39 more ...; with(index: number, value: string): string[]; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 5 | const m = /(a)(b)?/.exec("ab")!; 6 | const { index, ...rest } = m; diff --git a/tests/harness/__snapshots__/retired-engine-2103-optional-tuple-elements.ts.txt b/tests/harness/__snapshots__/retired-engine-2103-optional-tuple-elements.ts.txt index 4d3dd52c3..dfdb00720 100644 --- a/tests/harness/__snapshots__/retired-engine-2103-optional-tuple-elements.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2103-optional-tuple-elements.ts.txt @@ -43,7 +43,7 @@ retired-engine-2103-optional-tuple-elements.ts:19:1 - error SC2004: uses of 'f2' hint: the declaration of 'f2' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2103-optional-tuple-elements.ts:20:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +retired-engine-2103-optional-tuple-elements.ts:20:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 19 | f2([]); 20 | const t: [string?, number?] = ["x"]; @@ -59,7 +59,7 @@ retired-engine-2103-optional-tuple-elements.ts:21:13 - error SC2004: uses of 't' hint: the declaration of 't' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2103-optional-tuple-elements.ts:22:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +retired-engine-2103-optional-tuple-elements.ts:22:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 21 | console.log(t.length, `${t[0]} ${t[1]}`); 22 | const full: [string?, number?] = ["y", 7]; @@ -75,14 +75,14 @@ retired-engine-2103-optional-tuple-elements.ts:23:13 - error SC2004: uses of 'fu hint: the declaration of 'full' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2103-optional-tuple-elements.ts:24:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +retired-engine-2103-optional-tuple-elements.ts:24:7 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 23 | console.log(full.length, `${full[0]} ${full[1]}`); 24 | const none: [string?, number?] = []; | ^~~~ 25 | console.log(none.length); -retired-engine-2103-optional-tuple-elements.ts:24:34 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +retired-engine-2103-optional-tuple-elements.ts:24:34 - error SC2001: values of type '[(string | undefined)?, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 23 | console.log(full.length, `${full[0]} ${full[1]}`); 24 | const none: [string?, number?] = []; @@ -98,7 +98,7 @@ retired-engine-2103-optional-tuple-elements.ts:25:13 - error SC2004: uses of 'no hint: the declaration of 'none' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2103-optional-tuple-elements.ts:27:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +retired-engine-2103-optional-tuple-elements.ts:27:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 26 | // A rest-element tuple rides the same island representation. 27 | const r: [string, ...number[]] = ["head", 1, 2, 3]; diff --git a/tests/harness/__snapshots__/stdlib-fence.ts.txt b/tests/harness/__snapshots__/stdlib-fence.ts.txt index c183f7cdc..313a9391b 100644 --- a/tests/harness/__snapshots__/stdlib-fence.ts.txt +++ b/tests/harness/__snapshots__/stdlib-fence.ts.txt @@ -55,88 +55,65 @@ stdlib-fence.ts:23:11 - error SC2020: 'new Date with spread arguments' is part o 22 | // Constructors. 23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | const mutableDate = new Date(0); + 24 | const wr = new WeakRef({}); hint: new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported -stdlib-fence.ts:25:1 - error SC2020: 'Date.prototype.setUTCFullYear with arguments' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:24:7 - error SC2020: 'WeakRef<{}>' is part of the standard library types but has no scriptc lowering yet - 24 | const mutableDate = new Date(0); - 25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 26 | const wr = new WeakRef({}); - - hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering - -stdlib-fence.ts:26:7 - error SC2020: 'WeakRef<{}>' is part of the standard library types but has no scriptc lowering yet - - 25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice - 26 | const wr = new WeakRef({}); + 23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced + 24 | const wr = new WeakRef({}); | ^~ - 27 | const px = new Proxy({ a: 1 }, {}); + 25 | const px = new Proxy({ a: 1 }, {}); hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) -stdlib-fence.ts:26:12 - error SC2020: 'new WeakRef' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:24:12 - error SC2020: 'new WeakRef' is part of the standard library types but has no scriptc lowering yet - 25 | mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice - 26 | const wr = new WeakRef({}); + 23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced + 24 | const wr = new WeakRef({}); | ^~~~~~~~~~~~~~~ - 27 | const px = new Proxy({ a: 1 }, {}); + 25 | const px = new Proxy({ a: 1 }, {}); hint: deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead -stdlib-fence.ts:41:31 - error SC1090: '.split()' on a 'RegExp | string' separator (pass a string, or a regex literal) is not supported yet +stdlib-fence.ts:39:31 - error SC1090: '.split()' on a 'RegExp | string' separator (pass a string, or a regex literal) is not supported yet - 40 | const norm = "abc".normalize(); - 41 | const limited = "a,b,c".split(Math.random() ? "," : /,/, 2); // union separators stay fenced; limits lower + 38 | const norm = "abc".normalize(); + 39 | const limited = "a,b,c".split(Math.random() ? "," : /,/, 2); // union separators stay fenced; limits lower | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 42 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer) + 40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer) -stdlib-fence.ts:43:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:41:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet - 42 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer) - 43 | const localized = (1234.5).toLocaleString(); + 40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer) + 41 | const localized = (1234.5).toLocaleString(); | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 44 | // Unlowered call FORMS of lowered members. (push is fully variadic now — + 42 | // Unlowered call FORMS of lowered members. (push is fully variadic now — hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US") -stdlib-fence.ts:49:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:47:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet - 48 | const joined = nums.join(); // the omitted separator lowers - 49 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced + 46 | const joined = nums.join(); // the omitted separator lowers + 47 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 50 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp) + 48 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp) hint: the thisArg parameter has no lowering — use an arrow function -stdlib-fence.ts:77:9 - error SC1090: throwing Date values (catch bindings cannot preserve Date's object kind; throw an Error or primitive instead) are not supported yet +stdlib-fence.ts:84:10 - error SC1090: comparisons of related class values beside structural union members (narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms) are not supported yet - 76 | function throwDate(): void { - 77 | throw new Date(0); // the exception cell cannot preserve Date's object kind - | ^~~~~~~~~~~ - 78 | } - -stdlib-fence.ts:83:23 - error SC1090: .throw() with a Date value (the exception channel cannot preserve Date's object kind; throw an Error or primitive instead) is not supported yet - - 82 | } - 83 | dateGenerator().throw(new Date(0)); - | ^~~~~~~~~~~ - 84 | // Related classes may store one object under different union tags. A - -stdlib-fence.ts:94:10 - error SC1090: comparisons of related class values beside structural union members (narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms) are not supported yet - - 93 | function relatedStrict(a: FenceValue, b: FenceValue): boolean { - 94 | return a === b; + 83 | function relatedStrict(a: FenceValue, b: FenceValue): boolean { + 84 | return a === b; | ^~~~~~~ - 95 | } + 85 | } -stdlib-fence.ts:97:10 - error SC2020: 'Object.is over related class values beside structural union members' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:87:10 - error SC2020: 'Object.is over related class values beside structural union members' is part of the standard library types but has no scriptc lowering yet - 96 | function relatedSameValue(a: FenceValue, b: FenceSub): boolean { - 97 | return Object.is(a, b); + 86 | function relatedSameValue(a: FenceValue, b: FenceSub): boolean { + 87 | return Object.is(a, b); | ^~~~~~~~~~~~~~~ - 98 | } + 88 | } hint: narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms \ No newline at end of file diff --git a/tests/harness/__snapshots__/tuples.ts.txt b/tests/harness/__snapshots__/tuples.ts.txt index a6f58710e..440904095 100644 --- a/tests/harness/__snapshots__/tuples.ts.txt +++ b/tests/harness/__snapshots__/tuples.ts.txt @@ -12,14 +12,14 @@ tuples.ts:12:6 - error SC1090: tuple indexing with a non-literal index (tuples a | ^ 13 | -tuples.ts:15:7 - error SC2001: values of type '[string, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +tuples.ts:15:7 - error SC2001: values of type '[string, (number | undefined)?]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 14 | // Optional and rest elements have no fixed arity/shape. 15 | const opt: [string, number?] = ["x"]; | ^~~ 16 | const rest: [string, ...number[]] = ["y", 1, 2]; -tuples.ts:16:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +tuples.ts:16:7 - error SC2001: values of type '[string, ...number[]]' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 15 | const opt: [string, number?] = ["x"]; 16 | const rest: [string, ...number[]] = ["y", 1, 2]; diff --git a/tests/harness/__snapshots__/union-clone-override-width.ts.txt b/tests/harness/__snapshots__/union-clone-override-width.ts.txt index 6b5cc3566..8915968ef 100644 --- a/tests/harness/__snapshots__/union-clone-override-width.ts.txt +++ b/tests/harness/__snapshots__/union-clone-override-width.ts.txt @@ -1,4 +1,4 @@ -union-clone-override-width.ts:8:18 - error SC2001: values of type '{ kind: "left"; left: number; payload: WiderPayload; } | { kind: "right"; right: number; payload: WiderPayload; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +union-clone-override-width.ts:8:18 - error SC2001: values of type '{ kind: "left"; left: number; payload: WiderPayload; } | { kind: "right"; right: number; payload: WiderPayload; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, Dates, functions, classes, records, unions of those, and 'unknown') 7 | // copy its record. The inferred spread result keeps the wider payload. 8 | const result = { ...source, payload }; diff --git a/tests/harness/corpus-costs/8020-date-setters.ts.json b/tests/harness/corpus-costs/8020-date-setters.ts.json new file mode 100644 index 000000000..ad5d9a546 --- /dev/null +++ b/tests/harness/corpus-costs/8020-date-setters.ts.json @@ -0,0 +1 @@ +{ "plain": 2891, "san": 4454 } diff --git a/tests/harness/corpus-costs/8021-date-coercions.ts.json b/tests/harness/corpus-costs/8021-date-coercions.ts.json new file mode 100644 index 000000000..cd0090be6 --- /dev/null +++ b/tests/harness/corpus-costs/8021-date-coercions.ts.json @@ -0,0 +1 @@ +{ "plain": 2589, "san": 3385 } diff --git a/tests/harness/corpus-costs/8022-date-unions.ts.json b/tests/harness/corpus-costs/8022-date-unions.ts.json new file mode 100644 index 000000000..ce89111f9 --- /dev/null +++ b/tests/harness/corpus-costs/8022-date-unions.ts.json @@ -0,0 +1 @@ +{ "plain": 2786, "san": 3724 } diff --git a/tests/harness/corpus-costs/8023-date-clone-throw.ts.json b/tests/harness/corpus-costs/8023-date-clone-throw.ts.json new file mode 100644 index 000000000..50a23d33a --- /dev/null +++ b/tests/harness/corpus-costs/8023-date-clone-throw.ts.json @@ -0,0 +1 @@ +{ "plain": 2847, "san": 33492 } diff --git a/tests/harness/corpus-costs/8024-date-setters-dynamic.mjs.json b/tests/harness/corpus-costs/8024-date-setters-dynamic.mjs.json new file mode 100644 index 000000000..0007f1971 --- /dev/null +++ b/tests/harness/corpus-costs/8024-date-setters-dynamic.mjs.json @@ -0,0 +1 @@ +{ "plain": 2645, "san": 3529 } diff --git a/tests/harness/corpus-costs/8025-date-subclass-setters.ts.json b/tests/harness/corpus-costs/8025-date-subclass-setters.ts.json new file mode 100644 index 000000000..cb128790d --- /dev/null +++ b/tests/harness/corpus-costs/8025-date-subclass-setters.ts.json @@ -0,0 +1 @@ +{ "plain": 2383, "san": 3249 } diff --git a/tests/harness/corpus-costs/8026-date-subclass-setters-dynamic.mjs.json b/tests/harness/corpus-costs/8026-date-subclass-setters-dynamic.mjs.json new file mode 100644 index 000000000..0615e7224 --- /dev/null +++ b/tests/harness/corpus-costs/8026-date-subclass-setters-dynamic.mjs.json @@ -0,0 +1 @@ +{ "plain": 2483, "san": 3294 } From 5d8804db382fb30944dc90fc910ccb76b0325fcd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 22:40:04 -0500 Subject: [PATCH 040/395] Version string scans over the UTF-16 units of strings that are not ASCII `for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++` over a text with any non-ASCII character failed the loop version's ASCII guard and ran the checked loop. Each read tested the ASCII bit, compared the string with the runtime's published scan receiver, tested the bounds, loaded the units pointer, and kept the opaque fallback. The guard now also accepts a version's one string receiver when it is not ASCII. One runtime read publishes the string as the scan receiver (ScrStrScan), whose UTF-16 units the runtime holds once a scan has read it enough. The guard then tests the accesses against the published unit count, and a third copy of the loop reads each unit with one 16-bit load from the units pointer taken in the guard. The guard takes that path only while the runtime publishes the receiver itself, so the length it took is the receiver's UTF-16 length on both fast paths. Both fast copies read it for the receiver's `length`, which a while loop's condition otherwise asks the runtime for on every iteration. The runtime drops a string's units when the string is released or appended to in place, or when its index entry is evicted because another non-ASCII string is indexed. The receiver is a binding the loop does not write, so the first two cannot happen while the loop runs. A units copy is emitted only for a loop that uses unit-safe operations (string-unit-versions.ts): numbers, locals, records and class fields, typed arrays, number arrays, Math, the receiver's charCodeAt and length, and calls of functions made of the same operations that read no string. Tests of `s.charAt(i)` against one-unit literals now become charCodeAt compares in the IR pass (char-unit-locals.ts), so a version sees one kind of read. --- .../backend/llvm/bytes-loop-versions.test.ts | 4 +- .../src/backend/llvm/bytes-loop-versions.ts | 143 +++++++++++- .../compiler/src/backend/llvm/emit-module.ts | 6 +- .../src/backend/llvm/emit-statements.ts | 36 ++- packages/compiler/src/backend/llvm/emitter.ts | 18 ++ .../src/backend/llvm/expr-containers.ts | 19 +- .../compiler/src/backend/llvm/string-reads.ts | 22 ++ .../backend/llvm/string-unit-versions.test.ts | 86 +++++++ .../src/backend/llvm/string-unit-versions.ts | 144 ++++++++++++ .../compiler/src/ir/char-unit-locals.test.ts | 23 ++ packages/compiler/src/ir/char-unit-locals.ts | 100 +++++--- .../test/string-char-loop-emission.test.ts | 8 +- .../test/string-unit-loop-emission.test.ts | 94 ++++++++ .../7305-string-unit-loop-versions.ts.json | 6 + .../corpus/7305-string-unit-loop-versions.ts | 219 ++++++++++++++++++ .../7305-string-unit-loop-versions.ts.json | 1 + 16 files changed, 874 insertions(+), 55 deletions(-) create mode 100644 packages/compiler/src/backend/llvm/string-unit-versions.test.ts create mode 100644 packages/compiler/src/backend/llvm/string-unit-versions.ts create mode 100644 packages/compiler/test/string-unit-loop-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7305-string-unit-loop-versions.ts.json create mode 100644 tests/corpus/7305-string-unit-loop-versions.ts create mode 100644 tests/harness/corpus-costs/7305-string-unit-loop-versions.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 aef39ef75..0c0f1aaa0 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -580,7 +580,9 @@ test("the guard tests a string's ASCII bit and the fast copy reads bytes", () => const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; const guard = body.slice(0, body.search(/^bytes\.version\.slow\d*:/m)); expect(guard).toMatch(/load i32, ptr %\S+\n\s+%\S+ = icmp slt i32 %\S+, 0/); - const fast = body.slice(body.lastIndexOf("bytes.version.fast")); + // The byte copy, up to the units copy that follows it. + const units = body.search(/^bytes\.version\.fast\.units\d+:/m); + const fast = body.slice(body.search(/^bytes\.version\.fast\d+:/m), units < 0 ? undefined : units); expect(fast).toMatch(/load i8, ptr %\S+\n\s+%\S+ = uitofp i8/); expect(fast).not.toContain("@sc_str_char_code_at_int"); // The checked copy keeps the inline helper and its fallback. diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index 1b51deee1..301f65de2 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -12,7 +12,13 @@ import { } from "./counted-loops.js"; import { emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; import { f64Lit } from "./common.js"; -import { emitStringAsciiFlag, emitStringByteLength } from "./string-reads.js"; +import { + emitStringAsciiFlag, + emitStringByteLength, + STRING_CHAR_CODE_AT_DECL, + stringScanType, +} from "./string-reads.js"; +import { unitSafeLoop } from "./string-unit-versions.js"; type Node = IrExpr | IrStmt; type VarRef = IrExpr & { kind: "varRef" }; @@ -73,6 +79,14 @@ export interface BytesLoopVersion { receivers: { ref: VarRef; global: boolean; string: boolean }[]; /** Receivers the guard requires to have no shared storage. */ ownedReceivers: number[]; + /** The string reads among `bounds`. */ + stringReads: ReadonlySet; + /** The version's one string receiver, when the loop may also run over + * its UTF-16 units if it is not ASCII (string-unit-versions.ts). */ + unitsReceiver: number | null; + /** The units receiver's `length` reads in the loop. Both fast copies + * read the length the guard took (its UTF-16 length) in their place. */ + unitLengths: ReadonlySet; fixed: FixedCheck[]; affine: AffineCheck[]; /** While loops: accesses at a counter below a loop-invariant bound. */ @@ -114,6 +128,9 @@ export interface BytesLoopFacts { /** String bindings held as a slice of another string (string-slices.ts): * they have no string header for the guard to read. */ scalarStrings: ReadonlySet; + /** The functions a loop over a string's UTF-16 units may call; absent + * or null where emitted code does not read the runtime's scan receiver. */ + unitSafeFunctions?: ReadonlySet | null; } const MAX_COST = 400; @@ -670,6 +687,19 @@ export function matchBytesLoopVersion( // Renumber the receivers the kept checks use. const order = [...used].sort((a, b) => a - b); const renumber = new Map(order.map((old, index) => [old, index])); + const stringReads = new Set(); + for (const group of [...fixedKept, ...affineKept]) + if (receivers[group.check.receiver]!.string) + for (const node of group.nodes) stringReads.add(node); + for (const group of boundedKept) + if (receivers[group.check.receiver]!.string) + for (const { node } of group.nodes) stringReads.add(node); + const strings = order.filter((index) => receivers[index]!.string); + const unitLengths = + facts.unitSafeFunctions != null && strings.length === 1 + ? unitSafeLoop(loop, receivers[strings[0]!]!.ref.localId, facts.unitSafeFunctions) + : null; + const unitsReceiver = unitLengths !== null ? renumber.get(strings[0]!)! : null; const needsBound = affineKept.length > 0 && counted !== null && @@ -679,6 +709,9 @@ export function matchBytesLoopVersion( owned: ownedNodes, receivers: order.map((index) => receivers[index]!), ownedReceivers: ownedKept.map(([receiver]) => renumber.get(receiver)!), + stringReads, + unitsReceiver, + unitLengths: unitLengths ?? new Set(), fixed: fixedKept.map((g) => ({ receiver: renumber.get(g.check.receiver)!, end: g.check.end, @@ -1018,13 +1051,22 @@ function findWhileCounterAccesses( /** Branches to `fast` when every access the version proves is in bounds for * every iteration and every view it proves thread-owned has no shared * storage, and to `slow` otherwise (including a counted loop whose - * integer form is not established, or a module receiver not yet set). */ + * integer form is not established, or a module receiver not yet set). + * + * With `unitsFast`, the version's units receiver may instead be a string + * that is not ASCII: the guard has the runtime publish it as the scan + * receiver (one read), tests the same accesses against its UTF-16 length, + * and branches to `unitsFast`. It returns the units pointer that copy + * reads, which stays valid while the loop runs (string-unit-versions.ts), + * and for a version with a units receiver, the receiver's UTF-16 length + * on both fast paths (an i64). */ export function emitBytesLoopVersionGuard( host: LlvmEmitterContext, version: BytesLoopVersion, fast: string, slow: string, -): void { + unitsFast?: string, +): { units: string | null; length: string | null } | null { const B = host.B; const next = (cond: string): void => { if (cond === "true") return; @@ -1043,19 +1085,36 @@ export function emitBytesLoopVersionGuard( } return value; }); - const lengths = receivers.map((receiver, index) => { - const len = version.receivers[index]!.string - ? emitStringByteLength(host, receiver) - : emitBytesHeaderLength(host, receiver); + const unitsIndex = unitsFast !== undefined ? version.unitsReceiver : null; + const units: { read: { pointer: string; taken: string; published: string } | null } = { + read: null, + }; + const widen = (len: string): string => { if (host.sizeType === "i64") return len; const wide = B.tmp(); B.line(`${wide} = zext ${host.sizeType} ${len} to i64`); return wide; + }; + const lengths = receivers.map((receiver, index) => { + if (index === unitsIndex) { + const read = emitUnitsLength(host, receiver); + units.read = read; + return read.length; + } + return widen( + version.receivers[index]!.string + ? emitStringByteLength(host, receiver) + : emitBytesHeaderLength(host, receiver), + ); }); const conditions: string[] = []; + // The published count is the receiver's UTF-16 length only while the + // runtime publishes the receiver itself. + if (units.read) conditions.push(units.read.published); // A string's byte length is its UTF-16 length when it is ASCII. version.receivers.forEach(({ string }, index) => { - if (string) conditions.push(emitStringAsciiFlag(host, receivers[index]!)); + if (string && index !== unitsIndex) + conditions.push(emitStringAsciiFlag(host, receivers[index]!)); }); for (const index of version.ownedReceivers) { const owned = B.tmp(); @@ -1158,7 +1217,7 @@ export function emitBytesLoopVersionGuard( // Unreachable: every term has a proven range. Stay checked. B.br(slow); B.startBlock(B.newLabel("bytes.version.dead")); - return; + return null; } const wide = widenInteger(host, integer); const sum = B.tmp(); @@ -1215,8 +1274,70 @@ export function emitBytesLoopVersionGuard( valid = all; } } - if (valid === "true") B.br(fast); - else B.condBr(valid, fast, slow); + const length = version.unitsReceiver !== null ? lengths[version.unitsReceiver]! : null; + const taken = units.read; + const target = taken ? B.newLabel("bytes.version.units") : fast; + if (valid === "true") B.br(target); + else B.condBr(valid, target, slow); + if (!taken) return { units: null, length }; + B.startBlock(target); + B.condBr(taken.taken, unitsFast!, fast); + return { units: taken.pointer, length }; +} + +/** The length of a version's units receiver `s` in UTF-16 units: its byte + * length when it is ASCII; otherwise, after one runtime read publishes it + * as the scan receiver, the published unit count (0 when the runtime does + * not hold its units). Also the units pointer, whether the loop is to + * read it, and whether the published receiver is `s`. */ +function emitUnitsLength( + host: LlvmEmitterContext, + s: string, +): { length: string; pointer: string; taken: string; published: string } { + const B = host.B; + const sz = host.sizeType; + const type = stringScanType(sz); + const ascii = emitStringAsciiFlag(host, s); + const bytes = emitStringByteLength(host, s); + const from = B.currentLabel(); + const publish = B.newLabel("bytes.version.publish"), + join = B.newLabel("bytes.version.length"); + B.condBr(ascii, join, publish); + B.startBlock(publish); + host.declare(STRING_CHAR_CODE_AT_DECL); + const read = B.tmp(), + receiver = B.tmp(), + same = B.tmp(), + pointerSlot = B.tmp(), + pointer = B.tmp(), + countSlot = B.tmp(), + count = B.tmp(), + published = B.tmp(); + B.line(`${read} = call double @scr_str_char_code_at(ptr ${s}, double 0.0)`); + B.line(`${receiver} = load ptr, ptr @scr_str_scan`); + B.line(`${same} = icmp eq ptr ${receiver}, ${s}`); + B.line(`${pointerSlot} = getelementptr inbounds ${type}, ptr @scr_str_scan, i32 0, i32 1`); + B.line(`${pointer} = load ptr, ptr ${pointerSlot}`); + B.line(`${countSlot} = getelementptr inbounds ${type}, ptr @scr_str_scan, i32 0, i32 2`); + B.line(`${count} = load ${sz}, ptr ${countSlot}`); + B.line(`${published} = select i1 ${same}, ${sz} ${count}, ${sz} 0`); + const end = B.currentLabel(); + B.br(join); + B.startBlock(join); + const length = B.tmp(), + units = B.tmp(), + taken = B.tmp(); + B.line(`${length} = phi ${sz} [ ${bytes}, %${from} ], [ ${published}, %${end} ]`); + B.line(`${units} = phi ptr [ null, %${from} ], [ ${pointer}, %${end} ]`); + B.line(`${taken} = phi i1 [ false, %${from} ], [ true, %${end} ]`); + const exact = B.tmp(); + B.line(`${exact} = phi i1 [ true, %${from} ], [ ${same}, %${end} ]`); + let wide = length; + if (sz !== "i64") { + wide = B.tmp(); + B.line(`${wide} = zext ${sz} ${length} to i64`); + } + return { length: wide, pointer: units, taken, published: exact }; } function scaleCounter(host: LlvmEmitterContext, value: string, scale: number): string { diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index e5258e583..069f3b615 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -61,7 +61,7 @@ import { hasLoneSurrogate, wtf8Bytes } from "../literals.js"; import { undefinedArmTag } from "../../ir/analysis.js"; import { NULLABLE_ABSENT, NULLABLE_NULL } from "./nullable-unions.js"; import { literalEqualityDefinition } from "./string-literal-equality.js"; -import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js"; +import { STRING_READ_HELPERS, stringScanDecl, stringScanInline } from "./string-reads.js"; import { emitLibraryIdentityLines } from "../library-identity-markers.js"; import { InternalCompilerError } from "../../errors.js"; import { targetCpuAttribute } from "../target-cpu.js"; @@ -1586,9 +1586,7 @@ export function helperDefs(emitter: LlEmitter): string[] { // A scan's receiver reads inline too (ScrStrScan): from ordinary global // storage, or, in worker executables, from the thread's static TLS block, // one thread-pointer-relative load (see executableTls). - const scanInline = - emitter.executableTls || - (emitter.mod.workers !== true && emitter.mod.lib?.threadInstances !== true); + const scanInline = stringScanInline(emitter); let scanDeclared = false; for (const helper of STRING_READ_HELPERS) { if (!emitter.decls.has(helper.decl)) continue; diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 55e31219c..6de298ec4 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2082,28 +2082,56 @@ function emitVersionedLoop( const fast = B.newLabel("bytes.version.fast"), slow = B.newLabel("bytes.version.slow"), done = B.newLabel("bytes.version.done"); - emitBytesLoopVersionGuard(emitter, version, fast, slow); + const unitsFast = + version.unitsReceiver !== null ? B.newLabel("bytes.version.fast.units") : undefined; + const guard = emitBytesLoopVersionGuard(emitter, version, fast, slow, unitsFast); + const units = guard?.units ?? null; const originalBounds = emitter.bytesBounds, originalOwned = emitter.bytesOwned, - originalRanges = emitter.integerRanges; + originalRanges = emitter.integerRanges, + originalUnitReads = emitter.stringUnitReads, + originalLengthReads = emitter.stringLengthReads; LOOPS.of(emitter).bytesLoopVersioning.add(s); try { B.startBlock(slow); emitter.emitStmt(s); B.br(done); - B.startBlock(fast); - emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); if (version.owned.size > 0) emitter.bytesOwned = new Set([...originalOwned, ...version.owned]); if (version.ranges.size > 0) emitter.integerRanges = new Map([...originalRanges, ...version.ranges]); if (version.counted?.guarded || version.paired?.loop.guarded) LOOPS.of(emitter).provenCountedGuards.add(s); + B.startBlock(fast); + emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); + // Both fast copies read the string's length the guard took. + const length = guard?.length; + if (length != null && version.unitLengths.size > 0) + emitter.stringLengthReads = new Map([ + ...originalLengthReads, + ...[...version.unitLengths].map((node) => [node, length] as const), + ]); emitter.emitStmt(s); B.br(done); + if (unitsFast !== undefined && units !== null) { + // The string reads take the receiver's UTF-16 units. + B.startBlock(unitsFast); + emitter.bytesBounds = new Set([ + ...originalBounds, + ...[...version.bounds].filter((node) => !version.stringReads.has(node)), + ]); + emitter.stringUnitReads = new Map([ + ...originalUnitReads, + ...[...version.stringReads].map((node) => [node, units] as const), + ]); + emitter.emitStmt(s); + B.br(done); + } } finally { emitter.bytesBounds = originalBounds; emitter.bytesOwned = originalOwned; emitter.integerRanges = originalRanges; + emitter.stringUnitReads = originalUnitReads; + emitter.stringLengthReads = originalLengthReads; LOOPS.of(emitter).provenCountedGuards.delete(s); LOOPS.of(emitter).bytesLoopVersioning.delete(s); } diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 8106a6478..0a2756388 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -104,6 +104,8 @@ import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js"; import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js"; import { hoistFinalFieldReads } from "../../ir/field-values.js"; import { holdCharLocalsAsUnits } from "../../ir/char-unit-locals.js"; +import { unitSafeFunctions } from "./string-unit-versions.js"; +import { stringScanInline } from "./string-reads.js"; import { findInt32Abis, INT32_ABI_SUFFIX, @@ -1477,6 +1479,12 @@ export class LlEmitter { * (one-unit tests and switches, charAtUnitRead), with the charAt each * stands for: a loop version's proof for the charAt covers its read. */ readonly charAtUnitReads = new Map(); + /** String reads a loop version proved in bounds of a string's UTF-16 + * units, with the units pointer to read (string-unit-versions.ts). */ + stringUnitReads: ReadonlyMap = new Map(); + /** `length` reads of a loop version's string, with the i64 UTF-16 + * length its guard took (bytes-loop-versions.ts). */ + stringLengthReads: ReadonlyMap = new Map(); /** Worker programs: typed-array accesses whose views an enclosing version * guard proved thread-owned. */ bytesOwned: ReadonlySet = new Set(); @@ -4305,11 +4313,21 @@ export class LlEmitter { workers: this.mod.workers === true, owned: this.bytesOwned, scalarStrings: new Set(this.stringSlices.keys()), + unitSafeFunctions: this.unitSafeFunctions(), }, paired, ); } + private unitSafe: ReadonlySet | null | undefined; + /** The functions a loop over a string's UTF-16 units may call, or null + * where emitted reads do not use the runtime's scan receiver. */ + private unitSafeFunctions(): ReadonlySet | null { + if (this.unitSafe === undefined) + this.unitSafe = stringScanInline(this) ? unitSafeFunctions(this.mod) : null; + return this.unitSafe; + } + /** The alias tag of a whole-slot load of a binding (module globals only). */ slotAlias(id: string): string { const binding = this.binding(id); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 279b8c044..bba7629b9 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -53,6 +53,7 @@ import { STRING_CHAR_CODE_AT_INT_MARKER, emitAsciiChar, emitAsciiCharCode, + emitUnitCharCode, STRING_UTF16_LEN_DECL, } from "./string-reads.js"; import { exactInteger, widenInteger } from "./integer-values.js"; @@ -322,6 +323,15 @@ export function charAtUnitRead( return read; } +/** The UTF-16 units an enclosing loop version proved the index of `e` + * below the count of (string-unit-versions.ts). */ +function provenUnitsRead(host: LlvmEmitterContext, e: IrExpr): string | null { + const units = host.stringUnitReads.get(e); + if (units !== undefined) return units; + const origin = host.charAtUnitReads.get(e); + return origin !== undefined ? (host.stringUnitReads.get(origin) ?? null) : null; +} + /** An enclosing loop version proved the string of `e` ASCII and its index * below the length (bytes-loop-versions.ts). */ function provenStringRead(host: LlvmEmitterContext, e: IrExpr): boolean { @@ -364,9 +374,14 @@ export function emitStrIntrinsic( const method = e.method; switch (method) { case "length": { + const t = B.tmp(); + const length = host.stringLengthReads.get(e); + if (length !== undefined) { + B.line(`${t} = sitofp i64 ${length} to double`); + return { name: t, type: e.type }; + } // sc_str_utf16_len (string-reads.ts) answers proven-ASCII strings inline. host.declare(STRING_UTF16_LEN_DECL); - const t = B.tmp(); B.line(`${t} = call double @sc_str_utf16_len(ptr ${r.name})`); return { name: t, type: e.type }; } @@ -376,6 +391,8 @@ export function emitStrIntrinsic( const index = e.args[0] ? exactInteger(host, args[0]!, e.args[0]) : null; if (index) { const wide = widenInteger(host, index); + const units = provenUnitsRead(host, e); + if (units !== null) return { name: emitUnitCharCode(host, units, wide), type: e.type }; if (provenStringRead(host, e)) return { name: emitAsciiCharCode(host, r.name, wide), type: e.type }; host.declare(STRING_CHAR_CODE_AT_INT_MARKER); diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 441c925f3..08df79cf2 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -75,6 +75,19 @@ export function emitAsciiCharCode(host: LlvmEmitterContext, s: string, index: st return code; } +/** Unit `index` (an i64, below their count) of a string's UTF-16 `units` + * (ScrStrScan): one 16-bit load, as a double. */ +export function emitUnitCharCode(host: LlvmEmitterContext, units: string, index: string): string { + const B = host.B; + const pointer = B.tmp(), + unit = B.tmp(), + code = B.tmp(); + B.line(`${pointer} = getelementptr inbounds i16, ptr ${units}, i64 ${index}`); + B.line(`${unit} = load i16, ptr ${pointer}`); + B.line(`${code} = uitofp i16 ${unit} to double`); + return code; +} + /** The one-character string of unit `index` (an i64) of a proven-ASCII * string `s` whose length covers it: the immortal `@sc_ascii_chars` entry * for its byte, as the inline charAt helpers answer. */ @@ -114,6 +127,15 @@ function loadLength(sz: string): string[] { ]; } +/** Whether emitted reads use the runtime's scan receiver: from ordinary + * global storage, or, in worker executables, from the thread's static TLS + * block (executableTls). */ +export function stringScanInline(host: LlvmEmitterContext): boolean { + return ( + host.executableTls || (host.mod.workers !== true && host.mod.lib?.threadInstances !== true) + ); +} + /** The runtime's ScrStrScan record and the declaration naming it. */ export function stringScanType(sz: string): string { return `{ ptr, ptr, ${sz} }`; diff --git a/packages/compiler/src/backend/llvm/string-unit-versions.test.ts b/packages/compiler/src/backend/llvm/string-unit-versions.test.ts new file mode 100644 index 000000000..5b0634b1a --- /dev/null +++ b/packages/compiler/src/backend/llvm/string-unit-versions.test.ts @@ -0,0 +1,86 @@ +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 type { IrModule, IrStmt } from "../../ir/ir.js"; +import { unitSafeFunctions, unitSafeLoop } from "./string-unit-versions.js"; + +async function lower(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unit-versions-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return deserializeModule(await readFile(outPath, "utf8")); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +const loopOf = (mod: IrModule, name: string): IrStmt & { kind: "for" } => + mod.functions.find((f) => f.name === name)!.body.find((s) => s.kind === "for") as IrStmt & { + kind: "for"; + }; + +test("loops over a string's units call only functions that read no string", async () => { + const mod = await lower(` +function lower(c: number): boolean { + return c >= 97 && c <= 122; +} +function word(c: number): boolean { + return lower(c) || (c >= 48 && c <= 57) || Math.abs(c - 200) < 50; +} +function codeOf(t: string, i: number): number { + return t.charCodeAt(i); +} +function viaCodeOf(t: string): number { + return codeOf(t, 0); +} +function words(s: string): number { + let n = 0; + const starts = [0]; + for (let i = 0; i < s.length; i++) { + if (word(s.charCodeAt(i))) n++; + else starts.push(i); + } + return n + starts.length; +} +function pairs(s: string, t: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) n += s.charCodeAt(i) + codeOf(t, i); + return n; +} +function both(s: string, t: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) n += s.charCodeAt(i) + t.charCodeAt(i); + return n; +} +function concatenates(s: string): string { + let out = ""; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) out += "-"; + return out; +} +console.log(words("ab c"), pairs("ab", "cd"), both("ab", "cd"), concatenates("a b"), viaCodeOf("x")); +`); + const safe = unitSafeFunctions(mod); + expect(safe.has("lower")).toBe(true); + expect(safe.has("word")).toBe(true); + // A string read, directly or through a call, makes a function unsafe. + expect(safe.has("codeOf")).toBe(false); + expect(safe.has("viaCodeOf")).toBe(false); + + const param = (fn: string, index = 0): string => + mod.functions.find((f) => f.name === fn)!.params[index]!.localId; + // A unit-safe loop answers its receiver's length reads: here the + // condition's `s.length`. + const lengths = unitSafeLoop(loopOf(mod, "words"), param("words"), safe); + expect(lengths?.size).toBe(1); + expect([...lengths!].every((e) => e.kind === "strIntrinsic" && e.method === "length")).toBe(true); + expect(unitSafeLoop(loopOf(mod, "pairs"), param("pairs"), safe)).toBeNull(); + expect(unitSafeLoop(loopOf(mod, "both"), param("both"), safe)).toBeNull(); + expect(unitSafeLoop(loopOf(mod, "concatenates"), param("concatenates"), safe)).toBeNull(); +}); diff --git a/packages/compiler/src/backend/llvm/string-unit-versions.ts b/packages/compiler/src/backend/llvm/string-unit-versions.ts new file mode 100644 index 000000000..022c3a513 --- /dev/null +++ b/packages/compiler/src/backend/llvm/string-unit-versions.ts @@ -0,0 +1,144 @@ +import type { IrExpr, IrModule, IrStmt } from "../../ir/ir.js"; +import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; + +/* Loop versions over the UTF-16 units of a string that is not ASCII. + * + * The runtime publishes the receiver of an indexed scan with its UTF-16 + * units (ScrStrScan, string-reads.ts), and drops them when the string is + * released or appended to in place, or when its index entry is evicted: + * the runtime indexes another non-ASCII string. A loop version's string + * receiver is a binding the loop does not write, which keeps it alive and + * unappended for the whole loop. Its fast copy over units therefore only + * needs a body that indexes no other string: the operations below, and + * calls of functions made of the same operations without string reads + * (`unitSafeFunctions`). */ + +const ARRAY_METHODS = new Set(["push", "pop", "length"]); + +/** Whether `e` (or statement `s`) uses only operations that index no + * string: numbers, booleans, locals, records and class fields, typed + * arrays, number arrays and Math. `reads` admits the string reads a + * version covers; `calls` the callees. */ +function unitSafe( + node: { e: IrExpr } | { s: IrStmt }, + reads: (e: IrExpr & { kind: "strIntrinsic" }) => boolean, + calls: (callee: string) => boolean, +): boolean { + const expr = (e: IrExpr): boolean => { + switch (e.kind) { + case "varRef": + case "numLit": + case "boolLit": + case "bin": + case "unary": + case "logical": + case "ternary": + case "toBool": + case "seqExpr": + case "assignExpr": + case "incDec": + case "recordGet": + case "fieldGet": + case "bytesIntrinsic": + break; + case "arrayGet": + if (e.type.kind !== "f64" && e.type.kind !== "bool") return false; + break; + case "arrIntrinsic": { + // Growing or shrinking an array of numbers allocates; it reads no + // string. + const array = e.receiver.type; + if ( + !ARRAY_METHODS.has(e.method) || + array.kind !== "array" || + (array.elem.kind !== "f64" && array.elem.kind !== "bool") + ) + return false; + break; + } + case "libCall": + if (!e.fn.startsWith("math.")) return false; + break; + case "call": + if (!calls(e.callee)) return false; + break; + case "strIntrinsic": + if (!reads(e)) return false; + break; + default: + return false; + } + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + switch (s.kind) { + case "varDecl": + case "assign": + case "exprStmt": + case "if": + case "block": + case "while": + case "doWhile": + case "for": + case "break": + case "continue": + case "return": + case "recordSet": + case "fieldSet": + case "bytesSet": + break; + default: + return false; + } + return everyStmtChild(s, expr, stmt); + }; + return "e" in node ? expr(node.e) : stmt(node.s); +} + +/** The module's functions a units version may call: synchronous + * functions without captures whose bodies use only unit-safe operations, + * read no string, and call only such functions. */ +export function unitSafeFunctions(mod: IrModule): ReadonlySet { + const candidates = new Map( + mod.functions + .filter((fn) => !fn.async && !fn.generator && !(fn.captures?.length ?? 0)) + .map((fn) => [fn.name, fn]), + ); + const safe = new Set(candidates.keys()); + for (let settled = false; !settled;) { + settled = true; + for (const name of safe) { + const fn = candidates.get(name)!; + const ok = fn.body.every((s) => + unitSafe( + { s }, + () => false, + (callee) => safe.has(callee), + ), + ); + if (!ok) { + safe.delete(name); + settled = false; + } + } + } + return safe; +} + +/** The `length` reads of the binding `receiver` in a loop whose every + * part is unit-safe, reading only charCodeAt and length of `receiver` and + * calling only `safe` functions; null for any other loop. */ +export function unitSafeLoop( + loop: IrStmt & { kind: "for" | "while" }, + receiver: string, + safe: ReadonlySet, +): ReadonlySet | null { + const lengths = new Set(); + const reads = (e: IrExpr & { kind: "strIntrinsic" }): boolean => { + if (e.receiver.kind !== "varRef" || e.receiver.localId !== receiver) return false; + if (e.method === "length") lengths.add(e); + return e.method === "charCodeAt" || e.method === "length"; + }; + const calls = (callee: string): boolean => safe.has(callee); + return unitSafe({ s: loop }, reads, calls) ? lengths : null; +} diff --git a/packages/compiler/src/ir/char-unit-locals.test.ts b/packages/compiler/src/ir/char-unit-locals.test.ts index ad16ecebd..9ec25b75a 100644 --- a/packages/compiler/src/ir/char-unit-locals.test.ts +++ b/packages/compiler/src/ir/char-unit-locals.test.ts @@ -96,3 +96,26 @@ console.log(escapes("a b", 1), rebound(" x", 0), wide("ab", 0), against("a", "b expect(fn(after, name)).toBe(fn(before, name)); } }); + +test("charAt tests in place compare charCodeAt", async () => { + const mod = holdCharLocalsAsUnits( + await lower(` +function tests(s: string, i: number): number { + let n = 0; + if (s.charAt(i) === " ") n += 1; + if ("a" <= s.charAt(i) && s.charAt(i) < "n") n += 2; + if (s.charAt(i) === "ab") n += 4; + return n; +} +console.log(tests("a b", 1), tests("abc", 0), tests("", 2)); +`), + ); + // The two-unit literal keeps its string comparison. + expect(reads(fn(mod, "tests"))).toEqual([ + "charCodeAt", + "charCodeAt", + "charCodeAt", + "strEq", + "charAt", + ]); +}); diff --git a/packages/compiler/src/ir/char-unit-locals.ts b/packages/compiler/src/ir/char-unit-locals.ts index beeeffaf3..840b7478f 100644 --- a/packages/compiler/src/ir/char-unit-locals.ts +++ b/packages/compiler/src/ir/char-unit-locals.ts @@ -12,6 +12,8 @@ import { mapExprChildren, mapStmtChildren } from "./traverse.js"; type CharAt = IrExpr & { kind: "strIntrinsic" }; type UnitUse = IrExpr & { kind: "strEq" | "strCmp" }; +const FLIPPED = { "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as const; + /** Character locals held as their code unit. * * `const c = s[i]` (or `s.charAt(i)`) followed only by tests such as @@ -28,7 +30,8 @@ type UnitUse = IrExpr & { kind: "strEq" | "strCmp" }; * * Only immutable unboxed string locals qualify, with one declaration whose * initializer is a one-argument charAt, and with no other mention of the - * local. */ + * local. The same tests of `s.charAt(i)` in place compare + * `s.charCodeAt(i)`, so a loop version sees one kind of read. */ export function holdCharLocalsAsUnits(mod: IrModule): IrModule { let changed = false; const functions = mod.functions.map((fn) => { @@ -43,7 +46,6 @@ function rewriteFunction(fn: IrFunction): IrFunction { const candidates = new Set( fn.locals.filter((l) => !l.mutable && !l.boxed && l.type.kind === "string").map((l) => l.id), ); - if (candidates.size === 0) return fn; // Every mention of a candidate, in any field, against the mentions the // rewrite understands. const mentions = new Map(); @@ -67,7 +69,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const key in node) if (key !== "loc") count(node[key]); } }; - count(fn.body); + if (candidates.size > 0) count(fn.body); const held = new Set(); for (const id of candidates) if ( @@ -76,7 +78,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { mentions.get(id) === understood.get(id) ) held.add(id); - if (held.size === 0) return fn; + let changed = false; const unitRef = (id: string, loc: IrExpr["loc"]): IrExpr => ({ kind: "varRef", @@ -88,38 +90,28 @@ function rewriteFunction(fn: IrFunction): IrFunction { if (e.kind === "strEq" || e.kind === "strCmp") { const use = unitUse(e); if (use && held.has(use.id)) { - const code: IrExpr = { kind: "numLit", value: use.code, type: F64, loc: e.loc }; - const c = unitRef(use.id, e.loc); - if (e.kind === "strEq") - return { - kind: "bin", - op: e.negated ? "!==" : "===", - left: c, - right: code, - type: BOOL, - loc: e.loc, - }; - // The comparison with the local on the left. - const op = use.left - ? e.op - : ({ "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as const)[e.op]; - if (op === ">" || op === ">=") - return { kind: "bin", op, left: c, right: code, type: BOOL, loc: e.loc }; - const negation: IrExpr = { - kind: "bin", - op: op === "<" ? ">=" : ">", - left: c, - right: code, - type: BOOL, - loc: e.loc, + changed = true; + return charUnitTest(e, unitRef(use.id, e.loc), use.code, use.left); + } + // `s.charAt(i)` in place: its unit read, `s.charCodeAt(i)`. + const direct = charAtTest(e); + if (direct) { + changed = true; + const read: IrExpr = { + ...direct.charAt, + method: "charCodeAt", + receiver: rewriteExpr(direct.charAt.receiver), + args: direct.charAt.args.map(rewriteExpr), + type: F64, }; - return { kind: "unary", op: "!", operand: negation, type: BOOL, loc: e.loc }; + return charUnitTest(e, read, direct.code, direct.left); } } return mapExprChildren(e, rewriteExpr, rewriteStmt); }; const rewriteStmt = (s: IrStmt): IrStmt => { - if (s.kind === "varDecl" && held.has(s.localId) && s.init?.kind === "strIntrinsic") + if (s.kind === "varDecl" && held.has(s.localId) && s.init?.kind === "strIntrinsic") { + changed = true; return { ...s, init: { @@ -130,7 +122,9 @@ function rewriteFunction(fn: IrFunction): IrFunction { type: F64, }, }; - if (s.kind === "switch" && s.disc.kind === "varRef" && held.has(s.disc.localId)) + } + if (s.kind === "switch" && s.disc.kind === "varRef" && held.has(s.disc.localId)) { + changed = true; return { ...s, disc: unitRef(s.disc.localId, s.disc.loc), @@ -143,11 +137,14 @@ function rewriteFunction(fn: IrFunction): IrFunction { body: c.body.map(rewriteStmt), })), }; + } return mapStmtChildren(s, rewriteExpr, rewriteStmt); }; + const body = fn.body.map(rewriteStmt); + if (!changed) return fn; return { ...fn, - body: fn.body.map(rewriteStmt), + body, locals: fn.locals.map((l) => (held.has(l.id) ? unitLocal(l) : l)), }; } @@ -179,6 +176,45 @@ function understoodMention(node: Record): string | null { return null; } +/** A one-unit test of a string that reads one character or "" (charAt), + * on its unit `unit`, which reads NaN for "": an equality, or an ordered + * comparison with the unit on the left when `unitLeft`. "" is below every + * one-unit literal, and NaN fails every ordered comparison, so `c < "x"` + * becomes `!(c >= 120)`. */ +function charUnitTest(e: UnitUse, unit: IrExpr, code: number, unitLeft: boolean): IrExpr { + const literal: IrExpr = { kind: "numLit", value: code, type: F64, loc: e.loc }; + if (e.kind === "strEq") + return { + kind: "bin", + op: e.negated ? "!==" : "===", + left: unit, + right: literal, + type: BOOL, + loc: e.loc, + }; + const op = unitLeft ? e.op : FLIPPED[e.op]; + if (op === ">" || op === ">=") + return { kind: "bin", op, left: unit, right: literal, type: BOOL, loc: e.loc }; + const negation: IrExpr = { + kind: "bin", + op: op === "<" ? ">=" : ">", + left: unit, + right: literal, + type: BOOL, + loc: e.loc, + }; + return { kind: "unary", op: "!", operand: negation, type: BOOL, loc: e.loc }; +} + +/** A test between `s.charAt(i)` and a literal of one code unit. */ +function charAtTest(e: UnitUse): { charAt: CharAt; code: number; left: boolean } | null { + const literalRight = e.right.kind === "strLit"; + const literal = literalRight ? e.right : e.left; + const other = literalRight ? e.left : e.right; + if (literal.kind !== "strLit" || literal.value.length !== 1 || !isCharAt(other)) return null; + return { charAt: other, code: literal.value.charCodeAt(0), left: literalRight }; +} + function isCharAt(e: IrExpr): e is CharAt { return e.kind === "strIntrinsic" && e.method === "charAt" && e.args.length === 1; } diff --git a/packages/compiler/test/string-char-loop-emission.test.ts b/packages/compiler/test/string-char-loop-emission.test.ts index 6f5756ce8..9f2553d85 100644 --- a/packages/compiler/test/string-char-loop-emission.test.ts +++ b/packages/compiler/test/string-char-loop-emission.test.ts @@ -43,8 +43,12 @@ console.log(spaces("a b c"), letters("abC1z"), chars("xyz").join("-"), chars("é ll.match( new RegExp(`define internal [^\\n]+ @sc_bf_${name}\\([^\\n]*\\) #0 \\{[\\s\\S]*?\\n\\}`), )![0]; - // The fast copy follows the checked one. - const fast = (fn: string): string => fn.slice(fn.lastIndexOf("bytes.version.fast")); + // The byte copy follows the checked one, and the units copy (for + // strings that are not ASCII) follows it. + const fast = (fn: string): string => { + const units = fn.search(/^bytes\.version\.fast\.units\d+:/m); + return fn.slice(fn.search(/^bytes\.version\.fast\d+:/m), units < 0 ? undefined : units); + }; // `s[i] === " "` compares the byte; the checked copy keeps the helper. const spaces = body("spaces"); diff --git a/packages/compiler/test/string-unit-loop-emission.test.ts b/packages/compiler/test/string-unit-loop-emission.test.ts new file mode 100644 index 000000000..32ee8411b --- /dev/null +++ b/packages/compiler/test/string-unit-loop-emission.test.ts @@ -0,0 +1,94 @@ +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 { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; + +test("scans of strings that are not ASCII read their UTF-16 units", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unit-loops-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + ` +function spaces(s: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++; + return n; +} +function skip(s: string): number { + let pos = 0; + while (pos < s.length && s.charCodeAt(pos) !== 32) pos++; + return pos; +} +function pairs(s: string, t: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === t.charCodeAt(i % t.length)) n++; + return n; +} +let text = ""; +for (let k = 0; k < 400; k++) text += k % 3 === 0 ? "café " : "naïve façade "; +const other = text.replace(/a/g, "ä"); +let total = 0; +for (let pass = 0; pass < 4; pass++) + total += spaces(text) + spaces(other) + pairs(text, other) + skip(text); +console.log(total, spaces("plain ascii text"), spaces("")); +`, + ); + 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}(?:\\.i32)?\\([^\\n]*\\) #0 \\{[\\s\\S]*?\\n\\}`, + ), + )![0]; + + const spaces = body("spaces"); + // A string that is not ASCII is published as the scan receiver once, + // before the loop, and its copy loads units. + expect(spaces).toMatch(/call double @scr_str_char_code_at\(ptr %\S+, double 0\.0\)/); + // The copies follow the checked one: bytes, then units. + const bytesAt = spaces.search(/^bytes\.version\.fast\d+:/m); + const unitsAt = spaces.search(/^bytes\.version\.fast\.units\d+:/m); + expect(bytesAt).toBeGreaterThan(0); + expect(unitsAt).toBeGreaterThan(bytesAt); + const units = spaces.slice(unitsAt); + expect(units).toMatch(/load i16, ptr %\S+\n\s+%\S+ = uitofp i16/); + expect(units).not.toContain("@sc_str_char_code_at_int"); + // An ASCII string still reads bytes. + const bytes = spaces.slice(bytesAt, unitsAt); + expect(bytes).toMatch(/load i8, ptr %\S+\n\s+%\S+ = uitofp i8/); + expect(bytes).not.toContain("@sc_str_char_code_at_int"); + // A while loop over a counter below the length reads units too. + const skip = body("skip"); + expect(skip).toMatch(/^bytes\.version\.fast\.units\d+:/m); + // Its fast copies read the length the guard took; the checked copy + // still asks for it. + const skipFastAt = skip.search(/^bytes\.version\.fast\d+:/m); + expect(skip.slice(0, skipFastAt)).toContain("@sc_str_utf16_len"); + expect(skip.slice(skipFastAt)).not.toContain("@sc_str_utf16_len"); + // Two strings cannot both be the scan receiver. + expect(body("pairs")).not.toContain("bytes.version.fast.units"); + + const exe = join(dir, "main"); + const built = await compile(entry, { outputKind: "exe", outDir: dir, outPath: exe }); + if (!built.ok) throw new Error(JSON.stringify(built.diagnostics)); + let text = ""; + for (let k = 0; k < 400; k++) text += k % 3 === 0 ? "café " : "naïve façade "; + const other = text.replace(/a/g, "ä"); + const count = (s: string): number => s.split(" ").length - 1; + let pairs = 0; + for (let i = 0; i < text.length; i++) + if (text.charCodeAt(i) === other.charCodeAt(i % other.length)) pairs++; + const total = 4 * (count(text) + count(other) + pairs + text.indexOf(" ")); + expect(execFileSync(exe, { encoding: "utf8" })).toBe(`${total} 2 0\n`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7305-string-unit-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7305-string-unit-loop-versions.ts.json new file mode 100644 index 000000000..21b8255e4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7305-string-unit-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7305-string-unit-loop-versions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7305-string-unit-loop-versions.ts b/tests/corpus/7305-string-unit-loop-versions.ts new file mode 100644 index 000000000..c7600167d --- /dev/null +++ b/tests/corpus/7305-string-unit-loop-versions.ts @@ -0,0 +1,219 @@ +// charCodeAt scans of strings that are not ASCII, repeated so the runtime +// holds their UTF-16 units: counts, hashes, neighbor reads, descending +// loops, reads past the end, line starts pushed to a number array, helper +// functions that read no string, a helper that reads another string, a +// second non-ASCII string read in the same loop, two texts scanned in +// turn, a short text, a text appended to between scans, surrogate pairs +// and lone surrogates, ASCII texts beside them, and while loops over a +// counter below the length: word and identifier scans, peeks past the +// counter, and a lexer class whose position is a field. Loops also read +// the length in their bodies, and loops over a fresh text that no scan +// has published run from its end and over its last units. + +function isWordUnit(c: number): boolean { + return (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || c > 127; +} + +function isWordOrDigit(c: number): boolean { + return isWordUnit(c) || (c >= 48 && c <= 57); +} + +function spaces(s: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++; + return n; +} + +function hash(s: string): number { + let h = 7; + for (let i = 0; i < s.length; i++) h = (Math.imul(h, 31) + s.charCodeAt(i)) | 0; + return h; +} + +function rises(s: string): number { + let n = 0; + for (let i = 1; i < s.length - 1; i++) + if (s.charCodeAt(i) > s.charCodeAt(i - 1) && s.charCodeAt(i) >= s.charCodeAt(i + 1)) n++; + return n; +} + +function backwards(s: string): number { + let h = 0; + for (let i = s.length - 1; i >= 0; i -= 3) h = (h * 7 + s.charCodeAt(i)) % 1000003; + return h; +} + +function pastEnd(s: string): string { + let sum = 0; + for (let i = 0; i <= s.length; i++) sum += s.charCodeAt(i); + return String(sum); +} + +function lineStarts(s: string): number[] { + const starts = [0]; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) starts.push(i + 1); + return starts; +} + +function wordUnits(s: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) if (isWordOrDigit(s.charCodeAt(i))) n++; + return n; +} + +function codeOf(t: string, i: number): number { + return t.charCodeAt(i % t.length); +} + +function mixed(s: string, t: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) n = (n + s.charCodeAt(i) * codeOf(t, i)) % 1000003; + return n; +} + +function sameAt(a: string, b: string): number { + let n = 0; + const end = Math.min(a.length, b.length); + for (let i = 0; i < end; i++) if (a.charCodeAt(i) === b.charCodeAt(i)) n++; + return n; +} + +function surrogates(s: string): string { + let high = 0, + low = 0; + for (let i = 0; i < s.length; i++) { + const c = s.charCodeAt(i); + if (c >= 0xd800 && c < 0xdc00) high++; + else if (c >= 0xdc00 && c < 0xe000) low++; + } + return `${high}/${low}`; +} + +function wordCount(s: string): number { + let pos = 0, + n = 0; + const end = s.length; + while (pos < end) { + while (pos < end && s.charCodeAt(pos) === 32) pos++; + if (pos >= end) break; + n++; + while (pos < end && s.charCodeAt(pos) !== 32) pos++; + } + return n; +} + +function wordUnitRuns(s: string): number { + let pos = 0, + total = 0; + while (pos < s.length) { + if (isWordUnit(s.charCodeAt(pos))) { + const start = pos; + while (pos < s.length && isWordOrDigit(s.charCodeAt(pos))) pos++; + total += pos - start; + } else pos = (pos + 1) | 0; + } + return total; +} + +function doubled(s: string): number { + let pos = 0, + n = 0; + while (pos < s.length) { + if (s.charCodeAt(pos) === s.charCodeAt(pos + 1)) n++; + pos += 1; + } + return n; +} + +class Scanner { + pos = 0; + src: string; + constructor(src: string) { + this.src = src; + } + line(): number { + const src = this.src; + const start = this.pos; + let pos = start; + while (pos < src.length && src.charCodeAt(pos) !== 10) pos++; + this.pos = pos + 1; + return pos - start; + } +} + +function lineLengths(s: string): number { + const scanner = new Scanner(s); + let total = 0, + lines = 0; + while (scanner.pos < s.length) { + total = (total * 31 + scanner.line()) % 1000003; + lines++; + } + return total * 1000 + lines; +} + +function remaining(s: string): number { + let pos = 0; + let sum = 0; + while (pos < s.length) { + if (s.charCodeAt(pos) === 32) sum += s.length - pos; + pos++; + } + return sum; +} + +function lastLineLength(s: string): number { + let last = s.length; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) last = s.length - i - 1; + return last; +} + +function from(s: string, start: number): number { + let pos = start; + let sum = 0; + while (pos < s.length) { + sum = sum * 3 + s.charCodeAt(pos); + pos++; + } + return sum; +} + +const words = ["Größe", "naïve", "façade", "東京", "ok", "✓", "mañana", "plain", "😀", "end\n"]; +function text(count: number, seed: number): string { + let out = ""; + for (let k = 0; k < count; k++) out += words[(k * 7 + seed) % words.length] + (k % 5 === 0 ? " " : " "); + return out; +} + +const big = text(3000, 1); +const other = text(2000, 4); +const ascii = big.replace(/[^\x00-\x7f]/g, "x"); +const short = "café ✓ 東京"; +const lone = "a\uD800b\uDFFFc " + big.slice(0, 500) + "\uD83D"; +for (let pass = 0; pass < 3; pass++) { + console.log( + pass, + spaces(big), + hash(big), + rises(big), + backwards(big), + pastEnd(big), + lineStarts(big).length, + wordUnits(big), + ); + console.log(" ", spaces(other), hash(other), spaces(big), hash(other), wordUnits(other)); + console.log(" ", mixed(big, other), sameAt(big, other), surrogates(big), surrogates(lone)); + console.log(" ", spaces(ascii), hash(ascii), spaces(short), hash(short), hash(lone), rises(lone)); + console.log(" ", wordCount(big), wordUnitRuns(big), doubled(big), lineLengths(big)); + console.log(" ", wordCount(ascii), wordUnitRuns(other), doubled(lone), lineLengths(lone), wordCount("")); + const fresh = text(40 + pass, pass) + "ü"; + console.log(" ", remaining(big), remaining(ascii), lastLineLength(big), lastLineLength(other)); + console.log(" ", from(fresh, fresh.length), from(fresh, fresh.length - 3), from(big, big.length - 4)); +} + +let grown = text(1500, 2); +for (let round = 0; round < 3; round++) { + console.log("grown", round, grown.length, spaces(grown), hash(grown), hash(grown)); + grown += text(200, round) + "Ünïcödé"; +} +console.log("empty", spaces(""), hash(""), pastEnd(""), lineStarts("").length); diff --git a/tests/harness/corpus-costs/7305-string-unit-loop-versions.ts.json b/tests/harness/corpus-costs/7305-string-unit-loop-versions.ts.json new file mode 100644 index 000000000..f40375eee --- /dev/null +++ b/tests/harness/corpus-costs/7305-string-unit-loop-versions.ts.json @@ -0,0 +1 @@ +{ "plain": 10500, "san": 14500 } From 8c94f6f10c4cd613e65eb7cab70e333c219bda35 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:10:56 -0500 Subject: [PATCH 041/395] Fix Date setter edge cases and the library profile fences --- .../lowering/builtins/date-classes.ts | 10 ++-- .../src/frontend/lowering/builtins/dates.ts | 9 ++-- .../src/frontend/lowering/date-values.ts | 37 +++++++++++-- .../frontend/lowering/expressions/binary.ts | 22 ++++++++ .../corpus/8027-date-setter-order.ts.json | 6 +++ .../8028-date-setter-order-dynamic.mjs.json | 6 +++ tests/corpus/8027-date-setter-order.ts | 53 +++++++++++++++++++ .../corpus/8028-date-setter-order-dynamic.mjs | 17 ++++++ .../8027-date-setter-order.ts.json | 1 + .../8028-date-setter-order-dynamic.mjs.json | 1 + tests/harness/library-profile.test.ts | 1 + 11 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8027-date-setter-order.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8028-date-setter-order-dynamic.mjs.json create mode 100644 tests/corpus/8027-date-setter-order.ts create mode 100644 tests/corpus/8028-date-setter-order-dynamic.mjs create mode 100644 tests/harness/corpus-costs/8027-date-setter-order.ts.json create mode 100644 tests/harness/corpus-costs/8028-date-setter-order-dynamic.mjs.json diff --git a/packages/compiler/src/frontend/lowering/builtins/date-classes.ts b/packages/compiler/src/frontend/lowering/builtins/date-classes.ts index c8aab8687..9d152de56 100644 --- a/packages/compiler/src/frontend/lowering/builtins/date-classes.ts +++ b/packages/compiler/src/frontend/lowering/builtins/date-classes.ts @@ -255,17 +255,19 @@ export function lowerInheritedDateMethodCall( loc, }); if (isDateSetter(name) && name !== "setTime") { - // The field setters change the Date the class's time slot holds. + // The field setters change the Date the class's time slot holds once + // the arguments are evaluated: an argument that calls setTime on the + // instance gives the slot a new Date. const date = lowerer.declareHiddenLocal("%dateTime", DATE_T); + const current = varRef(date.id, DATE_T, loc); + const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [current], type: F64, loc }; + const next = dateSetterTime(lowerer, call, name, stmts, time); stmts.push({ kind: "varDecl", localId: date.id, init: timeOf(lowerer, value, root, loc), loc, }); - const current = varRef(date.id, DATE_T, loc); - const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [current], type: F64, loc }; - const next = dateSetterTime(lowerer, call, name, stmts, time); return done({ kind: "libCall", fn: "date.store", args: [current, next], type: F64, loc }); } if (name === "setTime") { diff --git a/packages/compiler/src/frontend/lowering/builtins/dates.ts b/packages/compiler/src/frontend/lowering/builtins/dates.ts index 79696d1ba..075047606 100644 --- a/packages/compiler/src/frontend/lowering/builtins/dates.ts +++ b/packages/compiler/src/frontend/lowering/builtins/dates.ts @@ -342,9 +342,9 @@ function lowerDateSetter( /** The new time value of setter `name` called with `call`'s arguments on a * Date whose time value `time` reads: the arguments evaluate into * temporaries appended to `stmts` (ToNumber of each, an undefined one is - * NaN), and the result reads `time` after them. Missing optional arguments - * keep their fields; arguments past a setter's last one are evaluated and - * ignored. */ + * NaN), and the result reads `time` after them. A missing first argument + * is NaN; missing optional arguments keep their fields; arguments past a + * setter's last one are evaluated and ignored. */ export function dateSetterTime( lowerer: Lowerer, call: ts.CallExpression, @@ -374,7 +374,8 @@ export function dateSetterTime( loc, }; const setter = DATE_FIELD_SETTERS.get(name)!; - const used = args.slice(0, setter.max); + // The first argument is required: a missing one is undefined, so NaN. + const used = args.length === 0 ? [number(Number.NaN)] : args.slice(0, setter.max); const operands = [...used]; while (operands.length < 4) operands.push(number(0)); return { diff --git a/packages/compiler/src/frontend/lowering/date-values.ts b/packages/compiler/src/frontend/lowering/date-values.ts index 669bc1dbc..f769e0866 100644 --- a/packages/compiler/src/frontend/lowering/date-values.ts +++ b/packages/compiler/src/frontend/lowering/date-values.ts @@ -102,7 +102,7 @@ function decideTimeValueDate(lowerer: Lowerer, decl: ts.VariableDeclaration): bo if (!ok) return; if (ts.isIdentifier(node) && node !== decl.name && node.text === decl.name.text) { if (lowerer.checker.getSymbolAtLocation(node) === symbol) { - const use = enclosingFunction(node) === fn ? timeValueUse(lowerer, node) : null; + const use = enclosingFunction(node) === fn ? timeValueUse(lowerer, node, symbol) : null; if (use === null) ok = false; else if (use === "set") sets = true; } @@ -174,7 +174,11 @@ function enclosingFunction(node: ts.Node): ts.FunctionLikeDeclaration | null { /** How a use of the binding uses its time value: "read" for a use that * reads only the time value, "set" for a setter call that replaces it, or * null for any other use. */ -function timeValueUse(lowerer: Lowerer, id: ts.Identifier): "read" | "set" | null { +function timeValueUse( + lowerer: Lowerer, + id: ts.Identifier, + symbol: ts.Symbol, +): "read" | "set" | null { let node: ts.Node = id; while (ts.isParenthesizedExpression(node.parent)) node = node.parent; const parent = node.parent; @@ -192,12 +196,34 @@ function timeValueUse(lowerer: Lowerer, id: ts.Identifier): "read" | "set" | nul if (isDateSetter(parent.name.text)) return "set"; return TIME_VALUE_METHODS.has(parent.name.text) ? "read" : null; } - return readsTimeValue(lowerer, node, parent) ? "read" : null; + return readsTimeValue(lowerer, node, parent, symbol) ? "read" : null; +} + +/** True when `node` calls a setter on the binding `symbol` names. */ +function callsSetterOf(lowerer: Lowerer, node: ts.Node, symbol: ts.Symbol): boolean { + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + isDateSetter(node.expression.name.text) + ) { + let receiver = node.expression.expression; + while (ts.isParenthesizedExpression(receiver)) receiver = receiver.expression; + if (ts.isIdentifier(receiver) && lowerer.checker.getSymbolAtLocation(receiver) === symbol) + return true; + } + return ( + ts.forEachChild(node, (child) => callsSetterOf(lowerer, child, symbol) || undefined) ?? false + ); } /** A use of the binding, other than a method call, that reads only its * time value. */ -function readsTimeValue(lowerer: Lowerer, node: ts.Node, parent: ts.Node): boolean { +function readsTimeValue( + lowerer: Lowerer, + node: ts.Node, + parent: ts.Node, + symbol: ts.Symbol, +): boolean { if (ts.isTemplateSpan(parent)) return parent.expression === node; // Unary + and -, the number hint. if (ts.isPrefixUnaryExpression(parent)) @@ -222,6 +248,9 @@ function readsTimeValue(lowerer: Lowerer, node: ts.Node, parent: ts.Node): boole ); } if (ts.isBinaryExpression(parent)) { + // A left operand converts after the right one is evaluated: a setter + // there changes the time value the comparison or concatenation reads. + if (parent.left === node && callsSetterOf(lowerer, parent.right, symbol)) return false; const other = parent.left === node ? parent.right : parent.left; const otherKind = lowerer.mapTypeOf(lowerer.typeOf(other))?.kind; if (RELATIONAL.has(parent.operatorToken.kind)) return otherKind === "date"; diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index de6648836..590fa745b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -2037,6 +2037,28 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr loc, }; if (left.type.kind === "string" || right.type.kind === "string") { + // A Date on the left converts after the right operand is + // evaluated, which can run its setters. + if (left.type.kind === "date" && !isSafeToRepeat(right)) { + const date = lowerer.declareHiddenLocal("%plus", left.type); + const other = lowerer.declareHiddenLocal("%plus", right.type); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: date.id, init: left, loc }, + { kind: "varDecl", localId: other.id, init: right, loc }, + ], + result: { + kind: "strConcat", + left: concatOperand(lowerer, varRef(date.id, left.type, loc), expr.left), + right: concatOperand(lowerer, varRef(other.id, right.type, loc), expr.right), + type: STRING, + loc, + }, + type: STRING, + loc, + }; + } return { kind: "strConcat", left: concatOperand(lowerer, left, expr.left), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8027-date-setter-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8027-date-setter-order.ts.json new file mode 100644 index 000000000..cfdfddfc6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8027-date-setter-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8027-date-setter-order.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8028-date-setter-order-dynamic.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8028-date-setter-order-dynamic.mjs.json new file mode 100644 index 000000000..0a1b69c5f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8028-date-setter-order-dynamic.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8028-date-setter-order-dynamic.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/8027-date-setter-order.ts b/tests/corpus/8027-date-setter-order.ts new file mode 100644 index 000000000..d0a175c2f --- /dev/null +++ b/tests/corpus/8027-date-setter-order.ts @@ -0,0 +1,53 @@ +// Date setters and evaluation order: a setter called without arguments +// makes the Date invalid, an argument that calls setTime on the same +// Date subclass instance is seen by the setter, and a comparison or +// concatenation reads its left Date after the right side runs a setter. +export {}; +process.env.TZ = "UTC"; + +const show = (label: string, d: Date): void => { + console.log(label, d.getTime(), Number.isNaN(d.getTime()) ? "invalid" : d.toISOString()); +}; + +// No arguments: the first argument is undefined, so NaN. +const local = new Date(Date.UTC(2026, 5, 15, 9, 30)); +// @ts-expect-error: the first argument is required +console.log(local.setHours(), Number.isNaN(local.getTime())); +const utc = new Date(Date.UTC(2026, 5, 15, 9, 30)); +// @ts-expect-error: the first argument is required +console.log(utc.setUTCMonth(), utc.setUTCFullYear(2020)); +show("utc after", utc); +const kept = new Date(Date.UTC(2026, 5, 15)); +const keep = kept; +// @ts-expect-error: the first argument is required +console.log(keep.setMinutes(), kept.getTime()); + +class Stamp extends Date { + label(): string { + return `stamp ${this.toISOString()}`; + } +} +const stamp = new Stamp(Date.UTC(2026, 0, 1)); +// @ts-expect-error: the first argument is required +console.log(stamp.setSeconds(), Number.isNaN(stamp.getTime())); +stamp.setTime(Date.UTC(2026, 0, 2)); +// @ts-expect-error: the first argument is required +console.log(stamp.setUTCDate(), stamp.setUTCFullYear(2024, 1, 29), stamp.label()); + +// An argument that calls setTime on the same instance. +const moved = new Stamp(Date.UTC(2026, 3, 10, 12)); +console.log(moved.setUTCHours((moved.setTime(Date.UTC(2000, 0, 1)), 5)), moved.label()); +const plain = new Date(Date.UTC(2026, 3, 10, 12)); +console.log(plain.setUTCMinutes((plain.setTime(Date.UTC(2001, 0, 1)), 7))); +show("plain", plain); + +// The left operand converts after the right operand runs a setter. +const a = new Date(Date.UTC(2026, 0, 1)); +const b = new Date(Date.UTC(2026, 0, 2)); +console.log(a < (a.setUTCDate(3), b), a > b); +const c = new Date(Date.UTC(2026, 0, 1)); +console.log(c >= (c.setTime(0), new Date(1)), c <= new Date(1)); +const s = new Date(Date.UTC(2026, 0, 1)); +console.log(s + (s.setUTCFullYear(1999), "!")); +const n = new Date(Date.UTC(2026, 0, 1)); +console.log(+n === (n.setTime(5), 5), +n); diff --git a/tests/corpus/8028-date-setter-order-dynamic.mjs b/tests/corpus/8028-date-setter-order-dynamic.mjs new file mode 100644 index 000000000..f52e410c1 --- /dev/null +++ b/tests/corpus/8028-date-setter-order-dynamic.mjs @@ -0,0 +1,17 @@ +// Date setters reached through untyped values: without arguments each +// one makes the Date invalid, and an argument that calls setTime on the +// same Date is seen by the setter. +process.env.TZ = "UTC"; + +const names = [ + "setMilliseconds", "setSeconds", "setMinutes", "setHours", "setDate", "setMonth", "setFullYear", + "setUTCMilliseconds", "setUTCSeconds", "setUTCMinutes", "setUTCHours", "setUTCDate", "setUTCMonth", "setUTCFullYear", +]; +for (const name of names) { + const d = new Date(Date.UTC(2026, 5, 15)); + console.log(name, d[name](), d.getTime()); +} +const d = new Date(Date.UTC(2026, 3, 10, 12)); +console.log(d.setUTCHours((d.setTime(Date.UTC(2000, 0, 1)), 5)), d.toISOString()); +const a = new Date(Date.UTC(2026, 0, 1)); +console.log(a < (a.setUTCDate(3), new Date(Date.UTC(2026, 0, 2)))); diff --git a/tests/harness/corpus-costs/8027-date-setter-order.ts.json b/tests/harness/corpus-costs/8027-date-setter-order.ts.json new file mode 100644 index 000000000..aff4bec01 --- /dev/null +++ b/tests/harness/corpus-costs/8027-date-setter-order.ts.json @@ -0,0 +1 @@ +{ "plain": 2489, "san": 3288 } diff --git a/tests/harness/corpus-costs/8028-date-setter-order-dynamic.mjs.json b/tests/harness/corpus-costs/8028-date-setter-order-dynamic.mjs.json new file mode 100644 index 000000000..a7aeb13a5 --- /dev/null +++ b/tests/harness/corpus-costs/8028-date-setter-order-dynamic.mjs.json @@ -0,0 +1 @@ +{ "plain": 2430, "san": 2975 } diff --git a/tests/harness/library-profile.test.ts b/tests/harness/library-profile.test.ts index b39daefa1..9ced69b5c 100644 --- a/tests/harness/library-profile.test.ts +++ b/tests/harness/library-profile.test.ts @@ -563,6 +563,7 @@ describe("library profile fences", () => { "stdlib.date.getUTCSeconds", "stdlib.date.now", "stdlib.date.parse", + "stdlib.date.setters", "stdlib.date.toDateString", "stdlib.date.toISOString", "stdlib.date.toString", From 0309a211061953c20cdfd6c7c7b2438591b67d63 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:29:39 -0500 Subject: [PATCH 042/395] Fix the tsgo and tsc-ts IR identity failure --- tests/corpus/7455-tls-als-contexts.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/corpus/7455-tls-als-contexts.mjs b/tests/corpus/7455-tls-als-contexts.mjs index 38783fdf9..bc59a78d4 100644 --- a/tests/corpus/7455-tls-als-contexts.mjs +++ b/tests/corpus/7455-tls-als-contexts.mjs @@ -21,7 +21,7 @@ const server = tls.createServer({ cert, key }, (socket) => { server.on("tlsClientError", (err) => serverEvents.push(`tlsClientError ${err.code} ${als.getStore()}`)); await als.run("listen", () => new Promise((resolve) => server.listen(0, "127.0.0.1", () => { serverEvents.push(`listening ${als.getStore()}`); - resolve(); + resolve(undefined); }))); const { port } = server.address(); await als.run("client", () => new Promise((resolve) => { @@ -32,7 +32,7 @@ await als.run("client", () => new Promise((resolve) => { client.on("data", () => clientEvents.push(`data ${als.getStore()}`)); client.on("close", () => { clientEvents.push(`close ${als.getStore()}`); - resolve(); + resolve(undefined); }); })); await als.run("untrusted", () => new Promise((resolve) => { From 82ebbd4df7f88682634541516213345369044eea Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:31:58 -0500 Subject: [PATCH 043/395] Fix the species trace, deep release, iteration bound and optional length findings --- packages/compiler/src/backend/llvm/classes.ts | 18 ++- .../lowering/builtins/array-classes.ts | 127 +++++++++++++----- .../lowering/containers/array-callbacks.ts | 30 +++++ .../7730-array-subclass-retyped-trace.ts.json | 6 + .../7731-array-subclass-deep-release.ts.json | 6 + .../7732-array-subclass-species-bound.ts.json | 6 + ...733-array-subclass-optional-length.ts.json | 6 + packages/runtime/src/scr_array.c | 19 ++- packages/runtime/src/scr_array.h | 7 +- .../7730-array-subclass-retyped-trace.ts | 33 +++++ .../7731-array-subclass-deep-release.ts | 17 +++ .../7732-array-subclass-species-bound.ts | 32 +++++ .../7733-array-subclass-optional-length.ts | 26 ++++ tests/diagnostics/array-subclass-fences.ts | 6 +- .../array-subclass-fences.ts.txt | 62 +++++---- .../7730-array-subclass-retyped-trace.ts.json | 1 + .../7731-array-subclass-deep-release.ts.json | 1 + .../7732-array-subclass-species-bound.ts.json | 1 + ...733-array-subclass-optional-length.ts.json | 1 + 19 files changed, 334 insertions(+), 71 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json create mode 100644 tests/corpus/7730-array-subclass-retyped-trace.ts create mode 100644 tests/corpus/7731-array-subclass-deep-release.ts create mode 100644 tests/corpus/7732-array-subclass-species-bound.ts create mode 100644 tests/corpus/7733-array-subclass-optional-length.ts create mode 100644 tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json create mode 100644 tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json create mode 100644 tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json create mode 100644 tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index d848b86fc..e80370405 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -1481,13 +1481,11 @@ export function emitClassShapes( tracedFields.forEach((f, i) => { tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name))); }); - // An Array subclass instance's elements, when they are cycle-capable. - const tracedElements = - cls.arrayElem !== undefined && - traceAdapter(host, { kind: "array", elem: cls.arrayElem }) !== null; - if (tracedElements) { - host.declare(`declare void @scr_arr_trace_v(ptr, ptr, ptr)`); - tr.push(` call void @scr_arr_trace_v(ptr %o, ptr %visit, ptr %ctx) ; the elements`); + // An Array subclass instance's elements, when its current element + // storage is cycle-capable (a species result can retype it). + if (cls.arrayElem !== undefined) { + host.declare(`declare void @scr_arr_subclass_trace(ptr, ptr, ptr)`); + tr.push(` call void @scr_arr_subclass_trace(ptr %o, ptr %visit, ptr %ctx) ; the elements`); } tr.push(` ret void`, `}`, ``); defs.push(...tr); @@ -1508,9 +1506,9 @@ export function emitClassShapes( if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw")); if (cls.arrayElem !== undefined) { // The elements: traced ones are the collector's, the others are - // released with the storage. - host.declare(`declare void @scr_arr_subclass_gc_drop(ptr, i1 zeroext)`); - gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o, i1 ${tracedElements})`); + // released with the storage (the same test as the trace's). + host.declare(`declare void @scr_arr_subclass_gc_drop(ptr)`); + gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o)`); } gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``); defs.push(...gf); diff --git a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts index c4ae4a60d..b416a8bec 100644 --- a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts +++ b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts @@ -12,6 +12,7 @@ import { isJsSourceFile, locOf } from "../../program.js"; import { stripTypeWrappers } from "../typed-arguments.js"; import { dynUndefinedExpr, nodeThrowExpr } from "../../../ir/build.js"; import { lowerArrayFromCall, lowerArrayOfCall } from "../containers/array-construction.js"; +import { boundedArrayHofHelper } from "../containers/array-callbacks.js"; import { storedClassValueType, type ClassInfo } from "../lower-classes.js"; import { BOOL, @@ -34,6 +35,18 @@ export function arrayClassBase( lowerer: Lowerer, heritage: ts.ExpressionWithTypeArguments, ): { elem: IrType } { + const decl = heritage.parent?.parent; + const self = + decl && ts.isClassLikeDeclaration(decl) + ? lowerer.checker.getSymbolAtLocation(decl.name ?? decl) + : undefined; + const element = heritage.typeArguments?.nodes[0]; + if ( + self && + element && + mentionsClass(lowerer, lowerer.checker.getTypeAtLocation(element), self, new Set()) + ) + lowerer.unsupported("SC1090", heritage, "classes that extend an Array of their own instances"); const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(heritage)); // `extends Array` in JavaScript reads `any[]`: dyn elements. if (isJsSourceFile(ts.getSourceFileOfNode(heritage)) && mapped?.kind !== "array") @@ -47,6 +60,26 @@ export function arrayClassBase( return { elem: mapped!.kind === "array" ? mapped!.elem : DYN }; } +/** Whether `type` is the class `self`'s instance type, or holds it through + * unions, intersections or type arguments (`Group | null`, `Group[]`, + * `Map`): the class's array type would contain itself. + * Another class's members do not count, its instances are references. */ +function mentionsClass( + lowerer: Lowerer, + type: ts.Type, + self: ts.Symbol, + seen: Set, +): boolean { + if (seen.has(type)) return false; + seen.add(type); + if (type.getSymbol() === self) return true; + if (type.isUnionType() || type.isIntersectionType()) + return ts.constituentTypes(type).some((part) => mentionsClass(lowerer, part, self, seen)); + return lowerer.checker + .getTypeArguments(type as ts.TypeReference) + .some((argument) => mentionsClass(lowerer, argument, self, seen)); +} + /** The class declared `extends Array` at the root of `info`'s chain, or null. */ export function arrayRootOf(info: ClassInfo | null | undefined): ClassInfo | null { for (let c = info ?? null; c; c = c.base) if (c.extendsArray) return c; @@ -349,6 +382,15 @@ export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunctio * take those arguments: they must be none, a forwarding rest (dyn or * number), or a number first parameter whose others may be omitted (whose * no-argument form throws). */ +/** A constructor parameter the species length argument can fill: a + * number, a checked-dynamic value, or a union with a number arm + * (`length?: number`). */ +function takesLength(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "f64" || type.kind === "dyn") return true; + if (type.kind !== "union") return false; + return lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === "f64") === true; +} + export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { const elem = arrayRootOf(info)!.extendsArray!.elem; const arrT = arrayOf(elem); @@ -381,31 +423,35 @@ export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFuncti withLength = [{ kind: "arrayLit", elems: [n()], type: params[0]!.type, loc } as IrExpr]; without = [{ kind: "arrayLit", elems: [], type: params[0]!.type, loc } as IrExpr]; } else if ( - params[0]!.mode === "required" && - (params[0]!.type.kind === "f64" || params[0]!.type.kind === "dyn") && + (params[0]!.mode === "required" || params[0]!.mode === "omittable") && + takesLength(lowerer, params[0]!.type) && params.slice(1).every((p) => p.mode === "omittable") ) { - const rest = params - .slice(1) - .map((p) => - lowerer.coerceToExpected( - { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, - p.type, - ), - ); - // A dyn first parameter (a JavaScript `constructor(size)`) takes the - // length as a number, and undefined in the no-argument form. - if (params[0]!.type.kind === "dyn") without = [dynUndefinedExpr(loc), ...rest.map((e) => e)]; - withLength = [ - params[0]!.type.kind === "dyn" ? { kind: "dynFrom", value: n(), type: DYN, loc } : n(), - ...params - .slice(1) - .map((p) => - lowerer.coerceToExpected( + // An omitted argument is what a call without it passes: the + // parameter's default, or undefined. + const blame = info.decl ?? null; + const omitted = (p: (typeof params)[number]): IrExpr => + p.callDefault ?? + (blame + ? lowerer.undefinedArgFor(p.type, loc, blame) + : lowerer.coerceToExpected( { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, p.type, - ), - ), + )); + const rest = params.slice(1).map(omitted); + // A dyn first parameter (a JavaScript `constructor(size)`) and an + // optional or defaulted one take the length as a number, and their + // omitted value in the no-argument form. + if (params[0]!.type.kind === "dyn" || params[0]!.mode === "omittable") + without = [ + params[0]!.type.kind === "dyn" ? dynUndefinedExpr(loc) : omitted(params[0]!), + ...rest, + ]; + withLength = [ + params[0]!.type.kind === "dyn" + ? { kind: "dynFrom", value: n(), type: DYN, loc } + : lowerer.coerceToExpected(n(), params[0]!.type), + ...params.slice(1).map(omitted), ]; } if (withLength === null) return null; @@ -517,14 +563,17 @@ export function lowerWithArraySpecies( const receiverRef: IrExpr = { kind: "varRef", localId: local.id, type: receiver.type, loc }; if (mode.callback) { // The species constructor runs before the first callback: the - // arguments are evaluated in order, then the instance is created (with - // the receiver's length for map, 0 otherwise), then the method runs and - // its elements move into the instance. - if ( - result.kind !== "call" || - result.args[0]?.kind !== "varRef" || - result.args[0].localId !== local.id - ) + // arguments are evaluated in order, the receiver's length is read, the + // instance is created (with that length for map, 0 otherwise), then + // the method runs to that length and its elements move into the + // instance. + const bounded = + result.kind === "call" && + result.args[0]?.kind === "varRef" && + result.args[0].localId === local.id + ? boundedArrayHofHelper(lowerer, result.callee) + : null; + if (bounded === null || result.kind !== "call") lowerer.unsupported( "SC1090", call, @@ -537,9 +586,25 @@ export function lowerWithArraySpecies( stmts.push({ kind: "varDecl", localId: held.id, init: arg, loc }); args.push({ kind: "varRef", localId: held.id, type: arg.type, loc }); } + const bound = lowerer.declareHiddenLocal("%speciesLength", F64); + stmts.push({ + kind: "varDecl", + localId: bound.id, + init: { + kind: "arrIntrinsic", + method: "length", + receiver: receiverRef, + args: [], + type: F64, + loc, + }, + loc, + }); + const boundRef: IrExpr = { kind: "varRef", localId: bound.id, type: F64, loc }; + args.push(boundRef); const instance = lowerer.declareHiddenLocal("%speciesInstance", receiver.type); const length: IrExpr = mode.resultLength - ? { kind: "arrIntrinsic", method: "length", receiver: receiverRef, args: [], type: F64, loc } + ? boundRef : { kind: "numLit", value: 0, type: F64, loc }; stmts.push({ kind: "varDecl", @@ -561,7 +626,7 @@ export function lowerWithArraySpecies( fn: "array.speciesFinish", args: [ { kind: "varRef", localId: instance.id, type: receiver.type, loc }, - { ...result, args }, + { ...result, callee: bounded, args }, { kind: "boolLit", value: mode.setLength, type: BOOL, loc }, ], type: result.type, diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index edf65ab2b..923224950 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -3171,3 +3171,33 @@ export function lowerTuplePredicateCall( loc, }; } + +/** The `%arr.` helper `name` (map, flatMap or filter) taking its + * iteration bound as a last parameter: the loop runs to the bound instead + * of the receiver's length when it starts. ArraySpeciesCreate reads the + * receiver's length before the species constructor runs, so a constructor + * that appends to the receiver does not extend the loop + * (lowerWithArraySpecies). Null when `name` is no such helper. */ +export function boundedArrayHofHelper(lowerer: Lowerer, name: string): string | null { + const info = lowerer.arrHofInfo.get(name); + if (!info || info.method === "forEach") return null; + const bounded = `${name}.bounded`; + if (lowerer.liftedFnNamed(bounded)) return bounded; + const fn = lowerer.liftedFnNamed(name); + if (!fn) return null; + let replaced = false; + const body = fn.body.map((stmt): IrStmt => { + if (replaced || stmt.kind !== "varDecl" || stmt.localId !== "n.0") return stmt; + replaced = true; + return { ...stmt, init: varRef("bound.0", F64, stmt.loc) }; + }); + if (!replaced) return null; + lowerer.liftedFns.push({ + ...fn, + name: bounded, + params: [...fn.params, { localId: "bound.0", name: "bound", type: F64 }], + locals: [...fn.locals, { id: "bound.0", name: "bound", type: F64, mutable: false }], + body, + }); + return bounded; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a02b66c5a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7730-array-subclass-retyped-trace.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..76a9b3988 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7731-array-subclass-deep-release.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..c36a8a1ba --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7732-array-subclass-species-bound.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..96cb08414 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7733-array-subclass-optional-length.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 3b243dbb0..40e0580a4 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1868,8 +1868,21 @@ const ScrArrClass *scr_arr_class_of(const ScrArr *a) { return scr_arr_ext_of((ScrArr *)a)->cls; } -void scr_arr_subclass_gc_drop(ScrArr *a, bool traced) { - if (!traced) scr_arr_release_contents(a); +/* Whether an Array subclass instance's elements are the collector's: its + * current element ops trace them. A species result can retype an empty + * instance's storage (scr_arr_species_move), so this follows the storage, + * not the element type the class declares. */ +static bool scr_arr_subclass_elements_traced(const ScrArr *a) { + const ScrArrOps *ops = scr_arr_ops(a); + return ops != NULL && ops->trace != NULL; +} + +void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx) { + if (scr_arr_subclass_elements_traced((ScrArr *)a)) scr_arr_trace_v(a, visit, ctx); +} + +void scr_arr_subclass_gc_drop(ScrArr *a) { + if (!scr_arr_subclass_elements_traced(a)) scr_arr_release_contents(a); scr_arr_free_parts(a); a->data = NULL; a->len = a->cap = a->filled = 0; @@ -1906,7 +1919,7 @@ void scr_arr_release(ScrArr *a) { /* An Array subclass instance: its class tears it down. */ if ((a->spare & SCR_ARR_HAS_EXT) && scr_arr_ext_of(a)->cls) { if (a->cyc) scr_cyc_dead(a); - scr_arr_ext_of(a)->cls->destroy(a); + scr_rc_destroy(a, scr_arr_ext_of(a)->cls->destroy); return; } /* Nothing to tear down: free the storage without scr_rc_destroy's diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index 56b7511e2..84057ffc9 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -185,7 +185,7 @@ typedef struct ScrArrClass { const char *name; /* Destroys an instance whose count reached zero: releases the class's own * fields, then scr_arr_subclass_drop, then frees the instance. */ - void (*destroy)(ScrArr *a); + void (*destroy)(void *a); /* The class's own fields as a dyn object in declaration order (the array * is passed owned, +1), or NULL when the class declares none. util.inspect * and Object.keys list them after the elements. */ @@ -503,7 +503,10 @@ void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const bool cyc); /* The collector's teardown of an Array subclass instance's storage: the * elements are released unless they are the collector's (`traced`). */ -void scr_arr_subclass_gc_drop(ScrArr *a, bool traced); +void scr_arr_subclass_gc_drop(ScrArr *a); +/* The collector trace of an Array subclass instance's elements: visits + * them when its current element ops trace them. */ +void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx); /* super(...args) in a class that extends Array (the Array constructor's * length-or-elements rule); `args` is borrowed. */ void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems); diff --git a/tests/corpus/7730-array-subclass-retyped-trace.ts b/tests/corpus/7730-array-subclass-retyped-trace.ts new file mode 100644 index 000000000..d1546c0af --- /dev/null +++ b/tests/corpus/7730-array-subclass-retyped-trace.ts @@ -0,0 +1,33 @@ +// A cycle-capable class that extends Array (its elements and its own field +// can close a cycle) whose map results take other element types: the +// collector traces and tears down each instance by the elements it holds. +// Garbage cycles make the collector run while the results are alive. +class Holder { + group: Group | null = null; +} + +class Group extends Array { + owner: Holder | null = null; +} + +function churn(rounds: number): number { + let made = 0; + for (let i = 0; i < rounds; i++) { + const holder = new Holder(); + const group = new Group(); + group.owner = holder; + holder.group = group; + group.push(holder); + made++; + } + return made; +} + +const groups = new Group(); +groups.push(new Holder(), null, new Holder()); +const numbers = groups.map((_, i) => i * 1.5 + 1e6); +const labels = groups.map((_, i) => `label-${i}`); +console.log(churn(20000)); +console.log(numbers instanceof Group, numbers.length, numbers.join(",")); +console.log(labels instanceof Group, labels.join(",")); +console.log(churn(20000), numbers[2], labels[1]); diff --git a/tests/corpus/7731-array-subclass-deep-release.ts b/tests/corpus/7731-array-subclass-deep-release.ts new file mode 100644 index 000000000..6e4b4c352 --- /dev/null +++ b/tests/corpus/7731-array-subclass-deep-release.ts @@ -0,0 +1,17 @@ +// Releasing a long chain of instances of a class that extends Array, each +// holding the next one, tears them down without growing the native stack. +class Link extends Array { + next: Link | null = null; +} + +let head: Link | null = new Link(); +let tail = head; +for (let i = 0; i < 300000; i++) { + const link = new Link(); + link.push(i); + tail.next = link; + tail = link; +} +console.log(tail.length, tail[0]); +head = null; +console.log("released", head); diff --git a/tests/corpus/7732-array-subclass-species-bound.ts b/tests/corpus/7732-array-subclass-species-bound.ts new file mode 100644 index 000000000..715c51165 --- /dev/null +++ b/tests/corpus/7732-array-subclass-species-bound.ts @@ -0,0 +1,32 @@ +// map, filter and flatMap read the receiver's length before the species +// constructor runs: elements the constructor appends to the receiver are +// not visited. +let watched: Growing | null = null; + +class Growing extends Array { + constructor(...args: number[]) { + super(...args); + if (watched !== null) watched.push(watched.length * 100); + } +} + +const source = Growing.from([1, 2, 3]); +watched = source; +const visits: number[] = []; +const doubled = source.map((value) => { + visits.push(value); + return value * 2; +}); +console.log(doubled.join(","), visits.join(","), source.join(",")); +visits.length = 0; +const kept = source.filter((value) => { + visits.push(value); + return value > 1; +}); +console.log(kept.join(","), visits.join(","), source.length); +visits.length = 0; +const spread = source.flatMap((value) => { + visits.push(value); + return [value]; +}); +console.log(spread.length, visits.length, source.length); diff --git a/tests/corpus/7733-array-subclass-optional-length.ts b/tests/corpus/7733-array-subclass-optional-length.ts new file mode 100644 index 000000000..302fc22cb --- /dev/null +++ b/tests/corpus/7733-array-subclass-optional-length.ts @@ -0,0 +1,26 @@ +// A class that extends Array whose constructor's length parameter is +// optional or has a default takes the species length argument: map, +// filter and slice construct their results through it. +class Optional extends Array { + constructor(length?: number) { + super(length ?? 0); + } +} + +class Defaulted extends Array { + tag: string; + constructor(length = 0, tag = "none") { + super(length); + this.tag = tag; + } +} + +const optional = Optional.from([1, 2, 3]); +const mapped = optional.map((value) => value * 10); +console.log(mapped instanceof Optional, mapped.join(","), optional.slice(1) instanceof Optional); +console.log(optional.filter((value) => value !== 2).join(",")); + +const defaulted = new Defaulted(2).fill("x"); +const sliced = defaulted.slice(0, 1); +if (sliced instanceof Defaulted) console.log(sliced.length, sliced.tag); +console.log(defaulted.map((s) => s + "!").join(",")); diff --git a/tests/diagnostics/array-subclass-fences.ts b/tests/diagnostics/array-subclass-fences.ts index cc2966201..478e615f9 100644 --- a/tests/diagnostics/array-subclass-fences.ts +++ b/tests/diagnostics/array-subclass-fences.ts @@ -1,6 +1,7 @@ // Array subclasses that do not compile yet, each refused where it is // declared: a generic subclass, a subclass of a class that extends Array, -// and a class that declares its own Symbol.species. +// a class that declares its own Symbol.species, and a class whose elements +// are its own instances. class Bag extends Array { firstOr(fallback: T): T { return this.length > 0 ? this[0]! : fallback; @@ -16,6 +17,9 @@ class Speciated extends Array { } } +class Nested extends Array {} + console.log(new Bag().firstOr(1)); console.log(new Derived().length); console.log(new Speciated().length); +console.log(new Nested().length); diff --git a/tests/harness/__snapshots__/array-subclass-fences.ts.txt b/tests/harness/__snapshots__/array-subclass-fences.ts.txt index 3d81a58a9..97e7bdc8d 100644 --- a/tests/harness/__snapshots__/array-subclass-fences.ts.txt +++ b/tests/harness/__snapshots__/array-subclass-fences.ts.txt @@ -1,41 +1,55 @@ -array-subclass-fences.ts:4:22 - error SC1090: a generic class that extends Array is not supported yet +array-subclass-fences.ts:5:22 - error SC1090: a generic class that extends Array is not supported yet - 3 | // and a class that declares its own Symbol.species. - 4 | class Bag extends Array { + 4 | // are its own instances. + 5 | class Bag extends Array { | ^~~~~~~~ - 5 | firstOr(fallback: T): T { + 6 | firstOr(fallback: T): T { -array-subclass-fences.ts:11:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet +array-subclass-fences.ts:12:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet - 10 | class Base extends Array {} - 11 | class Derived extends Base {} + 11 | class Base extends Array {} + 12 | class Derived extends Base {} | ^~~~ - 12 | + 13 | -array-subclass-fences.ts:14:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet +array-subclass-fences.ts:15:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet - 13 | class Speciated extends Array { - 14 | static get [Symbol.species](): ArrayConstructor { + 14 | class Speciated extends Array { + 15 | static get [Symbol.species](): ArrayConstructor { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 15 | return Array; + 16 | return Array; -array-subclass-fences.ts:19:13 - error SC1090: constructing values other than classes declared in the program is not supported yet +array-subclass-fences.ts:20:22 - error SC1090: classes that extend an Array of their own instances are not supported yet - 18 | - 19 | console.log(new Bag().firstOr(1)); + 19 | + 20 | class Nested extends Array {} + | ^~~~~~~~~~~~~~~~~~~~ + 21 | + +array-subclass-fences.ts:22:13 - error SC1090: constructing values other than classes declared in the program is not supported yet + + 21 | + 22 | console.log(new Bag().firstOr(1)); | ^~~~~~~~~~~~~~~~~ - 20 | console.log(new Derived().length); + 23 | console.log(new Derived().length); -array-subclass-fences.ts:20:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet +array-subclass-fences.ts:23:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet - 19 | console.log(new Bag().firstOr(1)); - 20 | console.log(new Derived().length); + 22 | console.log(new Bag().firstOr(1)); + 23 | console.log(new Derived().length); | ^~~~~~~~~~~~~ - 21 | console.log(new Speciated().length); + 24 | console.log(new Speciated().length); -array-subclass-fences.ts:21:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet +array-subclass-fences.ts:24:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet - 20 | console.log(new Derived().length); - 21 | console.log(new Speciated().length); + 23 | console.log(new Derived().length); + 24 | console.log(new Speciated().length); | ^~~~~~~~~~~~~~~ - 22 | \ No newline at end of file + 25 | console.log(new Nested().length); + +array-subclass-fences.ts:25:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + + 24 | console.log(new Speciated().length); + 25 | console.log(new Nested().length); + | ^~~~~~~~~~~~ + 26 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } From 3b951ef4e22df32a4d5a703eb86c4e3233c155d0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:43:30 -0500 Subject: [PATCH 044/395] Let objects inherit from JavaScript class prototypes --- .../limitations/builtin-apis/prototypes.mdx | 7 +- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/class-dynamic-dispatch.ts | 96 ++++++++++++- .../src/frontend/lowering/class-prototypes.ts | 71 +++++++++- .../src/frontend/lowering/lower-inspect.ts | 13 +- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 1 + packages/compiler/src/ir/runtime-features.ts | 2 + .../8260-class-prototype-views.mjs.json | 6 + packages/runtime/src/scr_json.c | 43 +++++- packages/runtime/src/scr_qs.h | 2 + packages/runtime/src/scr_util_callable.c | 54 ++++--- tests/corpus/8260-class-prototype-views.mjs | 134 ++++++++++++++++++ .../8260-class-prototype-views.mjs.json | 1 + tests/harness/errors.test.ts | 30 ++++ 15 files changed, 439 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8260-class-prototype-views.mjs.json create mode 100644 tests/corpus/8260-class-prototype-views.mjs create mode 100644 tests/harness/corpus-costs/8260-class-prototype-views.mjs.json diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index 170b11793..7a7e01635 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -1,5 +1,10 @@ - **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. - Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. + A module-level JavaScript class's prototype can become the prototype of other objects: `Object.create(C.prototype)`, `Object.setPrototypeOf(F.prototype, C.prototype)`, `Object.setPrototypeOf(object, C.prototype)`, and `util.inherits(F, C)` with an ordinary constructor `F`. + The inherited methods and accessors run with the inheriting object as `this`. + Methods that use `#private` members or `super` throw a TypeError for a receiver that is not an instance of the class. + `C.prototype` also reads as a value for `Object.getPrototypeOf` and for `===` and `!==` comparisons. + Changing the prototype of a class prototype (`Object.setPrototypeOf(C.prototype, X)`, `util.inherits(C, F)`, or `Object.setPrototypeOf` on a class prototype reached through an instance) is unsupported. + Other bare prototype reflection, TypeScript class prototypes as values, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not. `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 014191314..20ad7fa02 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -82,6 +82,7 @@ export const LIB_FN_SYMS: Record = { "util.isDeepStrictEqual": "scr_util_is_deep_strict_equal", "util.styleText": "scr_util_style_text", "util.inherits": "scr_util_inherits", + "util.inheritsPrototype": "scr_util_inherits_prototype", "util.deprecate": "scr_util_deprecate", "util.callbackify": "scr_util_callbackify", "util.promisifyDyn": "scr_util_promisify", diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index f219fd5e5..d658fb20a 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -4105,6 +4105,94 @@ export class ClassDynamicDispatch { return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; } + /** `%dyn.class.bagHasOwn[OrPrototype]`: whether an instance's property bag + * holds `key` as its own property, or, with `prototype`, whether the bag's + * prototype object does. False before the instance has a bag. */ + private readBagHasOwn( + lowerer: Lowerer, + bag: IrExpr, + key: IrExpr, + prototype: boolean, + loc: SrcLoc, + ): IrExpr { + const name = prototype ? "%dyn.class.bagHasOwnOrPrototype" : "%dyn.class.bagHasOwn"; + let helper = lowerer.liftedFnNamed(name); + if (!helper) { + const value = varRef("p.0", DYN, loc); + const keyRef = varRef("p.1", STRING, loc); + const proto = varRef("proto", DYN, loc); + const hasOwn = (target: IrExpr): IrExpr => ({ + kind: "libCall", + fn: "dyn.hasOwn", + args: [target, keyRef], + type: BOOL, + loc, + }); + const returnFalse: IrStmt = { + kind: "return", + value: { kind: "boolLit", value: false, type: BOOL, loc }, + loc, + }; + helper = { + name, + params: [ + { localId: "p.0", name: "bag", type: DYN }, + { localId: "p.1", name: "key", type: STRING }, + ], + returnType: BOOL, + locals: [ + { id: "p.0", name: "bag", type: DYN, mutable: false }, + { id: "p.1", name: "key", type: STRING, mutable: false }, + ...(prototype ? [{ id: "proto", name: "proto", type: DYN, mutable: false }] : []), + ], + body: [ + { + kind: "if", + cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc }, + then: [returnFalse], + else_: null, + loc, + }, + ...(prototype + ? ([ + { + kind: "if", + cond: hasOwn(value), + then: [ + { + kind: "return", + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, + ], + else_: null, + loc, + }, + { + kind: "varDecl", + localId: "proto", + init: { kind: "libCall", fn: "dyn.getPrototype", args: [value], type: DYN, loc }, + loc, + }, + { + kind: "if", + cond: { kind: "dynTest", test: "nullish", value: proto, type: BOOL, loc }, + then: [returnFalse], + else_: null, + loc, + }, + { kind: "return", value: hasOwn(proto), loc }, + ] satisfies IrStmt[]) + : [{ kind: "return", value: hasOwn(value), loc } satisfies IrStmt]), + ], + loc, + }; + lowerer.liftedFns.push(helper); + } + this.generated.add(helper); + return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; + } + /** The hints for which some boxed class declares a conversion method. A * string conversion calls toString first, so without a declared toString * or Symbol.toPrimitive it answers Object.prototype.toString's @@ -5866,12 +5954,18 @@ export class ClassDynamicDispatch { const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN); const bag = this.branchPropertyBag(lowerer, info, receiver, false, loc); const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + // `constructor` is an own property of every class prototype, so an + // ancestor's prototype never answers for an instance of this class: + // the instance's own property answers, then one this class's + // prototype object defines (a reflected prototype, or a write to + // `C.prototype.constructor`), and otherwise the class itself. + const answered = this.readBagHasOwn(lowerer, bag, key, hasClassPrototypeData(info), loc); return [ { kind: "return", value: { kind: "ternary", - cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + cond: answered, then: { kind: "libCall", fn: "dyn.bagGet", diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index cb2ab2280..595b64e05 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -13,7 +13,7 @@ import { type IrStmt, type SrcLoc, } from "../../ir/ir.js"; -import { locOf } from "../program.js"; +import { isJsSourceFile, locOf } from "../program.js"; import { classMemberNameOf, classValueRef, @@ -503,6 +503,69 @@ export function isErrorPrototypeValue( ); } +/** Whether a bare `C.prototype` read becomes the prototype of another object + * or is only compared or inspected: the argument of `Object.create` or + * `Object.getPrototypeOf` / `Reflect.getPrototypeOf`, the prototype argument + * of `Object.setPrototypeOf` / `Reflect.setPrototypeOf`, or an operand of + * `===` / `!==`. In these positions the complete view of the + * prototype (reflectClassPrototype) answers. Objects that inherit from it + * run the class's methods with themselves as the receiver, which the + * methods' receiver-independent lowering (reflectedClassMethodValue) + * supports. Other positions, such as `Object.setPrototypeOf(C.prototype, X)`, + * which would relink the class, keep the diagnostic. */ +function prototypeViewPosition(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { + let node: ts.Node = expr; + let parent = node.parent; + while (parent && ts.isParenthesizedExpression(parent)) { + node = parent; + parent = parent.parent; + } + if (!parent) return false; + if (ts.isCallExpression(parent) && ts.isPropertyAccessExpression(parent.expression)) { + const args = parent.arguments.nodes; + const objectMethod = lowerer.stdlibGlobalMember(parent.expression, "Object"); + const reflectMethod = lowerer.stdlibGlobalMember(parent.expression, "Reflect"); + if ( + objectMethod === "create" || + objectMethod === "getPrototypeOf" || + reflectMethod === "getPrototypeOf" + ) + return args[0] === node; + if (objectMethod === "setPrototypeOf" || reflectMethod === "setPrototypeOf") + return args[1] === node; + return false; + } + return ( + ts.isBinaryExpression(parent) && + (parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken) + ); +} + +/** A module-level JavaScript class named directly (not a class value that + * may hold a subclass at run time): its prototype has a complete view whose + * methods accept any receiver. */ +function viewableClass(lowerer: Lowerer, info: ClassInfo, receiver: ts.Expression): boolean { + return ( + info.decl !== null && + info.localClass === undefined && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && + exactClassOfReceiver(lowerer, receiver) === info + ); +} + +/** The complete prototype view of the class `node` names, when it is a + * viewable class (util.inherits's superCtor); null otherwise. */ +export function classPrototypeView( + lowerer: Lowerer, + node: ts.Expression, + loc: SrcLoc, +): IrExpr | null { + const info = exactClassOfReceiver(lowerer, node); + if (!info || !viewableClass(lowerer, info, node)) return null; + return reflectClassPrototype(lowerer, info, loc, classValueRef(lowerer, info, node)); +} + /** Named data and method slots have native descriptors. Bare reflection and * accessor replacement still require a complete view of the prototype. */ export function lowerClassPrototypeData( @@ -566,6 +629,12 @@ export function lowerClassPrototypeData( }); return reflectClassPrototype(lowerer, info, locOf(expr), receiver); } + if ( + name === null && + prototypeViewPosition(lowerer, expr) && + viewableClass(lowerer, info, expr.expression) + ) + return reflectClassPrototype(lowerer, info, locOf(expr), classValueRef(lowerer, info, expr)); if (name === null || (isCompiledPrototypeMember(lowerer, info, name) && !method)) { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index e7a43bde1..cb7eba971 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1,5 +1,6 @@ import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; +import { classPrototypeView } from "./class-prototypes.js"; import { dynUndefinedExpr, boolLit, @@ -3433,8 +3434,14 @@ function lowerUtilCallableCall( member === "inherits" && expr.arguments.nodes[1] !== undefined ? builtinClassKindOf(lowerer, expr.arguments.nodes[1]) : null; + // A superCtor class whose prototype has a complete view (a module-level + // JavaScript class named directly) passes that view to the runtime. + const superView = + member === "inherits" && builtinBase === null && expr.arguments.nodes[1] !== undefined + ? classPrototypeView(lowerer, expr.arguments.nodes[1], loc) + : null; for (const [index, arg] of expr.arguments.nodes.slice(0, classArgs).entries()) { - if (index === 1 && builtinBase !== null) continue; + if (index === 1 && (builtinBase !== null || superView !== null)) continue; if (lowerer.mapTypeOf(lowerer.typeOf(arg))?.kind === "classval") lowerer.noLowering( `util.${member} over a class constructor`, @@ -3466,7 +3473,9 @@ function lowerUtilCallableCall( for (const arg of expr.arguments.nodes.slice(arity)) stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc }); const type = fn === "util.inherits" ? VOID : DYN; - const result: IrExpr = { kind: "libCall", fn, args, type, loc }; + const result: IrExpr = superView + ? { kind: "libCall", fn: "util.inheritsPrototype", args: [...args, superView], type, loc } + : { kind: "libCall", fn, args, type, loc }; return { kind: "seqExpr", stmts, result, type: result.type, loc }; } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..38512fdef 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -591,6 +591,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "util.styleText", "util.toUSVString", "util.inherits", + "util.inheritsPrototype", "util.deprecate", "util.callbackify", "util.promisifyDyn", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e6ad0614a..b87ff24c6 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1146,6 +1146,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * arguments (scr_util_callable.c). The function results are native dyn * wrappers; call sites with a static function type check them back. */ "util.inherits": { argTypes: [DYN, DYN], result: VOID }, + "util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID }, "util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, "util.callbackify": { argTypes: [DYN], result: DYN }, /** util.promisify over an untyped function value: a native dyn function diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 2ae19375f..14402b6b5 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -144,6 +144,7 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "util.debuglog", "util.deprecate", "util.inherits", + "util.inheritsPrototype", "util.styleText", ]); @@ -173,6 +174,7 @@ const UTIL_CALLABLE_LIB_FNS: ReadonlySet = new Set([ "util.debuglog", "util.deprecate", "util.inherits", + "util.inheritsPrototype", ]); /** Entry points that link the regex unit: the regex and glob matchers (fs.glob's walk compiles its patterns with the glob matcher), and readline's terminal unit (scr_readline_tty.c), which measures with the regex unit's stripVTControlCharacters. */ diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8260-class-prototype-views.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8260-class-prototype-views.mjs.json new file mode 100644 index 000000000..6cf42e773 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8260-class-prototype-views.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8260-class-prototype-views.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 580b816c4..b8f3bf8f9 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2169,6 +2169,40 @@ static bool scr_dyn_prototype_valid(const ScrDyn *prototype) { } /* A valid receiver or prototype whose kind this operation does not handle. */ +/* Compiled class prototypes. Compiled code binds a class's methods, its + * base chain and its instanceof answers when it compiles, so the + * prototype's own [[Prototype]] is fixed: scr_dyn_set_prototype refuses to + * change it. Each module-level class prototype registers when it is + * created (scr_dyn_name_class_prototype) and stays alive until its thread + * exits, as the class does. */ +static SCR_TL ScrDyn **scr_dyn_fixed_prototypes; +static SCR_TL size_t scr_dyn_fixed_count, scr_dyn_fixed_cap; + +static void scr_dyn_fixed_prototypes_cleanup(void) { + for (size_t i = 0; i < scr_dyn_fixed_count; i++) scr_dyn_release(scr_dyn_fixed_prototypes[i]); + free(scr_dyn_fixed_prototypes); + scr_dyn_fixed_prototypes = NULL; + scr_dyn_fixed_count = scr_dyn_fixed_cap = 0; +} + +static void scr_dyn_fix_prototype(ScrDyn *prototype) { + if (scr_dyn_fixed_count == scr_dyn_fixed_cap) { + if (scr_dyn_fixed_cap == 0) scr_atexit(scr_dyn_fixed_prototypes_cleanup); + size_t cap = scr_dyn_fixed_cap ? scr_dyn_fixed_cap * 2 : 8; + ScrDyn **grown = realloc(scr_dyn_fixed_prototypes, cap * sizeof *grown); + if (!grown) scr_json_oom(); + scr_dyn_fixed_prototypes = grown; + scr_dyn_fixed_cap = cap; + } + scr_dyn_fixed_prototypes[scr_dyn_fixed_count++] = scr_dyn_retain(prototype); +} + +static bool scr_dyn_prototype_fixed(const ScrDyn *object) { + for (size_t i = 0; i < scr_dyn_fixed_count; i++) + if (scr_dyn_fixed_prototypes[i] == object) return true; + return false; +} + static void scr_dyn_prototype_unsupported(const char *operation) { ScrJsonBuf b; scr_jb_init(&b); @@ -2285,6 +2319,11 @@ ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) { return NULL; } if (!scr_dyn_prototype_valid(prototype)) return NULL; + if (object->kind == SCR_DYN_OBJ && scr_dyn_fixed_count && scr_dyn_prototype_fixed(object) && + !(prototype->kind == SCR_DYN_NULL ? object->null_proto : prototype == object->prototype)) { + scr_dyn_prototype_unsupported("Object.setPrototypeOf"); + return NULL; + } bool typed = scr_dyn_typed_ref_prototype(prototype); if (scr_exc_pending()) return NULL; if ((object->kind != SCR_DYN_OBJ && object->kind != SCR_DYN_ARR) || @@ -6354,7 +6393,9 @@ static void scr_dyn_name_prototype_chain(const ScrDyn *object) { } void scr_dyn_name_class_prototype(ScrDyn *prototype, const ScrStr *name) { - if (prototype->kind != SCR_DYN_OBJ || prototype->v.obj.class_name || name->len == 0) return; + if (prototype->kind != SCR_DYN_OBJ) return; + if (!scr_dyn_prototype_fixed(prototype)) scr_dyn_fix_prototype(prototype); + if (prototype->v.obj.class_name || name->len == 0) return; scr_dyn_stamp_prototype_name(prototype, name->data, name->len); } diff --git a/packages/runtime/src/scr_qs.h b/packages/runtime/src/scr_qs.h index fc5ca5da5..2232d2d48 100644 --- a/packages/runtime/src/scr_qs.h +++ b/packages/runtime/src/scr_qs.h @@ -62,6 +62,8 @@ bool scr_util_terminal_colors(void); * (scr_util_promisify.c). Arguments are borrowed; function results are +1 * native dyn functions, or NULL with Node's TypeError pending. */ void scr_util_inherits(struct ScrDyn *ctor, struct ScrDyn *super_ctor); +void scr_util_inherits_prototype(struct ScrDyn *ctor, struct ScrDyn *super_ctor, + struct ScrDyn *super_prototype); struct ScrDyn *scr_util_deprecate(struct ScrDyn *fn, struct ScrDyn *msg, struct ScrDyn *code, struct ScrDyn *options); struct ScrDyn *scr_util_callbackify(struct ScrDyn *original); diff --git a/packages/runtime/src/scr_util_callable.c b/packages/runtime/src/scr_util_callable.c index c905ea8ae..60de92121 100644 --- a/packages/runtime/src/scr_util_callable.c +++ b/packages/runtime/src/scr_util_callable.c @@ -149,23 +149,9 @@ static bool uc_define(ScrDyn *target, const char *key, ScrDyn *descriptor) { * Node 24's lib/util.js: nullish arguments and an undefined * superCtor.prototype throw ERR_INVALID_ARG_TYPE; then super_ becomes a * writable, configurable, non-enumerable own property and - * ctor.prototype's prototype becomes superCtor.prototype. */ -void scr_util_inherits(ScrDyn *ctor, ScrDyn *super_ctor) { - if (uc_nullish(ctor)) { - scr_dyn_arg_type_fail("ctor", "of type function", ctor); - return; - } - if (uc_nullish(super_ctor)) { - scr_dyn_arg_type_fail("superCtor", "of type function", super_ctor); - return; - } - if ((ctor->kind == SCR_DYN_FUNC && ctor->v.fn.class_obj) || - (super_ctor->kind == SCR_DYN_FUNC && super_ctor->v.fn.class_obj)) { - uc_fence("util.inherits over class constructors has no lowering; declare the subclass with extends"); - return; - } - ScrDyn *super_prototype = uc_get(super_ctor, "prototype"); - if (!super_prototype) return; + * ctor.prototype's prototype becomes superCtor.prototype. super_prototype + * is superCtor.prototype (owned), or NULL when superCtor is no object. */ +static void uc_inherits(ScrDyn *ctor, ScrDyn *super_ctor, ScrDyn *super_prototype) { if (super_prototype->kind == SCR_DYN_UNDEF) { scr_dyn_prop_type_fail("superCtor.prototype", "of type object", super_prototype); scr_dyn_release(super_prototype); @@ -198,6 +184,40 @@ void scr_util_inherits(ScrDyn *ctor, ScrDyn *super_ctor) { scr_dyn_release(super_prototype); } +void scr_util_inherits(ScrDyn *ctor, ScrDyn *super_ctor) { + if (uc_nullish(ctor)) { + scr_dyn_arg_type_fail("ctor", "of type function", ctor); + return; + } + if (uc_nullish(super_ctor)) { + scr_dyn_arg_type_fail("superCtor", "of type function", super_ctor); + return; + } + if ((ctor->kind == SCR_DYN_FUNC && ctor->v.fn.class_obj) || + (super_ctor->kind == SCR_DYN_FUNC && super_ctor->v.fn.class_obj)) { + uc_fence("util.inherits over class constructors has no lowering; declare the subclass with extends"); + return; + } + ScrDyn *super_prototype = uc_get(super_ctor, "prototype"); + if (!super_prototype) return; + uc_inherits(ctor, super_ctor, super_prototype); +} + +/* util.inherits(ctor, C) where C is a compiled class whose prototype the + * compiler exposes as super_prototype (borrowed): the class value's own + * properties do not hold it. ctor itself may not be a class. */ +void scr_util_inherits_prototype(ScrDyn *ctor, ScrDyn *super_ctor, ScrDyn *super_prototype) { + if (uc_nullish(ctor)) { + scr_dyn_arg_type_fail("ctor", "of type function", ctor); + return; + } + if (ctor->kind == SCR_DYN_FUNC && ctor->v.fn.class_obj) { + uc_fence("util.inherits over class constructors has no lowering; declare the subclass with extends"); + return; + } + uc_inherits(ctor, super_ctor, scr_dyn_retain(super_prototype)); +} + /* ── util.deprecate ──────────────────────────────────────────────────── * The wrapper warns once (and once per code across wrappers) before each * call reaches fn with the call's receiver and arguments; construction diff --git a/tests/corpus/8260-class-prototype-views.mjs b/tests/corpus/8260-class-prototype-views.mjs new file mode 100644 index 000000000..e08b8f206 --- /dev/null +++ b/tests/corpus/8260-class-prototype-views.mjs @@ -0,0 +1,134 @@ +// A class's prototype as the prototype of other objects: ES5 constructors +// that inherit from a class through Object.create, Object.setPrototypeOf and +// util.inherits, and plain objects that adopt a class prototype. Inherited +// methods, accessors and calls between methods run with the object as the +// receiver. +import util from "node:util"; + +class Shape { + constructor(name) { + this.name = name; + } + get label() { + return `${this.name} (${this.sides()} sides)`; + } + sides() { + return 0; + } + describe() { + return `${this.label}: area ${this.area().toFixed(2)}`; + } + area() { + return 0; + } +} + +class Polygon extends Shape { + sides() { + return this.points.length; + } + perimeter() { + let total = 0; + for (let i = 0; i < this.points.length; i++) { + const [x1, y1] = this.points[i]; + const [x2, y2] = this.points[(i + 1) % this.points.length]; + total += Math.hypot(x2 - x1, y2 - y1); + } + return total; + } +} + +// Object.create(Class.prototype) for an ES5 subclass. +function Square(size) { + this.name = "square"; + this.size = size; + this.points = [ + [0, 0], + [size, 0], + [size, size], + [0, size], + ]; +} +Square.prototype = Object.create(Polygon.prototype); +Square.prototype.constructor = Square; +Square.prototype.area = function () { + return this.size * this.size; +}; + +const square = new Square(3); +console.log(square.describe()); +console.log(square.perimeter(), square.sides(), square.label); +console.log(square instanceof Square, square instanceof Polygon, square instanceof Shape); +console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); +console.log(Object.getPrototypeOf(Polygon.prototype) === Shape.prototype); +console.log(Object.keys(square).join(",")); +const inherited = []; +for (const key in square) inherited.push(key); +console.log(inherited.join(",")); + +// Object.setPrototypeOf on an ES5 constructor's prototype. +function Circle(radius) { + this.name = "circle"; + this.radius = radius; +} +Circle.prototype.area = function () { + return Math.PI * this.radius * this.radius; +}; +Object.setPrototypeOf(Circle.prototype, Shape.prototype); +const circle = new Circle(2); +console.log(circle.describe(), circle instanceof Shape, circle instanceof Polygon); + +// util.inherits with a class as the superCtor. +function Triangle(a, b, c) { + this.name = "triangle"; + this.points = [a, b, c]; +} +util.inherits(Triangle, Polygon); +Triangle.prototype.area = function () { + const [[x1, y1], [x2, y2], [x3, y3]] = this.points; + return Math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2); +}; +const triangle = new Triangle([0, 0], [4, 0], [0, 3]); +console.log(Triangle.super_ === Polygon, triangle instanceof Polygon); +console.log(triangle.describe(), triangle.perimeter()); + +// A plain object that adopts a class prototype (rehydrating parsed data). +const parsed = JSON.parse('{"name":"lot","points":[[0,0],[2,0],[2,1]]}'); +Object.setPrototypeOf(parsed, Polygon.prototype); +console.log(parsed.sides(), parsed.perimeter().toFixed(3), parsed.label, parsed instanceof Polygon); +const fresh = Object.create(Shape.prototype); +fresh.name = "blank"; +console.log(fresh.describe(), fresh instanceof Shape, Object.getPrototypeOf(fresh) === Shape.prototype); + +// Native instances keep their own methods and answers. +const native = new Polygon("native"); +native.points = [ + [0, 0], + [1, 0], + [0, 1], +]; +console.log(native.describe(), native.perimeter().toFixed(3), native instanceof Polygon); + +// util.inspect names each object by its prototype's constructor. +function Plain() { + this.v = 1; +} +Object.setPrototypeOf(Plain.prototype, Shape.prototype); +console.log(new Square(1).size, fresh, new Plain(), Object.prototype.toString.call(fresh)); +console.log(util.inspect(new Plain()), util.inspect(parsed, { depth: 0 })); + +// A subclass keeps its own constructor below a base whose prototype other +// objects inherit: `new this.constructor()` in a base method builds the +// subclass, also while the subclass's prototype holds a replaceable method. +class Disc extends Shape { + area() { + return 3; + } + rebuild() { + return new this.constructor("copy of " + this.name); + } +} +const areaValue = Shape.prototype.area; +const disc = new Disc("d"); +const rebuilt = disc.rebuild(); +console.log(disc.constructor === Disc, rebuilt instanceof Disc, rebuilt.area(), rebuilt.name, typeof areaValue); diff --git a/tests/harness/corpus-costs/8260-class-prototype-views.mjs.json b/tests/harness/corpus-costs/8260-class-prototype-views.mjs.json new file mode 100644 index 000000000..f06add31f --- /dev/null +++ b/tests/harness/corpus-costs/8260-class-prototype-views.mjs.json @@ -0,0 +1 @@ +{ "plain": 8037, "san": 6172 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c8849a10f..add31681d 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -2190,3 +2190,33 @@ for (const run of runs) { ); }); }); + +describe("class prototype relinks (JS lane)", () => { + test("relinking a class prototype reached through an instance throws", async () => { + // Compiled code binds a class's inherited methods, so its prototype's + // [[Prototype]] cannot change at run time. Node would print "other"; + // the native program refuses instead of answering A's method. + const r = await compileAndRun( + "class-prototype-relink", + `'use strict'; +class A { + a() { + return 'A.a'; + } +} +class B extends A {} +const prototype = Object.getPrototypeOf(new B()); +Object.setPrototypeOf(prototype, Object.getPrototypeOf(prototype)); +console.log('same prototype', new B().a()); +Object.setPrototypeOf(prototype, { a: () => 'other' }); +console.log(new B().a()); +`, + "cjs", + ); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe("same prototype A.a\n"); + expect(r.stderr).toBe( + "Uncaught Error: Object.setPrototypeOf with this value is not supported yet\n", + ); + }); +}); From 1e2f9b2dfcbe82867161d745b8a2d47c053599c8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:52:50 -0500 Subject: [PATCH 045/395] Resolve computed createRequire specifiers at run time In JavaScript dependencies, a createRequire require call with a computed specifier, and require.resolve called with a computed request or read as a value, now compile. The new runtime unit scr_require.c resolves the specifier with Node 24's CommonJS algorithm (builtins, relative and absolute paths with Node's extension search, package "main", "exports" and self-reference, node_modules and NODE_PATH lookup) when the call runs: - require.resolve returns the id or real path Node returns, and throws Node's MODULE_NOT_FOUND error when nothing resolves. - require throws the same MODULE_NOT_FOUND error when nothing resolves, so packages that probe for optional addons, such as pi-tui, take their absent branch as in Node. When the specifier resolves to a native addon, a builtin or another file, Node would load that module: the program prints an SC2020 error naming it and exits with status 1, without running the catch clause around the call. Coverage lists these calls as deferred to runtime. TypeScript sources keep the compile-time refusal. Refs #1777. --- docs/content/limitations/native-addons.mdx | 10 + .../src/backend/executable-features.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 3 + .../compiler/src/backend/native/executable.ts | 2 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../compiler/src/diagnostics/diagnostic.ts | 15 + .../src/executable/executable-cache.ts | 1 + .../lowering/builtins/module-resolution.ts | 158 +++- .../lowering/expressions/identifiers.ts | 8 +- .../lowering/expressions/property-access.ts | 14 + .../src/frontend/lowering/import-meta.ts | 13 +- packages/compiler/src/frontend/program.ts | 63 +- packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 12 + packages/compiler/src/ir/runtime-features.ts | 4 + .../compiler/src/native/sanitizer.test.ts | 1 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_path.h | 19 +- packages/runtime/src/scr_require.c | 895 ++++++++++++++++++ .../8210-require-computed-missing/main.ts | 7 + .../node_modules/optional-probe/index.d.ts | 2 + .../node_modules/optional-probe/index.js | 46 + .../node_modules/optional-probe/package.json | 1 + .../8211-require-resolve-computed/main.ts | 7 + .../node_modules/@scope/scoped/lib/node.js | 1 + .../node_modules/@scope/scoped/lib/other.js | 1 + .../node_modules/@scope/scoped/package.json | 1 + .../node_modules/plain-index/index.js | 1 + .../node_modules/resolve-probe/data.json | 1 + .../node_modules/resolve-probe/index.d.ts | 1 + .../node_modules/resolve-probe/index.js | 50 + .../node_modules/resolve-probe/package.json | 1 + .../node_modules/with-exports/cjs.cjs | 1 + .../node_modules/with-exports/esm.mjs | 1 + .../node_modules/with-exports/lib/feature.js | 1 + .../node_modules/with-exports/lib/helper.js | 1 + .../node_modules/with-exports/main.js | 1 + .../node_modules/with-exports/package.json | 1 + .../node_modules/with-main/lib/entry.js | 1 + .../node_modules/with-main/package.json | 1 + .../main.ts | 18 + .../native-helper/dist/index.d.ts | 11 + .../node_modules/native-helper/dist/index.js | 2 + .../native-helper/dist/native-module-path.js | 20 + .../native-helper/dist/native-platform.js | 48 + .../linux-arm64/linux-helper-x11.node | 1 + .../prebuilds/linux-x64/linux-helper-x11.node | 1 + .../node_modules/native-helper/package.json | 1 + tests/fixtures/npm-static/addon-probe-cli.ts | 5 + .../npm-static/addon-probe-installed-cli.ts | 4 + .../node_modules/addon-probe/index.d.ts | 3 + .../node_modules/addon-probe/index.js | 38 + .../node_modules/addon-probe/installed.js | 1 + .../addon-probe/native/installed.node | 1 + .../node_modules/addon-probe/package.json | 7 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + tests/harness/npm-static.test.ts | 92 +- 65 files changed, 1597 insertions(+), 36 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json create mode 100644 packages/runtime/src/scr_require.c create mode 100644 tests/corpus/8210-require-computed-missing/main.ts create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/main.ts create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json create mode 100644 tests/corpus/8212-native-helper-probe-no-display/main.ts create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json create mode 100644 tests/fixtures/npm-static/addon-probe-cli.ts create mode 100644 tests/fixtures/npm-static/addon-probe-installed-cli.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.js create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/installed.js create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/package.json create mode 100644 tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json create mode 100644 tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json create mode 100644 tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json diff --git a/docs/content/limitations/native-addons.mdx b/docs/content/limitations/native-addons.mdx index 469d1cdd2..1df9470cd 100644 --- a/docs/content/limitations/native-addons.mdx +++ b/docs/content/limitations/native-addons.mdx @@ -2,5 +2,15 @@ Node-API (N-API) and V8 `.node` addons depend on Node.js itself, so compiled pro Direct `createRequire` calls to local addons are rejected at compile time. A package that requires an addon needs a JavaScript implementation that compiles, or a replacement through native FFI. +A package that looks for an optional addon at run time compiles with `--npm-static`. +In JavaScript dependencies, a `createRequire` require call with a computed specifier, and `require.resolve` called with a computed request or read as a value, resolve the specifier with the Node.js algorithm when the program runs. +`require.resolve` returns the path Node.js returns. +When nothing resolves, both throw the `MODULE_NOT_FOUND` error that Node.js throws, so a package that probes for a missing addon takes the same branch as in Node.js. +When the specifier of a require call resolves to a native addon, a builtin, or a file that is not part of the compiled program, the program prints an SC2020 error that names it and exits with status 1, because Node.js would load that module. +A `catch` clause around the call does not run in that case. +Resolution steps that the runtime does not reproduce, such as package `imports` specifiers, `require.resolve` options, and subpaths that a package does not export, end the program the same way. +Coverage lists these calls as deferred to runtime. +This applies until compiled programs can load addons through Node.js. + Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI function linked from an object or static archive. The guide includes a replacement for a small Node-API helper. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index a9076af22..0eae21565 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -33,6 +33,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), ...(features.builtinValues ? { builtinValues: true } : {}), + ...(features.computedRequire ? { computedRequire: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 014191314..49d0bed68 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -270,6 +270,8 @@ export const LIB_FN_SYMS: Record = { "module.id": "scr_module_id", "module.path": "scr_module_path", "module.paths": "scr_module_paths", + "module.requireComputed": "scr_require_computed", + "module.resolveComputed": "scr_require_resolve", "module.children": "scr_module_children", "module.parent": "scr_module_parent", "module.loaded": "scr_module_loaded", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index c073bb9a6..3ff6b0423 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -62,6 +62,9 @@ export interface NativeLinkFeatures { /** Builtin functions read as values (Buffer.byteLength, Buffer.alloc, * fs.realpathSync) link scr_builtin_values.c; absent (false) otherwise. */ builtinValues?: boolean; + /** createRequire's computed require and require.resolve link + * scr_require.c; absent (false) otherwise. */ + computedRequire?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 53e160984..6606745a3 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -131,6 +131,9 @@ export interface CcOptions { /** Buffer.prototype methods called on checked-dynamic values: * compiles scr_bytes_dyn.c. */ dynBuffer?: boolean; + /** createRequire's computed require and require.resolve: compiles + * scr_require.c. */ + computedRequire?: boolean; /** Builtin functions read as values: compiles scr_builtin_values.c. */ builtinValues?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 1578a0c80..8fde247a7 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -186,6 +186,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.dynBuffer === true && u.opts.dynInvoke, "scr_bytes_dyn.c"), // Builtin functions read as values in JavaScript dependencies. unit((u) => u.opts.builtinValues === true, "scr_builtin_values.c"), + // createRequire's computed require and require.resolve. + unit((u) => u.opts.computedRequire === true, "scr_require.c"), unit((u) => u.opts.dc, "scr_dc.c"), unit((u) => u.opts.dynTier, "scr_js_value.c", "scr_js_object.c"), unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"), diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 75953be59..4b8d20bac 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -91,6 +91,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { eventsOnceSignal: boolean; dynBuffer: boolean; builtinValues: boolean; + computedRequire: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -289,6 +290,7 @@ export function effectiveRuntimeFeatures( eventsOnceSignal: features.eventsOnceSignal === true, dynBuffer: features.dynBuffer === true, builtinValues: features.builtinValues === true, + computedRequire: features.computedRequire === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 2fd8b6fb0..4579f637f 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -776,6 +776,21 @@ export function nativeAddonDiag(specifier: string, loc: SrcLoc): ScrDiagnostic { }; } +/** A createRequire require or require.resolve call whose specifier is + * computed at run time, where the runtime resolves it like Node + * (scr_require.c). Coverage lists the call as deferred: a require whose + * specifier resolves to a module ends the program with this code, because + * the module graph is fixed at build time. */ +export function computedRequireDiag(surface: string, loc: SrcLoc): ScrDiagnostic { + return { + code: "SC2020", + message: `${surface} with a computed specifier resolves at run time; a require of a specifier that resolves to a module, such as a native addon, ends the program`, + loc, + milestone: "later", + hint: 'require("") compiles the module into the program; native addons need a replacement through native FFI (https://scriptc.dev/ffi#replacing-a-node-api-addon)', + }; +} + /** Records are monomorphic structs, so a value's shape must match the * expected shape exactly OR narrow through the width-copy family * (recordWidthPlan / the overflow capture: target fields copied off the diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 50f783761..a82fe967f 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -76,6 +76,7 @@ export interface EarlyExecutableNativeFeatures { eventsOnceSignal?: boolean; dynBuffer?: boolean; builtinValues?: boolean; + computedRequire?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index 6b2e9e589..f00e78b0a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -1,8 +1,8 @@ -import { nodeThrowExpr, boolLit, strLit, varRef } from "../../../ir/build.js"; +import { dynUndefinedExpr, nodeThrowExpr, boolLit, strLit, varRef } from "../../../ir/build.js"; import { dirname, isAbsolute, resolve } from "node:path"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, PoisonError } from "../lowerer.js"; -import { canonicalBuiltinModule, isNodeEsmFile, locOf } from "../../program.js"; +import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf } from "../../program.js"; import { isRelativeSpecifier } from "../../workspace-registry.js"; import { probeNodeRequireRefusal } from "../../npm.js"; import { isNpmStaticPackage } from "../../npm-static.js"; @@ -15,21 +15,25 @@ import { type RuntimeResolveResult, } from "../../runtime-resolve.js"; import { + computedRequireDiag, invalidJsonModuleDiag, nativeAddonDiag, unsupportedPackageImportDiag, } from "../../../diagnostics/diagnostic.js"; -import { NODE_BUILTIN_MODULES_V24 } from "../surfaces.js"; +import { NODE_BUILTIN_MODULES_V24, builtinModulesArrayLit } from "../surfaces.js"; +import { moduleFileName } from "../import-meta.js"; import { BOOL, DYN, type IrExpr, + type IrStmt, type IrType, NULL_T, STRING, type SrcLoc, UNDEFINED_T, arrayOf, + funcOf, } from "../../../ir/ir.js"; import { lowerFfiMemoryModule } from "../native-ffi.js"; import { @@ -38,6 +42,7 @@ import { createRequireCalleeFileOf, } from "./module-bindings.js"; import { stripTypeCasts } from "./arguments.js"; +import { keyedHelperName } from "../stable-names.js"; /** node:module's two compiler-only calls. isBuiltin compares one evaluated * string against the pinned Node 24 list (bare builtins also accept their @@ -134,6 +139,102 @@ export function lowerNodeModuleCall( }; } +/** The run-time arguments of scr_require.c's computed require and + * require.resolve after the specifier: the requiring file (Node's require + * stack names it), the call's "file:line" for the SC2020 refusal, and the + * builtin ids. The call joins the runtime-fence ledger, so coverage lists + * it as deferred to runtime. */ +function computedRequireArgs( + lowerer: Lowerer, + surface: string, + node: ts.Node, + baseFile: ts.SourceFile, +): IrExpr[] { + const loc = locOf(node); + lowerer.runtimeFences.push(computedRequireDiag(surface, loc)); + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; + return [ + strLit(moduleFileName(lowerer, baseFile), loc), + strLit(`${loc.file}:${line}`, loc), + builtinModulesArrayLit(loc), + ]; +} + +/** A createRequire loader's require.resolve with a computed request, in + * JavaScript source: Node's resolution at run time (scr_require.c), with + * the options argument checked there. */ +function computedResolveCall( + lowerer: Lowerer, + node: ts.Node, + baseFile: ts.SourceFile, + request: IrExpr, + options: IrExpr, + loc: SrcLoc, +): IrExpr { + return { + kind: "libCall", + fn: "module.resolveComputed", + args: [ + request, + options, + ...computedRequireArgs(lowerer, "createRequire's require.resolve", node, baseFile), + ], + type: STRING, + loc, + }; +} + +/** A createRequire loader's `resolve` read as a value in JavaScript source + * (passed on, or picked by `??`): a function that resolves its request at + * run time, so `(options.resolve ?? require.resolve)(id)` behaves as in + * Node. */ +export function createRequireResolveValue( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + loc: SrcLoc, +): IrExpr { + const baseFile = createRequireCalleeFileOf(lowerer, access.expression); + if (baseFile === null || !isJsSourceFile(ts.getSourceFileOfNode(access))) { + lowerer.noLowering( + "createRequire's require.resolve as a value", + access, + 'call it with a string literal (require.resolve("")), which resolves at build time', + ); + } + const name = keyedHelperName(lowerer, "%createRequire.resolve", `${loc.file}:${loc.start}`); + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const call = computedResolveCall( + lowerer, + access, + baseFile, + varRef("request", DYN, loc), + varRef("options", DYN, loc), + loc, + ); + lowerer.liftedFns.push({ + name, + params: [ + { localId: "request", name: "request", type: DYN }, + { localId: "options", name: "options", type: DYN }, + ], + locals: [ + { id: "request", name: "request", type: DYN, mutable: false }, + { id: "options", name: "options", type: DYN, mutable: false }, + ], + returnType: STRING, + body: [{ kind: "return", value: call, loc }], + loc, + }); + } + return { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([DYN, DYN], STRING), loc }, + fnName: access.name.text, + type: DYN, + loc, + }; +} + export function lowerCreateRequireCall( lowerer: Lowerer, call: ts.CallExpression, @@ -142,10 +243,35 @@ export function lowerCreateRequireCall( const cr = createRequireSpecOf(lowerer, call); if (cr === null) return null; if (cr.spec === null) { - lowerer.noLowering( - "createRequire's require with this argument shape", + const args = call.arguments.nodes; + if (!isJsSourceFile(ts.getSourceFileOfNode(call)) || args.some((a) => ts.isSpreadElement(a))) { + lowerer.noLowering( + "createRequire's require with this argument shape", + call, + 'the compiled module graph is fixed at build time — the one lowered form is require("")', + ); + } + // Node evaluates every argument, then requires the first. + const stmts: IrStmt[] = []; + let id = args.length > 0 ? lowerer.lowerExprExpecting(args[0]!, DYN) : dynUndefinedExpr(loc); + if (args.length > 1) { + const slot = lowerer.declareHiddenLocal("%require.id", DYN); + stmts.push({ kind: "varDecl", localId: slot.id, init: id, loc }); + for (const arg of args.slice(1)) + stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) }); + id = varRef(slot.id, DYN, loc); + } + const require: IrExpr = { + kind: "libCall", + fn: "module.requireComputed", + args: [id, ...computedRequireArgs(lowerer, "createRequire's require", call, cr.baseFile)], + type: DYN, + loc, + }; + return lowerer.coerceInto( call, - 'the compiled module graph is fixed at build time — the one lowered form is require("")', + stmts.length === 0 ? require : { kind: "seqExpr", stmts, result: require, type: DYN, loc }, + lowerer.irTypeOf(call), ); } const spec = cr.spec; @@ -380,6 +506,26 @@ export function lowerRequireResolveCall(lowerer: Lowerer, call: ts.CallExpressio ); } const specifier = staticString(call.arguments.nodes[0]); + // A computed request (or options) through a createRequire loader in + // JavaScript source resolves at run time. + if ( + (specifier === null || staticResolveOptionPaths(call.arguments.nodes[1]) === null) && + !pathsCall && + createRequireCalleeFileOf(lowerer, receiver) !== null && + isJsSourceFile(ts.getSourceFileOfNode(call)) + ) { + const [request, options] = call.arguments.nodes; + return computedResolveCall( + lowerer, + call, + baseFile, + lowerer.lowerExprExpecting(request!, DYN), + options === undefined + ? dynUndefinedExpr(locOf(call)) + : lowerer.lowerExprExpecting(options, DYN), + locOf(call), + ); + } if (specifier === null) { lowerer.noLowering( `${pathsCall ? "require.resolve.paths" : "require.resolve"} with a runtime-computed request`, diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index f7ba7a115..159873630 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -88,7 +88,7 @@ import { lowerGlobalValue } from "../lower-global-value.js"; import { fenceEnumObjectValue } from "../lower-enums.js"; import { mixinFnOfCallee } from "../lower-mixins.js"; import { isNavigatorGlobal, refuseNavigator } from "../builtins/navigator.js"; -import { wasiGuestPath } from "../../../wasi-paths.js"; +import { moduleFileName } from "../import-meta.js"; export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: SrcLoc): IrExpr { if (lowerer.isStdlibGlobal(expr, "console")) { @@ -1014,12 +1014,6 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: ); } -function moduleFileName(lowerer: Lowerer, sf: ts.SourceFile): string { - return lowerer.targetPlatform === "wasi" - ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) - : sf.fileName; -} - /** A computed-key access (`value[key]` after `typeof value === "object"`) * of a runtime-optional binding the checker narrowed to one record arm of a * wider union. The binding re-tags to that record or undefined, so the keyed diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 1a4883e5a..a26caf4d9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -146,6 +146,8 @@ import { nullishGenericBindingUnitOf } from "../binding-analysis.js"; import { blockedBindingUseDiag } from "../../../diagnostics/diagnostic.js"; import { npmStaticPackageOfPath } from "../../npm-static.js"; import { importMetaField, isImportMeta } from "../import-meta.js"; +import { createRequireCalleeFileOf } from "../builtins/module-bindings.js"; +import { createRequireResolveValue } from "../builtins/module-resolution.js"; import { builtinPrototypeMethod, lowerArrayIsArrayValue, @@ -286,6 +288,14 @@ const DGRAM_METHOD_VALUES = new Set([ "unref", ]); +function isCreateRequireResolveValue(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { + return ( + expr.name.text === "resolve" && + !expr.questionDotToken && + createRequireCalleeFileOf(lowerer, expr.expression) !== null + ); +} + export function lowerPropertyAccessExpr( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -1275,6 +1285,10 @@ export function lowerPropertyAccessExpr( return declared && declared.kind !== "void" ? lowerer.coerceToExpected(read, declared) : read; } } + // A createRequire loader's resolve read as a value (passed on, or + // picked by `??`): a function that resolves its request at run time. + if (isCreateRequireResolveValue(lowerer, expr)) + return createRequireResolveValue(lowerer, expr, loc); // The lib fence's PROPERTY chokepoint: a stdlib-declared member that // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all, // re.exec as a value, ...) reports SC2020 here. diff --git a/packages/compiler/src/frontend/lowering/import-meta.ts b/packages/compiler/src/frontend/lowering/import-meta.ts index 796e64b69..ccc86bfba 100644 --- a/packages/compiler/src/frontend/lowering/import-meta.ts +++ b/packages/compiler/src/frontend/lowering/import-meta.ts @@ -17,14 +17,19 @@ export function isImportMeta(expr: ts.Expression): boolean { ); } +/** The module's file name as the compiled program reports it + * (import.meta.filename): the WASI guest path on wasi targets. */ +export function moduleFileName(lowerer: Lowerer, sf: ts.SourceFile): string { + return lowerer.targetPlatform === "wasi" + ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) + : sf.fileName; +} + /** File-backed metadata is fixed by the compiled module graph. Reads share * one implementation across dot access, folded keys and selected bindings. */ export function importMetaField(lowerer: Lowerer, node: ts.Node, name: string): IrExpr { const sf = ts.getSourceFileOfNode(node); - const fileName = - lowerer.targetPlatform === "wasi" - ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) - : sf.fileName; + const fileName = moduleFileName(lowerer, sf); const loc = locOf(node); switch (name) { case "env": diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 8075379b8..517b9accf 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -2519,12 +2519,29 @@ export function isCreateRequireBinding7(program: ts.Program, callee: ts.Identifi ); } -/** Only literal calls and literal resolution queries can erase the loader - * value. In particular, assignments, exported aliases, shorthand properties, - * optional calls, and passing the loader to user code cannot pass this test. */ -function staticCreateRequireUse7(expr: ts.Expression): boolean { +/** How a use of a createRequire loader lowers: + * - "static": a literal call or a literal resolution query, which erases + * the loader value. + * - "runtime": in JavaScript source, a call with a computed specifier, a + * require.resolve call with a computed request, or `resolve` read as a + * value. The runtime resolves the specifier like Node + * (module-resolution.ts, scr_require.c). + * - "computed": the same uses in TypeScript source. + * - "escaping": the loader itself as a value, writes, optional calls, + * spread arguments, `resolve.paths` with a computed request or as a + * value, and other members. */ +type CreateRequireUse7 = "static" | "runtime" | "computed" | "escaping"; + +function createRequireUse7(expr: ts.Expression): CreateRequireUse7 { + const use = createRequireUseShape7(expr); + return use === "runtime" && !isJsSourceFile(ts.getSourceFileOfNode(expr)) ? "computed" : use; +} + +function createRequireUseShape7(expr: ts.Expression): CreateRequireUse7 { let use: ts.Node = expr; while (ts.isParenthesizedExpression(use.parent)) use = use.parent; + let member = false; + let paths = false; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && @@ -2532,21 +2549,35 @@ function staticCreateRequireUse7(expr: ts.Expression): boolean { !use.parent.questionDotToken ) { use = use.parent; + member = true; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && use.parent.name.text === "paths" && !use.parent.questionDotToken - ) + ) { use = use.parent; + paths = true; + } } const call = use.parent; + if (ts.isCallExpression(call) && call.expression === use) { + if (call.questionDotToken) return "escaping"; + const args = call.arguments.nodes; + if (args.length === 1 && ts.isStringLiteralLike(args[0]!)) return "static"; + if (paths || args.some((arg) => ts.isSpreadElement(arg))) return "escaping"; + return member && (args.length === 0 || args.length > 2) ? "escaping" : "runtime"; + } + return member && !paths && !isAssignmentTarget7(use) ? "runtime" : "escaping"; +} + +function isAssignmentTarget7(node: ts.Node): boolean { + const parent = node.parent; return ( - ts.isCallExpression(call) && - call.expression === use && - !call.questionDotToken && - call.arguments.nodes.length === 1 && - ts.isStringLiteralLike(call.arguments.nodes[0]!) + ts.isBinaryExpression(parent) && + parent.left === node && + parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment ); } @@ -2582,9 +2613,11 @@ function stableCreateRequireBindingReason7( : checker.getSymbolAtLocation(ref); if (target && (target.flags & ts.SymbolFlags.Alias) !== 0) target = checker.getAliasedSymbol(target); - if (target === symbol && !staticCreateRequireUse7(ref)) { + if (target !== symbol) continue; + const use = createRequireUse7(ref); + if (use === "computed") return "its createRequire binding is reassigned, escapes, or uses a computed specifier"; - } + if (use === "escaping") return "its createRequire binding is reassigned or escapes"; } const preceding = stmt.declarationList.declarations.nodes.slice( 0, @@ -2647,10 +2680,12 @@ function npmStaticModuleImportReason7( checker.prefetchSymbolNodesExact(identifierOccurrences7(sf, specifier.name.text)); let reason: string | null = null; const checkUse = (expr: ts.Expression): void => { - if (!staticCreateRequireUse7(expr)) { + const use = createRequireUse7(expr); + if (use === "computed") reason = "its createRequire result escapes or is used with a computed specifier (static requires need literal specifiers)"; - } + else if (use === "escaping") + reason = "its createRequire result escapes (static requires need literal specifiers)"; }; ts.walkPreorder(sf, (node) => { if ( diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index c0b02e8db..63f6d2945 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -609,6 +609,7 @@ async function compileExecutableNative( ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), ...(features.builtinValues ? { builtinValues: true } : {}), + ...(features.computedRequire ? { computedRequire: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..fbeb05dd3 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -679,6 +679,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.builtinId", "process.builtinModule", "process.builtinUnsupported", + "module.requireComputed", + "module.resolveComputed", "fs.callbackValue", "fs.callbackCall", "dyn.keyDelete", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e6ad0614a..a3efc12c8 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -378,6 +378,18 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "module.parent": { argTypes: [F64], result: F64 }, "module.loaded": { argTypes: [F64], result: BOOL }, "module.cacheGet": { argTypes: [STRING], result: F64 }, + /** createRequire's require(id) and require.resolve(request, options) + * with a specifier computed at run time (scr_require.c): the arguments, + * then the requiring file, the call's "file:line" and the builtin ids. + * Both resolve like Node at run time. requireComputed always throws + * (Node's argument errors or MODULE_NOT_FOUND), or ends the program with + * an SC2020 refusal when the specifier resolves; resolveComputed answers + * the resolved id or real path. */ + "module.requireComputed": { argTypes: [DYN, STRING, STRING, arrayOf(STRING)], result: DYN }, + "module.resolveComputed": { + argTypes: [DYN, DYN, STRING, STRING, arrayOf(STRING)], + result: STRING, + }, "module.cacheHas": { argTypes: [STRING], result: BOOL }, "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 2ae19375f..54c5ee5be 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -946,6 +946,9 @@ const RUNTIME_FEATURE_RULES = { fns: ["buffer.prototypeMember"], expr: (node, _tables) => node.kind === "dynInvoke" && DYN_BUFFER_METHODS.has(node.method), }), + /** createRequire's require and require.resolve with specifiers computed + * at run time (scr_require.c). */ + computedRequire: rule({ fns: ["module.requireComputed", "module.resolveComputed"] }), /** Builtin functions read as values (scr_builtin_values.c). */ builtinValues: rule({ fns: [ @@ -1182,6 +1185,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dynForAwait: false, dynBuffer: false, builtinValues: false, + computedRequire: false, symbol: false, tls: false, tlsCa: false, diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 33a4c19b9..c6063b2db 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -84,6 +84,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( eventsOnceSignal: false, dynBuffer: false, builtinValues: false, + computedRequire: false, warningReplace: false, stdioExtended: false, regex: true, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json new file mode 100644 index 000000000..659ee27aa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8210-require-computed-missing/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json new file mode 100644 index 000000000..44f6cb75f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8211-require-resolve-computed/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json new file mode 100644 index 000000000..2a83db362 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8212-native-helper-probe-no-display/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 0ab467236..0fc9fdf79 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -67,6 +67,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_bytes_dyn.c", all("dynBuffer", "dynInvoke")], // Builtin functions read as values in JavaScript dependencies. ["scr_builtin_values.c", "builtinValues"], + // createRequire's computed require and require.resolve. + ["scr_require.c", "computedRequire"], ["scr_dc.c", "dc"], // The dynamic tier's jsval values (two-tier design I3): linked only into // programs whose IR has dynamic-tier operations. diff --git a/packages/runtime/src/scr_path.h b/packages/runtime/src/scr_path.h index 9600b5e95..de5e85491 100644 --- a/packages/runtime/src/scr_path.h +++ b/packages/runtime/src/scr_path.h @@ -1,5 +1,6 @@ -/* scriptc runtime — node:path (scr_path.c) and path globs (scr_glob.c). - * Included by scr_runtime.h, which says where new declarations go. */ +/* scriptc runtime — node:path (scr_path.c), path globs (scr_glob.c), and + * module resolution at run time (scr_require.c). Included by scr_runtime.h, + * which says where new declarations go. */ #ifndef SCR_PATH_H #define SCR_PATH_H @@ -89,4 +90,18 @@ void scr_fs_glob_callback(ScrDyn *args); ScrArr *scr_fsp_glob_strings(const ScrDyn *pattern, const ScrDyn *options); ScrDyn *scr_fsp_glob_error(void); +/* createRequire's require(id) and require.resolve(request, options) with a + * specifier computed at run time (scr_require.c). Both resolve with Node + * 24's CommonJS algorithm from `importer`, the requiring file; `builtins` + * lists the builtin ids (process.getBuiltinModule's table), and `site` is + * the call's "file:line" for the SC2020 refusal. All arguments are + * borrowed. scr_require_computed always leaves an exception pending and + * returns NULL: Node's argument errors, or MODULE_NOT_FOUND when nothing + * resolves; a specifier that resolves ends the program with the refusal. + * scr_require_resolve answers the resolved id or real path (+1), or NULL + * with Node's error pending. */ +ScrDyn *scr_require_computed(ScrDyn *id, ScrStr *importer, ScrStr *site, ScrArr *builtins); +ScrStr *scr_require_resolve(ScrDyn *request, ScrDyn *options, ScrStr *importer, ScrStr *site, + ScrArr *builtins); + #endif /* SCR_PATH_H */ diff --git a/packages/runtime/src/scr_require.c b/packages/runtime/src/scr_require.c new file mode 100644 index 000000000..645273bce --- /dev/null +++ b/packages/runtime/src/scr_require.c @@ -0,0 +1,895 @@ +/* createRequire's require(id) and require.resolve(request) with a specifier + * computed at run time (module-resolution.ts lowers both calls here). + * + * The compiled module graph is fixed at build time, so neither call loads a + * module. Both resolve the specifier with Node 24's CommonJS algorithm + * (lib/internal/modules/cjs/loader.js: Module._resolveFilename and + * Module._findPath, with package.json "main", "exports" and self-reference) + * against the file system at run time: + * + * - require.resolve returns what Node returns: the builtin's id, or the real + * path of the resolved file. It throws Node's MODULE_NOT_FOUND error when + * nothing resolves. + * - require throws the same MODULE_NOT_FOUND error when nothing resolves, so + * packages that probe for optional files take their absent branch as in + * Node. When the specifier resolves (a builtin, or a file such as a native + * addon), Node would load the module, which a compiled program cannot do: + * the program ends with an SC2020 refusal naming the module. The refusal is + * not an exception, so a `catch` clause cannot take a fallback that Node + * would not take. + * + * Resolution steps whose Node result this file does not reproduce (package + * "imports", invalid package configs or export targets, unexported subpaths, + * percent-encoded export targets, an export target that names no file, a + * "main" that names no file) end the program with the same SC2020 refusal, + * naming the step. */ +#include "scr_runtime.h" + +#include +#include +#include + +typedef struct { + ScrStr *request; /* borrowed */ + ScrStr *importer; /* borrowed: the requiring file */ + ScrArr *builtins; /* borrowed: Node 24's require-able builtin ids */ + /* Set when resolution reaches a step it does not reproduce; the caller + * refuses with this reason. Static text. */ + const char *refusal; +} ScrRequire; + +static ScrStr *req_cstr(const char *s) { return scr_str_new(s, strlen(s)); } + +static bool req_has_prefix(const ScrStr *s, const char *prefix) { + size_t n = strlen(prefix); + return s->len >= n && memcmp(s->data, prefix, n) == 0; +} + +static bool req_has_suffix(const ScrStr *s, const char *suffix) { + size_t n = strlen(suffix); + return s->len >= n && memcmp(s->data + s->len - n, suffix, n) == 0; +} + +static bool req_eq(const ScrStr *s, const char *text) { + size_t n = strlen(text); + return s->len == n && memcmp(s->data, text, n) == 0; +} + +static ScrStr *req_concat(const ScrStr *a, const char *b, size_t b_len) { + ScrJsonBuf out; + scr_jb_init(&out); + scr_jb_write(&out, a->data, a->len); + scr_jb_write(&out, b, b_len); + return scr_jb_finish(&out); +} + +static ScrStr *req_resolve2(ScrStr *a, ScrStr *b) { + ScrArr *parts = scr_arr_new(SCR_ELEM_STR, 2); + scr_arr_push_ref(parts, scr_str_retain(a)); + scr_arr_push_ref(parts, scr_str_retain(b)); +#ifdef _WIN32 + ScrStr *out = scr_path_win32_resolve(parts); +#else + ScrStr *out = scr_path_resolve(parts); +#endif + scr_arr_release(parts); + return out; +} + +static ScrStr *req_dirname(ScrStr *path) { +#ifdef _WIN32 + return scr_path_win32_dirname(path); +#else + return scr_path_dirname(path); +#endif +} + +static bool req_is_absolute(ScrStr *path) { +#ifdef _WIN32 + return scr_path_win32_is_absolute(path); +#else + return scr_path_is_absolute(path); +#endif +} + +/* Node's internalModuleStat: 0 for a file, 1 for a directory, -1 when the + * path does not exist or cannot be read. Never throws. */ +static int req_stat(ScrStr *path) { + ScrStats *stats = scr_fs_stat_probe(path, false, false); + if (stats == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + return -1; + } + int kind = scr_stats_is_dir(stats) ? 1 : 0; + scr_stats_release(stats); + return kind; +} + +/* tryFile: the real path of `path` when it is a file (+1), else NULL. */ +static ScrStr *req_try_file(ScrStr *path) { + if (req_stat(path) != 0) return NULL; + ScrStr *real = scr_fs_realpath(path); + if (real != NULL) return real; + if (scr_exc_pending()) scr_exc_clear(); + return scr_str_retain(path); +} + +/* tryExtensions with Module._extensions' keys, in registration order. */ +static ScrStr *req_try_extensions(ScrStr *base) { + static const char *const extensions[] = { ".js", ".json", ".node" }; + for (size_t i = 0; i < sizeof extensions / sizeof extensions[0]; i++) { + ScrStr *candidate = req_concat(base, extensions[i], strlen(extensions[i])); + ScrStr *found = req_try_file(candidate); + scr_str_release(candidate); + if (found != NULL) return found; + } + return NULL; +} + +/* ── package.json ────────────────────────────────────────────────────── */ + +/* The package.json at `pjson` parsed (+1), or NULL when there is none. + * A file Node rejects as an invalid package config (unparsable JSON, a + * non-object document, a non-string "name" or "type") sets the refusal. */ +static ScrDyn *req_read_package(ScrRequire *r, ScrStr *pjson) { + if (req_stat(pjson) != 0) return NULL; + ScrStr *text = scr_fs_read_file(pjson); + if (text == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + return NULL; + } + ScrDyn *pkg = scr_json_parse(text); + scr_str_release(text); + if (pkg == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + r->refusal = "a package.json that is not valid JSON"; + return NULL; + } + const ScrDyn *name = pkg->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(pkg, "name", 4) : NULL; + const ScrDyn *type = pkg->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(pkg, "type", 4) : NULL; + if (pkg->kind != SCR_DYN_OBJ || (name != NULL && name->kind != SCR_DYN_STR) || + (type != NULL && type->kind != SCR_DYN_STR)) { + scr_dyn_release(pkg); + r->refusal = "an invalid package.json"; + return NULL; + } + return pkg; +} + +/* A string field of a parsed package.json (borrowed), or NULL. */ +static ScrStr *req_package_string(const ScrDyn *pkg, const char *key) { + const ScrDyn *value = scr_dyn_obj_get(pkg, key, strlen(key)); + return value != NULL && value->kind == SCR_DYN_STR ? value->v.str : NULL; +} + +/* "exports" or "imports" as Node keeps them: a string, an object or an + * array (borrowed); NULL for every other value. */ +static const ScrDyn *req_package_map(const ScrDyn *pkg, const char *key) { + const ScrDyn *value = scr_dyn_obj_get(pkg, key, strlen(key)); + if (value == NULL) return NULL; + return value->kind == SCR_DYN_STR || value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR ? value : NULL; +} + +/* tryPackage: a directory's "main" entry, then its index files. */ +static ScrStr *req_try_package(ScrRequire *r, ScrStr *dir) { + ScrStr *pjson_name = req_cstr("package.json"); + ScrStr *pjson = req_resolve2(dir, pjson_name); + scr_str_release(pjson_name); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + if (r->refusal != NULL) return NULL; + ScrStr *main = pkg != NULL ? req_package_string(pkg, "main") : NULL; + ScrStr *index_name = req_cstr("index"); + ScrStr *index = req_resolve2(dir, index_name); + ScrStr *found = NULL; + if (main == NULL) { + found = req_try_extensions(index); + } else { + ScrStr *filename = req_resolve2(dir, main); + found = req_try_file(filename); + if (found == NULL) found = req_try_extensions(filename); + if (found == NULL) { + ScrStr *main_index = req_resolve2(filename, index_name); + found = req_try_extensions(main_index); + scr_str_release(main_index); + } + scr_str_release(filename); + if (found == NULL) { + /* Node falls back to the index with a DEP0128 warning, or throws an + * error that names the "main" entry. */ + r->refusal = "a package.json \"main\" entry that names no file"; + } + } + scr_str_release(index); + scr_str_release(index_name); + if (pkg != NULL) scr_dyn_release(pkg); + return found; +} + +/* ── package exports (lib/internal/modules/esm/resolve.js) ───────────── */ + +/* A target or subpath segment Node rejects or warns about: "", ".", "..", + * "node_modules" in any case, and their percent-encoded spellings. The + * caller refuses '%' separately, so the plain spellings suffice. */ +static bool req_invalid_segment(const char *s, size_t len) { + size_t start = 0; + for (size_t i = 0; i <= len; i++) { + if (i < len && s[i] != '/' && s[i] != '\\') continue; + size_t n = i - start; + const char *seg = s + start; + if (n == 0 && i < len) return true; + if ((n == 1 && seg[0] == '.') || (n == 2 && seg[0] == '.' && seg[1] == '.')) return true; + if (n == 12) { + static const char nm[] = "node_modules"; + bool same = true; + for (size_t k = 0; k < 12 && same; k++) { + char c = seg[k]; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + same = c == nm[k]; + } + if (same) return true; + } + start = i + 1; + } + return false; +} + +/* Characters whose URL parsing changes the path Node resolves: percent + * escapes, backslashes, query and fragment delimiters, and the control + * characters the URL parser strips. */ +static bool req_url_sensitive(const char *s, size_t len) { + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)s[i]; + if (c == '%' || c == '\\' || c == '?' || c == '#' || c < 0x20 || c == 0x7f) return true; + } + return false; +} + +typedef enum { REQ_TARGET_PATH, REQ_TARGET_NULL, REQ_TARGET_UNDEFINED, REQ_TARGET_INVALID } ScrRequireTarget; + +static bool req_condition(const ScrStr *key) { + return req_eq(key, "default") || req_eq(key, "require") || req_eq(key, "node") || + req_eq(key, "node-addons") || req_eq(key, "module-sync"); +} + +static bool req_array_index(const ScrStr *key) { + if (key->len == 0 || key->len > 10) return false; + if (key->len > 1 && key->data[0] == '0') return false; + double n = 0; + for (size_t i = 0; i < key->len; i++) { + if (key->data[i] < '0' || key->data[i] > '9') return false; + n = n * 10 + (key->data[i] - '0'); + } + return n < 4294967295.0; +} + +/* resolvePackageTarget for an "exports" target. `pkg_dir` is the package + * directory, `subpath` the part the target receives. A path result is in + * `*out` (+1, not yet checked against the file system). */ +static ScrRequireTarget req_resolve_target(ScrRequire *r, ScrStr *pkg_dir, const ScrDyn *target, + const char *subpath, size_t subpath_len, bool pattern, + ScrStr **out) { + if (target->kind == SCR_DYN_STR) { + ScrStr *t = target->v.str; + if (subpath_len != 0 && !pattern && !req_has_suffix(t, "/")) return REQ_TARGET_INVALID; + if (!req_has_prefix(t, "./")) return REQ_TARGET_INVALID; + if (req_url_sensitive(t->data, t->len) || req_url_sensitive(subpath, subpath_len) || + req_invalid_segment(t->data + 2, t->len - 2) || + (subpath_len != 0 && req_invalid_segment(subpath, subpath_len))) { + r->refusal = "a package \"exports\" target that Node rejects, warns about or decodes"; + return REQ_TARGET_INVALID; + } + if (pattern && memchr(pkg_dir->data, '*', pkg_dir->len) != NULL) { + /* Node substitutes the pattern in the whole URL, directory included. */ + r->refusal = "a package \"exports\" pattern in a directory whose path contains '*'"; + return REQ_TARGET_INVALID; + } + ScrJsonBuf path; + scr_jb_init(&path); + scr_jb_write(&path, pkg_dir->data, pkg_dir->len); + const char *rest = t->data + 1; /* keeps the '/' after "." */ + size_t rest_len = t->len - 1; + if (pattern) { + for (size_t i = 0; i < rest_len; i++) { + if (rest[i] == '*') + scr_jb_write(&path, subpath, subpath_len); + else + scr_jb_putc(&path, rest[i]); + } + } else { + scr_jb_write(&path, rest, rest_len); + scr_jb_write(&path, subpath, subpath_len); + } + ScrStr *joined = scr_jb_finish(&path); +#ifdef _WIN32 + *out = scr_path_win32_normalize(joined); +#else + *out = scr_path_normalize(joined); +#endif + scr_str_release(joined); + return REQ_TARGET_PATH; + } + if (target->kind == SCR_DYN_ARR) { + double n = scr_dyn_arr_len(target); + if (n == 0) return REQ_TARGET_NULL; + ScrRequireTarget last = REQ_TARGET_UNDEFINED; + for (double i = 0; i < n; i++) { + ScrDyn *item = scr_dyn_arr_at(target, i); + ScrRequireTarget result = req_resolve_target(r, pkg_dir, item, subpath, subpath_len, pattern, out); + scr_dyn_release(item); + if (r->refusal != NULL) return REQ_TARGET_INVALID; + if (result == REQ_TARGET_PATH) return result; + if (result == REQ_TARGET_INVALID) last = REQ_TARGET_INVALID; + else if (result == REQ_TARGET_NULL) last = REQ_TARGET_NULL; + } + return last == REQ_TARGET_UNDEFINED ? REQ_TARGET_UNDEFINED : last; + } + if (target->kind == SCR_DYN_OBJ) { + ScrDyn *keys = scr_dyn_obj_own_keys(target); + double n = scr_dyn_arr_len(keys); + ScrRequireTarget result = REQ_TARGET_UNDEFINED; + for (double i = 0; i < n; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + bool index = req_array_index(key->v.str); + scr_dyn_release(key); + if (index) { + r->refusal = "numeric keys in a package's \"exports\""; + scr_dyn_release(keys); + return REQ_TARGET_INVALID; + } + } + for (double i = 0; i < n && result == REQ_TARGET_UNDEFINED; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + if (req_condition(key->v.str)) { + const ScrDyn *value = scr_dyn_obj_get(target, key->v.str->data, key->v.str->len); + result = req_resolve_target(r, pkg_dir, value, subpath, subpath_len, pattern, out); + if (r->refusal != NULL) result = REQ_TARGET_INVALID; + } + scr_dyn_release(key); + } + scr_dyn_release(keys); + return result; + } + if (target->kind == SCR_DYN_NULL) return REQ_TARGET_NULL; + return REQ_TARGET_INVALID; +} + +/* patternKeyCompare(a, b) === 1: `b` is the better pattern match. */ +static bool req_pattern_better(const ScrStr *a, const ScrStr *b) { + const char *as = a ? memchr(a->data, '*', a->len) : NULL; + const char *bs = memchr(b->data, '*', b->len); + size_t a_len = a ? a->len : 0; + size_t base_a = as ? (size_t)(as - a->data) + 1 : a_len; + size_t base_b = bs ? (size_t)(bs - b->data) + 1 : b->len; + if (base_a > base_b) return false; + if (base_b > base_a) return true; + if (as == NULL) return true; + if (bs == NULL) return false; + return b->len > a_len; +} + +/* packageExportsResolve for `subpath` ("." or "./..."), then + * finalizeEsmResolution's file check. +1 real path, or NULL with the + * refusal set. */ +static ScrStr *req_exports_resolve(ScrRequire *r, ScrStr *pkg_dir, const ScrDyn *exports, ScrStr *subpath) { + bool sugar; + if (exports->kind != SCR_DYN_OBJ) { + sugar = true; + } else { + sugar = false; + ScrDyn *keys = scr_dyn_obj_own_keys(exports); + double n = scr_dyn_arr_len(keys); + for (double i = 0; i < n; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + bool conditional = key->v.str->len == 0 || key->v.str->data[0] != '.'; + scr_dyn_release(key); + if (i == 0) { + sugar = conditional; + } else if (sugar != conditional) { + scr_dyn_release(keys); + r->refusal = "a package \"exports\" object that mixes subpaths and conditions"; + return NULL; + } + } + scr_dyn_release(keys); + } + if (exports->kind == SCR_DYN_STR && exports->v.str->len > 0 && + (exports->v.str->data[0] == '[' || exports->v.str->data[0] == '{')) { + r->refusal = "a package \"exports\" string that Node parses as JSON"; + return NULL; + } + ScrStr *path = NULL; + ScrRequireTarget result = REQ_TARGET_UNDEFINED; + bool has_star = memchr(subpath->data, '*', subpath->len) != NULL; + if (sugar) { + if (req_eq(subpath, ".")) + result = req_resolve_target(r, pkg_dir, exports, "", 0, false, &path); + } else { + const ScrDyn *exact = has_star || req_has_suffix(subpath, "/") + ? NULL + : scr_dyn_obj_get(exports, subpath->data, subpath->len); + if (exact != NULL) { + result = req_resolve_target(r, pkg_dir, exact, "", 0, false, &path); + } else { + ScrStr *best = NULL; + size_t best_start = 0, best_end = 0; + ScrDyn *keys = scr_dyn_obj_own_keys(exports); + double n = scr_dyn_arr_len(keys); + for (double i = 0; i < n; i++) { + ScrDyn *key_value = scr_dyn_arr_at(keys, i); + ScrStr *key = key_value->v.str; + const char *star = memchr(key->data, '*', key->len); + size_t at = star ? (size_t)(star - key->data) : 0; + if (star != NULL && subpath->len >= at && memcmp(subpath->data, key->data, at) == 0) { + size_t trailer = key->len - at - 1; + bool single = memchr(star + 1, '*', trailer) == NULL; + if (subpath->len >= key->len && + memcmp(subpath->data + subpath->len - trailer, star + 1, trailer) == 0 && + req_pattern_better(best, key) && single) { + if (best) scr_str_release(best); + best = scr_str_retain(key); + best_start = at; + best_end = subpath->len - trailer; + } + } + scr_dyn_release(key_value); + } + scr_dyn_release(keys); + if (best != NULL) { + if (req_has_suffix(subpath, "/")) { + r->refusal = "a package \"exports\" pattern matched by a subpath ending in '/'"; + } else { + const ScrDyn *target = scr_dyn_obj_get(exports, best->data, best->len); + result = req_resolve_target(r, pkg_dir, target, subpath->data + best_start, + best_end - best_start, true, &path); + } + scr_str_release(best); + } + } + } + if (r->refusal != NULL) { + if (path) scr_str_release(path); + return NULL; + } + if (result != REQ_TARGET_PATH) { + r->refusal = result == REQ_TARGET_INVALID ? "an invalid package \"exports\" target" + : "a subpath that the package's \"exports\" do not export"; + return NULL; + } + ScrStr *found = req_try_file(path); + scr_str_release(path); + if (found == NULL) r->refusal = "a package \"exports\" target that names no file"; + return found; +} + +/* ── Module._findPath ────────────────────────────────────────────────── */ + +/* EXPORTS_PATTERN: /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/ — the + * package name's length in `request`, or 0 when it does not match. */ +static size_t req_package_name_len(const ScrStr *request) { + const char *s = request->data; + size_t len = request->len, i = 0; + if (len > 0 && s[0] == '@') { + i = 1; + size_t start = i; + while (i < len && s[i] != '/' && s[i] != '\\' && s[i] != '%') i++; + if (i == start || i >= len || s[i] != '/') i = 0; + else i++; + } + if (i >= len || s[i] == '.' || s[i] == '/' || s[i] == '\\' || s[i] == '%') return 0; + while (i < len && s[i] != '/' && s[i] != '\\' && s[i] != '%') i++; + if (i < len && s[i] != '/') return 0; + return i; +} + +/* resolveExports: a package under `nm_dir` whose package.json has + * "exports". +1 real path; NULL when the package has no "exports" (or the + * refusal is set). */ +static ScrStr *req_resolve_exports(ScrRequire *r, ScrStr *nm_dir) { + size_t name_len = req_package_name_len(r->request); + if (name_len == 0) return NULL; + ScrStr *name = scr_str_new(r->request->data, name_len); + ScrStr *pkg_dir = req_resolve2(nm_dir, name); + scr_str_release(name); + ScrStr *pjson = req_concat(pkg_dir, "/package.json", 13); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + ScrStr *found = NULL; + const ScrDyn *exports = pkg != NULL ? req_package_map(pkg, "exports") : NULL; + if (exports != NULL) { + ScrJsonBuf sub; + scr_jb_init(&sub); + scr_jb_putc(&sub, '.'); + scr_jb_write(&sub, r->request->data + name_len, r->request->len - name_len); + ScrStr *subpath = scr_jb_finish(&sub); + found = req_exports_resolve(r, pkg_dir, exports, subpath); + scr_str_release(subpath); + } + if (pkg != NULL) scr_dyn_release(pkg); + scr_str_release(pkg_dir); + return found; +} + +/* isRelative in Module._findPath: ".", "..", and "./" or "../" prefixes. */ +static bool req_is_relative(const ScrStr *request) { + if (req_eq(request, ".") || req_eq(request, "..")) return true; + if (req_has_prefix(request, "./") || req_has_prefix(request, "../")) return true; +#ifdef _WIN32 + if (req_has_prefix(request, ".\\") || req_has_prefix(request, "..\\")) return true; +#endif + return false; +} + +static bool req_trailing_slash(const ScrStr *request) { + const char *s = request->data; + size_t n = request->len; + if (n == 0) return false; + if (s[n - 1] == '/') return true; + if (s[n - 1] != '.') return false; + if (n == 1 || s[n - 2] == '/') return true; + return s[n - 2] == '.' && (n == 2 || s[n - 3] == '/'); +} + +/* Module._findPath over `paths` (borrowed strings). +1 real path or NULL. */ +static ScrStr *req_find_path(ScrRequire *r, ScrStr *const *paths, size_t count) { + ScrStr *request = r->request; + bool absolute = req_is_absolute(request); + ScrStr *empty = NULL; + if (absolute) { + empty = scr_str_new("", 0); + paths = ∅ + count = 1; + } + bool trailing = req_trailing_slash(request); + bool inside = true; + if (req_is_relative(request)) { +#ifdef _WIN32 + ScrStr *normalized = scr_path_win32_normalize(request); +#else + ScrStr *normalized = scr_path_normalize(request); +#endif + inside = !req_has_prefix(normalized, ".."); + scr_str_release(normalized); + } + ScrStr *found = NULL; + for (size_t i = 0; i < count && found == NULL && r->refusal == NULL; i++) { + ScrStr *dir = paths[i]; + if (inside && dir->len > 0 && req_stat(dir) < 1) continue; + if (!absolute) { + found = req_resolve_exports(r, dir); + if (found != NULL || r->refusal != NULL) break; + } + ScrStr *base = req_resolve2(dir, request); + int kind = req_stat(base); + if (!trailing) { + if (kind == 0) found = req_try_file(base); + if (found == NULL) found = req_try_extensions(base); + } + if (found == NULL && kind == 1) found = req_try_package(r, base); + scr_str_release(base); + } + if (empty) scr_str_release(empty); + return found; +} + +/* Module._nodeModulePaths(from) followed by Module.globalPaths' entries + * that come from the environment (NODE_PATH, then the home directory's + * .node_modules and .node_libraries). Node also searches the lib/node + * directory of its own installation prefix, which a compiled program does + * not have. Fills `out` (+1 each) and answers the count. */ +static size_t req_lookup_paths(ScrStr *from, ScrStr ***out) { + size_t cap = 16, n = 0; + ScrStr **paths = malloc(cap * sizeof *paths); + if (!paths) scr_trap("scriptc: out of memory\n"); +#define REQ_PUSH(value) \ + do { \ + if (n == cap) { \ + cap *= 2; \ + ScrStr **grown = realloc(paths, cap * sizeof *paths); \ + if (!grown) scr_trap("scriptc: out of memory\n"); \ + paths = grown; \ + } \ + paths[n++] = (value); \ + } while (0) + ScrStr *dot = scr_str_new(".", 1); + ScrStr *abs = req_resolve2(from, dot); + scr_str_release(dot); + static const char nm[] = "node_modules"; + const char *s = abs->data; +#ifdef _WIN32 + if (abs->len >= 2 && s[abs->len - 1] == '\\' && s[abs->len - 2] == ':') { + REQ_PUSH(req_concat(abs, "node_modules", 12)); + } else { + for (long i = (long)abs->len - 1, p = 0, last = (long)abs->len; i >= 0; --i) { + char c = s[i]; + if (c == '\\' || c == '/' || c == ':') { + if (p != 12) { + ScrStr *prefix = scr_str_new(s, (size_t)last); + REQ_PUSH(req_concat(prefix, "\\node_modules", 13)); + scr_str_release(prefix); + } + last = i; + p = 0; + } else if (p != -1) { + p = (p < 12 && nm[11 - p] == c) ? p + 1 : -1; + } + } + } +#else + if (abs->len == 1 && s[0] == '/') { + REQ_PUSH(scr_str_new("/node_modules", 13)); + } else { + for (long i = (long)abs->len - 1, p = 0, last = (long)abs->len; i >= 0; --i) { + char c = s[i]; + if (c == '/') { + if (p != 12) { + ScrStr *prefix = scr_str_new(s, (size_t)last); + REQ_PUSH(req_concat(prefix, "/node_modules", 13)); + scr_str_release(prefix); + } + last = i; + p = 0; + } else if (p != -1) { + p = (p < 12 && nm[11 - p] == c) ? p + 1 : -1; + } + } + REQ_PUSH(scr_str_new("/node_modules", 13)); + } +#endif + scr_str_release(abs); +#ifdef _WIN32 + const char *node_path = scr_getenv("NODE_PATH"); + const char *home = scr_getenv("USERPROFILE"); + const char delimiter = ';'; +#else + const char *node_path = scr_getenv("NODE_PATH"); + const char *home = scr_getenv("HOME"); + const char delimiter = ':'; +#endif + if (node_path != NULL) { + for (const char *p = node_path; *p;) { + const char *end = strchr(p, delimiter); + size_t len = end ? (size_t)(end - p) : strlen(p); + if (len > 0) REQ_PUSH(scr_str_new(p, len)); + p += len; + if (*p) p++; + } + } + if (home != NULL && *home) { + ScrStr *home_dir = req_cstr(home); + static const char *const globals[] = { ".node_modules", ".node_libraries" }; + for (size_t i = 0; i < 2; i++) { + ScrStr *name = req_cstr(globals[i]); + REQ_PUSH(req_resolve2(home_dir, name)); + scr_str_release(name); + } + scr_str_release(home_dir); + } +#undef REQ_PUSH + *out = paths; + return n; +} + +/* trySelf: the nearest package.json above the importer (Node stops below + * the root and at a node_modules directory) resolves the request through + * its own "exports" when its "name" prefixes it. */ +static ScrStr *req_try_self(ScrRequire *r) { + ScrStr *dir = req_dirname(r->importer); + ScrStr *found = NULL; + for (;;) { + ScrStr *parent = req_dirname(dir); + bool root = scr_str_eq(parent, dir); + scr_str_release(parent); + if (root) break; + ScrStr *dot = scr_str_new(".", 1); + ScrStr *abs = req_resolve2(dir, dot); + scr_str_release(dot); + bool boundary = req_has_suffix(abs, "/node_modules") +#ifdef _WIN32 + || req_has_suffix(abs, "\\node_modules") +#endif + ; + scr_str_release(abs); + if (boundary) break; + ScrStr *pjson_name = req_cstr("package.json"); + ScrStr *pjson = req_resolve2(dir, pjson_name); + scr_str_release(pjson_name); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + if (r->refusal != NULL) break; + if (pkg != NULL) { + ScrStr *name = req_package_string(pkg, "name"); + const ScrDyn *exports = req_package_map(pkg, "exports"); + if (req_has_prefix(r->request, "#") && req_package_map(pkg, "imports") != NULL) { + r->refusal = "a package \"imports\" specifier"; + } else if (name != NULL && exports != NULL) { + bool whole = scr_str_eq(name, r->request); + bool prefixed = r->request->len > name->len && + memcmp(r->request->data, name->data, name->len) == 0 && + r->request->data[name->len] == '/'; + if (whole || prefixed) { + ScrJsonBuf sub; + scr_jb_init(&sub); + scr_jb_putc(&sub, '.'); + scr_jb_write(&sub, r->request->data + name->len, r->request->len - name->len); + ScrStr *subpath = scr_jb_finish(&sub); + found = req_exports_resolve(r, dir, exports, subpath); + scr_str_release(subpath); + } + } + scr_dyn_release(pkg); + break; + } + ScrStr *next = req_dirname(dir); + scr_str_release(dir); + dir = next; + } + scr_str_release(dir); + return found; +} + +/* The builtin id `request` names (+1), or NULL. */ +static ScrStr *req_builtin(ScrRequire *r) { + ScrDyn *id = scr_dyn_new_str(r->request); + ScrStr *bare = scr_process_builtin_id(id, r->builtins); + scr_dyn_release(id); + if (bare == NULL || bare->len == 0) { + if (bare) scr_str_release(bare); + return NULL; + } + scr_str_release(bare); + return scr_str_retain(r->request); +} + +/* Module._resolveFilename: +1 resolved id or path; NULL when nothing + * resolves (or the refusal is set). */ +static ScrStr *req_resolve_filename(ScrRequire *r) { + ScrStr *builtin = req_builtin(r); + if (builtin != NULL) return builtin; + ScrStr *found = req_try_self(r); + if (found != NULL || r->refusal != NULL) return found; + bool lookup = r->request->len == 0 || r->request->data[0] != '.' || + (r->request->len > 1 && r->request->data[1] != '.' && r->request->data[1] != '/' +#ifdef _WIN32 + && r->request->data[1] != '\\' +#endif + ); + if (lookup) { + ScrStr *dir = req_dirname(r->importer); + ScrStr **paths = NULL; + size_t count = req_lookup_paths(dir, &paths); + scr_str_release(dir); + found = req_find_path(r, paths, count); + for (size_t i = 0; i < count; i++) scr_str_release(paths[i]); + free(paths); + } else { + ScrStr *dir = req_dirname(r->importer); + found = req_find_path(r, &dir, 1); + scr_str_release(dir); + } + return found; +} + +/* ── entry points ────────────────────────────────────────────────────── */ + +/* Ends the program with the SC2020 refusal: it names the native addon, the + * builtin or file the specifier resolves to, or the resolution step, then + * the program exits with status 1 as after an uncaught error, running the + * 'exit' listeners. */ +static void req_refuse(ScrRequire *r, const char *call, const ScrStr *found, bool builtin, ScrStr *site) { + ScrJsonBuf text; + scr_jb_init(&text); + if (r->refusal != NULL) { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of '"); + scr_jb_write(&text, r->request->data, r->request->len); + scr_jb_puts(&text, "' reaches "); + scr_jb_puts(&text, r->refusal); + scr_jb_puts(&text, ", which scriptc does not resolve at run time"); + } else if (builtin) { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of the builtin '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "' with a computed specifier is not supported; require it with a string literal"); + } else if (req_has_suffix(found, ".node")) { + scr_jb_puts(&text, "native addon '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "' requires Node's addon runtime, which is unavailable in scriptc"); + } else { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of '"); + scr_jb_write(&text, r->request->data, r->request->len); + scr_jb_puts(&text, "' resolves to '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "', which is not part of the compiled program: the module graph is fixed at build time"); + } + scr_jb_puts(&text, " [SC2020 at "); + scr_jb_write(&text, site->data, site->len); + scr_jb_puts(&text, "]\n"); + ScrStr *message = scr_jb_finish(&text); + fflush(stdout); + fputs("scriptc: ", stderr); + fwrite(message->data, 1, message->len, stderr); + fflush(stderr); + scr_str_release(message); + scr_process_exit(1); +} + +static void req_throw(const char *code, const char *prefix, ScrRequire *r, bool stack) { + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, prefix); + scr_jb_write(&text, r->request->data, r->request->len); + if (stack) { + scr_jb_puts(&text, "'\nRequire stack:\n- "); + scr_jb_write(&text, r->importer->data, r->importer->len); + } + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, code); + scr_str_release(message); +} + +ScrDyn *scr_require_computed(ScrDyn *id, ScrStr *importer, ScrStr *site, ScrArr *builtins) { + if (id->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("id", "of type string", id); + return NULL; + } + if (id->v.str->len == 0) { + static const char message[] = "The argument 'id' must be a non-empty string. Received ''"; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, "ERR_INVALID_ARG_VALUE"); + return NULL; + } + ScrRequire r = { id->v.str, importer, builtins, NULL }; + ScrStr *builtin = req_builtin(&r); + if (builtin == NULL && req_has_prefix(r.request, "node:")) { + req_throw("ERR_UNKNOWN_BUILTIN_MODULE", "No such built-in module: ", &r, false); + return NULL; + } + ScrStr *found = builtin != NULL ? builtin : req_resolve_filename(&r); + if (found == NULL && r.refusal == NULL) { + req_throw("MODULE_NOT_FOUND", "Cannot find module '", &r, true); + return NULL; + } + req_refuse(&r, "createRequire's require", found, builtin != NULL, site); + if (found) scr_str_release(found); + return NULL; +} + +/* require.resolve's options: Node reads `paths` and `conditions` from an + * object; other values select the default lookup. */ +static const char *req_options_refusal(const ScrDyn *options) { + switch (options->kind) { + case SCR_DYN_UNDEF: + case SCR_DYN_NULL: + case SCR_DYN_NUM: + case SCR_DYN_BOOL: + case SCR_DYN_STR: + return NULL; + case SCR_DYN_OBJ: { + const ScrDyn *paths = scr_dyn_obj_get(options, "paths", 5); + const ScrDyn *conditions = scr_dyn_obj_get(options, "conditions", 10); + if (paths != NULL && paths->kind != SCR_DYN_UNDEF) return "require.resolve's paths option"; + if (conditions != NULL && conditions->kind != SCR_DYN_UNDEF) return "require.resolve's conditions option"; + return NULL; + } + default: + return "require.resolve options that are not a plain object"; + } +} + +ScrStr *scr_require_resolve(ScrDyn *request, ScrDyn *options, ScrStr *importer, ScrStr *site, + ScrArr *builtins) { + if (request->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("request", "of type string", request); + return NULL; + } + ScrRequire r = { request->v.str, importer, builtins, req_options_refusal(options) }; + ScrStr *found = r.refusal == NULL ? req_resolve_filename(&r) : NULL; + if (r.refusal != NULL) { + if (found) scr_str_release(found); + req_refuse(&r, "createRequire's require.resolve", NULL, false, site); + return NULL; + } + if (found == NULL) req_throw("MODULE_NOT_FOUND", "Cannot find module '", &r, true); + return found; +} diff --git a/tests/corpus/8210-require-computed-missing/main.ts b/tests/corpus/8210-require-computed-missing/main.ts new file mode 100644 index 000000000..685222157 --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/main.ts @@ -0,0 +1,7 @@ +// A package that probes for optional files through createRequire with +// computed specifiers. A specifier that names no file throws Node's error, +// so the package takes its absent branch as in Node. +import { probeAddon, requireErrors } from "optional-probe"; + +console.log(probeAddon("clipboard")); +for (const line of requireErrors()) console.log(line); diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts new file mode 100644 index 000000000..4eb024b0c --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts @@ -0,0 +1,2 @@ +export declare function probeAddon(name: string): string; +export declare function requireErrors(): string[]; diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js new file mode 100644 index 000000000..c824302ba --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js @@ -0,0 +1,46 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const importer = fileURLToPath(import.meta.url); +const load = createRequire(import.meta.url); +const here = dirname(importer); + +// Looks for a prebuilt addon in each packaging location. +export function probeAddon(name) { + const codes = []; + const dirs = [join(here, "prebuilds", `${process.platform}-${process.arch}`), join(here, "native"), here]; + for (const dir of dirs) { + try { + const addon = load(join(dir, `${name}.node`)); + return `loaded ${typeof addon}`; + } catch (error) { + codes.push(error.code); + } + } + return `absent: ${codes.join(", ")}`; +} + +function describe(spec) { + try { + load(spec); + return "loaded"; + } catch (error) { + return `${error.name} ${error.code}: ${error.message.replaceAll(importer, "")}`; + } +} + +export function requireErrors() { + const specs = [ + "./missing.js", + "./missing", + "../missing-directory/", + "scriptc-corpus-missing-package", + "@scriptc-corpus/missing/sub", + "node:not-a-builtin", + "", + 42, + undefined, + ]; + return specs.map((spec) => `${typeof spec === "string" ? JSON.stringify(spec) : String(spec)} -> ${describe(spec)}`); +} diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json new file mode 100644 index 000000000..50ad53129 --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json @@ -0,0 +1 @@ +{ "name": "optional-probe", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/8211-require-resolve-computed/main.ts b/tests/corpus/8211-require-resolve-computed/main.ts new file mode 100644 index 000000000..d8e441d2f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/main.ts @@ -0,0 +1,7 @@ +// require.resolve with requests computed at run time, called directly and +// read as a value, resolves like Node: builtins, relative files with +// Node's extension search, package "main" and "exports" entries, a +// package's own name, and MODULE_NOT_FOUND for requests that name nothing. +import { resolveAll } from "resolve-probe"; + +for (const line of resolveAll()) console.log(line); diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js new file mode 100644 index 000000000..c414b6b26 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js @@ -0,0 +1 @@ +module.exports = "node"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js new file mode 100644 index 000000000..7f4090ad9 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js @@ -0,0 +1 @@ +module.exports = "other"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json new file mode 100644 index 000000000..25326e6e5 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json @@ -0,0 +1 @@ +{ "name": "@scope/scoped", "version": "1.0.0", "exports": { "node": "./lib/node.js", "default": "./lib/other.js" } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js new file mode 100644 index 000000000..0d24e266f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js @@ -0,0 +1 @@ +module.exports = "index"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json new file mode 100644 index 000000000..6d94e13bd --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json @@ -0,0 +1 @@ +{ "kind": "data" } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts new file mode 100644 index 000000000..1b8cc8a0e --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts @@ -0,0 +1 @@ +export declare function resolveAll(): string[]; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js new file mode 100644 index 000000000..870a14401 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -0,0 +1,50 @@ +import { createRequire } from "node:module"; +import { dirname, relative } from "node:path"; +import { fileURLToPath } from "node:url"; + +const importer = fileURLToPath(import.meta.url); +const root = dirname(dirname(dirname(importer))); +const load = createRequire(import.meta.url); + +function show(resolved) { + return resolved.startsWith("/") || /^[A-Za-z]:\\/.test(resolved) ? relative(root, resolved).replaceAll("\\", "/") : resolved; +} + +function describe(resolve, request) { + try { + return show(resolve(request)); + } catch (error) { + return `${error.name} ${error.code}: ${error.message.replaceAll(importer, "")}`; + } +} + +const requests = [ + "fs", + "node:fs", + "node:test", + "./data.json", + "./data", + "./index", + "resolve-probe", + "resolve-probe/data", + "with-exports", + "with-exports/feature", + "with-exports/lib/helper", + "with-main", + "plain-index", + "@scope/scoped", + "./missing", + "scriptc-corpus-missing-package", + "", + 42, +]; + +export function resolveAll(options = {}) { + const resolve = options.resolve ?? load.resolve; + const label = (request) => (typeof request === "string" ? JSON.stringify(request) : String(request)); + return [ + ...requests.map((request) => `call ${label(request)} -> ${describe((r) => load.resolve(r), request)}`), + ...requests.slice(0, 4).map((request) => `value ${label(request)} -> ${describe(resolve, request)}`), + `options ${describe((r) => load.resolve(r, {}), "with-main")}`, + ]; +} diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json new file mode 100644 index 000000000..d0935f646 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json @@ -0,0 +1 @@ +{ "name": "resolve-probe", "version": "1.0.0", "type": "module", "exports": { ".": { "types": "./index.d.ts", "default": "./index.js" }, "./data": "./data.json" } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs new file mode 100644 index 000000000..2cd122bdc --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs @@ -0,0 +1 @@ +module.exports = "cjs"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs new file mode 100644 index 000000000..06c676e54 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs @@ -0,0 +1 @@ +export default "esm"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js new file mode 100644 index 000000000..33535fc6f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js @@ -0,0 +1 @@ +module.exports = "feature"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js new file mode 100644 index 000000000..4f51f7bfa --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js @@ -0,0 +1 @@ +module.exports = "helper"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js new file mode 100644 index 000000000..0470f8201 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js @@ -0,0 +1 @@ +module.exports = "main"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json new file mode 100644 index 000000000..09d74787c --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json @@ -0,0 +1 @@ +{ "name": "with-exports", "version": "1.0.0", "main": "./main.js", "exports": { ".": { "import": "./esm.mjs", "require": "./cjs.cjs" }, "./feature": "./lib/feature.js", "./lib/*": "./lib/*.js", "./private/*": null } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js new file mode 100644 index 000000000..6555fae7c --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js @@ -0,0 +1 @@ +module.exports = "entry"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json new file mode 100644 index 000000000..8c473085d --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json @@ -0,0 +1 @@ +{ "name": "with-main", "version": "1.0.0", "main": "lib/entry" } diff --git a/tests/corpus/8212-native-helper-probe-no-display/main.ts b/tests/corpus/8212-native-helper-probe-no-display/main.ts new file mode 100644 index 000000000..a9e021017 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/main.ts @@ -0,0 +1,18 @@ +// A terminal package that loads prebuilt native helpers the way pi-tui does: +// it builds candidate paths (through require.resolve read as a value) and +// requires each one inside a try block. On Linux without DISPLAY the +// clipboard helper is never loaded, and on other platforms no helper for +// the platform is installed, so the package uses its JavaScript fallback as +// in Node. +import { getNativeClipboard, getNativeModuleCandidates, isNativeModifierPressed } from "native-helper"; +import { relative } from "node:path"; + +delete process.env["DISPLAY"]; +console.log("clipboard", typeof getNativeClipboard()); +console.log("modifier", isNativeModifierPressed("shift")); +const root = import.meta.dirname; +for (const candidate of getNativeModuleCandidates("native/helper.node")) { + console.log("candidate", relative(root, candidate).replaceAll("\\", "/")); +} +const custom = getNativeModuleCandidates("native/helper.node", { resolvePackage: () => "/opt/helper/dist/index.js" }); +console.log("custom", custom[0]!.replaceAll("\\", "/")); diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts new file mode 100644 index 000000000..8803ba552 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts @@ -0,0 +1,11 @@ +export interface NativeHelper { + getText(): string; + getImage(): Uint8Array | undefined; + isModifierPressed?(key: string): boolean; +} +export declare function getNativeClipboard(): NativeHelper | undefined; +export declare function isNativeModifierPressed(key: string): boolean; +export declare function getNativeModuleCandidates( + nativePath: string, + options?: { moduleUrl?: string; resolvePackage?: (id: string) => string; execPath?: string }, +): string[]; diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js new file mode 100644 index 000000000..874130379 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js @@ -0,0 +1,2 @@ +export { getNativeClipboard, isNativeModifierPressed } from "./native-platform.js"; +export { getNativeModuleCandidates } from "./native-module-path.js"; diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js new file mode 100644 index 000000000..2a82b41f7 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js @@ -0,0 +1,20 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const moduleRequire = createRequire(import.meta.url); + +// Where a prebuilt helper can live: next to the installed package, next to +// this module, and next to the executable. +export function getNativeModuleCandidates(nativePath, options = {}) { + const moduleDir = dirname(fileURLToPath(options.moduleUrl ?? import.meta.url)); + const candidates = []; + try { + const entry = (options.resolvePackage ?? moduleRequire.resolve)("native-helper"); + candidates.push(join(dirname(entry), "..", nativePath)); + } catch { + // A standalone executable has no installed package. + } + candidates.push(join(moduleDir, "..", nativePath), join(moduleDir, nativePath)); + return Array.from(new Set(candidates)); +} diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js new file mode 100644 index 000000000..9aaed1dc1 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js @@ -0,0 +1,48 @@ +import { createRequire } from "node:module"; +import * as path from "node:path"; +import { getNativeModuleCandidates } from "./native-module-path.js"; + +const load = createRequire(import.meta.url); +const helpers = new Map(); + +function loadHelper(platform, suffix = "") { + const arch = process.arch; + if (arch !== "x64" && arch !== "arm64") return undefined; + const nativePath = path.join("native", platform, "prebuilds", `${platform}-${arch}`, `${platform}-helper${suffix}.node`); + if (helpers.has(nativePath)) return helpers.get(nativePath); + for (const modulePath of getNativeModuleCandidates(nativePath)) { + try { + const helper = load(modulePath); + if (typeof helper?.getText === "function") { + helpers.set(nativePath, helper); + return helper; + } + } catch { + // Try the next location. + } + } + helpers.set(nativePath, undefined); + return undefined; +} + +function getPlatformHelper() { + if (process.platform !== "darwin" && process.platform !== "win32") return undefined; + return loadHelper(process.platform); +} + +// The clipboard helper opens the X display, so it loads only when one is set. +export function getNativeClipboard() { + if (process.platform !== "linux") return getPlatformHelper(); + if (!process.env.DISPLAY) return undefined; + return loadHelper("linux", "-x11"); +} + +export function isNativeModifierPressed(key) { + const helper = getPlatformHelper(); + if (!helper?.isModifierPressed) return false; + try { + return helper.isModifierPressed(key) === true; + } catch { + return false; + } +} diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node new file mode 100644 index 000000000..72d2c39bd --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node @@ -0,0 +1 @@ +placeholder for a prebuilt X11 clipboard helper diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node new file mode 100644 index 000000000..72d2c39bd --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node @@ -0,0 +1 @@ +placeholder for a prebuilt X11 clipboard helper diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json new file mode 100644 index 000000000..475c1d5af --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json @@ -0,0 +1 @@ +{ "name": "native-helper", "version": "1.0.0", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts" } diff --git a/tests/fixtures/npm-static/addon-probe-cli.ts b/tests/fixtures/npm-static/addon-probe-cli.ts new file mode 100644 index 000000000..4515fb5bf --- /dev/null +++ b/tests/fixtures/npm-static/addon-probe-cli.ts @@ -0,0 +1,5 @@ +import { probe, resolveHelper } from "addon-probe"; +import { join } from "node:path"; + +console.log(join("a", "b"), [probe("clipboard"), probe("keys"), resolveHelper("addon-probe-helper")].join(",")); +console.log(resolveHelper("other", (id) => `custom:${id}`)); diff --git a/tests/fixtures/npm-static/addon-probe-installed-cli.ts b/tests/fixtures/npm-static/addon-probe-installed-cli.ts new file mode 100644 index 000000000..5568ce086 --- /dev/null +++ b/tests/fixtures/npm-static/addon-probe-installed-cli.ts @@ -0,0 +1,4 @@ +import { loadInstalled } from "addon-probe"; + +console.log("before"); +console.log(loadInstalled(process.argv[2] === "module" ? "module" : "addon")); diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts new file mode 100644 index 000000000..7de7bc1fd --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts @@ -0,0 +1,3 @@ +export declare function probe(name: string): string; +export declare function resolveHelper(name: string, resolvePackage?: (id: string) => string): string; +export declare function loadInstalled(kind: "addon" | "module"): string; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.js b/tests/fixtures/npm-static/node_modules/addon-probe/index.js new file mode 100644 index 000000000..b718d7e98 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.js @@ -0,0 +1,38 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const load = createRequire(import.meta.url); +const here = dirname(fileURLToPath(import.meta.url)); + +// Looks for a prebuilt addon in each packaging location, the way +// packages with optional native helpers do. +export function probe(name) { + for (const dir of ["prebuilds", "native"]) { + try { + const addon = load(join(here, dir, `${name}.node`)); + if (typeof addon?.run === "function") return "loaded"; + } catch { + // Try the next location. + } + } + return "absent"; +} + +export function resolveHelper(name, resolvePackage) { + try { + return (resolvePackage ?? load.resolve)(name); + } catch { + return "unresolved"; + } +} + +// Requires a file that is installed: an addon, or a module that is not part +// of the compiled program. +export function loadInstalled(kind) { + try { + return typeof load(join(here, kind === "addon" ? "native/installed.node" : "installed.js")); + } catch { + return "fallback"; + } +} diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/installed.js b/tests/fixtures/npm-static/node_modules/addon-probe/installed.js new file mode 100644 index 000000000..8476dc24e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/installed.js @@ -0,0 +1 @@ +export const installed = true; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node b/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node new file mode 100644 index 000000000..3d1ca6ec7 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node @@ -0,0 +1 @@ +placeholder for a prebuilt addon diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/package.json b/tests/fixtures/npm-static/node_modules/addon-probe/package.json new file mode 100644 index 000000000..a2f7406f5 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/package.json @@ -0,0 +1,7 @@ +{ + "name": "addon-probe", + "version": "1.0.0", + "type": "module", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json b/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json b/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json b/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 388da2138..962dc75b8 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -423,13 +423,99 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { }, ); + test.each(["llvm"] as const)( + "a package probing for optional addons through createRequire resolves at run time (%s)", + async (backend) => { + const entry = join(pilotRoot, "addon-probe-cli.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toEqual([{ package: "addon-probe", status: "static" }]); + expect(coverage.diagnostics).toHaveLength(0); + expect( + (coverage.runtimeFences ?? []).filter((f) => f.code === "SC2020").map((f) => f.message), + ).toEqual([ + expect.stringContaining( + "createRequire's require with a computed specifier resolves at run time", + ), + expect.stringContaining( + "createRequire's require.resolve with a computed specifier resolves at run time", + ), + ]); + const outDir = join(cacheDir, "addon-probe", sanitize ? "san" : "plain", backend); + const result = await compile(entry, { + backend, + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary(process.execPath, [entry]), + runBinary(result.binaryPath, []), + ]); + expect(nativeRes.stdout.toString()).toBe("a/b absent,absent,unresolved\ncustom:other\n"); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, + ); + + // Node would load the installed file, which the compiled program cannot: + // the require ends the program with the refusal instead of throwing, so + // the package's catch clause does not take its fallback. test.each([ [ - "computed specifier", - 'const spec = "node:path"; const path = load(spec);', - "computed specifier", + "addon", + /^scriptc: native addon '.*[\\/]native[\\/]installed\.node' requires Node's addon runtime, which is unavailable in scriptc \[SC2020 at .*index\.js:\d+\]\n$/, ], + [ + "module", + /^scriptc: createRequire's require of '.*[\\/]installed\.js' resolves to '.*[\\/]installed\.js', which is not part of the compiled program: the module graph is fixed at build time \[SC2020 at .*index\.js:\d+\]\n$/, + ], + ] as const)( + "a computed require of an installed %s ends the program with a refusal", + async (kind, stderr) => { + const entry = join(pilotRoot, "addon-probe-installed-cli.ts"); + const outDir = join(cacheDir, "addon-probe-installed", sanitize ? "san" : "plain"); + const result = await compile(entry, { + backend: "llvm", + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const nativeRes = await runBinary(result.binaryPath, [kind]); + expect(nativeRes.stdout.toString()).toBe("before\n"); + expect(comparableStderr(nativeRes.stderr).toString()).toMatch(stderr); + expect(nativeRes.exitCode).toBe(1); + }, + ); + + test.each([ ["escaped require", 'const alias = load; const path = alias("node:path");', "escapes"], + [ + "escaped require in a try block", + 'try { const alias = load; alias("node:path"); } catch {} const path = load("node:path");', + "escapes", + ], + [ + "computed resolve.paths request", + 'const spec = "node:path"; load.resolve.paths(spec); const path = load("node:path");', + "escapes", + ], + [ + "resolve.paths value", + 'const paths = load.resolve.paths; const path = load("node:path");', + "escapes", + ], + [ + "spread require arguments", + 'const specs = ["node:path"]; load(...specs); const path = load("node:path");', + "escapes", + ], [ "reassigned require", 'let load = createLoader(import.meta.url); load = () => ({}); const path = load("node:path");', From 970a53c1ba17516b203fe445de2769392321ab2f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 00:05:48 -0500 Subject: [PATCH 046/395] Store (T | undefined)[] elements as T so widened arrays stay one array A number[], string[] or boolean[] seen as (T | undefined)[] through a cast, a binding or a parameter is the same array in Node: undefined written through either name is visible through both. The type mapper now stores (T | undefined)[] of a number, string or boolean T as T elements, which hold a present undefined by their slot state, so the wider name shares the storage instead of converting to a separate union array. Writers of these arrays store undefined as a present undefined: element writes, push, unshift and splice, literals, Array.from (with a length or a mapper), Array.of, generator drains, flatMap, rest packs, copies from Map and Set values and Object.values, and checked conversions from dynamic values. Promise.all over Promise[] fills the result inside the combinator's settlement, so it settles on Node's turn. The element-state analysis counts callbacks and iterables whose checker type admits undefined. Readers keep their optional reads: index reads through a union of arrays, searches with a direct needle, and asserted reads, which throw a TypeError for undefined. Comparators and callbacks that receive present elements take the element type, without an adapter. --- packages/compiler/src/backend/llvm/dyn.ts | 25 ++- .../src/backend/llvm/expr-stream-bridges.ts | 19 +- .../src/frontend/lowering/array-values.ts | 140 +++++++++++++++ .../frontend/lowering/builtins/promises.ts | 162 +++++++++++++++++- .../src/frontend/lowering/call-arguments.ts | 10 ++ .../frontend/lowering/coercions/structural.ts | 110 ++++++++++-- .../lowering/containers/array-callbacks.ts | 66 +++++-- .../lowering/containers/array-construction.ts | 67 +++++++- .../lowering/containers/array-methods.ts | 106 +++++++++++- .../lowering/containers/callback-arguments.ts | 13 +- .../lowering/containers/collection-methods.ts | 12 ++ .../lowering/containers/indexed-objects.ts | 106 +++++++++--- .../lowering/expressions/array-literals.ts | 23 ++- .../lowering/expressions/element-access.ts | 45 +++++ .../frontend/lowering/iterator-adapters.ts | 58 +++---- .../compiler/src/frontend/lowering/lowerer.ts | 58 ++++++- .../lowering/runtime-optional-elements.ts | 9 +- .../frontend/lowering/sanitize-class-types.ts | 2 + packages/compiler/src/frontend/type-mapper.ts | 19 +- packages/compiler/src/ir/ir.ts | 11 +- packages/compiler/src/ir/validate.ts | 10 +- ...ional-element-arrays-share-storage.ts.json | 6 + .../8001-optional-element-writers.ts.json | 6 + ...0-optional-element-arrays-share-storage.ts | 61 +++++++ tests/corpus/8001-optional-element-writers.ts | 103 +++++++++++ ...ional-element-arrays-share-storage.ts.json | 1 + .../8001-optional-element-writers.ts.json | 1 + 27 files changed, 1145 insertions(+), 104 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json create mode 100644 tests/corpus/8000-optional-element-arrays-share-storage.ts create mode 100644 tests/corpus/8001-optional-element-writers.ts create mode 100644 tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json create mode 100644 tests/harness/corpus-costs/8001-optional-element-writers.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 6d02daa8b..f3c8231e1 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -547,6 +547,13 @@ export class LlDyn { /* ── dyn node plumbing ─────────────────────────────────────────────── */ /** Loads a dyn node's kind tag (after the size_t rc word). */ + /** i1: the checked-dynamic value `d` is undefined. */ + isUndefined(B: BlockBuilder, d: string): string { + const is = B.tmp(); + B.line(`${is} = icmp eq i32 ${this.kindOf(B, d)}, ${DYN_KIND.UNDEF}`); + return is; + } + private kindOf(B: BlockBuilder, d: string): string { const p = B.tmp(); const k = B.tmp(); @@ -1655,7 +1662,8 @@ export class LlDyn { * BUILD the typed value (+1), or throw the catchable path-annotated * TypeError and return a dummy with the pending flag set. */ dynCheckHelper(t: IrType, preserveRefs = false): string { - const key = typeKey(t); + // A `(T | undefined)[]` stored as T elements also takes undefined ones. + const key = t.kind === "array" && t.optionalElements ? `${typeKey(t)}?` : typeKey(t); const builderKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynBuilders.get(builderKey); if (existing) return existing; @@ -2435,6 +2443,21 @@ export class LlDyn { B.line(`${ip} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 2`); B.line(`store ${host.sizeType} ${i}, ptr ${ip}`); const e = this.itemAt(B, items, i); + if (t.optionalElements) { + // An undefined element is a present undefined element. + const undef = B.tmp(); + const storeUndefined = B.newLabel("dca.undef"); + const checkLabel = B.newLabel("dca.check"); + B.line(`${undef} = icmp eq i32 ${this.kindOf(B, e)}, ${DYN_KIND.UNDEF}`); + B.condBr(undef, storeUndefined, checkLabel); + B.startBlock(storeUndefined); + host.declare(`declare void @scr_arr_set_undefined(ptr, double)`); + const at = B.tmp(); + B.line(`${at} = ${sizeToDouble(this.S)} ${this.S} ${i} to double`); + B.line(`call void @scr_arr_set_undefined(ptr ${a}, double ${at})`); + B.br(doneLabel); + B.startBlock(checkLabel); + } const v = B.tmp(); B.line(`${v} = call ${this.valTy(elem)} @${c}(ptr ${e}, ptr ${pathSlot})`); this.pendingBail( diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 46c2d884f..814ef76b5 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -744,13 +744,16 @@ export function streamTypedRefMaterializeAdapter( t: IrType, ): LlStreamTypedRefAdapter { const key = typeKey(t); - const existing = host.liveDynRefAdapters.get(key); + // A `(T | undefined)[]` stored as T elements shares T[]'s capsule type, + // but its adapters also store undefined elements. + const adapterKey = t.kind === "array" && t.optionalElements ? `${key}?` : key; + const existing = host.liveDynRefAdapters.get(adapterKey); if (existing) return existing; // The converter depends only on the static type. Intern the complete // graph across roots and union arms so recursive types emit once. const snapshot = `sc_ldr_${host.liveDynRefAdapters.size}_materialize`; const adapter: LlStreamTypedRefAdapter = { snapshot, commit: "null" }; - host.liveDynRefAdapters.set(key, adapter); + host.liveDynRefAdapters.set(adapterKey, adapter); host.journal?.allocated( snapshot.replace(/_materialize$/, ""), "streamTypedRefMaterializeAdapter", @@ -1257,6 +1260,18 @@ function liveArrayOps( G.terminate(`ret ptr ${boxed}`); const S = new BlockBuilder(); + if (t.optionalElements) { + // A `(T | undefined)[]` stores undefined as a present undefined. + const undef = host.dyn.isUndefined(S, "%v"); + const storeUndefined = S.newLabel("live.array.undefined"); + const other = S.newLabel("live.array.value"); + S.condBr(undef, storeUndefined, other); + S.startBlock(storeUndefined); + host.declare(`declare void @scr_arr_set_undefined(ptr, double)`); + S.line(`call void @scr_arr_set_undefined(ptr %p, double %i)`); + S.terminate(`ret i1 true`); + S.startBlock(other); + } const fits = S.tmp(); S.line(`${fits} = call zeroext i1 @${host.dyn.dynMatchHelper(elem)}(ptr %v)`); const store = S.newLabel("live.array.store"); diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index f191ad942..6e4a45f86 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -269,6 +269,140 @@ function readsMutableLocal(lowerer: Lowerer, e: IrExpr): boolean { }); } +/** A number, string or boolean element type. Arrays of it hold a present + * undefined in the element's state, so `(T | undefined)[]` stores T + * elements (the type mapper) and a T[] seen as one stays the same array. */ +export function primitiveElement(elem: IrType): boolean { + return elem.kind === "f64" || elem.kind === "string" || elem.kind === "bool"; +} + +/** `value` is the element type with undefined, stored into an array of a + * primitive element type: a present undefined or the element. */ +export function optionalElementValue(lowerer: Lowerer, value: IrExpr, elem: IrType): boolean { + if (!primitiveElement(elem)) return false; + if (value.type.kind === "undefinedT") return true; + return ( + value.type.kind === "union" && + lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(value.type), elem) + ); +} + +/** A value of a primitive element type that holds a slot until the value + * stored there replaces it (insertedValues), or stands for a slot that is + * not read. */ +export function elementPlaceholder(elem: IrType, loc: SrcLoc): IrExpr { + if (elem.kind === "f64") return { kind: "numLit", value: 0, type: F64, loc }; + if (elem.kind === "bool") return { kind: "boolLit", value: false, type: BOOL, loc }; + return { kind: "strLit", value: "", type: STRING, loc }; +} + +/** Values inserted into an array (push, unshift, splice, a literal), in + * source order. A value that can be undefined (optionalElementValue) is + * kept in a temporary: the insertion takes a placeholder for it, and + * `fill` then stores the value at the placeholder's index. */ +export function insertedValues( + lowerer: Lowerer, + values: readonly IrExpr[], + elem: IrType, + coerce: (value: IrExpr, index: number) => IrExpr, + loc: SrcLoc, +): { + stmts: IrStmt[]; + args: IrExpr[]; + optional: boolean; + fill: (arr: IrExpr, first: IrExpr) => IrStmt[]; +} { + if (!values.some((value) => optionalElementValue(lowerer, value, elem))) + return { stmts: [], args: values.map(coerce), optional: false, fill: () => [] }; + const stmts: IrStmt[] = []; + const args: IrExpr[] = []; + const held: { offset: number; value: IrExpr }[] = []; + values.forEach((value, offset) => { + // Every value is evaluated in order before the insertion. + if (value.type.kind === "undefinedT") { + if (value.kind !== "unitLit") stmts.push({ kind: "exprStmt", expr: value, loc }); + args.push(elementPlaceholder(elem, loc)); + held.push({ offset, value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } }); + return; + } + const temp = lowerer.declareHiddenLocal("%inserted", value.type); + stmts.push({ kind: "varDecl", localId: temp.id, init: value, loc }); + const stable = varRef(temp.id, temp.type, loc); + if (optionalElementValue(lowerer, value, elem)) { + args.push(elementPlaceholder(elem, loc)); + held.push({ offset, value: stable }); + } else { + args.push(coerce(stable, offset)); + } + }); + const fill = (arr: IrExpr, first: IrExpr): IrStmt[] => + held.map(({ offset, value }) => + arrayValueStore( + lowerer, + arr, + offset === 0 + ? first + : { + kind: "bin", + op: "+", + left: first, + right: { kind: "numLit", value: offset, type: F64, loc }, + type: F64, + loc, + }, + value, + elem, + loc, + ), + ); + return { stmts, args, optional: true, fill }; +} + +/** Statements appending `value` to `arr` inside a lifted helper. A value + * that can be undefined in a primitive array (optionalElementValue) is held + * in the helper's local `slot` (which the caller declares with the value's + * type) and stored as a present undefined or the element; any other value + * converts to the element type. */ +export function appendElement( + lowerer: Lowerer, + arr: IrExpr, + value: IrExpr, + elem: IrType, + slot: string, + loc: SrcLoc, +): IrStmt[] { + if (optionalElementValue(lowerer, value, elem) && value.type.kind !== "undefinedT") { + const held = varRef(slot, value.type, loc); + const length: IrExpr = { + kind: "arrIntrinsic", + method: "length", + receiver: arr, + args: [], + type: F64, + loc, + }; + return [ + { kind: "varDecl", localId: slot, init: value, loc }, + arrayValueStore(lowerer, arr, length, held, elem, loc), + ]; + } + return [ + { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "push", + receiver: arr, + args: [lowerer.coerceToExpected(value, elem)], + type: F64, + loc, + }, + loc, + }, + ]; +} + /** Store a stabilized value without changing the array's element ABI. */ export function arrayValueStore( lowerer: Lowerer, @@ -279,6 +413,12 @@ export function arrayValueStore( loc: SrcLoc, ): IrStmt { if (typeEquals(value.type, elem)) return { kind: "arraySet", arr, index, value, loc }; + // An undefined value of a `(T | undefined)[]` (primitiveElement). + if (value.type.kind === "undefinedT" && primitiveElement(elem)) { + const write: IrStmt = { kind: "arraySetUndefined", arr, index, loc }; + if (value.kind === "unitLit") return write; + return { kind: "block", body: [{ kind: "exprStmt", expr: value, loc }, write], loc }; + } if (value.type.kind === "union" && lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0) { // The tag test and the VALUE arm extraction both consume the value. Keep // an arbitrary expression (especially an array read or call) single diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index e9d4322ad..c3b2cd479 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -3,7 +3,8 @@ import { checkedPromiseAll, thenReader } from "../checked-promise-all.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; -import { varRef } from "../../../ir/build.js"; +import { countedFor, varRef } from "../../../ir/build.js"; +import { arrayValueStore, optionalElementValue, primitiveElement } from "../array-values.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { @@ -14,6 +15,7 @@ import { type IrFunction, type IrStmt, type IrType, + type SrcLoc, UNDEFINED_T, VOID, arrayOf, @@ -435,6 +437,28 @@ export function lowerPromiseStaticCall( inner.kind === "void" ? { kind: "promise", inner: VOID } : lowerer.mapTypeOf(lowerer.typeOf(call)); + // Promise[] combines into a `(T | undefined)[]`, which + // stores T elements (primitiveElement): collect the values, then copy + // them in with their present undefined values. + if ( + resultT?.kind === "promise" && + resultT.inner.kind === "array" && + primitiveElement(resultT.inner.elem) && + optionalElementValue( + lowerer, + { kind: "unitLit", unit: "undefined", type: inner, loc }, + resultT.inner.elem, + ) + ) { + const joined = optionalPromiseAll( + lowerer, + entries, + inner, + resultT as IrType & { kind: "promise" }, + loc, + ); + if (joined) return joined; + } if ( resultT?.kind !== "promise" || (inner.kind !== "void" && @@ -954,3 +978,139 @@ export function lowerAwaitedUniformPromiseAll( loc, }; } + +/** `Promise.all(ps)` over `Promise[]` for a number, string or + * boolean T, whose result is a `(T | undefined)[]` (stored as T elements). + * The combinator joins the entries (promise.allTuple), and its builder + * reads each fulfilled entry into the result with its present undefined, + * inside the combinator's settlement, so the result settles on Node's + * turn. Entries of another type keep the general combinator. */ +function optionalPromiseAll( + lowerer: Lowerer, + entries: IrExpr, + inner: IrType, + resultT: IrType & { kind: "promise" }, + loc: SrcLoc, +): IrExpr | null { + const entriesT = entries.type; + if (entriesT.kind !== "array" || entriesT.elem.kind !== "promise") return null; + if (!typeEquals(entriesT.elem.inner, inner)) return null; + const outT = resultT.inner as IrType & { kind: "array" }; + const voidPromise: IrType & { kind: "promise" } = { kind: "promise", inner: VOID }; + const joinedT = arrayOf(voidPromise); + const key = `promise.allOptional:${typeKey(entriesT)}:${typeKey(outT)}`; + let helper = lowerer.arrHofHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%promise.allOptional", key); + lowerer.arrHofHelpers.set(key, helper); + const builder = keyedHelperName(lowerer, "%promise.allOptionalValues", key); + const length = (arr: IrExpr): IrExpr => ({ + kind: "arrIntrinsic", + method: "length", + receiver: arr, + args: [], + type: F64, + loc, + }); + const joined = varRef("joined.0", joinedT, loc); + const out = varRef("out.0", outT, loc); + lowerer.liftedFns.push({ + name: builder, + params: [{ localId: "joined.0", name: "joined", type: joinedT }], + returnType: outT, + locals: [ + { id: "joined.0", name: "joined", type: joinedT, mutable: false }, + { id: "out.0", name: "out", type: outT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + { id: "v.0", name: "v", type: inner, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "out.0", + init: { kind: "arrayLit", elems: [], type: outT, loc }, + loc, + }, + countedFor(loc, length(joined), (index) => [ + { + kind: "varDecl", + localId: "v.0", + init: { + kind: "libCall", + fn: "promise.fulfilledValue", + args: [{ kind: "arrayGet", arr: joined, index, type: voidPromise, loc }], + type: inner, + loc, + }, + loc, + }, + arrayValueStore(lowerer, out, index, varRef("v.0", inner, loc), outT.elem, loc), + ]), + { kind: "return", value: out, loc }, + ], + loc, + }); + const ps = varRef("ps.0", entriesT, loc); + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "ps.0", name: "ps", type: entriesT }], + returnType: resultT, + locals: [ + { id: "ps.0", name: "ps", type: entriesT, mutable: false }, + { id: "joined.0", name: "joined", type: joinedT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + ], + body: [ + { + kind: "varDecl", + localId: "joined.0", + init: { kind: "arrayLit", elems: [], type: joinedT, loc }, + loc, + }, + countedFor(loc, length(ps), (index) => [ + { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "push", + receiver: joined, + args: [ + { + kind: "promiseVoidWiden", + value: { kind: "arrayGet", arr: ps, index, type: entriesT.elem, loc }, + type: voidPromise, + loc, + }, + ], + type: F64, + loc, + }, + loc, + }, + ]), + { + kind: "return", + value: { + kind: "libCall", + fn: "promise.allTuple", + args: [ + joined, + { + kind: "closure", + fnName: builder, + captures: [], + type: funcOf([joinedT], outT), + loc, + }, + ], + type: resultT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [entries], type: resultT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 060ffe9d2..6da0cb337 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -28,6 +28,7 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; +import { lowerArraySpreadItems } from "./containers/array-construction.js"; import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( @@ -403,6 +404,15 @@ export function completeArgs( } } if (restType.kind !== "array") lowerer.unsupported("SC1090", blame, "this call form"); + // A `(T | undefined)[]` rest (stored as T elements) packs undefined + // arguments as present undefined elements. + const restNodes = sources.slice(restAt); + if (restType.optionalElements === true && !restNodes.some(isIr)) { + out.push( + lowerArraySpreadItems(lowerer, restNodes as ts.Expression[], restType.elem, restType, loc), + ); + return out; + } // The rest pack is a fresh array per call; surplus SPREADS copy // their elements in (JS-exact — `f(a, ...xs, b, ...ys)` packs in // order, sources untouched). diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 4321a5ead..93ae1c231 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -1,12 +1,13 @@ import { keyedHelperName } from "../stable-names.js"; import { constructedValue } from "../constructor-returns.js"; -import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; +import { countedFor, dynUndefinedExpr, varRef } from "../../../ir/build.js"; import type { FieldLift } from "./structural-plans.js"; import { buildArrayConversion } from "./builders.js"; +import { arrayValueRead, arrayValueStore, primitiveElement } from "../array-values.js"; import type { WidthLift } from "../width-lift.js"; import { InternalCompilerError } from "../../../errors.js"; import type { IrExpr, IrFunction, IrRecordShape, IrStmt, IrType, SrcLoc } from "../../../ir/ir.js"; -import { BOOL, DYN, F64, STRING, UNDEFINED_T } from "../../../ir/ir.js"; +import { BOOL, DYN, F64, STRING, UNDEFINED_T, typeEquals } from "../../../ir/ir.js"; import { typeKey } from "../../type-mapper.js"; import { findStaticOn, findGenericStaticOn } from "../lower-classes.js"; import { lowerRecordOvfCaptureHelper } from "../containers/indexed-objects.js"; @@ -54,6 +55,9 @@ export function widthCoerce(lowerer: Lowerer, expr: IrExpr, expected: IrType): I return lowerer.classStaticsProjection(expr.type.className, expected.shapeId, expr.loc); } if (expected.kind === "array" && expr.type.kind === "array") { + const optional = optionalArrayHelper(lowerer, expr.type, expected, expr.loc); + if (optional) + return { kind: "call", callee: optional, args: [expr], type: expected, loc: expr.loc }; const helper = lowerer.arrayWidthHelper(expr.type, expected, expr.loc); if (helper) return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc }; @@ -518,6 +522,69 @@ export function emptyArrayLiftHelper( return name; } +/** Copy an array of `T | undefined` unions (a collection's values, an + * iterator drain) into a `(T | undefined)[]` of a number, string or + * boolean T, which stores T elements: undefined values become present + * undefined elements. Null for other element types. */ +function optionalArrayHelper( + lowerer: Lowerer, + fromT: IrType & { kind: "array" }, + toT: IrType & { kind: "array" }, + loc: SrcLoc, +): string | null { + const from = fromT.elem; + const to = toT.elem; + if ( + !primitiveElement(to) || + from.kind !== "union" || + lowerer.armTag(from.unionId, UNDEFINED_T) < 0 || + !typeEquals(lowerer.stripUndefinedArm(from), to) + ) + return null; + const key = `arrOptional:${typeKey(from)}:${typeKey(to)}`; + const existing = lowerer.valueHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%arr.width", key); + lowerer.valueHelpers.set(key, name); + const input = varRef("a.0", fromT, loc); + const out = varRef("out.0", toT, loc); + lowerer.liftedFns.push({ + name, + params: [{ localId: "a.0", name: "a", type: fromT }], + returnType: toT, + locals: [ + { id: "a.0", name: "a", type: fromT, mutable: true }, + { id: "out.0", name: "out", type: toT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + { id: "v.0", name: "v", type: from, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "out.0", + init: { kind: "arrayLit", elems: [], type: toT, loc }, + loc, + }, + countedFor( + loc, + { kind: "arrIntrinsic", method: "length", receiver: input, args: [], type: F64, loc }, + (index) => [ + { + kind: "varDecl", + localId: "v.0", + init: arrayValueRead(lowerer, input, index, from, loc), + loc, + }, + arrayValueStore(lowerer, out, index, varRef("v.0", from, loc), to, loc), + ], + ), + { kind: "return", value: out, loc }, + ], + loc, + }); + return name; +} + /** Copy an array through its validated element conversion. Intern before * building the body so recursive element conversions can reuse this helper. */ export function arrayWidthHelper( @@ -542,14 +609,37 @@ export function arrayWidthHelper( name, arrT, { kind: "arrayLit", elems: [], type: outT, loc }, - (element, _index, result) => ({ - kind: "arrIntrinsic", - method: "push", - receiver: result, - args: [lowerer.applyWidthLift(elemLift, element, toElem, loc)], - type: F64, - loc, - }), + (element, index, result) => { + let value = lowerer.applyWidthLift(elemLift, element, toElem, loc); + // A number, string or boolean array can hold present undefined + // elements (`(T | undefined)[]` stores T): they, and holes, copy + // as the target's undefined. + const missing = primitiveElement(fromElem) ? lowerer.wrappedUndefined(toElem, loc) : null; + if (missing !== null) + value = { + kind: "ternary", + cond: { + kind: "bin", + op: "===", + left: { kind: "arrayState", arr: varRef("a.0", arrT, loc), index, type: F64, loc }, + right: { kind: "numLit", value: 1, type: F64, loc }, + type: BOOL, + loc, + }, + then: value, + else_: missing, + type: toElem, + loc, + }; + return { + kind: "arrIntrinsic", + method: "push", + receiver: result, + args: [value], + type: F64, + loc, + }; + }, loc, ), ); diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index edf65ab2b..1062c56b1 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -42,6 +42,7 @@ import { arrayValueStore, arrayValueType, currentArrayIndexPresent, + primitiveElement, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -1689,6 +1690,8 @@ function flatMapUnionPlan( } const converts = (t: IrType): boolean => { if (typeEquals(t, outElem)) return true; + // A `(T | undefined)[]` result stores T elements (primitiveElement). + if (t.kind === "undefinedT" && primitiveElement(outElem)) return true; if (outElem.kind !== "union") return false; return lowerer.armTag(outElem.unionId, t) >= 0; }; @@ -1754,24 +1757,21 @@ function flatMapUnionHelper( }; if (plan.arms[tag] === "empty") return []; if (plan.arms[tag] === "value") { + // A unit arm's value is the unit itself, which has no narrowed + // representation. + const value: IrExpr = isUnitType(arm) + ? { kind: "unitLit", unit: arm.kind === "nullT" ? "null" : "undefined", type: arm, loc } + : narrowed; return [ arrayValueStore( lowerer, outRef(), outLength(), - lowerer.coerceToExpected( - // A unit arm's value is the unit itself, which has no - // narrowed representation. - isUnitType(arm) - ? { - kind: "unitLit", - unit: arm.kind === "nullT" ? "null" : "undefined", - type: arm, - loc, - } - : narrowed, - plan.outElem, - ), + // An undefined value is a present undefined element of a + // primitive array (arrayValueStore). + arm.kind === "undefinedT" && primitiveElement(plan.outElem) + ? value + : lowerer.coerceToExpected(value, plan.outElem), plan.outElem, loc, ), @@ -2347,6 +2347,43 @@ function reduceHelper( return name; } +/** The comparator of a sort over a `(T | undefined)[]`, which stores T + * elements. The comparator only receives present elements (undefined + * elements sort last without a call), so the unannotated parameters of an + * inline comparator take the element type, and the runtime calls it + * without an adapter. */ +function lowerOptionalElementComparator( + lowerer: Lowerer, + argNode: ts.Expression, + comparator: ts.Expression, + elem: IrType, + arrT: IrType & { kind: "array" }, +): IrExpr { + if ( + arrT.optionalElements !== true || + !(ts.isArrowFunction(comparator) || ts.isFunctionExpression(comparator)) || + isJsSourceFile(ts.getSourceFileOfNode(comparator)) + ) + return lowerer.lowerExpr(argNode); + const rebound: { symbol: ts.Symbol; previous: IrType | undefined }[] = []; + for (const param of comparator.parameters.nodes.slice(0, 2)) { + if (param.type || param.initializer || param.dotDotDotToken || !ts.isIdentifier(param.name)) + continue; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol) continue; + rebound.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) }); + lowerer.runtimeOptionalBindingTypes.set(symbol, elem); + } + try { + return lowerer.lowerExpr(argNode); + } finally { + for (const { symbol, previous } of rebound) { + if (previous === undefined) lowerer.runtimeOptionalBindingTypes.delete(symbol); + else lowerer.runtimeOptionalBindingTypes.set(symbol, previous); + } + } +} + /** `a.sort(cmp)` / `a.toSorted(cmp)`, desugared to an interned synthetic * function like the other array HOFs. sort mutates and returns the receiver; * toSorted takes its shallow snapshot INSIDE the helper, after the receiver @@ -2435,7 +2472,8 @@ export function lowerArraySortCall( loc, }; } - let fnArg = undefinedArg ?? lowerer.lowerExpr(argNode); + let fnArg = + undefinedArg ?? lowerOptionalElementComparator(lowerer, argNode, unwrappedArg, elem, arrT); // Reusable JS comparators often accept checked values. Adapt the array's // element ABI just as other array callbacks do before sorting. if ( diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 1ca51f526..25a98c8a6 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -35,7 +35,13 @@ import { typeEquals, } from "../../../ir/ir.js"; import { isJsSourceFile, locOf } from "../../program.js"; -import { arrayValueRead, arrayValueStore, arrayValueType } from "../array-values.js"; +import { + arrayValueRead, + arrayValueStore, + arrayValueType, + optionalElementValue, + primitiveElement, +} from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { newFnCtx } from "../lowerer.js"; import { iteratorCanStep, iteratorValue } from "../iterator-consumption.js"; @@ -88,7 +94,8 @@ export function lowerArrayValueItems( ]; for (const value of values) { const stored = - lowerer.runtimeOptionalWidening(value.type, elem) !== null + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) ? value : lowerer.coerceToExpected(value, elem); body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); @@ -212,7 +219,8 @@ export function lowerArraySpreadItems( } else { const value = lowerer.lowerExpr(node); const stored = - lowerer.runtimeOptionalWidening(value.type, elem) !== null + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) ? value : lowerer.coerceInto(node, value, elem); body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); @@ -518,6 +526,20 @@ export function lowerArrayOfCall( if (call.arguments.nodes.some(ts.isSpreadElement)) { return lowerArraySpreadItems(lowerer, call.arguments.nodes, result.elem, result, locOf(call)); } + // A `(T | undefined)[]` (primitiveElement) stores undefined arguments as + // present undefined elements. + if (result.optionalElements === true) { + const nodes = call.arguments.nodes; + const values = nodes.map((arg) => lowerer.lowerExpr(arg)); + if (values.some((value) => optionalElementValue(lowerer, value, result.elem))) + return lowerArrayValueItems(lowerer, values, result.elem, result, locOf(call)); + return { + kind: "arrayLit", + elems: values.map((value, i) => lowerer.coerceInto(nodes[i]!, value, result.elem)), + type: result, + loc: locOf(call), + }; + } return { kind: "arrayLit", elems: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)), @@ -959,6 +981,31 @@ export function lowerArrayFromCall( const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); if (arrT?.kind !== "array") lowerer.badType(call, lowerer.typeOf(call)); const elem = arrT.elem; + // A `(T | undefined)[]` (primitiveElement): n present undefined + // elements. + const elemTs = lowerer.checker.isArrayType(lowerer.typeOf(call)) + ? lowerer.checker.getTypeArguments(lowerer.typeOf(call) as ts.TypeReference)[0] + : undefined; + if ( + primitiveElement(elem) && + elemTs !== undefined && + (elemTs.isUnionType() ? ts.constituentTypes(elemTs) : [elemTs]).some( + (part) => (part.flags & ts.TypeFlags.Undefined) !== 0, + ) + ) { + return { + kind: "arrIntrinsic", + method: "fillUndefined", + receiver: { kind: "arrayNewLen", length: n, type: arrT, loc }, + // To the end: fill clamps an infinite end to the length. + args: [ + numLit(0, loc), + { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }, + ], + type: arrT, + loc, + }; + } const absent = elem.kind === "union" ? lowerer.wrappedUndefined(elem, loc) !== null || @@ -1085,7 +1132,10 @@ export function lowerArrayFromCall( // A generator (or an iterator adapted into one) drains in order. if (src.type.kind === "generator" && args.length === 1) { const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); - const drained = arrT?.kind === "array" ? generatorDrain(lowerer, src, arrT.elem, loc) : null; + const drained = + arrT?.kind === "array" + ? generatorDrain(lowerer, src, arrT.elem, loc, arrT.optionalElements === true) + : null; if (drained) return drained; } lowerer.noLowering( @@ -1159,6 +1209,11 @@ function buildArrayFromArrayFn( type: fnRet, loc, }; + // A result of another type than the element (`T | undefined` into a + // `(T | undefined)[]`, which stores T elements) is held in a local, so + // the store tests and converts one value. + const held = !typeEquals(fnRet, outElem); + const value: IrExpr = held ? varRef("v.0", fnRet, loc) : mapped; return { name, params: [ @@ -1171,6 +1226,7 @@ function buildArrayFromArrayFn( { id: "f.0", name: "f", type: fnT, mutable: true }, { id: "out.0", name: "out", type: outT, mutable: false }, { id: "i.0", name: "i", type: F64, mutable: true }, + ...(held ? [{ id: "v.0", name: "v", type: fnRet, mutable: false }] : []), ], body: [ { @@ -1191,7 +1247,8 @@ function buildArrayFromArrayFn( loc, }, body: [ - arrayValueStore(lowerer, out, i, mapped, outElem, loc), + ...(held ? [{ kind: "varDecl", localId: "v.0", init: mapped, loc } as const] : []), + arrayValueStore(lowerer, out, i, value, outElem, loc), { kind: "assign", localId: "i.0", diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 4ef203edd..f406e57eb 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -32,6 +32,10 @@ import { arrayValueRead, arrayValueStore, arrayValueType, + elementPlaceholder, + insertedValues, + optionalElementValue, + primitiveElement, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -125,6 +129,14 @@ function lowerOptionalArrayPush( return { kind: "seqExpr", stmts, result: length(), type: F64, loc }; } +/** The checker type of `node` admits undefined. */ +function checkerTypeAdmitsUndefined(lowerer: Lowerer, node: ts.Expression): boolean { + const type = lowerer.typeOf(node); + return (type.isUnionType() ? ts.constituentTypes(type) : [type]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); +} + /** `a.splice(start, count, v)` where v may hold undefined at run time. * Receiver, start, count, and v evaluate once, in order; a present v is * inserted as a plain element literal (which a statement-position splice @@ -658,7 +670,10 @@ export function lowerArrayMethodCall( const hasSpread = itemNodes.some(ts.isSpreadElement); const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg)); const statefulItems = itemProbes.some( - (probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null, + (probe) => + probe !== null && + (lowerer.runtimeOptionalWidening(probe.type, elem) !== null || + optionalElementValue(lowerer, probe, elem)), ); const items: IrExpr = hasSpread ? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc) @@ -875,6 +890,65 @@ export function lowerArrayMethodCall( loc, }; } + // A `(T | undefined)[]` (primitiveElement) takes undefined values as + // present undefined elements, stored after the insertion. + if ( + (name === "push" || name === "unshift") && + primitiveElement(elem) && + call.arguments.nodes.some((a) => checkerTypeAdmitsUndefined(lowerer, a)) + ) { + const nodes = call.arguments.nodes; + const values = nodes.map((a) => lowerer.lowerExpr(a)); + const inserted = insertedValues( + lowerer, + values, + elem, + (value, i) => lowerer.coerceInto(nodes[i]!, value, elem), + loc, + ); + if (inserted.optional) { + const array = lowerer.declareHiddenLocal("%insertInto", receiver.type); + const arr = varRef(array.id, array.type, loc); + const length = lowerer.declareHiddenLocal("%insertLength", F64); + const count: IrExpr = numLit(values.length, loc); + const first: IrExpr = + name === "push" + ? { + kind: "bin", + op: "-", + left: varRef(length.id, F64, loc), + right: count, + type: F64, + loc, + } + : numLit(0, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: array.id, init: receiver, loc }, + ...inserted.stmts, + { + kind: "varDecl", + localId: length.id, + init: { + kind: "arrIntrinsic", + method: name, + receiver: arr, + args: inserted.args, + type: F64, + loc, + }, + loc, + }, + ...inserted.fill(arr, first), + ], + result: varRef(length.id, F64, loc), + type: F64, + loc, + }; + } + return { kind: "arrIntrinsic", method: name, receiver, args: inserted.args, type: F64, loc }; + } // Inserted values flow into the element slot like an assignment would: // union-element arrays wrap plain arm values (coerceInto is inert // when the types already agree). @@ -996,9 +1070,12 @@ export function lowerArrayMethodCall( const hasSpread = itemNodes.some(ts.isSpreadElement); const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg)); const statefulItems = itemProbes.some( - (probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null, + (probe) => + probe !== null && + (lowerer.runtimeOptionalWidening(probe.type, elem) !== null || + optionalElementValue(lowerer, probe, elem)), ); - if (statefulItems && itemNodes.length === 1) { + if (statefulItems && itemNodes.length === 1 && itemProbes[0]?.type.kind === "union") { return lowerOptionalSpliceItem( lowerer, receiver, @@ -1734,7 +1811,28 @@ function arraySearchHelper( type: valueT, loc, }; - const value = directNeedle ? rawRead : optionalValue; + // A direct needle matches only a present value (`match` tests the state + // first); a present undefined of a `(T | undefined)[]` (primitiveElement) + // must not be read as T. + const value = !directNeedle + ? optionalValue + : primitiveElement(elem) + ? { + kind: "ternary" as const, + cond: { + kind: "bin" as const, + op: "===" as const, + left: stateRef, + right: numLit(1, loc), + type: BOOL, + loc, + }, + then: rawRead, + else_: elementPlaceholder(elem, loc), + type: elem, + loc, + } + : rawRead; const readT = directNeedle ? elem : valueT; const equal = arraySearchEquality( lowerer, diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index d95640978..de027e08c 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -2,6 +2,7 @@ import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; +import { primitiveElement } from "../array-values.js"; import { varRef } from "../../../ir/build.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -144,12 +145,22 @@ export function lowerArrayCallback( const untyped = (checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0; const raw = untyped ? null : lowerer.mapTypeOf(checkerType); const mapped = raw ? lowerer.runtimeOptionalBindingType(param.name, raw) : null; + // A helper that passes present number, string or boolean elements + // (a `(T | undefined)[]` stores T elements) binds a `T | undefined` + // parameter to T, so the callback takes them without an adapter. + const presentElement = + mapped !== null && + primitiveElement(expected) && + mapped.kind === "union" && + lowerer.armTag(mapped.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(mapped), expected); if ( untyped ? !bindUntyped : mapped === null || (lowerer.coercibleValue(expected, mapped) && - !runtimeOptionalPairVariant(lowerer, expected, mapped)) + !runtimeOptionalPairVariant(lowerer, expected, mapped) && + !presentElement) ) return; const symbol = lowerer.checker.getSymbolAtLocation(param.name); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index e043daf68..f18291a63 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -1,4 +1,5 @@ import { keyedHelperName } from "../stable-names.js"; +import { primitiveElement } from "../array-values.js"; import { nodeThrowExpr, boolLit, countedFor, numLit, strLit, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; @@ -1137,6 +1138,17 @@ function setFromSeedValue( seed = strCharsCall(lowerer, source, loc); } else if (source.type.kind === "array" && typeEquals(source.type.elem, setT.elem)) { seed = source; + } else if ( + source.type.kind === "array" && + primitiveElement(source.type.elem) && + setT.elem.kind === "union" && + lowerer.armTag(setT.elem.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(setT.elem), source.type.elem) + ) { + // A `(T | undefined)[]` stores T elements: its present undefined + // elements copy as the Set's undefined. + seed = lowerer.coerceToExpected(source, arrayOf(setT.elem)); + if (!typeEquals(seed.type, arrayOf(setT.elem))) return null; } else if (source.type.kind === "record") { const shape = lowerer.shapes.get(source.type.shapeId); if (!shape?.tuple || !shape.fields.every((field) => typeEquals(field.type, setT.elem))) diff --git a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts index 668603d47..1c07885ae 100644 --- a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts +++ b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts @@ -1,6 +1,7 @@ import { keyedHelperName } from "../stable-names.js"; import { nullProtoRecordShape, recordNullProtoTest } from "../null-prototype.js"; import { countedFor, numLit, varRef } from "../../../ir/build.js"; +import { arrayValueStore, optionalElementValue, primitiveElement } from "../array-values.js"; import { objectEnumerationReceiver } from "../object-enumeration-receiver.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; @@ -156,7 +157,14 @@ export function objectIterOverIndexShape( // also holds numbers): each overflow value checks into the element type, // so a value of a dropped arm throws instead of mistyping. let ivNarrows = false; - if (valueT) { + // `T | undefined` values into a `(T | undefined)[]`, which stores T + // elements (primitiveElement): each value is stored with its present + // undefined. + const optionalValues = + member === "values" && + valueT !== null && + optionalElementValue(lowerer, { kind: "unitLit", unit: "undefined", type: iv, loc }, valueT); + if (valueT && !optionalValues) { const ivSurfaces = typeEquals(iv, valueT) || (valueT.kind === "union" && lowerer.armTag(valueT.unionId, iv) >= 0) || @@ -350,7 +358,25 @@ export function objectIterOverIndexShape( loc, }, then: [fieldStmt], - else_: undefinedElement ? [push(pushedOf(undefinedElement))] : null, + else_: undefinedElement + ? [push(pushedOf(undefinedElement))] + : member === "values" && valueT && primitiveElement(valueT) + ? [ + { + kind: "arraySetUndefined", + arr: outRef, + index: { + kind: "arrIntrinsic", + method: "length", + receiver: outRef, + args: [], + type: F64, + loc, + }, + loc, + }, + ] + : null, loc, }; } @@ -374,35 +400,45 @@ export function objectIterOverIndexShape( const kRef = varRef("k.0", STRING, loc); const readValue: IrExpr | null = !valueT ? null - : ivNarrows + : optionalValues ? { - kind: "dynCheck", - value: { - kind: "dynFrom", - value: { - kind: "recordKeyGet", - obj: rRef, - shapeId: argIr.shapeId, - key: kRef, - overflowOnly: true, - type: iv, - loc, - }, - type: DYN, - loc, - }, - type: valueT, - loc, - } - : { kind: "recordKeyGet", obj: rRef, shapeId: argIr.shapeId, key: kRef, overflowOnly: true, - type: valueT, + type: iv, loc, - }; + } + : ivNarrows + ? { + kind: "dynCheck", + value: { + kind: "dynFrom", + value: { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: iv, + loc, + }, + type: DYN, + loc, + }, + type: valueT, + loc, + } + : { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: valueT, + loc, + }; const loopPushed: IrExpr = member === "keys" ? kRef @@ -440,7 +476,26 @@ export function objectIterOverIndexShape( }, loc, }, - push(loopPushed), + ...(optionalValues + ? [ + { kind: "varDecl" as const, localId: "v.0", init: readValue!, loc }, + arrayValueStore( + lowerer, + outRef, + { + kind: "arrIntrinsic", + method: "length", + receiver: outRef, + args: [], + type: F64, + loc, + }, + varRef("v.0", iv, loc), + valueT!, + loc, + ), + ] + : [push(loopPushed)]), ], ), { kind: "return", value: outRef, loc }, @@ -457,6 +512,7 @@ export function objectIterOverIndexShape( { id: "ks.0", name: "ks", type: ksT, mutable: false }, { id: "i.0", name: "i", type: F64, mutable: true }, { id: "k.0", name: "k", type: STRING, mutable: false }, + ...(optionalValues ? [{ id: "v.0", name: "v", type: iv, mutable: false }] : []), ], body, loc, diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index c1b54a417..773f4cf0b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -31,7 +31,7 @@ import { strCharsCall } from "../containers/array-construction.js"; import { generatorDrain } from "../iterator-adapters.js"; import { isSearchParamsIterable } from "../builtins/url.js"; import { varRef } from "../../../ir/build.js"; -import { arrayValueStore } from "../array-values.js"; +import { arrayValueStore, primitiveElement } from "../array-values.js"; import { classGeneratorIterable } from "../class-iterables.js"; /** JavaScript: whether `expr` is or holds an existing checked-dynamic @@ -102,6 +102,13 @@ function emptyNeverLiteral(lowerer: Lowerer, expr: ts.ArrayLiteralExpression): b * tsc's API doesn't surface it (ternary arms under an array context — * tsc accepts the arm covariantly, but a tagged element representation * must be BUILT as the slot's element type). */ +/** The checker type of `node` admits undefined. */ +function mayHoldUndefined(lowerer: Lowerer, node: ts.Expression): boolean { + const type = lowerer.typeOf(node); + const parts = type.isUnionType() ? ts.constituentTypes(type) : [type]; + return parts.some((part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0); +} + export function lowerArrayLiteral( lowerer: Lowerer, expr: ts.ArrayLiteralExpression, @@ -515,7 +522,13 @@ export function lowerArrayLiteral( // `[...it]` over a generator (or an iterator adapted into one): // drain it once, in order, into a fresh element array. if (src.type.kind === "generator") { - const drained = generatorDrain(lowerer, src, type.elem, locOf(el)); + const drained = generatorDrain( + lowerer, + src, + type.elem, + locOf(el), + type.optionalElements === true, + ); if (drained) src = drained; } // `[...sp]` and `[...sp.entries()]` over a URLSearchParams: its @@ -606,6 +619,9 @@ export function lowerArrayLiteral( let lowered: IrExpr; const directOptionalCandidate = runtimeOptionalElements.has(i) || + // An element that can be undefined in a `(T | undefined)[]` + // (primitiveElement) is stored as a present undefined. + (primitiveElement(type.elem) && mayHoldUndefined(lowerer, el)) || ts.isElementAccessExpression(el) || (ts.isCallExpression(el) && ts.isPropertyAccessExpression(el.expression) && @@ -683,7 +699,8 @@ export function lowerArrayLiteral( continue; } let value = el; - if (!typeEquals(value.type, type.elem)) { + // An undefined element is stored directly (arrayValueStore). + if (!typeEquals(value.type, type.elem) && value.type.kind !== "undefinedT") { const temp = lowerer.declareHiddenLocal("%arrayValue", value.type); body.push({ kind: "varDecl", localId: temp.id, init: value, loc }); value = varRef(temp.id, temp.type, loc); diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 9ac6a04b6..70909ca7b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -61,6 +61,8 @@ import { arrayValueType, lowerSafeBytesRead, lowerSafeIndexRead, + primitiveElement, + unionArrayValueRead, } from "../array-values.js"; import { lowerOptionalStringIndex, lowerUnionStringIndex } from "../string-index.js"; import { InternalCompilerError } from "../../../errors.js"; @@ -594,6 +596,8 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre } } if (key.type.kind === "f64") { + const optional = optionalArmIndexRead(lowerer, value, key, locOf(expr)); + if (optional) return lowerer.maybeNarrow(optional, expr); const safe = lowerOptionalArrayIndexRead(lowerer, expr, value, key); if (safe) return safe; } @@ -884,9 +888,14 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre // must be the element type (library ABI checks and the catchable // RangeError contract for an out-of-bounds assert). Where undefined is // representable, the assertion keeps JavaScript's unchecked read. + // A declared `(T | undefined)[]` element (stored as T) keeps its + // optional read: the assertion then narrows it with a catchable + // TypeError, as for a union element. + const declaredOptional = arr.type.optionalElements === true; const provenPresent = ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr && + !declaredOptional && !assertedElementKeepsUndefined(lowerer, expr.parent); if (provenPresent) { return lowerer.maybeNarrow( @@ -2437,3 +2446,39 @@ export function lowerEnvironmentKey(lowerer: Lowerer, node: ts.Expression): IrEx return { kind: "libCall", fn: "dyn.toStringCoerce", args: [key], type: STRING, loc: key.loc }; return lowerRecordPropertyKey(lowerer, key, node); } + +/** A number-keyed read of a union of arrays with a `(T | undefined)[]` arm + * (stored as T elements, primitiveElement) whose element facts admit + * missing values: each arm reads its element by its state + * (unionArrayValueRead), where the keyed read would trap on a present + * undefined. The receiver and key evaluate once. Null for other unions. */ +function optionalArmIndexRead( + lowerer: Lowerer, + value: IrExpr, + key: IrExpr, + loc: SrcLoc, +): IrExpr | null { + if (value.type.kind !== "union") return null; + const arms = lowerer.unions.get(value.type.unionId)?.arms ?? []; + if ( + !arms.some( + (arm) => + arm.kind === "array" && + primitiveElement(arm.elem) && + arrayValueType(lowerer, arm.elem).kind === "union", + ) + ) + return null; + if (unionArrayValueRead(lowerer, value, key, loc) === null) return null; + const stmts: IrStmt[] = []; + const stable = (e: IrExpr, name: string): IrExpr => { + if (e.kind === "varRef" || e.kind === "numLit") return e; + const local = lowerer.declareHiddenLocal(name, e.type); + stmts.push({ kind: "varDecl", localId: local.id, init: e, loc }); + return varRef(local.id, e.type, loc); + }; + const receiver = stable(value, "%indexReceiver"); + const index = stable(key, "%indexKey"); + const read = unionArrayValueRead(lowerer, receiver, index, loc)!; + return stmts.length === 0 ? read : { kind: "seqExpr", stmts, result: read, type: read.type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index 9f2b1e31f..4909e7f45 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -1,3 +1,4 @@ +import { appendElement, optionalElementValue, primitiveElement } from "./array-values.js"; import { keyedHelperName } from "./stable-names.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { @@ -964,16 +965,27 @@ function generatorRetypeAdapter( return { kind: "call", callee: name, args: [expr], type: expected, loc }; } +/** A checked-dynamic yielded value for a `(T | undefined)[]` of a + * number, string or boolean T: converted to `T | undefined`, which the + * drain stores as a T element or a present undefined. */ +function optionalYield(lowerer: Lowerer, value: IrExpr, elem: IrType, optional: boolean): IrExpr { + if (!optional || value.type.kind !== "dyn" || !primitiveElement(elem)) return value; + return lowerer.coerceToExpected(value, lowerer.withUndefinedArm(elem)); +} + /** Drain a synchronous generator into a fresh `elem[]` — the eager * consumers of an iterator (`[...it]`, `Array.from(it)`). Each step resumes * the generator once and appends its value; completion stops the drain * without appending the return value, as in JS. Null when the yielded - * values cannot be extracted into the element type. */ + * values cannot be extracted into the element type. `optional`: the + * result is a `(T | undefined)[]`, which stores T elements, so a + * checked-dynamic value may also be undefined. */ export function generatorDrain( lowerer: Lowerer, source: IrExpr, elem: IrType, loc: SrcLoc, + optional = false, ): IrExpr | null { const gen = source.type; if (gen.kind !== "generator" || gen.async || gen.yieldT.kind === "void") return null; @@ -982,14 +994,16 @@ export function generatorDrain( const valueT = shape?.fields.find((field) => field.name === "value")?.type; if (!resultType || !valueT) return null; const arrT: IrType = { kind: "array", elem }; - const key = `genDrain:${typeKey(gen)}:${typeKey(elem)}`; + const key = `genDrain:${typeKey(gen)}:${typeKey(elem)}${optional ? "?" : ""}`; let name = lowerer.valueHelpers.get(key); if (!name && stepDrivable(gen)) { // The record-free drive: test done, then take the yielded value. const genRef = varRef("gen.0", gen, loc) as IrExpr & { type: typeof gen }; const outRef = varRef("out.0", arrT, loc); - const value = lowerer.coerceToExpected(genValue(genRef, loc), elem); - if (!typeEquals(value.type, elem)) return null; + const yielded = optionalYield(lowerer, genValue(genRef, loc), elem, optional); + const present = optionalElementValue(lowerer, yielded, elem); + const value = present ? yielded : lowerer.coerceToExpected(yielded, elem); + if (!present && !typeEquals(value.type, elem)) return null; name = keyedHelperName(lowerer, "%gen.drain", key); lowerer.valueHelpers.set(key, name); lowerer.liftedFns.push({ @@ -999,6 +1013,7 @@ export function generatorDrain( locals: [ { id: "gen.0", name: "gen", type: gen, mutable: false }, { id: "out.0", name: "out", type: arrT, mutable: false }, + ...(present ? [{ id: "v.0", name: "v", type: value.type, mutable: false }] : []), ], body: [ { @@ -1010,20 +1025,7 @@ export function generatorDrain( { kind: "while", cond: { kind: "unary", op: "!", operand: genStep(genRef, loc), type: BOOL, loc }, - body: [ - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: outRef, - args: [value], - type: F64, - loc, - }, - loc, - }, - ], + body: appendElement(lowerer, outRef, value, elem, "v.0", loc), loc, }, { kind: "return", value: outRef, loc }, @@ -1050,8 +1052,10 @@ export function generatorDrain( loc, ); if (!raw) return null; - const value = lowerer.coerceToExpected(raw, elem); - if (!typeEquals(value.type, elem)) return null; + const yielded = optionalYield(lowerer, raw, elem, optional); + const present = optionalElementValue(lowerer, yielded, elem); + const value = present ? yielded : lowerer.coerceToExpected(yielded, elem); + if (!present && !typeEquals(value.type, elem)) return null; name = keyedHelperName(lowerer, "%gen.drain", key); lowerer.valueHelpers.set(key, name); lowerer.liftedFns.push({ @@ -1062,6 +1066,7 @@ export function generatorDrain( { id: "gen.0", name: "gen", type: gen, mutable: false }, { id: "out.0", name: "out", type: arrT, mutable: false }, { id: "step.0", name: "step", type: resultType, mutable: false }, + ...(present ? [{ id: "v.0", name: "v", type: value.type, mutable: false }] : []), ], body: [ { @@ -1101,18 +1106,7 @@ export function generatorDrain( else_: null, loc, }, - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: outRef, - args: [value], - type: F64, - loc, - }, - loc, - }, + ...appendElement(lowerer, outRef, value, elem, "v.0", loc), ], loc, }, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index af89c61a4..2ddf02dfc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6515,6 +6515,17 @@ export class Lowerer { mayBeUndefined: (node) => { if (explicitlyNonNull(node)) return assertedReadMayBeUndefined(node); if (mayBeOptional(node)) return true; + // A value the checker types as possibly undefined: `(T | undefined)[]` + // stores T elements (the type mapper), so its undefined values are + // facts of T's arrays. + const checked = typeOrNull(node); + if ( + checked !== null && + (checked.isUnionType() ? ts.constituentTypes(checked) : [checked]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ) + ) + return true; // `undefined as unknown as T` keeps the undefined value. for ( let inner: ts.Expression = node; @@ -6533,7 +6544,37 @@ export class Lowerer { const symbol = symbolOf(e); return symbol !== null && iterationSymbols.has(symbol) && !provenPresent(e, symbol); }, - callbackReturnsUndefined: (node) => callbackReturnsOptional(node), + iterationMayYieldUndefined: (node) => { + const type = typeOrNull(node); + if (type === null || this.checker.isArrayType(type) || this.checker.isTupleType(type)) + return false; + // The first type argument of Set, Map and Set iterators, + // generators and iterables is the value they yield. + const element = + (type.flags & ts.TypeFlags.Object) !== 0 + ? panicSafe(() => this.checker.getTypeArguments(type as ts.TypeReference)[0], undefined) + : undefined; + if (element === undefined) return false; + return (element.isUnionType() ? ts.constituentTypes(element) : [element]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); + }, + callbackReturnsUndefined: (node) => { + if (callbackReturnsOptional(node)) return true; + // A callback the checker types as possibly returning undefined + // (`(m) => map.get(m)`): its results are present undefined + // elements of a `(T | undefined)[]`, which stores T elements. + const type = typeOrNull(node); + return ( + type !== null && + this.checker.getCallSignatures(type).some((signature) => { + const result = this.checker.getReturnTypeOfSignature(signature); + return (result.isUnionType() ? ts.constituentTypes(result) : [result]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); + }) + ); + }, isGlobal: (node, name) => this.isStdlibGlobal(node, name), isString: (node) => { const type = typeOrNull(node); @@ -6594,6 +6635,7 @@ export class Lowerer { const cached = scanSites.get(sf); if (cached !== undefined) return cached; const nodes: ts.Node[] = []; + const values: ts.Node[] = []; ts.walkPreorder(sf, (node) => { if ( ts.isVariableDeclaration(node) || @@ -6609,7 +6651,21 @@ export class Lowerer { ts.isPostfixUnaryExpression(node) ) nodes.push(node); + // The values these sites store, whose checker types mayBeUndefined + // reads: batched here, since dead bodies are scanned too. + if (ts.isArrayLiteralExpression(node)) + for (const element of node.elements.nodes) + values.push(ts.isSpreadElement(element) ? element.expression : element); + else if (ts.isCallExpression(node) || ts.isNewExpression(node)) + for (const arg of node.arguments?.nodes ?? []) + values.push(ts.isSpreadElement(arg) ? arg.expression : arg); + else if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + values.push(node.right); }); + this.checker.prefetchCollectionTypes(values); scanSites.set(sf, nodes); return nodes; }; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 96d20d2c4..fffe79a51 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -16,6 +16,9 @@ export interface ElementStateHost { elementAdmitsUndefined(key: string): boolean; /** The value stored by `node` can be `undefined` at runtime. */ mayBeUndefined(node: ts.Expression): boolean; + /** Iterating `node`, an iterable other than an array (a Set, a Map's + * values, a generator), can yield `undefined`. */ + iterationMayYieldUndefined?(node: ts.Expression): boolean; /** A callback argument can return `undefined` at runtime. */ callbackReturnsUndefined(node: ts.Expression): boolean; /** The value may be a plain array-like object, whose missing indices @@ -353,7 +356,11 @@ export class ArrayElementStates { /** One stored value, or the elements a spread copies. */ private valueMayBeUndefined(value: ts.Expression): boolean { - if (ts.isSpreadElement(value)) return this.mayYieldUndefined(value.expression); + if (ts.isSpreadElement(value)) + return ( + this.mayYieldUndefined(value.expression) || + (this.host.iterationMayYieldUndefined?.(value.expression) ?? false) + ); return this.host.mayBeUndefined(value); } diff --git a/packages/compiler/src/frontend/lowering/sanitize-class-types.ts b/packages/compiler/src/frontend/lowering/sanitize-class-types.ts index 77478d76d..6b89b446a 100644 --- a/packages/compiler/src/frontend/lowering/sanitize-class-types.ts +++ b/packages/compiler/src/frontend/lowering/sanitize-class-types.ts @@ -16,6 +16,8 @@ export function sanitizeUnregisteredClassTypes( case "object": return hasClass(type.className) ? type : F64; case "array": + type.elem = rewrite(type.elem); + break; case "set": type.elem = rewrite(type.elem); break; diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 161f73d27..16b77b7f5 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -2341,6 +2341,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { elem = unitOnlyUnion(unions); if (!elem) return null; + // `(T | undefined)[]` of a number, string or boolean T stores T + // elements: an array slot holds a present undefined by its own state, + // so a T[] seen as `(T | undefined)[]` stays the same array. + let optionalElements = false; + if (elem.kind === "union") { + const arms = unions.get(elem.unionId)?.arms; + const present = + arms?.length === 2 ? arms.find((arm) => arm.kind !== "undefinedT") : undefined; + if ( + present !== undefined && + arms!.some((arm) => arm.kind === "undefinedT") && + (present.kind === "f64" || present.kind === "string" || present.kind === "bool") + ) { + elem = present; + optionalElements = true; + } + } // A dyn ELEMENT makes the WHOLE array the checked-dynamic value: // `unknown[]`, `object[]`, and the collapsed `(string | object)[]` // (the plugins-slot shape) — the checked-dynamic tree has real arrays, so length/ @@ -2362,7 +2379,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // drain of the auxiliary-server registries): handles are ordinary // refcounted REF elements (their `_v` adapters, no trace — both drop // their closures at their terminal event). - return arrayOf(elem); + return optionalElements ? { kind: "array", elem, optionalElements: true } : arrayOf(elem); } // Tuples: `[string, string]` maps to an interned RECORD shape flagged // `tuple`, with one field per position named by its index ("0", "1", ...) diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 8fb636608..58edd52d3 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -87,7 +87,16 @@ export type IrType = | { kind: "date" } | { kind: "string" } // heap, refcounted, UTF-8 | { kind: "bool" } - | { kind: "array"; elem: IrType } // heap, refcounted, monomorphic elements + | { + kind: "array"; + elem: IrType; + /** A `(T | undefined)[]` of a number, string or boolean T, stored as + * T elements (which hold a present undefined by their state). Not + * part of the type's identity (typeEquals, typeKey): it only lets a + * checked conversion from a dynamic value accept undefined + * elements. */ + optionalElements?: true; + } // heap, refcounted, monomorphic elements /** ES `Map` — heap, refcounted, insertion-ordered hash map with ONE * runtime representation (ScrMap) and type-directed key/value handling, * exactly the array pattern (never per-instantiation structs). Keys use diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index a694e8d2c..b9996fc6c 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -2050,12 +2050,18 @@ function validateFunction( } case "arrayNewLen": { // Mapper-less Array.from({length: n}): absent-slot fill exists - // only for refcounted element kinds (frontend fences scalars). + // only for refcounted element kinds. Number and boolean slots are + // filled with present undefined values right after + // (`(T | undefined)[]`); the frontend fences other scalar uses. if (e.type.kind !== "array") { err(`arrayNewLen must be array-typed, got ${e.type.kind}`, e.loc); break; } - if (!isRefCounted(e.type.elem)) { + if ( + !isRefCounted(e.type.elem) && + e.type.elem.kind !== "f64" && + e.type.elem.kind !== "bool" + ) { err( `arrayNewLen with non-refcounted ${e.type.elem.kind} elements (no absent value)`, e.loc, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json new file mode 100644 index 000000000..8cdd32baf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8000-optional-element-arrays-share-storage.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json new file mode 100644 index 000000000..fe45249bd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8001-optional-element-writers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8000-optional-element-arrays-share-storage.ts b/tests/corpus/8000-optional-element-arrays-share-storage.ts new file mode 100644 index 000000000..fa31fcad2 --- /dev/null +++ b/tests/corpus/8000-optional-element-arrays-share-storage.ts @@ -0,0 +1,61 @@ +// A number[] or string[] seen as `(T | undefined)[]` is the same array, as +// in Node: undefined written through either name is visible through both, +// whether the wider view is a cast, a binding or a parameter. + +const c: number[] = [1.5, 2]; +(c as (number | undefined)[])[1] = undefined; +console.log(c); +const e: number[] = [1.5, 2]; +const f = e as (number | undefined)[]; +f[1] = undefined; +console.log(e, f, e === f); + +function clearFirst(values: (number | undefined)[]): void { + values[0] = undefined; + values.push(undefined, 4); +} +const a: number[] = [1, 2, 3]; +clearFirst(a); +console.log(a, a.length, a.indexOf(4), a.includes(undefined as unknown as number)); + +const names: string[] = ["x"]; +const wider: (string | undefined)[] = names; +wider.push(undefined); +wider.unshift(undefined); +console.log(names, wider === names, names.length, JSON.stringify(names)); + +// The wider arrays themselves: literals, pushes, splices and copies keep +// their undefined elements. +const lit: (number | undefined)[] = [1, undefined, 3]; +const maybe = (i: number): number | undefined => (i % 2 ? i : undefined); +const built: (number | undefined)[] = []; +for (let i = 0; i < 4; i++) built.push(maybe(i)); +built.splice(1, 0, undefined); +console.log(lit, built, lit.indexOf(undefined), built.filter((x): boolean => x === undefined).length); +console.log(built.map((x) => x ?? -1), built.find((x) => x !== undefined), [...built].reverse()); +let sum = 0; +for (const x of built) sum += x ?? 0; +console.log(sum, built.join("-"), JSON.stringify(built)); + +const flags: (boolean | undefined)[] = [true, undefined, false]; +const strs: (string | undefined)[] = ["a", undefined]; +console.log(flags, flags.filter(Boolean).length, strs.map((s) => s?.toUpperCase())); + +// Values from other containers keep their undefined. +const m = new Map([["a", undefined], ["b", 2]]); +const fromMap = [...m.values()]; +const fromSet = Array.from(new Set(["s", undefined])); +const sized = Array.from({ length: 2 }); +console.log(fromMap, fromSet, sized, sized.length); +const dict: { [key: string]: number | undefined } = { a: 1, b: undefined }; +console.log(Object.values(dict)); + +// An asserted element that is undefined throws a catchable TypeError. +function lengthAt(items: (string | undefined)[], i: number): number { + try { + return items[i]!.length; + } catch { + return -1; + } +} +console.log(lengthAt(strs, 0), lengthAt(strs, 1)); diff --git a/tests/corpus/8001-optional-element-writers.ts b/tests/corpus/8001-optional-element-writers.ts new file mode 100644 index 000000000..5dff693c3 --- /dev/null +++ b/tests/corpus/8001-optional-element-writers.ts @@ -0,0 +1,103 @@ +// Every way a value reaches a `(T | undefined)[]` of numbers, strings or +// booleans keeps its undefined: callbacks, generators, Array.of, rest +// packs, destructuring, class fields and copies into Sets and Maps. + +const slots = new Map([["a", 1]]); +const looked = ["x", "a", "y"].map((key) => slots.get(key)); +console.log(looked[0] === undefined, looked, looked.length); +const fromMapped = Array.from(["a", "b"], (key) => slots.get(key)); +console.log(fromMapped[1] === undefined, fromMapped); +const objects: { n?: number }[] = [{ n: 1 }, {}]; +const fields = objects.map((o) => o.n); +function pick(o: { n?: number }): number | undefined { + return o.n; +} +console.log(fields[1] === undefined, objects.map(pick)[1] === undefined, fields); +function wrap(value: T): T[] { + return [value]; +} +console.log(wrap(undefined)[0] === undefined); + +function* maybe(): Generator { + yield undefined; + yield 2; +} +const drained = [...maybe()]; +console.log(drained[0] === undefined, drained, Array.from(maybe())); +const yielding = function* (): Generator { + yield "s"; + yield undefined; +}; +console.log([...yielding()]); +console.log(Array.of(undefined, 1), Array.of(true)); + +function sum(...values: (number | undefined)[]): number { + return values.reduce((total, v) => total + (v ?? 0), 0); +} +const spread: (number | undefined)[] = [undefined, 4]; +console.log(sum(1, undefined, 2), sum(...spread), sum(...spread, undefined, 1)); + +const [first, second = 7, third] = [3, undefined, 1] as (number | undefined)[]; +console.log(first, second, third); +const strings: (string | undefined)[] = ["a", undefined, "a"]; +const seen = new Set(strings); +console.log(seen.size, seen.has(undefined), [...seen]); +for (const [i, s] of strings.entries()) console.log(i, s); +for (const key in strings) console.log("key", key); + +class Holder { + values: (number | undefined)[] = []; + add(value: number | undefined): void { + this.values.push(value); + } + first(): number | undefined { + return this.values[0]; + } +} +const holder = new Holder(); +holder.add(undefined); +holder.add(2); +console.log(holder.first(), holder.values, holder.values.length); + +const xs: (number | undefined)[] = [3, undefined, 1]; +xs.forEach((x, i) => console.log(i, x, typeof x)); +console.log(xs.findIndex((x) => x === undefined), xs.findLast((x) => x !== undefined)); +console.log(xs.some((x) => x === undefined), xs.every((x) => x !== undefined)); +console.log(xs.reduce((acc, x) => acc + (x ?? 10), 0), xs.join("/"), xs.at(1), xs.at(-1)); +console.log(xs.toSorted((a, b) => (a ?? 0) - (b ?? 0)), [...xs].sort()); +xs[1] ??= 5; +console.log(xs); +const flags: (boolean | undefined)[] = [undefined, true]; +console.log(flags.map((b) => !b), flags.indexOf(undefined), flags.includes(true), String(flags)); +const nested: (number | undefined)[][] = [[undefined], [1]]; +console.log(nested.flat(), nested[0]![0]); +const byKey = new Map([["k", [undefined, 1]]]); +console.log(byKey.get("k")![0], byKey); + +// Promise.all over `Promise[]` settles on Node's turn, with +// its undefined values. +const lookup = async (key: string): Promise => { + if (key === "x") throw new Error("missing " + key); + return slots.get(key); +}; +const turns: string[] = []; +const pending: Promise[] = [lookup("a"), lookup("b")]; +void Promise.all(pending).then((vs) => turns.push("all " + vs.join(",") + " " + (vs[1] === undefined))); +const failing: Promise[] = [lookup("a"), lookup("x")]; +Promise.all(failing).then( + () => turns.push("unexpected"), + (error: unknown) => turns.push("rejected " + (error as Error).message), +); +void Promise.all([] as Promise[]).then((vs) => turns.push("empty " + vs.length)); +void Promise.resolve() + .then(() => turns.push("t1")) + .then(() => turns.push("t2")) + .then(() => turns.push("t3")) + .then(() => console.log(turns.join(" "))); + +// Index reads through a union of arrays, and searches with a number +// needle, see the present undefined elements. +const pairs: ((null | undefined)[] | (number | undefined)[])[] = [[null, undefined], [undefined, 0]]; +for (const pair of pairs) console.log(pair[0], pair[1], pair[2]); +const sparse: (number | undefined)[] = [1, , undefined, 1]; +console.log(sparse.lastIndexOf(undefined), sparse.lastIndexOf(1, 2), sparse.indexOf(1, 1)); diff --git a/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json b/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json new file mode 100644 index 000000000..b3db55643 --- /dev/null +++ b/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json @@ -0,0 +1 @@ +{ "plain": 6206, "san": 8487 } diff --git a/tests/harness/corpus-costs/8001-optional-element-writers.ts.json b/tests/harness/corpus-costs/8001-optional-element-writers.ts.json new file mode 100644 index 000000000..31695bc47 --- /dev/null +++ b/tests/harness/corpus-costs/8001-optional-element-writers.ts.json @@ -0,0 +1 @@ +{ "plain": 3690, "san": 4000 } From bfb360a65952bfec75dc2d9d18f61e60794b2d9f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 00:30:36 -0500 Subject: [PATCH 047/395] Add a runner for Node's test/parallel files tests/node-parallel/run.mjs builds files from a Node.js checkout's test/parallel with scriptc, runs each executable in that directory, and compares its exit status with Node's, writing a pass, fail or refused verdict and the first error line per file (results.json, results.md). --common minimal (the default) runs the files against a minimal test/common (common/index.cjs, common/wpt.cjs) whose helpers keep Node's semantics, so a verdict measures the module under test; --common node uses Node's own test/common. --- tests/node-parallel/README.md | 23 +++++ tests/node-parallel/common/index.cjs | 131 ++++++++++++++++++++++++++ tests/node-parallel/common/wpt.cjs | 32 +++++++ tests/node-parallel/run.mjs | 134 +++++++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 tests/node-parallel/README.md create mode 100644 tests/node-parallel/common/index.cjs create mode 100644 tests/node-parallel/common/wpt.cjs create mode 100644 tests/node-parallel/run.mjs diff --git a/tests/node-parallel/README.md b/tests/node-parallel/README.md new file mode 100644 index 000000000..35af64464 --- /dev/null +++ b/tests/node-parallel/README.md @@ -0,0 +1,23 @@ +# Node test/parallel files + +`run.mjs` builds files from a Node.js checkout's `test/parallel` with scriptc, runs each executable in that directory, and compares its exit status with Node's. It records a verdict per file: `pass` (same exit status), `fail` (different exit status), or `refused` (the build failed), with the first error line. + +```bash +git clone --depth 1 --branch v24.22.0 https://github.com/nodejs/node /path/to/node +node tests/node-parallel/run.mjs --node /path/to/node --pattern '^test-(url-|whatwg-url-|querystring)' +``` + +Options: + +- `--node`: a Node.js checkout, or its `test` directory. Use the version in `.node-version`. +- `--pattern`: a regular expression over file names in `test/parallel`. +- `--common`: `minimal` (the default) or `node`. + - `minimal` copies the selected files into `/tree/test/parallel` and uses `common/index.cjs` and `common/wpt.cjs` from this directory as `test/common/index.js` and `test/common/wpt.js`, with the checkout's `test/fixtures` and `test/common/fixtures.js`. The minimal common has the helpers these files use (`mustCall`, `mustNotCall`, `skip`, `expectWarning`, `invalidArgTypeHelper`, `spawnPromisified`, `hasIntl`, `isWindows`, ...) with Node's semantics, so a verdict measures the module under test rather than Node's full `test/common`. + - `node` builds the files in place against Node's own `test/common`. +- `--out`: where executables and results go (default `node_modules/.cache/scriptc-node-parallel`). +- `--cli`: the scriptc CLI entry (default `packages/cli/dist/main.js`). +- `--jobs`: concurrent files (default 4). + +Node runs each file with its `// Flags:` line. Results are written to `/results.json` and `/results.md`. + +Before a lane relies on the minimal common, run its files under Node in `/tree/test/parallel`: they should all pass there. diff --git a/tests/node-parallel/common/index.cjs b/tests/node-parallel/common/index.cjs new file mode 100644 index 000000000..b52bba638 --- /dev/null +++ b/tests/node-parallel/common/index.cjs @@ -0,0 +1,131 @@ +"use strict"; +// A minimal test/common for building Node's test/parallel files with scriptc: +// the helpers those files use, with the semantics of Node's +// test/common/index.js. Add a helper here, with Node's semantics, when a +// lane's files need it. +const assert = require("assert"); +const { inspect } = require("util"); + +const hasIntl = true; +const isDebug = false; +const isWindows = process.platform === "win32"; +const mustCallChecks = []; + +function runCallChecks(exitCode) { + if (exitCode !== 0) return; + const failed = mustCallChecks.filter((context) => { + if ("minimum" in context) return context.actual < context.minimum; + return context.actual !== context.exact; + }); + for (const context of failed) { + console.log( + "Mismatched %s function calls. Expected %s, actual %d.", + context.name, + String(context.exact ?? context.minimum), + context.actual, + ); + } + if (failed.length) process.exit(1); +} + +function noop() {} + +function mustCallInner(fn, criteria, field) { + if (typeof fn === "number") { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + if (criteria === undefined) criteria = 1; + const context = { actual: 0, name: fn.name || "" }; + context[field] = criteria; + if (mustCallChecks.length === 0) process.on("exit", runCallChecks); + mustCallChecks.push(context); + return function (...args) { + context.actual++; + return fn.apply(this, args); + }; +} + +function mustCall(fn, exact) { + return mustCallInner(fn, exact, "exact"); +} + +function mustCallAtLeast(fn, minimum) { + return mustCallInner(fn, minimum, "minimum"); +} + +function mustNotCall(msg) { + return function mustNotCall() { + assert.fail(msg || "function should not have been called"); + }; +} + +function skip(msg) { + console.log(`1..0 # Skipped: ${msg}`); + process.exit(0); +} + +function expectWarning(name, expected, code) { + if (typeof expected === "string") expected = [[expected, code]]; + else if (expected.length !== 0 && !Array.isArray(expected[0])) + expected = [[expected[0], expected[1]]]; + const check = mustCall((warning) => { + const [message, warningCode] = expected.shift(); + assert.strictEqual(warning.name, name); + if (typeof message === "string") assert.strictEqual(warning.message, message); + else assert.match(warning.message, message); + assert.strictEqual(warning.code, warningCode); + }, expected.length); + process.on("warning", (warning) => { + if (warning.name !== name) + throw new TypeError(`"${warning.name}" was triggered without being expected.`); + check(warning); + }); +} + +function invalidArgTypeHelper(input) { + if (input == null) return ` Received ${input}`; + if (typeof input === "function") return ` Received function ${input.name}`; + if (typeof input === "object") { + const name = input.constructor?.name; + if (typeof name === "string" && name !== "") return ` Received an instance of ${name}`; + return ` Received ${inspect(input, { depth: -1 })}`; + } + let inspected = inspect(input, { colors: false }); + if (inspected.length > 28) inspected = `${inspected.slice(inspected, 0, 25)}...`; + return ` Received type ${typeof input} (${inspected})`; +} + +function spawnPromisified(...args) { + const { spawn } = require("child_process"); + let stderr = ""; + let stdout = ""; + const child = spawn(...args); + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (data) => { + stderr += data; + }); + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (data) => { + stdout += data; + }); + return new Promise((resolve, reject) => { + child.on("close", (code, signal) => resolve({ code, signal, stderr, stdout })); + child.on("error", (code, signal) => reject({ code, signal, stderr, stdout })); + }); +} + +module.exports = { + hasIntl, + isDebug, + isWindows, + mustCall, + mustCallAtLeast, + mustNotCall, + skip, + expectWarning, + invalidArgTypeHelper, + spawnPromisified, +}; diff --git a/tests/node-parallel/common/wpt.cjs b/tests/node-parallel/common/wpt.cjs new file mode 100644 index 000000000..eeb7d9770 --- /dev/null +++ b/tests/node-parallel/common/wpt.cjs @@ -0,0 +1,32 @@ +"use strict"; +// The harness object of Node's test/common/wpt.js, without its WPT runner. +const assert = require("assert"); + +const harness = { + test: (fn, desc) => { + try { + fn(); + } catch (err) { + console.error(`In ${desc}:`); + throw err; + } + }, + assert_equals: assert.strictEqual, + assert_true: (value, message) => assert.strictEqual(value, true, message), + assert_false: (value, message) => assert.strictEqual(value, false, message), + assert_throws: (code, func, desc) => { + assert.throws( + func, + function (err) { + return typeof err === "object" && "name" in err && err.name.startsWith(code.name); + }, + desc, + ); + }, + assert_array_equals: assert.deepStrictEqual, + assert_unreached(desc) { + assert.fail(`Reached unreachable code: ${desc}`); + }, +}; + +module.exports = { harness }; diff --git a/tests/node-parallel/run.mjs b/tests/node-parallel/run.mjs new file mode 100644 index 000000000..4f2230c6b --- /dev/null +++ b/tests/node-parallel/run.mjs @@ -0,0 +1,134 @@ +// Builds Node's test/parallel files with scriptc and runs them next to Node. +// See README.md for the options. +import { spawn } from "node:child_process"; +import { + cpSync, + existsSync, + mkdirSync, + readFileSync, + readdirSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; + +const here = dirname(fileURLToPath(import.meta.url)); +const repo = resolve(here, "../.."); +const { values } = parseArgs({ + options: { + node: { type: "string" }, + pattern: { type: "string" }, + common: { type: "string", default: "minimal" }, + out: { type: "string", default: join(repo, "node_modules/.cache/scriptc-node-parallel") }, + cli: { type: "string", default: join(repo, "packages/cli/dist/main.js") }, + jobs: { type: "string", default: "4" }, + }, +}); +if (!values.node || !values.pattern || !["minimal", "node"].includes(values.common)) { + console.error( + "usage: node tests/node-parallel/run.mjs --node --pattern [--common minimal|node] [--out ] [--cli ] [--jobs ]", + ); + process.exit(2); +} +const nodeTest = existsSync(join(values.node, "test", "parallel")) + ? join(values.node, "test") + : values.node; +const pattern = new RegExp(values.pattern); +const out = resolve(values.out); +mkdirSync(out, { recursive: true }); + +// The minimal lane copies the selected files into a tree whose test/common is +// this directory's index.cjs and wpt.cjs, next to the checkout's fixtures and +// common/fixtures.js. +let testRoot = nodeTest; +const files = readdirSync(join(nodeTest, "parallel")) + .filter((name) => name.endsWith(".js") && pattern.test(name)) + .sort(); +if (values.common === "minimal") { + testRoot = join(out, "tree", "test"); + rmSync(join(out, "tree"), { recursive: true, force: true }); + mkdirSync(join(testRoot, "parallel"), { recursive: true }); + mkdirSync(join(testRoot, "common"), { recursive: true }); + for (const name of files) + cpSync(join(nodeTest, "parallel", name), join(testRoot, "parallel", name)); + symlinkSync(join(nodeTest, "fixtures"), join(testRoot, "fixtures")); + cpSync(join(nodeTest, "common", "fixtures.js"), join(testRoot, "common", "fixtures.js")); + cpSync(join(here, "common", "index.cjs"), join(testRoot, "common", "index.js")); + cpSync(join(here, "common", "wpt.cjs"), join(testRoot, "common", "wpt.js")); +} + +function run(cmd, args, opts) { + return new Promise((done) => { + const child = spawn(cmd, args, { ...opts, stdio: ["ignore", "pipe", "pipe"] }); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (d) => (stdout += d)); + child.stderr.on("data", (d) => (stderr += d)); + const timer = setTimeout(() => child.kill("SIGKILL"), opts.timeout); + child.on("close", (code, signal) => { + clearTimeout(timer); + done({ code, signal, stdout, stderr }); + }); + }); +} + +const firstCause = (text) => + ( + text.split("\n").find((line) => /error|Error|assert|SC\d{4}/.test(line)) ?? + text.split("\n")[0] ?? + "" + ) + .replaceAll(testRoot, "test") + .trim() + .slice(0, 300); + +async function one(file) { + const dir = join(testRoot, "parallel"); + const source = readFileSync(join(dir, file), "utf8"); + const flags = (/^\/\/ Flags: (.*)$/m.exec(source)?.[1] ?? "").split(/\s+/).filter(Boolean); + const node = await run(process.execPath, [...flags, file], { cwd: dir, timeout: 120_000 }); + const exe = join(out, "bin", `${file}.exe`); + mkdirSync(join(out, "bin"), { recursive: true }); + const build = await run(process.execPath, [values.cli, "build", file, "-o", exe], { + cwd: dir, + timeout: 900_000, + }); + const result = { file, flags: flags.join(" "), nodeExit: node.code }; + if (build.code !== 0) { + result.verdict = "refused"; + result.cause = firstCause(build.stdout + build.stderr); + return result; + } + const native = await run(exe, [], { cwd: dir, timeout: 120_000 }); + result.exit = native.code ?? native.signal; + result.verdict = native.code === node.code ? "pass" : "fail"; + if (result.verdict !== "pass") result.cause = firstCause(native.stderr + native.stdout); + return result; +} + +const results = []; +const queue = [...files]; +await Promise.all( + Array.from({ length: Number(values.jobs) }, async () => { + for (let file = queue.shift(); file !== undefined; file = queue.shift()) { + const result = await one(file); + results.push(result); + console.log(`${result.verdict.padEnd(8)} ${file}${result.cause ? ` ${result.cause}` : ""}`); + } + }), +); +results.sort((a, b) => (a.file < b.file ? -1 : 1)); +const count = (verdict) => results.filter((r) => r.verdict === verdict).length; +const table = [ + "| file | verdict | first cause |", + "| --- | --- | --- |", + ...results.map((r) => `| ${r.file} | ${r.verdict} | ${(r.cause ?? "").replaceAll("|", "\\|")} |`), + "", + `pass ${count("pass")}, fail ${count("fail")}, refused ${count("refused")} of ${results.length} (test/common: ${values.common})`, +]; +writeFileSync(join(out, "results.json"), `${JSON.stringify(results, null, 2)}\n`); +writeFileSync(join(out, "results.md"), `${table.join("\n")}\n`); +console.log(table.at(-1)); From d99fc851e66ffc3eadb95b30b226dbdcce5745c0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 01:22:42 -0500 Subject: [PATCH 048/395] Type untyped JavaScript Maps, Sets and arrays from their uses In a JavaScript file, `const m = new Map()`, `new Set()` and `[]` type as Map, Set and any[], so every use lowered to the checked-dynamic collection operations: boxed keys and values, generic method dispatch, and dynamic arithmetic on what they returned. js-collection-types.ts proves types for such bindings from their uses. A `const` qualifies when every reference is a direct use of the collection: - for a Map: get/has/delete/clear, size, and set as a statement; - for a Set: has/delete/clear, size, and add as a statement; - for an array: push/pop/shift/at/includes/indexOf/join and length. The value then never leaves the binding. Its key, value or element type is the one primitive (string, number or boolean) of every expression the program writes. That is found by a fixed point that types `m.get(k) ?? d`, arithmetic, comparisons and constants defined from them, and it requires lookups of the same type. Any other reference (passing, returning, storing, exporting, iterating, inspecting, an optional call), a direct `eval` in the file, or any doubt keeps the binding untyped. A proven binding answers the lowerer's typeOf with a carrier type declared in scriptc-overrides.d.ts, which only the lowering program includes. `new Map()` and `new Set()` adopt it, so the binding lowers as the typed Map, Set or T[], as a JSDoc @type would make it. 7985 covers the typed cases and every refusal. --- .../compiler/ambient/scriptc-overrides.d.ts | 21 + .../frontend/lowering/js-collection-types.ts | 519 ++++++++++++++++++ .../src/frontend/lowering/lower-classes.ts | 10 +- .../compiler/src/frontend/lowering/lowerer.ts | 8 + .../compiler/test/js-collection-types.test.ts | 62 +++ .../7985-untyped-collection-types.mjs.json | 6 + .../corpus/7985-untyped-collection-types.mjs | 116 ++++ .../7985-untyped-collection-types.mjs.json | 1 + 8 files changed, 741 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/js-collection-types.ts create mode 100644 packages/compiler/test/js-collection-types.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7985-untyped-collection-types.mjs.json create mode 100644 tests/corpus/7985-untyped-collection-types.mjs create mode 100644 tests/harness/corpus-costs/7985-untyped-collection-types.mjs.json diff --git a/packages/compiler/ambient/scriptc-overrides.d.ts b/packages/compiler/ambient/scriptc-overrides.d.ts index 2dafbcdf0..376eba9d5 100644 --- a/packages/compiler/ambient/scriptc-overrides.d.ts +++ b/packages/compiler/ambient/scriptc-overrides.d.ts @@ -70,3 +70,24 @@ interface PromiseConstructor { reject: (reason: Error) => void; }; } + +/* Carriers of the types the lowerer gives untyped JavaScript collections + * whose uses prove them (js-collection-types.ts). Nothing reads them at run + * time. */ +declare namespace __scriptcInferredCollections { + const map_string_string: Map; + const map_string_number: Map; + const map_string_boolean: Map; + const map_number_string: Map; + const map_number_number: Map; + const map_number_boolean: Map; + const map_boolean_string: Map; + const map_boolean_number: Map; + const map_boolean_boolean: Map; + const set_string: Set; + const set_number: Set; + const set_boolean: Set; + const array_string: string[]; + const array_number: number[]; + const array_boolean: boolean[]; +} diff --git a/packages/compiler/src/frontend/lowering/js-collection-types.ts b/packages/compiler/src/frontend/lowering/js-collection-types.ts new file mode 100644 index 000000000..2be3c067f --- /dev/null +++ b/packages/compiler/src/frontend/lowering/js-collection-types.ts @@ -0,0 +1,519 @@ +/* Types of untyped JavaScript collections that their uses prove. + * + * In a JavaScript file, `const m = new Map()`, `new Set()` and `[]` type as + * Map, Set and any[] to the checker, so every use lowers to + * the checked-dynamic collection operations. When every reference of such a + * `const` is a direct use of the collection, the value never leaves the + * binding. If its keys, values or elements are then each one primitive type + * wherever the program writes and looks them up, the binding lowers as the + * typed Map, Set or T[], as a JSDoc @type would make it: + * + * - Map: `m.get(k)`, `m.has(k)`, `m.delete(k)`, `m.clear()`, `m.size`, and + * `m.set(k, v)` as a statement (its result is the map). + * - Set: `s.add(k)` as a statement, `s.has(k)`, `s.delete(k)`, `s.clear()`, + * `s.size`. + * - Array: `a.push(...)`, `a.pop()`, `a.shift()`, `a.at(i)`, `a.includes(v)`, + * `a.indexOf(v)`, `a.join(separator)`, `a.length`. + * + * Any other reference (passing, returning, storing, exporting, iterating, + * spreading, inspecting the collection, an optional call), a direct `eval` + * in the file, or any doubt about a type keeps the binding untyped. + * + * The types come from carrier declarations in scriptc-overrides.d.ts, which + * only the lowering program includes. */ + +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFile } from "../program.js"; +import { overridesDtsPath } from "../dts-paths.js"; +import type { Lowerer } from "./lowerer.js"; + +type Primitive = "string" | "number" | "boolean"; +const PRIMITIVES: readonly Primitive[] = ["string", "number", "boolean"]; + +type Collection = "map" | "set" | "array"; + +interface Candidate { + readonly kind: Collection; + readonly declaration: ts.VariableDeclaration; + /** Every identifier that names the binding, its declaration's included. */ + readonly names: ts.Identifier[]; + /** Map keys or Set members written by set/add. */ + readonly keyWrites: ts.Expression[]; + /** Map values written by set. Array elements written by push. */ + readonly valueWrites: ts.Expression[]; + /** Arguments compared with keys or elements: get/has/delete, includes/indexOf. */ + readonly lookups: ts.Expression[]; + /** Array indexes passed to at(). */ + readonly indexes: ts.Expression[]; + /** Separators passed to join(). */ + readonly separators: ts.Expression[]; + key: Primitive | undefined; + value: Primitive | undefined; +} + +const MAP_LOOKUPS = new Set(["get", "has", "delete"]); +const SET_LOOKUPS = new Set(["has", "delete"]); +const ARRAY_READS = new Set(["pop", "shift"]); +const ARRAY_SEARCHES = new Set(["includes", "indexOf"]); + +/** The inferred type of the binding a `new Map()` or `new Set()` initializes, + * which the construction adopts. */ +export function initializedCollectionType( + lowerer: Lowerer, + expr: ts.NewExpression, +): ts.Type | null { + const declaration = expr.parent; + return declaration !== undefined && + ts.isVariableDeclaration(declaration) && + declaration.initializer === expr && + ts.isIdentifier(declaration.name) + ? inferredCollectionType(lowerer, declaration.name) + : null; +} + +/** The inferred collection type of an identifier that names a proven + * binding, or null. Computed for every JavaScript file of the program on + * first use. */ +export function inferredCollectionType(lowerer: Lowerer, node: ts.Identifier): ts.Type | null { + const types = (lowerer.inferredCollectionTypes ??= inferProgram(lowerer)); + return types.size === 0 ? null : (types.get(node) ?? null); +} + +function inferProgram(lowerer: Lowerer): Map { + const out = new Map(); + const carriers = carrierTypes(lowerer); + if (carriers === null) return out; + for (const file of lowerer.program.getSourceFiles()) { + if (file.isDeclarationFile || !isJsSourceFile(file)) continue; + for (const candidate of inferFile(lowerer, file)) { + const name = + candidate.kind === "map" + ? `map_${candidate.key}_${candidate.value}` + : candidate.kind === "set" + ? `set_${candidate.key}` + : `array_${candidate.value}`; + const type = carriers.get(name); + if (type === undefined) continue; + for (const id of candidate.names) out.set(id, type); + } + } + return out; +} + +/** The carrier declarations' types, by name (`map_string_number`, + * `set_number`, `array_string`, ...), or null when the lowering program does + * not include them. */ +function carrierTypes(lowerer: Lowerer): Map | null { + const file = lowerer.program.getSourceFile(overridesDtsPath()); + if (file === undefined) return null; + const out = new Map(); + for (const statement of file.statements.nodes) { + if ( + !ts.isModuleDeclaration(statement) || + !ts.isIdentifier(statement.name) || + statement.name.text !== "__scriptcInferredCollections" || + statement.body === undefined || + !ts.isModuleBlock(statement.body) + ) + continue; + for (const member of statement.body.statements.nodes) { + if (!ts.isVariableStatement(member)) continue; + for (const declaration of member.declarationList.declarations.nodes) + if (ts.isIdentifier(declaration.name)) + out.set(declaration.name.text, lowerer.checker.getTypeAtLocation(declaration.name)); + } + } + return out.size > 0 ? out : null; +} + +function inferFile(lowerer: Lowerer, file: ts.SourceFile): Candidate[] { + const candidates = new Map(); + let directEval = false; + const declarations = (node: ts.Node): void => { + if ( + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === "eval" + ) + directEval = true; + if (ts.isVariableDeclaration(node)) { + const kind = candidateKind(lowerer, node); + const symbol = kind !== null ? lowerer.checker.getSymbolAtLocation(node.name) : undefined; + if (kind !== null && symbol !== undefined) + candidates.set(symbol, { + kind, + declaration: node, + names: [node.name as ts.Identifier], + keyWrites: [], + valueWrites: [], + lookups: [], + indexes: [], + separators: [], + key: undefined, + value: undefined, + }); + } + ts.forEachChild(node, declarations); + }; + declarations(file); + if (directEval || candidates.size === 0) return []; + + const names = new Set( + [...candidates.values()].map((c) => (c.declaration.name as ts.Identifier).text), + ); + const refused = new Set(); + const references = (node: ts.Node): void => { + if (ts.isIdentifier(node) && names.has(node.text)) { + const symbol = referencedSymbol(lowerer, node); + const candidate = symbol !== undefined ? candidates.get(symbol) : undefined; + if (symbol !== undefined && candidate !== undefined && node !== candidate.declaration.name) { + if (recordUse(candidate, node)) candidate.names.push(node); + else refused.add(symbol); + } + } + ts.forEachChild(node, references); + }; + references(file); + for (const symbol of refused) candidates.delete(symbol); + const proven = [...candidates.values()].filter( + (c) => c.valueWrites.length > 0 || c.keyWrites.length > 0, + ); + solve(lowerer, proven); + return proven.filter((c) => + c.kind === "map" ? c.key && c.value : c.kind === "set" ? c.key : c.value, + ); +} + +/** The collection a `const` declaration starts empty, when the checker + * types it as an untyped Map, Set or array. */ +function candidateKind(lowerer: Lowerer, declaration: ts.VariableDeclaration): Collection | null { + if (!ts.isIdentifier(declaration.name) || declaration.initializer === undefined) return null; + const list = declaration.parent; + if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) return null; + const statement = list.parent; + if ( + ts.isVariableStatement(statement) && + ts.getModifiers(statement)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) + ) + return null; + const init = declaration.initializer; + let kind: Collection | null = null; + if (ts.isArrayLiteralExpression(init) && init.elements.nodes.length === 0) kind = "array"; + else if ( + ts.isNewExpression(init) && + init.typeArguments === undefined && + (init.arguments?.nodes.length ?? 0) === 0 + ) { + if (lowerer.isStdlibGlobal(init.expression, "Map")) kind = "map"; + else if (lowerer.isStdlibGlobal(init.expression, "Set")) kind = "set"; + } + if (kind === null) return null; + // A JSDoc type, or anything else the checker already knows, stands. + const type = lowerer.checker.getTypeAtLocation(declaration.name); + const args = + kind === "array" + ? lowerer.checker.isArrayType(type) + ? lowerer.checker.getTypeArguments(type as ts.TypeReference) + : [] + : lowerer.checker.getTypeArguments(type as ts.TypeReference); + const arity = kind === "map" ? 2 : 1; + if (args.length !== arity || !args.every((a) => (a.flags & ts.TypeFlags.Any) !== 0)) return null; + return kind; +} + +function referencedSymbol(lowerer: Lowerer, id: ts.Identifier): ts.Symbol | undefined { + const parent = id.parent; + if (parent !== undefined && ts.isShorthandPropertyAssignment(parent) && parent.name === id) + return lowerer.checker.getShorthandAssignmentValueSymbol(parent); + return lowerer.checker.getSymbolAtLocation(id); +} + +/** Records a reference's use, or answers false when the reference is not + * one of the collection uses this inference proves. */ +function recordUse(candidate: Candidate, id: ts.Identifier): boolean { + const access = id.parent; + if ( + access === undefined || + !ts.isPropertyAccessExpression(access) || + access.expression !== id || + access.questionDotToken !== undefined + ) + return false; + const name = access.name.text; + const call = access.parent; + if (call !== undefined && ts.isCallExpression(call) && call.expression === access) { + if (call.questionDotToken !== undefined) return false; + const args = call.arguments.nodes; + if (args.some(ts.isSpreadElement)) return false; + switch (candidate.kind) { + case "map": + if (MAP_LOOKUPS.has(name) && args.length === 1) + return (candidate.lookups.push(args[0]!), true); + if (name === "clear") return args.length === 0; + if (name === "set" && args.length === 2 && isStatement(call)) { + candidate.keyWrites.push(args[0]!); + candidate.valueWrites.push(args[1]!); + return true; + } + return false; + case "set": + if (SET_LOOKUPS.has(name) && args.length === 1) + return (candidate.lookups.push(args[0]!), true); + if (name === "clear") return args.length === 0; + if (name === "add" && args.length === 1 && isStatement(call)) + return (candidate.keyWrites.push(args[0]!), true); + return false; + case "array": + if (name === "push") return (candidate.valueWrites.push(...args), true); + if (ARRAY_READS.has(name)) return args.length === 0; + if (name === "at" && args.length === 1) return (candidate.indexes.push(args[0]!), true); + if (ARRAY_SEARCHES.has(name) && args.length === 1) + return (candidate.lookups.push(args[0]!), true); + if (name === "join" && args.length <= 1) return (candidate.separators.push(...args), true); + return false; + } + } + return (candidate.kind === "array" ? name === "length" : name === "size") && isPlainRead(access); +} + +function isStatement(call: ts.CallExpression): boolean { + let node: ts.Node = call; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + return node.parent !== undefined && ts.isExpressionStatement(node.parent); +} + +/** A read that neither writes nor deletes the property. */ +function isPlainRead(access: ts.PropertyAccessExpression): boolean { + let node: ts.Node = access; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + if (parent === undefined) return false; + if ( + ts.isBinaryExpression(parent) && + parent.left === node && + isAssignment(parent.operatorToken.kind) + ) + return false; + if ( + (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && + (parent.operator === ts.SyntaxKind.PlusPlusToken || + parent.operator === ts.SyntaxKind.MinusMinusToken) + ) + return false; + return !ts.isDeleteExpression(parent); +} + +function isAssignment(kind: ts.SyntaxKind): boolean { + return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment; +} + +/** Decides each candidate's key and value types: a fixed point in which a + * decided candidate's reads type the expressions built from them. */ +function solve(lowerer: Lowerer, candidates: Candidate[]): void { + const bySymbol = new Map(); + for (const c of candidates) { + const symbol = lowerer.checker.getSymbolAtLocation(c.declaration.name); + if (symbol) bySymbol.set(symbol, c); + } + for (let changed = true; changed;) { + changed = false; + for (const c of candidates) { + if (c.kind !== "array" && c.key === undefined) { + const key = uniform(lowerer, bySymbol, c.keyWrites); + if (key !== null) { + c.key = key; + changed = true; + } + } + if (c.kind !== "set" && c.value === undefined) { + // Each candidate type is assumed for this collection's own reads. + const accepted = PRIMITIVES.filter((assumed) => { + c.value = assumed; + const found = uniform(lowerer, bySymbol, c.valueWrites); + c.value = undefined; + return found === assumed; + }); + if (accepted.length === 1) { + c.value = accepted[0]; + changed = true; + } + } + } + } + for (const c of candidates) { + const element = c.kind === "array" ? c.value : c.key; + // Lookups compare with the stored type; an index is a number. + const lookupsAgree = + element !== undefined && + c.lookups.every((e) => primitiveOf(lowerer, bySymbol, e, new Set()) === element) && + c.indexes.every((e) => primitiveOf(lowerer, bySymbol, e, new Set()) === "number") && + c.separators.every((e) => primitiveOf(lowerer, bySymbol, e, new Set()) === "string"); + if (!lookupsAgree) { + c.key = undefined; + c.value = undefined; + } + } +} + +/** The one primitive type of every expression, or null. */ +function uniform( + lowerer: Lowerer, + bySymbol: Map, + exprs: readonly ts.Expression[], +): Primitive | null { + let found: Primitive | null = null; + for (const e of exprs) { + const p = primitiveOf(lowerer, bySymbol, e, new Set()); + if (p === null || (found !== null && p !== found)) return null; + found = p; + } + return found; +} + +function checkerPrimitive(type: ts.Type): Primitive | null { + const flags = type.flags; + if (flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) return "string"; + if (flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral)) return "number"; + if (flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) return "boolean"; + return null; +} + +/** The primitive type `e` always evaluates to, or null. The checker answers + * where it knows; where it types `e` as `any`, the value's structure does: + * a read of a decided candidate, arithmetic and comparisons over such + * values, and constants defined from them. */ +function primitiveOf( + lowerer: Lowerer, + bySymbol: Map, + e: ts.Expression, + visiting: Set, +): Primitive | null { + const type = lowerer.checker.getTypeAtLocation(e); + if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0) return checkerPrimitive(type); + const of = (x: ts.Expression): Primitive | null => primitiveOf(lowerer, bySymbol, x, visiting); + if (ts.isParenthesizedExpression(e)) return of(e.expression); + if (ts.isPrefixUnaryExpression(e)) { + if (e.operator === ts.SyntaxKind.ExclamationToken) return "boolean"; + return e.operator !== ts.SyntaxKind.PlusPlusToken && + e.operator !== ts.SyntaxKind.MinusMinusToken && + of(e.operand) === "number" + ? "number" + : null; + } + if (ts.isConditionalExpression(e)) { + const yes = of(e.whenTrue); + return yes !== null && yes === of(e.whenFalse) ? yes : null; + } + if (ts.isBinaryExpression(e)) { + const op = e.operatorToken.kind; + switch (op) { + case ts.SyntaxKind.LessThanToken: + case ts.SyntaxKind.GreaterThanToken: + case ts.SyntaxKind.LessThanEqualsToken: + case ts.SyntaxKind.GreaterThanEqualsToken: + case ts.SyntaxKind.EqualsEqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsEqualsToken: + case ts.SyntaxKind.EqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsToken: + return "boolean"; + case ts.SyntaxKind.QuestionQuestionToken: + case ts.SyntaxKind.BarBarToken: { + // `m.get(k) ?? d` and `a.pop() || d`: the stored type or a default of it. + const stored = storedRead(lowerer, bySymbol, e.left); + const fallback = of(e.right); + return stored !== null && stored === fallback ? stored : null; + } + case ts.SyntaxKind.PlusToken: { + const left = of(e.left); + const right = of(e.right); + if (left === "number" && right === "number") return "number"; + return left !== null && right !== null && (left === "string" || right === "string") + ? "string" + : null; + } + case ts.SyntaxKind.MinusToken: + case ts.SyntaxKind.AsteriskToken: + case ts.SyntaxKind.SlashToken: + case ts.SyntaxKind.PercentToken: + case ts.SyntaxKind.AsteriskAsteriskToken: + case ts.SyntaxKind.BarToken: + case ts.SyntaxKind.AmpersandToken: + case ts.SyntaxKind.CaretToken: + case ts.SyntaxKind.LessThanLessThanToken: + case ts.SyntaxKind.GreaterThanGreaterThanToken: + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + return of(e.left) === "number" && of(e.right) === "number" ? "number" : null; + default: + return null; + } + } + if (ts.isCallExpression(e) || ts.isPropertyAccessExpression(e)) + return candidateRead(lowerer, bySymbol, e); + // A constant defined from such an expression. + if (ts.isIdentifier(e) && !visiting.has(e)) { + const symbol = lowerer.checker.getSymbolAtLocation(e); + const declaration = symbol?.valueDeclaration; + if ( + declaration !== undefined && + ts.isVariableDeclaration(declaration) && + declaration.initializer !== undefined && + ts.isVariableDeclarationList(declaration.parent) && + (declaration.parent.flags & ts.NodeFlags.Const) !== 0 + ) { + visiting.add(e); + const p = primitiveOf(lowerer, bySymbol, declaration.initializer, visiting); + visiting.delete(e); + return p; + } + } + return null; +} + +/** The candidate a use reads, when `e` is `c.member` or `c.member(...)` of a + * candidate `c`. */ +function candidateOf( + lowerer: Lowerer, + bySymbol: Map, + e: ts.Expression, +): { candidate: Candidate; member: string } | null { + const access = ts.isCallExpression(e) ? e.expression : e; + if (!ts.isPropertyAccessExpression(access) || !ts.isIdentifier(access.expression)) return null; + const symbol = lowerer.checker.getSymbolAtLocation(access.expression); + const candidate = symbol !== undefined ? bySymbol.get(symbol) : undefined; + return candidate !== undefined ? { candidate, member: access.name.text } : null; +} + +/** The type of a candidate's read that may answer undefined: `m.get(k)`, + * `a.pop()`, `a.shift()`, `a.at(i)`. */ +function storedRead( + lowerer: Lowerer, + bySymbol: Map, + e: ts.Expression, +): Primitive | null { + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isCallExpression(e)) return null; + const read = candidateOf(lowerer, bySymbol, e); + if (read === null) return null; + const { candidate, member } = read; + if (candidate.kind === "map" && member === "get") return candidate.value ?? null; + if (candidate.kind === "array" && (ARRAY_READS.has(member) || member === "at")) + return candidate.value ?? null; + return null; +} + +/** The type of a candidate's read that always answers a value: size, + * length, has, includes, indexOf, push. */ +function candidateRead( + lowerer: Lowerer, + bySymbol: Map, + e: ts.Expression, +): Primitive | null { + const read = candidateOf(lowerer, bySymbol, e); + if (read === null) return null; + const { candidate, member } = read; + if (!ts.isCallExpression(e)) return member === "size" || member === "length" ? "number" : null; + if (member === "has" || member === "includes" || member === "delete") return "boolean"; + if (member === "indexOf" || (candidate.kind === "array" && member === "push")) return "number"; + if (candidate.kind === "array" && member === "join") return "string"; + return null; +} diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b829b6ddc..c4dd6bce1 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -113,6 +113,7 @@ import { PoisonError, dynFallbackType, jsFuncNameOf, newFnCtx, own } from "./low import { isAssertClassNew } from "./assert-instances.js"; import { isCjsRequireGlobal } from "./lower-node-module.js"; import { lowerArrayConstructor } from "./containers/array-construction.js"; +import { initializedCollectionType } from "./js-collection-types.js"; import { lowerMapSeedNew, lowerSetSeedNew } from "./containers/collection-methods.js"; import { lowerObjectConstructor } from "./containers/indexed-objects.js"; import { knownBufEncoding, lowerBufferConstruct } from "./containers/bytes.js"; @@ -10568,7 +10569,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // expression). The CONTEXTUAL type carries the annotation — adopt // it when it is a supported map. TS type arguments keep winning: // their expression type already maps. - if (mapped?.kind !== "map" || contextualSeed) { + const inferred = initializedCollectionType(lowerer, expr); + if (inferred) { + tsType = inferred; + mapped = lowerer.mapTypeOf(inferred); + } else if (mapped?.kind !== "map" || contextualSeed) { const ctx = lowerer.checker.getContextualType(expr); const ctxMapped = ctx ? lowerer.mapTypeOf(ctx) : null; if (ctx && ctxMapped?.kind === "map") { @@ -10708,7 +10713,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // `new Set()`: construct + bulk add from supported values, // including live collection cursors. Duplicates collapse in insertion order. if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Set")) { - const tsType = lowerer.typeOf(expr); + const inferred = initializedCollectionType(lowerer, expr); + const tsType = inferred ?? lowerer.typeOf(expr); let mapped = lowerer.mapTypeOf(tsType); const fieldType = assignedThisFieldType(lowerer, expr); if (mapped?.kind !== "set" && fieldType?.kind === "set") mapped = fieldType; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index af89c61a4..78d1fafad 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -156,6 +156,7 @@ import { trackClassFieldCreation, } from "./class-dynamic-dispatch.js"; import { guardClassAttributeWrites } from "./class-attributes.js"; +import { inferredCollectionType } from "./js-collection-types.js"; import { guardFrozenWrites, markSloppyRecordWrites } from "./frozen-records.js"; import { applyHandleProperties } from "./handle-properties.js"; import { installBufferValue } from "./containers/bytes.js"; @@ -2846,6 +2847,9 @@ export class Lowerer { /** Parameters read as `any` in the error-path copy of an error-first * callback. */ readonly anySymbols = new Set(); + /** The collection types that uses prove for untyped JavaScript bindings, + * by every identifier naming one (js-collection-types.ts). */ + inferredCollectionTypes: Map | undefined = undefined; /** overload-choice's answers per call, binding and function. */ readonly dynamicOverloadAnswers = new Map(); /** Error parameters of an error-first callback with a known value in @@ -9055,6 +9059,10 @@ export class Lowerer { } typeOf(node: ts.Node): ts.Type { + if (ts.isIdentifier(node)) { + const inferred = inferredCollectionType(this, node); + if (inferred) return inferred; + } if (this.anySymbols.size > 0 && this.readsAnySymbol(node)) return this.checker.getAnyType(); // A checked-dynamic argument that another overload could take at run // time leaves the resolved overload's return a guess, and JS bindings diff --git a/packages/compiler/test/js-collection-types.test.ts b/packages/compiler/test/js-collection-types.test.ts new file mode 100644 index 000000000..88e2006b7 --- /dev/null +++ b/packages/compiler/test/js-collection-types.test.ts @@ -0,0 +1,62 @@ +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule, validateModule } from "../src/index.js"; + +// Corpus 7985 exercises every refusal: only the collections whose uses +// prove their types lower as typed collections; the rest stay untyped. +test("untyped JavaScript collections take the types their uses prove, and only those", async () => { + const dir = mkdtempSync(join(tmpdir(), "scriptc-js-collection-types-")); + try { + const entry = join( + import.meta.dirname, + "../../../tests/corpus/7985-untyped-collection-types.mjs", + ); + const output = join(dir, "main.ir.json"); + const lowered = await compile(entry, { outputKind: "ir", outDir: dir, outPath: output }); + if (!lowered.ok) throw new Error(JSON.stringify(lowered.diagnostics)); + const module = deserializeModule(readFileSync(output, "utf8")); + expect(validateModule(module)).toEqual([]); + const types = new Map(module.globals.map((g) => [g.name, JSON.stringify(g.type)])); + const map = (key: string, value: string) => + JSON.stringify({ kind: "map", key: { kind: key }, value: { kind: value } }); + const set = (elem: string) => JSON.stringify({ kind: "set", elem: { kind: elem } }); + const array = (elem: string) => JSON.stringify({ kind: "array", elem: { kind: elem } }); + // Untyped collections hold checked-dynamic keys, values and elements. + const typed = (t: string) => !t.includes('"dyn"'); + expect(Object.fromEntries([...types].filter(([, t]) => typed(t)))).toEqual({ + counts: map("string", "f64"), + seen: set("f64"), + stack: array("f64"), + words: array("string"), + byNumber: map("f64", "string"), + nums: array("f64"), + flags: set("bool"), + cache: map("string", "f64"), + "%loaded": '{"kind":"bool"}', + }); + for (const untyped of [ + "strings", + "numbers", + "mixedKeys", + "mixedValues", + "mixedElements", + "passed", + "stored", + "iterated", + "eachSet", + "shown", + "chained", + "optional", + "indexed", + "truncated", + "spread", + "unread", + "exported", + ]) + expect(typed(types.get(untyped)!), untyped).toBe(false); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7985-untyped-collection-types.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7985-untyped-collection-types.mjs.json new file mode 100644 index 000000000..a807063cc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7985-untyped-collection-types.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7985-untyped-collection-types.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/7985-untyped-collection-types.mjs b/tests/corpus/7985-untyped-collection-types.mjs new file mode 100644 index 000000000..a60a2ea04 --- /dev/null +++ b/tests/corpus/7985-untyped-collection-types.mjs @@ -0,0 +1,116 @@ +// Untyped Maps, Sets and arrays whose uses prove their key, value and +// element types compile as typed collections; any other use keeps them +// untyped. Both must answer as Node does, including SameValueZero keys, +// lookups of another type, and every use that refuses the typing. + +// Typed: counters, membership sets and stacks used only through their +// methods. +const counts = new Map(); +const seen = new Set(); +const stack = []; +const words = []; +for (let i = 0; i < 40; i++) { + const key = "k" + (i % 7); + counts.set(key, (counts.get(key) ?? 0) + 1); + seen.add(i % 5); + stack.push(i * 2); + if (stack.length > 6) stack.shift(); + words.push(key); +} +console.log(counts.size, counts.get("k3"), counts.get("missing"), counts.has("k6"), counts.delete("k0"), counts.size); +console.log(seen.size, seen.has(4), seen.has(5), seen.delete(4), seen.has(4)); +console.log(stack.length, stack.at(-1), stack.at(0), stack.pop(), stack.includes(70), stack.indexOf(72), words.join("|").length); + +// SameValueZero keys and NaN elements on typed collections. +const byNumber = new Map(); +byNumber.set(NaN, "nan"); +byNumber.set(-0, "zero"); +byNumber.set(1.5, "x"); +console.log(byNumber.get(NaN), byNumber.get(0), byNumber.get(-0), byNumber.size, byNumber.has(1.5)); +const nums = []; +nums.push(NaN); +nums.push(-0); +console.log(nums.includes(NaN), nums.indexOf(NaN), nums.includes(0), nums.indexOf(0)); +const flags = new Set(); +flags.add(true); +console.log(flags.has(true), flags.has(false), flags.size); + +// Captured by a closure that only uses it. +const cache = new Map(); +const latest = () => cache.get("c") ?? -1; +console.log(latest()); +for (let i = 0; i < 3; i++) cache.set("c", i * 10); +console.log(latest(), cache.size); + +// Lookups of another type: the typing refuses, and the answers stay Node's. +const strings = new Map(); +strings.set("1", "one"); +console.log(strings.get(1), strings.get("1"), strings.has(1)); +const numbers = []; +numbers.push(1); +console.log(numbers.includes("1"), numbers.indexOf("1"), numbers.at("0")); + +// Mixed key, value and element types. +const mixedKeys = new Map(); +mixedKeys.set(1, "number"); +mixedKeys.set("1", "string"); +console.log(mixedKeys.size, mixedKeys.get(1), mixedKeys.get("1")); +const mixedValues = new Map(); +mixedValues.set("a", 1); +mixedValues.set("b", "two"); +console.log(mixedValues.get("a"), mixedValues.get("b")); +const mixedElements = []; +mixedElements.push(1); +mixedElements.push("two"); +console.log(mixedElements.join(","), mixedElements.length); + +// Escapes and other uses that keep the collection untyped. +const passed = new Map(); +passed.set("x", 1); +function sizeOf(m) { + return m.size; +} +console.log(sizeOf(passed)); +function make() { + const made = new Set(); + made.add("y"); + return made; +} +console.log(make().has("y")); +const stored = []; +stored.push(3); +const holder = { list: stored }; +holder.list.push(4); +console.log(stored.length, holder.list.join("+")); +const iterated = new Map(); +iterated.set("a", 1); +iterated.set("b", 2); +for (const [k, v] of iterated) console.log("entry", k, v); +const eachSet = new Set(); +eachSet.add(3); +eachSet.forEach((v) => console.log("member", v)); +const shown = new Map(); +shown.set("s", 1); +console.log(shown); +const chained = new Map(); +chained.set("a", 1).set("b", 2); +console.log(chained.size); +const optional = new Map(); +optional.set("o", 1); +console.log(optional?.get("o"), optional.get?.("o")); +const indexed = []; +indexed.push(1); +indexed[3] = 2; +console.log(indexed.length, indexed.join(",")); +const truncated = []; +truncated.push(1, 2, 3); +truncated.length = 1; +console.log(truncated.length, truncated.at(0)); +const spread = []; +spread.push(5); +console.log(Math.max(...spread)); +const unread = new Map(); +console.log(unread.size, unread.get("none")); +export const exported = new Map(); +exported.set("e", 1); +console.log(exported.get("e")); diff --git a/tests/harness/corpus-costs/7985-untyped-collection-types.mjs.json b/tests/harness/corpus-costs/7985-untyped-collection-types.mjs.json new file mode 100644 index 000000000..6915a85fe --- /dev/null +++ b/tests/harness/corpus-costs/7985-untyped-collection-types.mjs.json @@ -0,0 +1 @@ +{ "plain": 2400, "san": 2600 } From 1ddec6df7cc7c81eb934d90675651724f3c15922 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 01:52:11 -0500 Subject: [PATCH 049/395] Freeze the symbol-keyed fields of class instances A frozen class instance kept its symbol-keyed fields writable through typed references, and Object.isFrozen, isSealed and isExtensible reported it unfrozen, because the field write checks and the instance's checked view covered its string-keyed fields only. This held for Object.freeze through an untyped value and for a typed Object.freeze alike. - Writes to a symbol-keyed field of a hierarchy with an integrity word are checked like other field writes, and throw Node's TypeError naming the symbol ("Cannot assign to read only property 'Symbol(tag)' ..."). - The checked view applies the instance's integrity state to its symbol-keyed properties too, and the commit of an untyped freeze, seal or preventExtensions counts them. - The commit removes a symbol-keyed field's entry from the property bag whatever its attributes (scr_dyn_obj_delete_symbol_field), as it does for string-keyed fields, so a sealed view no longer leaves the field's old value in the bag. Corpus 7407 covers freeze and seal of symbol-keyed fields through unknown and through typed calls, on a class and a subclass, and matches Node. --- .../src/backend/llvm/expr-stream-bridges.ts | 8 ++- .../src/frontend/lowering/class-attributes.ts | 40 ++++++++++++--- packages/runtime/src/scr_dyn.h | 3 ++ packages/runtime/src/scr_json.c | 50 +++++++++++++++---- tests/corpus/7407-frozen-class-instances.ts | 38 ++++++++++++++ 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 757e4349f..357db2fae 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -498,7 +498,9 @@ export function streamTypedRefCommitAdapter( host.declare( meta.def.tracksOwnFields ? `declare void @scr_dyn_symbol_key_placeholder(ptr, ptr)` - : `declare void @scr_dyn_key_delete_computed(ptr, ptr, i1 zeroext)`, + : withAttributes + ? `declare void @scr_dyn_obj_delete_symbol_field(ptr, ptr)` + : `declare void @scr_dyn_key_delete_computed(ptr, ptr, i1 zeroext)`, ); [...symbols.values()].forEach((globalId, index) => lines.push( @@ -506,7 +508,9 @@ export function streamTypedRefCommitAdapter( ` %bag_sym${index} = call ptr @${host.dyn.toDynHelper(SYMBOL_T)}(ptr %bag_symraw${index})`, meta.def.tracksOwnFields ? ` call void @scr_dyn_symbol_key_placeholder(ptr %bag, ptr %bag_sym${index})` - : ` call void @scr_dyn_key_delete_computed(ptr %bag, ptr %bag_sym${index}, i1 zeroext false)`, + : withAttributes + ? ` call void @scr_dyn_obj_delete_symbol_field(ptr %bag, ptr %bag_sym${index})` + : ` call void @scr_dyn_key_delete_computed(ptr %bag, ptr %bag_sym${index}, i1 zeroext false)`, ` call void @scr_dyn_release_v(ptr %bag_sym${index})`, ), ); diff --git a/packages/compiler/src/frontend/lowering/class-attributes.ts b/packages/compiler/src/frontend/lowering/class-attributes.ts index de7cc8ca7..cdd53efa6 100644 --- a/packages/compiler/src/frontend/lowering/class-attributes.ts +++ b/packages/compiler/src/frontend/lowering/class-attributes.ts @@ -303,6 +303,32 @@ export function lowerClassFieldDefine( }; } +/** The property key of `field` when it is the storage of a symbol-keyed + * field of `info`, as the read-only TypeError names it: the symbol's + * String() form ("Symbol(tag)"), read from the module global that holds + * the symbol. Null for other fields. */ +function symbolFieldKey( + lowerer: Lowerer, + info: ClassInfo, + field: string, + loc: SrcLoc, +): IrExpr | null { + if (info.symbolFields === undefined) return null; + for (const [symbol, name] of info.symbolFields) { + if (name !== field) continue; + const global = lowerer.globalsBySymbol.get(symbol); + if (global?.type.kind !== "symbol") return null; + return { + kind: "libCall", + fn: "sym.toString", + args: [varRef(global.id, global.type, loc)], + type: STRING, + loc, + }; + } + return null; +} + function classDisplayName(info: ClassInfo): string { return info.def.jsName ?? info.decl?.name?.text ?? info.def.name.replace(/^%/, ""); } @@ -367,12 +393,15 @@ export function guardClassAttributeWrites(lowerer: Lowerer, functions: IrFunctio if ( statement.kind !== "fieldSet" || statement.attributesChecked || - !guarded.has(statement.className) || - !isClassOwnEnumerableFieldName(statement.field) + !guarded.has(statement.className) ) return statement; const info = lowerer.classes.get(statement.className)!; const loc = statement.loc; + // A symbol-keyed field is an own property under its symbol. + const symbolKey = symbolFieldKey(lowerer, info, statement.field, loc); + if (!symbolKey && !isClassOwnEnumerableFieldName(statement.field)) return statement; + const key = (): IrExpr => symbolKey ?? strLit(statement.field, loc); const receiver = local(statement.obj.type, loc); const value = local(statement.value.type, loc); const masked = (field: string, bit: number): IrExpr => ({ @@ -405,7 +434,7 @@ export function guardClassAttributeWrites(lowerer: Lowerer, functions: IrFunctio expr: { kind: "libCall", fn: "class.throwNotExtensible", - args: [strLit(statement.field, loc)], + args: [key()], type: VOID, loc, }, @@ -420,10 +449,7 @@ export function guardClassAttributeWrites(lowerer: Lowerer, functions: IrFunctio expr: { kind: "libCall", fn: "class.throwReadOnly", - args: [ - strLit(statement.field, loc), - instanceClassName(lowerer, info, receiver, helpers, loc), - ], + args: [key(), instanceClassName(lowerer, info, receiver, helpers, loc)], type: VOID, loc, }, diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index bf3b8d426..9df51f90a 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1220,6 +1220,9 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict); /* Removes the own property `key` of an ordinary object whatever its * attributes, as scr_dyn_obj_set_field replaces values. */ void scr_dyn_obj_delete_field(ScrDyn *obj, const ScrStr *key); +/* The same for the own symbol-keyed property `symbol` of an ordinary + * object. */ +void scr_dyn_obj_delete_symbol_field(ScrDyn *obj, ScrDyn *symbol); /* This thread's global object box (borrowed). Every spelling of the global * object answers it; its symbol-keyed properties are the global object's * (scr_node_builtin.c). */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 2f8e4b6f8..bc14a8a1e 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2415,10 +2415,14 @@ double scr_dyn_class_view_integrity(const ScrDyn *view, double bits, ScrDyn *bag state |= SCR_CLASS_NOT_EXTENSIBLE; if (bag && bag->kind == SCR_DYN_OBJ) bag->non_extensible = true; bool sealed = true, frozen = true; - for (size_t i = 0; i < view->v.obj.len; i++) { - const ScrDynEntry *entry = &view->v.obj.entries[i]; - if (entry->configurable) sealed = false; - if (!entry->accessor && entry->writable) frozen = false; + /* The string-keyed entries, then the symbol-keyed ones. */ + for (const ScrDyn *table = view; table; table = table == view ? view->symbol_properties : NULL) { + if (table->kind != SCR_DYN_OBJ) continue; + for (size_t i = 0; i < table->v.obj.len; i++) { + const ScrDynEntry *entry = &table->v.obj.entries[i]; + if (entry->configurable) sealed = false; + if (!entry->accessor && entry->writable) frozen = false; + } } if (sealed) state |= SCR_CLASS_SEALED; if (sealed && frozen) state |= SCR_CLASS_FROZEN; @@ -7969,6 +7973,22 @@ bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key) { return s bool scr_dyn_has_own_computed(const ScrDyn *value, const ScrDyn *key) { return scr_dyn_key_probe_computed(value, key, 1); } bool scr_dyn_property_is_enumerable_computed(const ScrDyn *value, const ScrDyn *key) { return scr_dyn_key_probe_computed(value, key, 2); } +static void scr_json_delete_member(ScrDyn *object, const ScrStr *key); + +void scr_dyn_obj_delete_symbol_field(ScrDyn *obj, ScrDyn *symbol) { + if (obj->kind != SCR_DYN_OBJ || !obj->symbol_properties) return; + ScrStr *name = scr_dyn_symbol_storage_key(symbol); + scr_json_delete_member(obj->symbol_properties, name); + scr_str_release(name); + for (size_t i = 0; obj->symbol_keys && i < obj->symbol_keys->v.arr.len; i++) { + if (!scr_dyn_strict_eq(obj->symbol_keys->v.arr.items[i], symbol)) continue; + scr_dyn_release(obj->symbol_keys->v.arr.items[i]); + size_t remaining = --obj->symbol_keys->v.arr.len - i; + memmove(&obj->symbol_keys->v.arr.items[i], &obj->symbol_keys->v.arr.items[i + 1], remaining * sizeof(ScrDyn *)); + break; + } +} + void scr_dyn_key_delete_computed(ScrDyn *value, const ScrDyn *raw_key, bool strict) { ScrDyn *key = scr_dyn_property_key_value(raw_key); if (!key) return; @@ -16914,17 +16934,27 @@ void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits) { if (b & SCR_FIELD_NOT_CONFIGURABLE) entry->configurable = false; } -void scr_dyn_class_view_attributes(ScrDyn *view, double bits) { - unsigned b = (unsigned)bits; - if (b == 0 || view->kind != SCR_DYN_OBJ) return; - view->non_extensible = true; - for (size_t i = 0; i < view->v.obj.len; i++) { - ScrDynEntry *entry = &view->v.obj.entries[i]; +/* The integrity state `b` on the entries of `table`: a view, or the table + * of its symbol-keyed properties. */ +static void scr_dyn_class_table_attributes(ScrDyn *table, unsigned b) { + table->non_extensible = true; + for (size_t i = 0; i < table->v.obj.len; i++) { + ScrDynEntry *entry = &table->v.obj.entries[i]; if (b & (SCR_CLASS_SEALED | SCR_CLASS_FROZEN)) entry->configurable = false; if ((b & SCR_CLASS_FROZEN) && !entry->accessor) entry->writable = false; } } +void scr_dyn_class_view_attributes(ScrDyn *view, double bits) { + unsigned b = (unsigned)bits; + if (b == 0 || view->kind != SCR_DYN_OBJ) return; + scr_dyn_class_table_attributes(view, b); + /* Symbol-keyed fields, and symbol-keyed properties added to the + * instance, take the same state. */ + if (view->symbol_properties && view->symbol_properties->kind == SCR_DYN_OBJ) + scr_dyn_class_table_attributes(view->symbol_properties, b); +} + /* Object.defineProperty on a declared class field. The descriptor applies to * a one-property object holding the field's current value and attributes, * so validation and errors match ordinary objects. Answers that object (+1), diff --git a/tests/corpus/7407-frozen-class-instances.ts b/tests/corpus/7407-frozen-class-instances.ts index 0732b8c35..6a3a14c54 100644 --- a/tests/corpus/7407-frozen-class-instances.ts +++ b/tests/corpus/7407-frozen-class-instances.ts @@ -136,8 +136,46 @@ function added(): void { console.log((p as any).extra, JSON.stringify(p)); } +const tag = Symbol("tag"); + +class Tagged { + [tag] = 1; + name = "n"; + bump(): void { + this[tag] += 1; + } +} + +class Subtagged extends Tagged { + extra = 0; +} + +function symbols(): void { + const t = new Tagged(); + freezeUnknown(t); + console.log(integrity(t), Object.getOwnPropertyDescriptor(t, tag)?.writable); + try { t[tag] = 5; } catch (e) { report(e); } + try { t.bump(); } catch (e) { report(e); } + console.log(t[tag]); + + const s = new Subtagged(); + sealUnknown(s); + s[tag] = 3; + console.log(integrity(s), s[tag], Object.getOwnPropertyDescriptor(s, tag)?.configurable); + freezeUnknown(s); + try { s[tag] = 4; } catch (e) { report(e); } + console.log(integrity(s), s[tag]); + + const u = new Tagged(); + Object.freeze(u); + console.log(integrity(u)); + try { u[tag] = 9; } catch (e) { report(e); } + console.log(u[tag]); +} + frozen(); sealed(); notExtensible(); untouched(); added(); +symbols(); From 03e677a9c4d2d6af3ff71f30451ca94c5b5103b4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 03:14:24 -0500 Subject: [PATCH 050/395] Inherit the base constructor after a prototype's constructor is deleted A constructor read on a class instance through an untyped value answered the class itself whenever the class's prototype object did not hold an own `constructor`. After `delete Object.getPrototypeOf(new Middle()).constructor`, Node reads the property the base class's prototype holds, and past the last class's prototype Object.prototype's. The read now checks the instance's own property, then walks the class's prototypes: each answers its own `constructor`; a prototype without one answers its class unless reflection had populated it, in which case the program deleted the property and the read continues to the base class's prototype, and from the last one to Object.prototype. Reflection sets a per-class flag when the prototype initializes, so the answer does not depend on whether the reflection was compiled before the read. Corpus 8260 gains the deletion case. --- .../lowering/class-dynamic-dispatch.ts | 128 +++++++++--------- .../src/frontend/lowering/class-prototypes.ts | 23 ++++ .../src/frontend/lowering/lower-classes.ts | 4 + tests/corpus/8260-class-prototype-views.mjs | 21 +++ 4 files changed, 111 insertions(+), 65 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index d658fb20a..68e755592 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -52,6 +52,7 @@ import { import { classPrototypeData, hasClassPrototypeData, + prototypeReflectedFlag, observedPrototypeMethod, reflectClassPrototype, } from "./class-prototypes.js"; @@ -4105,34 +4106,13 @@ export class ClassDynamicDispatch { return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; } - /** `%dyn.class.bagHasOwn[OrPrototype]`: whether an instance's property bag - * holds `key` as its own property, or, with `prototype`, whether the bag's - * prototype object does. False before the instance has a bag. */ - private readBagHasOwn( - lowerer: Lowerer, - bag: IrExpr, - key: IrExpr, - prototype: boolean, - loc: SrcLoc, - ): IrExpr { - const name = prototype ? "%dyn.class.bagHasOwnOrPrototype" : "%dyn.class.bagHasOwn"; + /** `%dyn.class.bagHasOwn`: whether an instance's property bag holds `key` + * as its own property. False before the instance has a bag. */ + private readBagHasOwn(lowerer: Lowerer, bag: IrExpr, key: IrExpr, loc: SrcLoc): IrExpr { + const name = "%dyn.class.bagHasOwn"; let helper = lowerer.liftedFnNamed(name); if (!helper) { const value = varRef("p.0", DYN, loc); - const keyRef = varRef("p.1", STRING, loc); - const proto = varRef("proto", DYN, loc); - const hasOwn = (target: IrExpr): IrExpr => ({ - kind: "libCall", - fn: "dyn.hasOwn", - args: [target, keyRef], - type: BOOL, - loc, - }); - const returnFalse: IrStmt = { - kind: "return", - value: { kind: "boolLit", value: false, type: BOOL, loc }, - loc, - }; helper = { name, params: [ @@ -4143,47 +4123,28 @@ export class ClassDynamicDispatch { locals: [ { id: "p.0", name: "bag", type: DYN, mutable: false }, { id: "p.1", name: "key", type: STRING, mutable: false }, - ...(prototype ? [{ id: "proto", name: "proto", type: DYN, mutable: false }] : []), ], body: [ { kind: "if", cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc }, - then: [returnFalse], + then: [ + { kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }, + ], else_: null, loc, }, - ...(prototype - ? ([ - { - kind: "if", - cond: hasOwn(value), - then: [ - { - kind: "return", - value: { kind: "boolLit", value: true, type: BOOL, loc }, - loc, - }, - ], - else_: null, - loc, - }, - { - kind: "varDecl", - localId: "proto", - init: { kind: "libCall", fn: "dyn.getPrototype", args: [value], type: DYN, loc }, - loc, - }, - { - kind: "if", - cond: { kind: "dynTest", test: "nullish", value: proto, type: BOOL, loc }, - then: [returnFalse], - else_: null, - loc, - }, - { kind: "return", value: hasOwn(proto), loc }, - ] satisfies IrStmt[]) - : [{ kind: "return", value: hasOwn(value), loc } satisfies IrStmt]), + { + kind: "return", + value: { + kind: "libCall", + fn: "dyn.hasOwn", + args: [value, varRef("p.1", STRING, loc)], + type: BOOL, + loc, + }, + loc, + }, ], loc, }; @@ -4193,6 +4154,45 @@ export class ClassDynamicDispatch { return { kind: "call", callee: name, args: [bag, key], type: BOOL, loc }; } + /** The `constructor` an instance of `info` inherits when it has no own + * one. The class's prototype view answers its own `constructor` (a + * replacement, or the class once reflection installed it). A view + * without one answers the class itself unless reflection had populated it + * (prototypeReflectedFlag, set when the view initializes): then the + * program deleted the property, and the read continues to the base + * class's prototype and past the last one to Object.prototype, as in + * Node. */ + private inheritedConstructor( + lowerer: Lowerer, + info: ClassInfo, + key: IrExpr, + loc: SrcLoc, + ): IrExpr { + const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl!), DYN); + const prototype = hasClassPrototypeData(info) ? classPrototypeData(lowerer, info, loc) : null; + if (!prototype) return cls; + const base = info.base; + const next: IrExpr = + base && !base.builtinError && base.decl && !base.generic && !base.localClass + ? this.inheritedConstructor(lowerer, base, key, loc) + : { kind: "dynKeyGet", value: prototype, key, type: DYN, loc }; + return { + kind: "ternary", + cond: { kind: "libCall", fn: "dyn.hasOwn", args: [prototype, key], type: BOOL, loc }, + then: { kind: "dynKeyGet", value: prototype, key, type: DYN, loc }, + else_: { + kind: "ternary", + cond: varRef(prototypeReflectedFlag(lowerer, info), BOOL, loc), + then: next, + else_: cls, + type: DYN, + loc, + }, + type: DYN, + loc, + }; + } + /** The hints for which some boxed class declares a conversion method. A * string conversion calls toString first, so without a declared toString * or Symbol.toPrimitive it answers Object.prototype.toString's @@ -5951,15 +5951,13 @@ export class ClassDynamicDispatch { !info.classDecorators ) { this.constructors.set(info.def.name, info); - const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN); const bag = this.branchPropertyBag(lowerer, info, receiver, false, loc); const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; // `constructor` is an own property of every class prototype, so an - // ancestor's prototype never answers for an instance of this class: - // the instance's own property answers, then one this class's - // prototype object defines (a reflected prototype, or a write to - // `C.prototype.constructor`), and otherwise the class itself. - const answered = this.readBagHasOwn(lowerer, bag, key, hasClassPrototypeData(info), loc); + // ancestor's prototype answers for an instance of this class only + // after the program deletes the class's own: the instance's own + // property answers, then the class's prototype (inheritedConstructor). + const answered = this.readBagHasOwn(lowerer, bag, key, loc); return [ { kind: "return", @@ -5973,7 +5971,7 @@ export class ClassDynamicDispatch { type: DYN, loc, }, - else_: cls, + else_: this.inheritedConstructor(lowerer, info, key, loc), type: DYN, loc, }, diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index 595b64e05..d005e64d1 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -299,6 +299,23 @@ function declaresPrototypeMembers(decl: ts.ClassLikeDeclaration): boolean { ); } +/** The global that is true once the class's prototype view has been + * populated by reflection (reflectClassPrototype): the view then holds an + * own `constructor` unless the program deleted it. A view that was never + * reflected leaves `constructor` implicit. */ +export function prototypeReflectedFlag(lowerer: Lowerer, info: ClassInfo): string { + if (info.prototypeReflectedFlag) return info.prototypeReflectedFlag; + const id = `%g.%prototype.data.${info.def.name}.reflected`; + lowerer.globalsList.push({ + id, + name: `${info.def.jsName ?? info.def.name}.prototype.reflected`, + type: BOOL, + mutable: true, + }); + info.prototypeReflectedFlag = id; + return id; +} + /** Populate the shared prototype view before exposing it to reflection. * Methods and accessors are non-enumerable and retain declaration identity. */ export function reflectClassPrototype( @@ -399,6 +416,12 @@ export function reflectClassPrototype( { key: "value", value: classValueRef(lowerer, info, info.decl) }, writable, ]), + { + kind: "assign", + localId: prototypeReflectedFlag(lowerer, info), + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, ); const context = newFnCtx(false, null, null, DYN); lowerer.fnStack.push(context); diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index ac1864051..2394b728b 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -481,6 +481,10 @@ export interface ClassInfo { localPrototypeData?: true; prototypeMethodValues?: Map; prototypeReflectionReady?: true; + /** The global that records at run time that the prototype view was + * populated by reflection, so it holds its own `constructor` until the + * program deletes it (prototypeReflectedFlag). */ + prototypeReflectedFlag?: string; prototypeObservedMethodsReady?: true; /** The globals holding each observed method's original prototype value * (set when the prototype data is built), by method name. A typed call diff --git a/tests/corpus/8260-class-prototype-views.mjs b/tests/corpus/8260-class-prototype-views.mjs index e08b8f206..35e55f5f4 100644 --- a/tests/corpus/8260-class-prototype-views.mjs +++ b/tests/corpus/8260-class-prototype-views.mjs @@ -132,3 +132,24 @@ const areaValue = Shape.prototype.area; const disc = new Disc("d"); const rebuilt = disc.rebuild(); console.log(disc.constructor === Disc, rebuilt instanceof Disc, rebuilt.area(), rebuilt.name, typeof areaValue); + +// Deleting a reflected prototype's constructor: instances read the +// constructor their base class's prototype holds, then Object's; a +// replacement answers again. +class Root { + hi() { + return "hi"; + } +} +class Middle extends Root {} +class Tip extends Middle {} +const members = [new Middle(), new Tip(), new Root(), {}]; +const constructorNames = () => members.map((member) => member.constructor.name).join(","); +console.log(constructorNames(), Object.getOwnPropertyNames(Object.getPrototypeOf(new Middle())).join("|")); +delete Object.getPrototypeOf(new Middle()).constructor; +console.log(constructorNames(), Object.getOwnPropertyNames(Object.getPrototypeOf(new Middle())).join("|")); +console.log(new Middle().constructor === Root, new Tip().constructor === Tip); +delete Object.getPrototypeOf(new Root()).constructor; +console.log(constructorNames(), new Middle().constructor === Object); +Middle.prototype.constructor = function Named() {}; +console.log(constructorNames()); From 949c715f7bc52771d1f3978a721a20371ff88c3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 03:27:00 -0500 Subject: [PATCH 051/395] Keep comparator sorts and untyped-source copies out of units loops A units copy reads the receiver's UTF-16 units through the pointer its guard took, which stays valid only while nothing indexes another non-ASCII string: that evicts the receiver's index entry and frees the units. The classifier admitted every typed-array and Buffer method, so a loop that sorted a typed array with a comparator function value, or copied an untyped source into one (`ta.set(source)`, whose element reads can run getters), got a units copy, and a comparator or getter that indexed other strings made the loop read freed memory. The units copy now admits only the typed-array and Buffer methods that call no program code and read no checked-dynamic value or string. Corpus 7307 sorts with such a comparator and copies from such a source inside scans of a warmed non-ASCII text; the sanitized build of the previous commit reports a heap-use-after-free on it. --- .../backend/llvm/string-unit-versions.test.ts | 40 ++++++++++ .../src/backend/llvm/string-unit-versions.ts | 66 ++++++++++++++++- .../7307-string-unit-loop-evictions.ts.json | 6 ++ .../corpus/7307-string-unit-loop-evictions.ts | 73 +++++++++++++++++++ .../7307-string-unit-loop-evictions.ts.json | 1 + 5 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7307-string-unit-loop-evictions.ts.json create mode 100644 tests/corpus/7307-string-unit-loop-evictions.ts create mode 100644 tests/harness/corpus-costs/7307-string-unit-loop-evictions.ts.json diff --git a/packages/compiler/src/backend/llvm/string-unit-versions.test.ts b/packages/compiler/src/backend/llvm/string-unit-versions.test.ts index 5b0634b1a..e21803948 100644 --- a/packages/compiler/src/backend/llvm/string-unit-versions.test.ts +++ b/packages/compiler/src/backend/llvm/string-unit-versions.test.ts @@ -84,3 +84,43 @@ console.log(words("ab c"), pairs("ab", "cd"), both("ab", "cd"), concatenates("a expect(unitSafeLoop(loopOf(mod, "both"), param("both"), safe)).toBeNull(); expect(unitSafeLoop(loopOf(mod, "concatenates"), param("concatenates"), safe)).toBeNull(); }); + +test("loops that sort with a comparator or copy an untyped source get no units copy", async () => { + // Either can run program code that indexes other strings, which evicts + // the receiver's units while the loop reads them. + const mod = await lower(` +function sorts(s: string, values: Float64Array, compare: (a: number, b: number) => number): number { + let n = 0; + for (let i = 0; i < s.length; i++) { + n += s.charCodeAt(i); + if (i === 3) values.sort(compare); + } + return n; +} +function copies(s: string, values: Float64Array, source: any): number { + let n = 0; + for (let i = 0; i < s.length; i++) { + n += s.charCodeAt(i); + if (i === 3) values.set(source); + } + return n; +} +function sortsNumbers(s: string, values: Float64Array): number { + let n = 0; + for (let i = 0; i < s.length; i++) { + n += s.charCodeAt(i); + if (i === 3) values.sort(); + } + return n; +} +const values = new Float64Array([2, 1]); +console.log(sorts("abcdef", values, (a, b) => a - b), copies("abcdef", values, [4, 3]), sortsNumbers("abcdef", values)); +`); + const safe = unitSafeFunctions(mod); + const param = (fn: string): string => + mod.functions.find((f) => f.name === fn)!.params[0]!.localId; + expect(unitSafeLoop(loopOf(mod, "sorts"), param("sorts"), safe)).toBeNull(); + expect(unitSafeLoop(loopOf(mod, "copies"), param("copies"), safe)).toBeNull(); + // A numeric sort calls no program code. + expect(unitSafeLoop(loopOf(mod, "sortsNumbers"), param("sortsNumbers"), safe)).not.toBeNull(); +}); diff --git a/packages/compiler/src/backend/llvm/string-unit-versions.ts b/packages/compiler/src/backend/llvm/string-unit-versions.ts index 022c3a513..368b8eae8 100644 --- a/packages/compiler/src/backend/llvm/string-unit-versions.ts +++ b/packages/compiler/src/backend/llvm/string-unit-versions.ts @@ -1,4 +1,4 @@ -import type { IrExpr, IrModule, IrStmt } from "../../ir/ir.js"; +import type { IrBytesIntrinsicMethod, IrExpr, IrModule, IrStmt } from "../../ir/ir.js"; import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; /* Loop versions over the UTF-16 units of a string that is not ASCII. @@ -15,6 +15,68 @@ import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; const ARRAY_METHODS = new Set(["push", "pop", "length"]); +/* The typed-array and Buffer methods a units copy may run: those that + * call no program code and read no checked-dynamic value or string. A + * comparator sort (sortCompare) calls back into the program, and a copy + * from a checked-dynamic source (setFromDyn) reads its elements through + * getters; either can index another string, which evicts the receiver's + * units while the loop reads them. */ +const UNIT_SAFE_BYTES_METHODS: ReadonlySet = new Set([ + "buffer", + "length", + "byteLength", + "byteOffset", + "get", + "slice", + "subarray", + "copyWithin", + "sortNumeric", + "toReversed", + "with", + "toArray", + "setFrom", + "readNum", + "writeNum", + "readNumVar", + "writeNumVar", + "equals", + "compareBuf", + "indexOfNum", + "lastIndexOfNum", + "includesNum", + "indexOfElem", + "lastIndexOfElem", + "includesElem", + "fill", + "fillNum", + "fillElem", + "copy", + "swap16", + "swap32", + "swap64", + "dataViewNew", + "dvGetUint8", + "dvGetInt8", + "dvGetUint16", + "dvGetInt16", + "dvGetUint32", + "dvGetInt32", + "dvGetFloat16", + "dvGetFloat32", + "dvGetFloat64", + "dvGetBigUint64Number", + "dvGetBigInt64Number", + "dvSetUint8", + "dvSetInt8", + "dvSetUint16", + "dvSetInt16", + "dvSetUint32", + "dvSetInt32", + "dvSetFloat16", + "dvSetFloat32", + "dvSetFloat64", +]); + /** Whether `e` (or statement `s`) uses only operations that index no * string: numbers, booleans, locals, records and class fields, typed * arrays, number arrays and Math. `reads` admits the string reads a @@ -39,7 +101,9 @@ function unitSafe( case "incDec": case "recordGet": case "fieldGet": + break; case "bytesIntrinsic": + if (!UNIT_SAFE_BYTES_METHODS.has(e.method)) return false; break; case "arrayGet": if (e.type.kind !== "f64" && e.type.kind !== "bool") return false; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7307-string-unit-loop-evictions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7307-string-unit-loop-evictions.ts.json new file mode 100644 index 000000000..cb5864986 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7307-string-unit-loop-evictions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7307-string-unit-loop-evictions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7307-string-unit-loop-evictions.ts b/tests/corpus/7307-string-unit-loop-evictions.ts new file mode 100644 index 000000000..94e98e5b4 --- /dev/null +++ b/tests/corpus/7307-string-unit-loop-evictions.ts @@ -0,0 +1,73 @@ +// A units copy must not run program code that indexes other strings: a +// comparator sort or a copy from an untyped source can evict the scanned +// text's UTF-16 units while the loop reads them. + +function bigText(seed: number): string { + let s = ""; + for (let i = 0; i < 9000; i++) s += String.fromCharCode(0xe9 + ((i * seed) % 7)) + "ab "; + return s; +} + +const others = [bigText(3), bigText(5), bigText(11), bigText(13), bigText(17)]; + +let touched = 0; +function touchOthers(): void { + for (let k = 0; k < others.length; k++) { + const o = others[k]; + for (let j = 0; j < 80; j++) touched += o.charCodeAt((j * 997 + k) % o.length); + } +} + +function spaces(s: string): number { + let n = 0; + for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++; + return n; +} + +function sortedWhileScanning( + s: string, + values: Float64Array, + compare: (a: number, b: number) => number, +): number { + let sum = 0; + for (let i = 0; i < s.length; i++) { + sum += s.charCodeAt(i); + if (i % 4096 === 100) values.sort(compare); + } + return sum; +} + +function copiedWhileScanning(s: string, values: Float64Array, source: any): number { + let sum = 0; + for (let i = 0; i < s.length; i++) { + sum += s.charCodeAt(i); + if (i % 4096 === 100) values.set(source); + } + return sum; +} + +const text = bigText(7); +// Repeated scans make the runtime hold the text's units. +let warm = 0; +for (let r = 0; r < 4; r++) warm += spaces(text); +console.log("warm", warm); + +const values = new Float64Array([3, 1, 2]); +const byValue = (a: number, b: number): number => { + touchOthers(); + return a - b; +}; +console.log("sorted", sortedWhileScanning(text, values, byValue), values.join(",")); +for (let r = 0; r < 2; r++) warm += spaces(text); + +const source: any = { + length: 3, + get 0() { + touchOthers(); + return 9; + }, + 1: 8, + 2: 7, +}; +console.log("copied", copiedWhileScanning(text, values, source), values.join(",")); +console.log("touched", touched > 0, spaces(text)); diff --git a/tests/harness/corpus-costs/7307-string-unit-loop-evictions.ts.json b/tests/harness/corpus-costs/7307-string-unit-loop-evictions.ts.json new file mode 100644 index 000000000..bb380e241 --- /dev/null +++ b/tests/harness/corpus-costs/7307-string-unit-loop-evictions.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3000 } From 4581dd2f123d917ddba44829c0699441acbe8047 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 04:05:54 -0500 Subject: [PATCH 052/395] Keep a Date boxed when a toJSON argument calls its setter A Date local kept as its time value was read as toJSON's receiver before the arguments ran, so a setter in an argument (d.toJSON(d.setTime(0))) did not change the result. Such a local now keeps its Date object, and toJSON reads it after its arguments, as in Node. --- .../src/frontend/lowering/date-values.ts | 6 +++++- tests/corpus/8027-date-setter-order.ts | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/date-values.ts b/packages/compiler/src/frontend/lowering/date-values.ts index f769e0866..8931d0f80 100644 --- a/packages/compiler/src/frontend/lowering/date-values.ts +++ b/packages/compiler/src/frontend/lowering/date-values.ts @@ -43,7 +43,8 @@ const RELATIONAL = new Set([ /** True when a local `const` (or a `let` that is never assigned again) * initialized with `new Date(...)` can keep the Date's time value instead * of a Date object: no use observes the Date's identity. Every use reads - * the time value as a Date method receiver (a setter replaces it), a + * the time value as a Date method receiver (a setter replaces it; the + * arguments of a method that reads it call no setter on it), a * String(), Number() or template operand, a unary + or - operand, a * relational or string `+` operand, or `new Date(d)`'s argument, inside * the declaring function, or inside the block or loop that scopes a @@ -194,6 +195,9 @@ function timeValueUse( ) return null; if (isDateSetter(parent.name.text)) return "set"; + // The method reads the time value after its arguments are evaluated: + // a setter there (`d.toJSON(d.setTime(0))`) changes what it reads. + if (call.arguments.nodes.some((arg) => callsSetterOf(lowerer, arg, symbol))) return null; return TIME_VALUE_METHODS.has(parent.name.text) ? "read" : null; } return readsTimeValue(lowerer, node, parent, symbol) ? "read" : null; diff --git a/tests/corpus/8027-date-setter-order.ts b/tests/corpus/8027-date-setter-order.ts index d0a175c2f..b87f3a656 100644 --- a/tests/corpus/8027-date-setter-order.ts +++ b/tests/corpus/8027-date-setter-order.ts @@ -1,7 +1,8 @@ // Date setters and evaluation order: a setter called without arguments // makes the Date invalid, an argument that calls setTime on the same -// Date subclass instance is seen by the setter, and a comparison or -// concatenation reads its left Date after the right side runs a setter. +// Date subclass instance is seen by the setter, a comparison or +// concatenation reads its left Date after the right side runs a setter, +// and toJSON reads its Date after an argument runs a setter. export {}; process.env.TZ = "UTC"; @@ -51,3 +52,15 @@ const s = new Date(Date.UTC(2026, 0, 1)); console.log(s + (s.setUTCFullYear(1999), "!")); const n = new Date(Date.UTC(2026, 0, 1)); console.log(+n === (n.setTime(5), 5), +n); + +// Locals a function keeps as their time value: a method reads the time +// value after its arguments run a setter. +function locals(): void { + const json = new Date(Date.UTC(2026, 0, 1)); + console.log(json.toJSON(json.setUTCFullYear(1999)), json.toJSON()); + const key = new Date(Date.UTC(2026, 0, 1)); + console.log(key.toJSON(String(key.setTime(Date.UTC(2021, 5, 1))))); + const read = new Date(Date.UTC(2026, 0, 1)); + console.log(read.toJSON(), read.getTime()); +} +locals(); From 1d1fccccd59249a22cabcb04bae88979879b7a06 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 05:56:14 -0500 Subject: [PATCH 053/395] Compare identical references and accept any message in the test262 adapter assert.sameValue, assert.notSameValue and assert.compareArray elements treat identical references as the same value and a reference as different from every primitive. Only two distinct references still refuse, because a conversion at the native boundary can copy a reference but never merge two. Assertion messages may be any value and convert with String, as in the upstream helpers. --- tests/harness/test262.test.ts | 57 +++++++++++++++++++++--- tests/test262/README.md | 2 +- tests/test262/harness.ts | 84 ++++++++++++++++++++--------------- 3 files changed, 101 insertions(+), 42 deletions(-) diff --git a/tests/harness/test262.test.ts b/tests/harness/test262.test.ts index f1b616578..fd35a08fb 100644 --- a/tests/harness/test262.test.ts +++ b/tests/harness/test262.test.ts @@ -237,6 +237,47 @@ assert.notSameValue(1, "1"); status: "fail", source: "assert.throws(TypeError, () => { throw 1; });", }, + { + name: "identical references", + status: "pass", + source: ` +const object = {}; +const list = [1]; +const fn = function () {}; +assert.sameValue(object, object); +assert.sameValue(list, list); +assert.sameValue(fn, fn); +assert.compareArray([object, fn], [object, fn]); +`, + }, + { + name: "a reference is not a primitive", + status: "pass", + source: + "assert.notSameValue({}, 1); assert.notSameValue([], undefined); assert.notSameValue(null, {});", + }, + { name: "reference and primitive mismatch", status: "fail", source: "assert.sameValue([], 0);" }, + { + name: "identical references are not different", + status: "fail", + source: "const object = {}; assert.notSameValue(object, object);", + }, + { + name: "array element reference and primitive mismatch", + status: "fail", + source: "assert.compareArray([{}], [1]);", + }, + { + name: "non-string messages", + status: "pass", + source: + "assert.sameValue(1, 1, false); assert.notSameValue(1, 2, 0); assert(true, null); assert.compareArray([], [], 1);", + }, + { + name: "failure with a non-string message", + status: "fail", + source: "assert.sameValue(1, 2, false);", + }, ]; describe(`Test262 host assertion contract${shardSuffix()}`, () => { @@ -259,8 +300,8 @@ describe(`Test262 host assertion contract${shardSuffix()}`, () => { ["{}", "[1]", "function () {}"], (value) => `reference:${value}`, )) { - test(`reference assertions are explicitly refused: ${value}`, async () => { - const source = `const value = ${value}; try { assert.sameValue(value, value); } catch { }`; + test(`distinct reference assertions are explicitly refused: ${value}`, async () => { + const source = `try { assert.sameValue(${value}, ${value}); } catch { } try { assert.notSameValue(${value}, ${value}); } catch { }`; expect(() => runUpstream(source)).not.toThrow(); const result = await runSource(source, { sanitize }); expect(result, JSON.stringify(result)).toMatchObject({ @@ -270,8 +311,8 @@ describe(`Test262 host assertion contract${shardSuffix()}`, () => { }); } - test("array element identity assertions are refused", async () => { - const source = "const value = {}; try { assert.compareArray([value], [value]); } catch { }"; + test("distinct array element references are refused", async () => { + const source = "try { assert.compareArray([{}], [{}]); } catch { }"; expect(() => runUpstream(source)).not.toThrow(); const result = await runSource(source, { sanitize }); expect(result, JSON.stringify(result)).toMatchObject({ @@ -359,9 +400,15 @@ const propertyControls = shardSelect( }, { name: "reference value", - status: "harness-refusal", + status: "pass", source: "const f = function() {}; const o = { a: f }; verifyProperty(o, 'a', { value: f });", }, + { + name: "distinct reference value", + status: "harness-refusal", + source: + "const o = { a: function() {} }; try { verifyProperty(o, 'a', { value: function() {} }); } catch { }", + }, ], (item) => `property:${item.name}`, ); diff --git a/tests/test262/README.md b/tests/test262/README.md index 2f8a0ed3e..5474fa2c3 100644 --- a/tests/test262/README.md +++ b/tests/test262/README.md @@ -32,7 +32,7 @@ The current profile is `static-adapted-v6`. It adapts strict scripts to standalo The unmodified upstream assertion helper currently reaches scriptc's JavaScript function-expando refusal. Test262 explicitly permits host implementations of harness functions in its [interpretation rules](https://github.com/tc39/test262/blob/7ab7fafa0003f73fc85c1b95d88094d33f7eb8bd/INTERPRETING.md#host-defined-functions). `harness.ts` supplies `assert`, `assert.sameValue`, `assert.notSameValue`, `assert.compareArray`, `assert.throws` for selected Error constructors, `$DONE`, and `Test262Error`. Scalar SameValue and scalar array-element comparisons use the statically compiled `node:assert/strict` implementation. Contract tests compare successful and failing assertions with the original Test262 helpers under Node, including NaN, signed zero, null/undefined, differing primitive types, and premature termination. -Reference-equality assertions terminate with a distinct harness-refusal result: conversion at a native function boundary can copy references, so this adapter cannot faithfully compare every object, array, or function identity. Tests cannot catch this refusal and accidentally pass. Sparse array comparisons are not yet handled faithfully across that boundary. The adapted `propertyHelper.js` admits direct `verifyProperty` calls with an inline descriptor literal or `undefined`; reference-valued comparisons still refuse, while other property helpers and descriptor expressions remain excluded. The adapted `testTypedArray.js` keeps the upstream constructor lists, argument factories and `testWith*` helpers. It omits the `TypedArray` (%TypedArray%) binding, so tests that name it are excluded. Its resizable and immutable buffer factories run only where `ArrayBuffer.prototype` provides `resize` and `transferToImmutable`, as the upstream feature checks intend. Contract tests compare its successful and failing uses with the upstream helper under Node. Assertion aliases, mutation, reflection, additional assertion methods, and other `includes` helpers remain excluded. +Conversion at a native function boundary can copy references, so one object, array, or function may reach the adapter as two distinct references. A copy cannot merge two references into one. `assert.sameValue`, `assert.notSameValue`, and `assert.compareArray` elements therefore treat identical references as the same value and a reference as different from every primitive. Two distinct references terminate with a distinct harness-refusal result, which tests cannot catch and turn into a pass. Assertion messages may be any value; the adapter converts them with `String`, as the upstream helpers do. Sparse array comparisons are not yet handled faithfully across that boundary. The adapted `propertyHelper.js` admits direct `verifyProperty` calls with an inline descriptor literal or `undefined`; reference-valued comparisons follow the same identity rule, while other property helpers and descriptor expressions remain excluded. The adapted `testTypedArray.js` keeps the upstream constructor lists, argument factories and `testWith*` helpers. It omits the `TypedArray` (%TypedArray%) binding, so tests that name it are excluded. Its resizable and immutable buffer factories run only where `ArrayBuffer.prototype` provides `resize` and `transferToImmutable`, as the upstream feature checks intend. Contract tests compare its successful and failing uses with the upstream helper under Node. Assertion aliases, mutation, reflection, additional assertion methods, and other `includes` helpers remain excluded. Raw tests, module variants, agents, resolution negatives, and runtime negatives remain excluded. Parse negatives pass only when a source-matched syntax or vetted JavaScript early-error diagnostic appears; TypeScript type errors and harness-only failures never satisfy a negative expectation. `$262`, dynamic evaluation, observable script-global state, and other unsupported host requirements are excluded as well. The runtime and compiler continue to enforce their existing refusal boundaries for admitted source. diff --git a/tests/test262/harness.ts b/tests/test262/harness.ts index c44d32869..b478b4555 100644 --- a/tests/test262/harness.ts +++ b/tests/test262/harness.ts @@ -17,68 +17,80 @@ export function $DONE(error?: unknown): void { console.log("__scriptc_test262_complete__"); } -function scalar(value: unknown): void { - // Native-to-unknown conversion can copy references (notably arrays). - // Refuse these assertions before comparing altered identities. Terminate +function reference(value: unknown): boolean { + return (typeof value === "object" && value !== null) || typeof value === "function"; +} + +function refuse(): never { + // Native-to-unknown conversion can copy references (notably arrays), so + // two distinct references may be one value in the program. Terminate // instead of throwing so a test's catch block cannot turn this into a pass. - if ((typeof value === "object" && value !== null) || typeof value === "function") { - console.error("SCRIPTC_TEST262_HARNESS: reference equality requires an identity-preserving adapter"); - process.exit(86); + console.error("SCRIPTC_TEST262_HARNESS: reference equality requires an identity-preserving adapter"); + process.exit(86); +} + +function scalar(value: unknown): void { + if (reference(value)) refuse(); +} + +/** The upstream helpers accept any message value and convert it to a string. */ +function text(message: unknown, fallback: string): string { + return message === undefined ? fallback : String(message); +} + +/** SameValue of two operands. A copy made at the conversion boundary can only + * separate one reference into two, so identical references are the same value + * and a reference is never the same value as a primitive. Two distinct + * references refuse. */ +function same(actual: unknown, expected: unknown): boolean { + if (reference(actual) || reference(expected)) { + if (actual === expected) return true; + if (reference(actual) && reference(expected)) refuse(); + return false; + } + try { + nodeAssert.strictEqual(actual, expected); + return true; + } catch { + return false; } } -export function assert(value: unknown, message = "Expected true"): void { - if (value !== true) throw new Test262Error(message); +export function assert(value: unknown, message?: unknown): void { + if (value !== true) throw new Test262Error(text(message, "Expected true")); } export namespace assert { - export function sameValue(actual: unknown, expected: unknown, message = "Expected SameValue"): void { - scalar(actual); - scalar(expected); - try { - nodeAssert.strictEqual(actual, expected); - } catch { - throw new Test262Error(message); - } + export function sameValue(actual: unknown, expected: unknown, message?: unknown): void { + if (!same(actual, expected)) throw new Test262Error(text(message, "Expected SameValue")); } - export function notSameValue(actual: unknown, expected: unknown, message = "Expected different values"): void { - scalar(actual); - scalar(expected); - try { - nodeAssert.notStrictEqual(actual, expected); - } catch { - throw new Test262Error(message); - } + export function notSameValue(actual: unknown, expected: unknown, message?: unknown): void { + if (same(actual, expected)) throw new Test262Error(text(message, "Expected different values")); } - export function throws(expectedErrorConstructor: unknown, func: () => unknown, message = "Expected exception"): void { + export function throws(expectedErrorConstructor: unknown, func: () => unknown, message?: unknown): void { const outcome = throwsOutcome(expectedErrorConstructor, func); if (outcome === "invalid") throw new Test262Error("assert.throws requires a function to run"); if (outcome === "unsupported") { console.error("SCRIPTC_TEST262_HARNESS: assert.throws needs an Error instance"); process.exit(86); } - if (outcome !== "pass") throw new Test262Error(message); + if (outcome !== "pass") throw new Test262Error(text(message, "Expected exception")); } - export function compareArray(actual: unknown, expected: unknown, message = "Expected matching array contents"): void { + export function compareArray(actual: unknown, expected: unknown, message?: unknown): void { + const failure = text(message, "Expected matching array contents"); if (!Array.isArray(actual) || !Array.isArray(expected)) { scalar(actual); scalar(expected); - throw new Test262Error(message); + throw new Test262Error(failure); } - if (actual.length !== expected.length) throw new Test262Error(message); + if (actual.length !== expected.length) throw new Test262Error(failure); for (let i = 0; i < actual.length; i++) { const left: unknown = i in actual ? actual[i] : undefined; const right: unknown = i in expected ? expected[i] : undefined; - scalar(left); - scalar(right); - try { - nodeAssert.strictEqual(left, right); - } catch { - throw new Test262Error(message); - } + if (!same(left, right)) throw new Test262Error(failure); } } } From 4c0572c49e9fa7d72a7fa24ac28d6f24c157def1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 06:25:57 -0500 Subject: [PATCH 054/395] Decode an undefined TextDecoder input as no bytes In Node, `decoder.decode(undefined)` returns "" like `decode()`, and flushes bytes pending from streamed input. scriptc refused a statically undefined argument and an argument typed `Uint8Array | undefined` with SC2020, and `decode(x!)` on an undefined value threw a TypeError. The decode input lowering now evaluates a statically undefined argument for its effects and decodes an empty Uint8Array, and reads the undefined arm of `Uint8Array | undefined` as an empty Uint8Array, in both the decoder-value and the `new TextDecoder(label).decode(...)` paths. A non-null assertion passed to decode keeps the undefined it removes, instead of the checked narrowing other optional standard library parameters keep. Inputs that may be null are refused as before (`decode(null)` throws a TypeError in Node). --- .../frontend/lowering/builtins/text-codecs.ts | 26 ++++++++++++++-- .../src/frontend/lowering/lower-exprs.ts | 15 +++++++++ .../text-decoder-undefined-input.ts.json | 6 ++++ ...-non-null-assertion-nullish-destination.ts | 4 +-- tests/corpus/text-decoder-undefined-input.ts | 31 +++++++++++++++++++ .../text-decoder-undefined-input.ts.json | 1 + 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/text-decoder-undefined-input.ts.json create mode 100644 tests/corpus/text-decoder-undefined-input.ts create mode 100644 tests/harness/corpus-costs/text-decoder-undefined-input.ts.json diff --git a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts index 8d47c8e3a..34cc7aa14 100644 --- a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts +++ b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts @@ -16,6 +16,7 @@ import { type IrType, STRING, type SrcLoc, + typeEquals, } from "../../../ir/ir.js"; import { lowerBuiltinValuePreservingUndefined, @@ -24,6 +25,27 @@ import { } from "./arguments.js"; import { type BuiltinModuleFn } from "../surfaces.js"; +/** The input of `decoder.decode(input)`. An undefined input decodes no bytes, + * as `decode()` does in Node: a statically undefined argument (after its + * effects) and the undefined arm of `Uint8Array | undefined` read as an + * empty Uint8Array. */ +function lowerDecodeInput(lowerer: Lowerer, node: ts.Expression, loc: SrcLoc): IrExpr { + const empty: IrExpr = { kind: "bytesNew", source: null, type: BYTES_U8, loc }; + const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node); + if (undefinedArg) return defaultAfterUndefined(undefinedArg, empty); + const value = lowerer.lowerExpr(node); + if (value.type.kind === "union") { + const def = lowerer.unions.get(value.type.unionId); + if ( + def?.arms.length === 2 && + def.arms.some((arm) => arm.kind === "undefinedT") && + def.arms.some((arm) => typeEquals(arm, BYTES_U8)) + ) + return { kind: "nullish", left: value, right: empty, type: BYTES_U8, loc }; + } + return value; +} + /** StringDecoder writes preserve pending bytes; end(input) performs the * final write and flushes the state. String input passes through unchanged. */ export function lowerStringDecoderMethodCall( @@ -413,7 +435,7 @@ function lowerStoredTextCodecCall( : { kind: "bytesNew", source: null, type: BYTES_U8, loc } : cls === "TextEncoder" ? lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING) - : lowerer.lowerExpr(call.arguments.nodes[0]!); + : lowerDecodeInput(lowerer, call.arguments.nodes[0]!, loc); if ( cls === "TextDecoder" && (arg.type.kind === "dyn" || (arg.type.kind === "bytes" && arg.type.elem !== "u8")) @@ -622,7 +644,7 @@ export function lowerTextCodecCall( ); } const argNode = call.arguments.nodes[0]!; - let arg = lowerer.lowerExpr(argNode); + let arg = lowerDecodeInput(lowerer, argNode, loc); if (arg.type.kind === "dyn" || (arg.type.kind === "bytes" && arg.type.elem !== "u8")) { arg = { kind: "libCall", diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 033f482d1..8d9b9d410 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -975,6 +975,18 @@ function assertionDestinationHoldsNullish( return constituentTypes(contextual).some((type) => (type.flags & nullish) !== 0); } +/** Whether `declaration` is TextDecoder's `decode` method. */ +function isTextDecoderDecode(declaration: ts.Node): boolean { + if (!ts.isMethodSignatureDeclaration(declaration) && !ts.isMethodDeclaration(declaration)) + return false; + if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "decode") return false; + const owner = declaration.parent; + return ( + (ts.isInterfaceDeclaration(owner) || ts.isClassDeclaration(owner)) && + owner.name?.text === "TextDecoder" + ); +} + /** Whether `argument` of `call` binds an optional parameter of a standard * library declaration (`decoder.decode(input?)`). The undefined such a * parameter's type holds comes from its optionality, and the builtin @@ -989,6 +1001,9 @@ function optionalStdlibParameter( const declaration = lowerer.checker.getResolvedSignature(call)?.declaration; if (declaration === undefined || !lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; + // TextDecoder.decode reads an undefined input as no bytes, as Node does + // (builtins/text-codecs.ts lowerDecodeInput). + if (isTextDecoderDecode(declaration)) return false; const index = call.arguments?.nodes.indexOf(argument) ?? -1; const parameter = declaration.parameters?.nodes[index]; return ( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/text-decoder-undefined-input.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/text-decoder-undefined-input.ts.json new file mode 100644 index 000000000..a1f5f49d9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/text-decoder-undefined-input.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/text-decoder-undefined-input.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7189-non-null-assertion-nullish-destination.ts b/tests/corpus/7189-non-null-assertion-nullish-destination.ts index 632655cff..4cd7e291f 100644 --- a/tests/corpus/7189-non-null-assertion-nullish-destination.ts +++ b/tests/corpus/7189-non-null-assertion-nullish-destination.ts @@ -46,8 +46,8 @@ console.log(String(names.get("a")!), String(names.get("b")!)); console.log(Number(amounts.get("a")!), Number(amounts.get("b")!)); console.log(kindOf(names.get("a")!), kindOf(amounts.get("a")!), kindOf(names.get("b")!)); -// An optional parameter of a standard library method keeps the checked -// narrowing: `decode(input?)` reads the asserted Uint8Array. +// TextDecoder.decode reads the asserted Uint8Array; an undefined input +// decodes no bytes (text-decoder-undefined-input.ts). const chunks = new Map([["a", new TextEncoder().encode("chunk")]]); interface Read { done: boolean; diff --git a/tests/corpus/text-decoder-undefined-input.ts b/tests/corpus/text-decoder-undefined-input.ts new file mode 100644 index 000000000..6a3be354c --- /dev/null +++ b/tests/corpus/text-decoder-undefined-input.ts @@ -0,0 +1,31 @@ +// TextDecoder.decode(undefined) decodes no bytes, as decode() does: it +// returns "" and, after streamed input, flushes the pending bytes. +const decoder = new TextDecoder(); +const encoded = new TextEncoder().encode("bytes"); +const inputs = new Map([ + ["present", encoded], + ["absent", undefined], +]); + +console.log(JSON.stringify(decoder.decode(undefined))); +console.log(JSON.stringify(decoder.decode(inputs.get("present"))), JSON.stringify(decoder.decode(inputs.get("absent")))); +console.log(JSON.stringify(decoder.decode(inputs.get("absent")!)), JSON.stringify(decoder.decode(inputs.get("missing")!))); +console.log(JSON.stringify(new TextDecoder("utf-8").decode(undefined))); +console.log(JSON.stringify(new TextDecoder("utf-8").decode(inputs.get("absent")))); + +let calls = 0; +function nothing(): undefined { + calls++; + return undefined; +} +console.log(JSON.stringify(decoder.decode(nothing())), calls); + +interface Chunk { + done: boolean; + value?: Uint8Array; +} +const chunks: Chunk[] = [{ done: false, value: new Uint8Array([0xe2, 0x82]) }, { done: true }]; +const streaming = new TextDecoder(); +let text = ""; +for (const chunk of chunks) text += streaming.decode(chunk.value, { stream: !chunk.done }); +console.log(JSON.stringify(text), text.length); diff --git a/tests/harness/corpus-costs/text-decoder-undefined-input.ts.json b/tests/harness/corpus-costs/text-decoder-undefined-input.ts.json new file mode 100644 index 000000000..b156827d6 --- /dev/null +++ b/tests/harness/corpus-costs/text-decoder-undefined-input.ts.json @@ -0,0 +1 @@ +{ "plain": 1937, "san": 2233 } From b6050f96bd04ea373b5ea18f4f8c3581a19d5c83 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 06:40:00 -0500 Subject: [PATCH 055/395] Read the runtime ABI marker in the native toolchain fixture from the compiler - The native toolchain test's fixture runtime pack hard-coded ABI v37, so with this branch's marker (v40) the toolchain refused the pack and the test saw exit status 1. It now takes RUNTIME_ABI_VERSION and RUNTIME_ABI_MARKER from runtime-abi.ts, as the native driver test does. --- tests/harness/self-hosting-native-toolchain.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c5476328e..4b4af5ac6 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -9,6 +9,10 @@ import { nativeCodegenTarget } from "../../packages/compiler/src/backend/targets import type { NativeLinkFeatures } from "../../packages/compiler/src/backend/native-link-info.js"; import type { RuntimePackManifest } from "../../packages/compiler/src/backend/runtime-pack-core.js"; import type { NativeToolchainManifest } from "../../packages/compiler/src/native/toolchain.js"; +import { + RUNTIME_ABI_MARKER, + RUNTIME_ABI_VERSION, +} from "../../packages/compiler/src/backend/runtime-abi.js"; const root = join(import.meta.dirname, "../.."); const entry = join(root, "tests/fixtures/self-hosting/native-toolchain.ts"); @@ -109,7 +113,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: RUNTIME_ABI_VERSION, marker: RUNTIME_ABI_MARKER }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From 854f3ae8103d5573f9c39e829f850b8a7f76661f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 06:41:00 -0500 Subject: [PATCH 056/395] Construct readline.Interface and builtin classes bound through require() members in JavaScript --- .../lowering/builtins/module-bindings.ts | 26 ++++++++++ .../frontend/lowering/builtins/readline.ts | 36 ++++++++----- .../src/frontend/lowering/lower-classes.ts | 16 ++++-- packages/compiler/src/frontend/tsc-codes.ts | 6 +++ ...30-readline-interface-constructor.cjs.json | 6 +++ .../8231-builtin-member-binding-new.cjs.json | 6 +++ .../8230-readline-interface-constructor.cjs | 51 +++++++++++++++++++ .../8231-builtin-member-binding-new.cjs | 35 +++++++++++++ ...30-readline-interface-constructor.cjs.json | 1 + .../8231-builtin-member-binding-new.cjs.json | 1 + 10 files changed, 167 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-readline-interface-constructor.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-builtin-member-binding-new.cjs.json create mode 100644 tests/corpus/8230-readline-interface-constructor.cjs create mode 100644 tests/corpus/8231-builtin-member-binding-new.cjs create mode 100644 tests/harness/corpus-costs/8230-readline-interface-constructor.cjs.json create mode 100644 tests/harness/corpus-costs/8231-builtin-member-binding-new.cjs.json diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 5465aeafa..4ded700fe 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -185,6 +185,32 @@ export function builtinMemberRequireDecl( return spec !== null && canonicalBuiltinModule(spec) !== null; } +/** The member's own symbol for a variable bound to a builtin module's + * member (`const SD = require("string_decoder").StringDecoder`, + * builtinMemberRequireDecl) when the member is a class, so the variable + * resolves as a named import of the class does. The symbol itself + * otherwise. */ +export function builtinMemberBindingTarget( + lowerer: Lowerer, + symbol: ts.Symbol | null, +): ts.Symbol | null { + const decl = symbol?.valueDeclaration; + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + !builtinMemberRequireDecl(decl.name, decl.initializer) + ) + return symbol; + const init = decl.initializer as ts.PropertyAccessExpression; + let member = lowerer.checker.getSymbolAtLocation(init.name); + if (member && member.flags & ts.SymbolFlags.Alias) + member = lowerer.checker.getAliasedSymbol(member); + // Only a class member: `require("events").EventEmitter` is a static + // property of the exported class, which builtinEmitterInfoOf follows + // from the variable itself. + return member?.declarations.some(ts.isClassDeclaration) ? member : symbol; +} + /** The supported builtin module when `decl` destructures a builtin * NAMESPACE binding (`const crypto = require('crypto'); const { * createSign, sign: mySign } = crypto;` — test/common/crypto.js's diff --git a/packages/compiler/src/frontend/lowering/builtins/readline.ts b/packages/compiler/src/frontend/lowering/builtins/readline.ts index aca209017..c2b6984ce 100644 --- a/packages/compiler/src/frontend/lowering/builtins/readline.ts +++ b/packages/compiler/src/frontend/lowering/builtins/readline.ts @@ -302,6 +302,15 @@ function interfaceClassModule(lowerer: Lowerer, node: ts.Expression): string | n : null; } +/** `new readline.Interface(...)` and `new readlinePromises.Interface(...)`: + * the constructor createInterface calls, with the same arguments. Null for + * other constructors. */ +export function lowerReadlineInterfaceNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null { + const module = interfaceClassModule(lowerer, expr.expression); + if (module === null) return null; + return lowerReadlineCreateCall(lowerer, expr, locOf(expr), module === "readline/promises"); +} + /** `rl instanceof readline.Interface` and `rl instanceof * readlinePromises.Interface` over a readline Interface: an interface is * an instance of the class of the module that created it (Node's @@ -1059,10 +1068,12 @@ function lowerOutput(lowerer: Lowerer, node: ts.Expression): IrExpr | "stdout" | * goes through rl.terminal with the completer. */ export function lowerReadlineCreateCall( lowerer: Lowerer, - expr: ts.CallExpression, + expr: ts.CallExpression | ts.NewExpression, loc: SrcLoc, promises: boolean, ): IrExpr { + const argNodes = expr.arguments?.nodes ?? []; + const what = ts.isNewExpression(expr) ? "new Interface" : "createInterface"; let input: IrExpr | null = null; let output: IrExpr | "stdout" | "none" = "none"; let crlf: IrExpr = numLit(0, loc); @@ -1112,39 +1123,38 @@ export function lowerReadlineCreateCall( }); terminal = { kind: "varRef", localId: local.id, type: BOOL, loc }; }; - const optsNode = expr.arguments.nodes[0]; + const optsNode = argNodes[0]; if (optsNode === undefined) { lowerer.noLowering( - "createInterface without arguments", + `${what} without arguments`, expr, "pass { input: process.stdin, output: process.stdout } or createInterface(input, output)", ); } if (!ts.isObjectLiteralExpression(optsNode)) { // createInterface(input, output?, completer?, terminal?) - if (expr.arguments.nodes.length > 4) { + if (argNodes.length > 4) { lowerer.noLowering( - `createInterface with ${expr.arguments.nodes.length} arguments`, - expr.arguments.nodes[4]!, + `${what} with ${argNodes.length} arguments`, + argNodes[4]!, "pass createInterface(input, output, completer, terminal) or one options object", ); } input = lowerInput(lowerer, optsNode); - if (expr.arguments.nodes[1] !== undefined) - output = lowerOutput(lowerer, expr.arguments.nodes[1]); - const completerNode = expr.arguments.nodes[2]; + if (argNodes[1] !== undefined) output = lowerOutput(lowerer, argNodes[1]); + const completerNode = argNodes[2]; if ( completerNode !== undefined && !(ts.isIdentifier(completerNode) && completerNode.text === "undefined") ) { setCompleter(completerNode); } - if (expr.arguments.nodes[3] !== undefined) setTerminal(expr.arguments.nodes[3]); + if (argNodes[3] !== undefined) setTerminal(argNodes[3]); } else { - if (expr.arguments.nodes.length > 1) { + if (argNodes.length > 1) { lowerer.noLowering( - "createInterface with more than one argument", - expr.arguments.nodes[1]!, + `${what} with more than one argument`, + argNodes[1]!, "pass every option in the one options object", ); } diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b829b6ddc..8bd5e57d7 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -18,8 +18,11 @@ import { superIntoResultSlot, } from "./constructor-returns.js"; import { checkedIterableSpread } from "./checked-iterable-spread.js"; -import { lowerReadlineUiNew } from "./builtins/readline.js"; -import { builtinMemberRequireDecl } from "./builtins/module-bindings.js"; +import { lowerReadlineInterfaceNew, lowerReadlineUiNew } from "./builtins/readline.js"; +import { + builtinMemberBindingTarget, + builtinMemberRequireDecl, +} from "./builtins/module-bindings.js"; import { checkedClassUnionAssertion } from "./class-assertions.js"; import { collectClassMethodMutations } from "./class-method-mutations.js"; import { InternalCompilerError } from "../../errors.js"; @@ -9819,7 +9822,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } const worker = lowerWorkerNew(lowerer, expr); if (worker) return worker; - const readlineUi = lowerReadlineUiNew(lowerer, expr); + const readlineUi = lowerReadlineUiNew(lowerer, expr) ?? lowerReadlineInterfaceNew(lowerer, expr); if (readlineUi) return readlineUi; const channel = lowerMessageChannelNew(lowerer, expr) ?? lowerBroadcastChannelNew(lowerer, expr); if (channel) return channel; @@ -10132,7 +10135,12 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // (a no-op for every non-import= binding). if (ts.isIdentifier(expr.expression)) fenceEarlyAliasUse(lowerer, expr.expression, expr); const identifier = ts.isIdentifier(expr.expression) ? expr.expression : expr.expression.name; - const symbol = ts.isIdentifier(identifier) ? lowerer.resolveValueSymbol(identifier) : null; + // `const SD = require("string_decoder").StringDecoder; new SD()` + // constructs the member, as a named import of it does. + const symbol = builtinMemberBindingTarget( + lowerer, + ts.isIdentifier(identifier) ? lowerer.resolveValueSymbol(identifier) : null, + ); if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Array")) { return lowerArrayConstructor(lowerer, expr, expr.arguments?.nodes ?? []); } diff --git a/packages/compiler/src/frontend/tsc-codes.ts b/packages/compiler/src/frontend/tsc-codes.ts index e6c0be7cd..8816615cf 100644 --- a/packages/compiler/src/frontend/tsc-codes.ts +++ b/packages/compiler/src/frontend/tsc-codes.ts @@ -128,6 +128,12 @@ export const JS_RELAXED_TSC_CODES: ReadonlySet = new Set([ // JavaScript subclasses may widen fields or add method parameters. // Class lowering checks native storage and override ABIs at each use. 2415, + // a private or protected constructor (`new readline.Interface(...)`, + // whose declaration is protected): JavaScript has no constructor + // accessibility, and Node constructs the class. Each `new` meets the + // lowerer's per-site constructor fences. + 2673, + 2674, // unresolvable modules: Node throws WHEN the require executes — the // binding types as any and reached uses fence instead of passing unchecked 2307, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-readline-interface-constructor.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-readline-interface-constructor.cjs.json new file mode 100644 index 000000000..05573974c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-readline-interface-constructor.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8230-readline-interface-constructor.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-builtin-member-binding-new.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-builtin-member-binding-new.cjs.json new file mode 100644 index 000000000..1f54f4886 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-builtin-member-binding-new.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8231-builtin-member-binding-new.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/8230-readline-interface-constructor.cjs b/tests/corpus/8230-readline-interface-constructor.cjs new file mode 100644 index 000000000..6d7eea0e4 --- /dev/null +++ b/tests/corpus/8230-readline-interface-constructor.cjs @@ -0,0 +1,51 @@ +// `new readline.Interface(...)`: Node's createInterface calls this +// constructor with the same arguments, and a JavaScript file may call it +// directly. Its declaration is protected, which JavaScript does not +// enforce. +"use strict"; +const readline = require("readline"); +const readlinePromises = require("readline/promises"); +const { Interface } = require("readline"); +const { PassThrough } = require("stream"); + +// The options form. +const a = new PassThrough(); +const rlA = new readline.Interface({ input: a, terminal: false }); +console.log("instanceof", rlA instanceof readline.Interface); +const linesA = []; +rlA.on("line", (line) => { + linesA.push(line); +}); +rlA.on("close", () => { + console.log("options", JSON.stringify(linesA)); +}); +a.write("one\ntwo\n"); +a.end("three"); + +// The positional form, through a destructured binding. +const b = new PassThrough(); +const outB = new PassThrough(); +const rlB = new Interface(b, outB); +const linesB = []; +rlB.on("line", (line) => { + linesB.push(line); +}); +rlB.on("close", () => { + console.log("positional", JSON.stringify(linesB)); +}); +b.write("four\r\nfive\n"); +b.end(); + +// readline/promises' Interface. +const c = new PassThrough(); +const rlC = new readlinePromises.Interface({ input: c, crlfDelay: Infinity }); +console.log("promises instanceof", rlC instanceof readlinePromises.Interface); +const linesC = []; +rlC.on("line", (line) => { + linesC.push(line); +}); +rlC.on("close", () => { + console.log("promises", JSON.stringify(linesC)); +}); +c.end("six\nseven"); + diff --git a/tests/corpus/8231-builtin-member-binding-new.cjs b/tests/corpus/8231-builtin-member-binding-new.cjs new file mode 100644 index 000000000..1549425f7 --- /dev/null +++ b/tests/corpus/8231-builtin-member-binding-new.cjs @@ -0,0 +1,35 @@ +// `new` of a builtin class bound through a member of a require() result +// (`const SD = require("string_decoder").StringDecoder`, the spelling of +// Node's own tests): the binding resolves to the class, as the +// destructured `const { StringDecoder } = require(...)` does. +"use strict"; +const SD = require("string_decoder").StringDecoder; +const Readable = require("stream").Readable; +const PassThrough = require("stream").PassThrough; +const Transform = require("stream").Transform; + +const decoder = new SD("utf8"); +console.log(decoder.encoding); +console.log(JSON.stringify(decoder.write(Buffer.from([0xe2, 0x82])))); +console.log(decoder.end(Buffer.from([0xac]))); +const hex = new SD("hex"); +console.log(hex.encoding, hex.write(Buffer.from("hi"))); +console.log(new SD().encoding); + +const r = new Readable({ read() {} }); +r.on("data", (chunk) => console.log("readable", String(chunk))); +r.on("end", () => console.log("readable end")); +r.push("abc"); +r.push(null); + +const p = new PassThrough(); +p.on("data", (chunk) => console.log("passthrough", String(chunk))); +p.end("xyz"); + +const upper = new Transform({ + transform(chunk, encoding, callback) { + callback(null, String(chunk).toUpperCase()); + }, +}); +upper.on("data", (chunk) => console.log("transform", String(chunk))); +upper.end("shout"); diff --git a/tests/harness/corpus-costs/8230-readline-interface-constructor.cjs.json b/tests/harness/corpus-costs/8230-readline-interface-constructor.cjs.json new file mode 100644 index 000000000..9b459042f --- /dev/null +++ b/tests/harness/corpus-costs/8230-readline-interface-constructor.cjs.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3000 } diff --git a/tests/harness/corpus-costs/8231-builtin-member-binding-new.cjs.json b/tests/harness/corpus-costs/8231-builtin-member-binding-new.cjs.json new file mode 100644 index 000000000..54e11997a --- /dev/null +++ b/tests/harness/corpus-costs/8231-builtin-member-binding-new.cjs.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 2500 } From a5eca18f1eb62446a109069f75aae5077683d9e5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 07:22:00 -0500 Subject: [PATCH 057/395] Read fs.promises as a module, end CommonJS entries at a top-level return, and check fs read and write arguments as Node does fs.promises read as a member of the fs module (a binding, an inline require('fs').promises, destructuring) is the node:fs/promises namespace, as in Node. It failed at run time before. A return at the top level of the entry CommonJS module ends its evaluation, as in Node's module wrapper: preflight admits TS1108 in CommonJS JavaScript files. A CommonJS module the program requires with a top-level return is refused with SC1090, because the exports assigned after the return would stay undefined, which their storage cannot hold. fs.ReadStream(...) and fs.WriteStream(...) called without new construct their stream, as Node's stream constructors do. fs.read, fs.readSync and fs.write check their arguments in Node's order and with Node's errors: the fd, the buffer, the offset, `length |= 0`, number and bigint positions (validatePosition), the empty-buffer error and the buffer window. fs.read accepts a Proxy options object. A readSync call whose JavaScript arguments are untyped or bigint takes the same checks at run time (fs.readSyncDyn). fs.write threw no error for an invalid fd, offset or length before. --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/builtins/filesystem-calls.ts | 24 +- .../frontend/lowering/builtins/fs-streams.ts | 31 ++- .../lowering/builtins/module-bindings.ts | 4 +- .../src/frontend/lowering/lower-calls.ts | 10 +- .../src/frontend/lowering/lower-modules.ts | 36 +++ .../compiler/src/frontend/lowering/lowerer.ts | 11 +- packages/compiler/src/frontend/program.ts | 22 +- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 1 + .../corpus/8270-fs-promises-member.cjs.json | 6 + .../corpus/8271-cjs-top-level-return.cjs.json | 6 + .../8272-fs-stream-call-without-new.cjs.json | 6 + ...273-fs-read-write-argument-checks.cjs.json | 6 + .../main.js.json | 7 + packages/runtime/src/scr_bytes.h | 2 + packages/runtime/src/scr_bytes_io.c | 244 ++++++++++++++---- packages/runtime/src/scr_lib.c | 40 +-- tests/corpus/8270-fs-promises-member.cjs | 25 ++ tests/corpus/8271-cjs-top-level-return.cjs | 20 ++ .../8272-fs-stream-call-without-new.cjs | 21 ++ .../8273-fs-read-write-argument-checks.cjs | 69 +++++ .../cjs-required-top-level-return/lib.js | 5 + .../cjs-required-top-level-return/main.js | 8 + .../package.json | 3 + .../cjs-required-top-level-return/main.js.txt | 6 + .../8270-fs-promises-member.cjs.json | 1 + .../8271-cjs-top-level-return.cjs.json | 1 + .../8272-fs-stream-call-without-new.cjs.json | 1 + ...273-fs-read-write-argument-checks.cjs.json | 1 + 30 files changed, 551 insertions(+), 68 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8270-fs-promises-member.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8271-cjs-top-level-return.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8272-fs-stream-call-without-new.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8273-fs-read-write-argument-checks.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-required-top-level-return/main.js.json create mode 100644 tests/corpus/8270-fs-promises-member.cjs create mode 100644 tests/corpus/8271-cjs-top-level-return.cjs create mode 100644 tests/corpus/8272-fs-stream-call-without-new.cjs create mode 100644 tests/corpus/8273-fs-read-write-argument-checks.cjs create mode 100644 tests/diagnostics/cjs-required-top-level-return/lib.js create mode 100644 tests/diagnostics/cjs-required-top-level-return/main.js create mode 100644 tests/diagnostics/cjs-required-top-level-return/package.json create mode 100644 tests/harness/__snapshots__/cjs-required-top-level-return/main.js.txt create mode 100644 tests/harness/corpus-costs/8270-fs-promises-member.cjs.json create mode 100644 tests/harness/corpus-costs/8271-cjs-top-level-return.cjs.json create mode 100644 tests/harness/corpus-costs/8272-fs-stream-call-without-new.cjs.json create mode 100644 tests/harness/corpus-costs/8273-fs-read-write-argument-checks.cjs.json diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 7a16c37e8..40daa8205 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -523,6 +523,7 @@ export const LIB_FN_SYMS: Record = { "fs.openSync": "scr_fs_open", "fs.openNumericSync": "scr_fs_open_numeric", "fs.readSync": "scr_fs_read_sync", + "fs.readSyncDyn": "scr_fs_read_sync_dyn", "fs.writeSync": "scr_fs_write_sync", "fs.writeStrSync": "scr_fs_write_str_sync", "fs.closeSync": "scr_fs_close", diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem-calls.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem-calls.ts index 0bbc3b3fa..6b5e2d8ae 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem-calls.ts @@ -2,7 +2,7 @@ import { dynUndefinedExpr, boolLit, numLit, varRef } from "../../../ir/build.js" import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { lowerFsPath } from "./fs-paths.js"; -import { locOf } from "../../program.js"; +import { isJsSourceFile, locOf } from "../../program.js"; import { type BuiltinModuleFn, FS_WRITE_FILE_DOCUMENTED_OPTIONS, @@ -403,6 +403,28 @@ export function lowerFsReadSyncCall( } lowerer.noLowering("readSync with a non-literal options argument", optionsNode, supported); } + // JavaScript arguments outside the typed signature (an untyped fd or + // buffer, a bigint or untyped position) take Node's argument checks at + // run time, then the same read. + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + const nodes = expr.arguments.nodes; + const typed = [F64, BYTES_U8, F64, F64, F64].every((want, i) => { + const node = nodes[i]; + if (node === undefined) return true; + let value: ts.Expression = node; + while (ts.isParenthesizedExpression(value)) value = value.expression; + if (i === 4 && value.kind === ts.SyntaxKind.NullKeyword) return true; + const t = lowerer.mapTypeOf(lowerer.typeOf(node)); + return t !== null && (want.kind === "bytes" ? t.kind === "bytes" : t.kind === "f64"); + }); + if (!typed) { + const args = [0, 1, 2, 3, 4].map((i): IrExpr => { + const node = nodes[i]; + return node === undefined ? dynUndefinedExpr(loc) : lowerer.lowerExprExpecting(node, DYN); + }); + return { kind: "libCall", fn: "fs.readSyncDyn", args, type: F64, loc }; + } + } const args: IrExpr[] = [ lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, F64), lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, BYTES_U8), diff --git a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts index 498f9b6d4..6c6f8fa79 100644 --- a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts +++ b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts @@ -4,7 +4,7 @@ * and everything inherited goes through the stream spoke. */ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; -import { locOf } from "../../program.js"; +import { isJsSourceFile, locOf } from "../../program.js"; import { isNodeTypesPath } from "../../dts-paths.js"; import { dynUndefinedExpr } from "../../../ir/build.js"; import { @@ -178,3 +178,32 @@ export function lowerFsStreamNew( locOf(expr), ); } + +/** `fs.ReadStream(path, options)` / `fs.WriteStream(...)` called without + * `new` in JavaScript: Node's stream constructors construct when called as + * functions, so the call is the `new` form. Null for other calls. */ +export function lowerFsStreamCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr | null { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; + const callee = expr.expression; + const name = ts.isPropertyAccessExpression(callee) + ? callee.name + : ts.isIdentifier(callee) + ? callee + : null; + if (name === null || !ts.isIdentifier(name)) return null; + const symbol = lowerer.checker.getSymbolAtLocation(name); + const resolved = + symbol && symbol.flags & ts.SymbolFlags.Alias + ? lowerer.checker.getAliasedSymbol(symbol) + : symbol; + const cls = fsStreamClassOf(lowerer, resolved); + if (cls === null) return null; + return fsStreamCreateExpr( + lowerer, + expr.arguments.nodes, + cls === "ReadStream", + `fs.${cls}`, + expr, + locOf(expr), + ); +} diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 5465aeafa..5bac5651a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -213,7 +213,9 @@ export function builtinNamespaceDestructureModuleOf( if (ts.isCallExpression(init)) { const cr = createRequireSpecOf(lowerer, init); module = cr !== null && cr.spec !== null ? canonicalBuiltinModule(cr.spec) : null; - } else if (ts.isIdentifier(init)) { + } else if (ts.isIdentifier(init) || ts.isPropertyAccessExpression(init)) { + // `const { open } = fs.promises` / `= require("fs").promises`: a member + // that is itself a supported module. module = lowerer.builtinNamespaceModuleOf(init); } if (module === null) return null; diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 14565aebc..884c106b7 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -24,7 +24,11 @@ import { import { lowerMockTimersElementCall } from "./lower-test.js"; import { objectEnumerationReceiver } from "./object-enumeration-receiver.js"; import { InternalCompilerError } from "../../errors.js"; -import { isFsStreamMember, lowerFsStreamMethodCall } from "./builtins/fs-streams.js"; +import { + isFsStreamMember, + lowerFsStreamCall, + lowerFsStreamMethodCall, +} from "./builtins/fs-streams.js"; import * as ts from "../ts7/adapter.js"; import { isZlibStreamMember, @@ -375,6 +379,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const buffer = lowerBufferConstruct(lowerer, expr); if (buffer) return { kind: "dynFrom", value: buffer, type: DYN, loc }; } + { + const stream = lowerFsStreamCall(lowerer, expr); + if (stream) return stream; + } // A package shim's runtime intrinsic (a bodiless `declare function` // named scriptcUnixDgram* in a shipped shim): a direct runtime call. if (ts.isIdentifier(expr.expression) && isShimIntrinsicName(expr.expression.text)) { diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 19f7243ed..5c30c2250 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -1346,6 +1346,25 @@ export function collectGlobals( // "m.". const rawTag = lowerer.fileTag.get(sf) ?? ""; const tag = rawTag === "" ? "e." : rawTag.replace(/^%/, ""); + // A `return` at the top level of a CommonJS module ends its evaluation + // (preflight admits it). In the entry module that ends the program's + // top-level code. A module the program requires would leave the exports + // assigned after the return undefined, which its typed export storage + // cannot hold, so it is refused there. + if (rawTag !== "" && isJsSourceFile(sf)) { + const early = topLevelReturnOf(sf); + if (early !== null) { + try { + lowerer.unsupported( + "SC1090", + early, + "top-level return statements in CommonJS modules the program requires (only the entry module can end its evaluation early)", + ); + } catch (e) { + if (!(e instanceof PoisonError)) throw e; + } + } + } // Expando function members (`foo.bar = 12` anywhere in the file) // register their module globals first — reads inside function bodies // collected earlier in this pass must resolve them (lower-expando.ts). @@ -3579,3 +3598,20 @@ export function buildMain(lowerer: Lowerer): IrFunction { loc, }; } + +/** The first `return` statement at the top level of `sf` (outside every + * function body and class), which a CommonJS module may contain. */ +function topLevelReturnOf(sf: ts.SourceFile): ts.ReturnStatement | null { + let found: ts.ReturnStatement | null = null; + const visit = (node: ts.Node): void => { + if (found !== null || ts.isFunctionLike(node)) return; + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; + if (ts.isReturnStatement(node)) { + found = node; + return; + } + ts.forEachChild(node, visit); + }; + for (const stmt of sf.statements.nodes) visit(stmt); + return found; +} diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 78980d322..eb7928dd4 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -13766,7 +13766,7 @@ export class Lowerer { if ( ts.isPropertyAccessExpression(expr) && !expr.questionDotToken && - ts.isIdentifier(expr.expression) + (ts.isIdentifier(expr.expression) || requireSpecOf(expr.expression) !== null) ) { const outer = this.builtinNamespaceModuleOf(expr.expression); if (outer === null) return null; @@ -13971,6 +13971,15 @@ export class Lowerer { `library functions as values (call '${ts.getTextOfNode(expr)}' directly)`, ); } + // A member that is a supported module in its own right (`fs.promises` + // is node:fs/promises, as in Node) reads as that module's namespace. + { + const sub = this.builtinNamespaceModuleOf(expr); + if (sub !== null) { + const type: IrType = { kind: "moduleNs", moduleId: `builtin:${sub}` }; + return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc }; + } + } this.noLowering( `${bi.module}.${bi.member}`, expr, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index bf75127c4..f9be2b832 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1379,9 +1379,16 @@ function suppressedJsStrictness7(d: ts.Diagnostic): boolean { return ts.flattenDiagnosticMessageText(d, "\n").includes("'any'"); } // The deprecated Buffer(...) call without `new`, which Node's Buffer - // function accepts (containers/bytes.ts lowers it). + // function accepts (containers/bytes.ts lowers it), and fs.ReadStream(...) + // and fs.WriteStream(...), which construct their stream + // (builtins/fs-streams.ts). if (d.code === 2348) { - return ts.flattenDiagnosticMessageText(d, "\n").includes("'BufferConstructor'"); + const text = ts.flattenDiagnosticMessageText(d, "\n"); + return ( + text.includes("'BufferConstructor'") || + text.includes("'typeof ReadStream'") || + text.includes("'typeof WriteStream'") + ); } return false; } @@ -5358,6 +5365,16 @@ function preflight7(load: LoadResult): { return new RegExp(`\\btypeof\\s*\\(?\\s*${name.replace(/\$/g, "\\$")}(?![\\w$])`).test(sf.text); }; + /** A CommonJS file runs inside Node's module wrapper function, so a + * `return` at its top level ends the module's evaluation (TS1108 in a + * plain script). The lowering ends the module initializer there. */ + const cjsTopLevelReturnSuppressed = (p: ts.Program, d: ts.Diagnostic): boolean => { + if (d.code !== 1108 || d.fileName === undefined || !isJsSourceFileName(d.fileName)) + return false; + const sf = p.getSourceFile(d.fileName); + return sf !== undefined && isCjsJsFile(sf, p); + }; + const workspaceImplicitAnySuppressed = (p: ts.Program, d: ts.Diagnostic): boolean => { if (d.code !== 7016 || d.fileName === undefined || d.pos === undefined || d.end === undefined) return false; @@ -5404,6 +5421,7 @@ function preflight7(load: LoadResult): { !isNpmStaticSubclassArgument(p, d) && !workspaceImplicitAnySuppressed(p, d) && !typeofTestedNameSuppressed(p, d) && + !cjsTopLevelReturnSuppressed(p, d) && !jsdocTypeSuppressed(p, d, commentDup), ); }; diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..fb24f6596 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -1028,6 +1028,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "fs.openSync", "fs.openNumericSync", "fs.readSync", + "fs.readSyncDyn", "fs.writeSync", "fs.writeStrSync", "fs.closeSync", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 216e46a4d..a1d969667 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1195,6 +1195,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "fs.openSync": { argTypes: [STRING, STRING], result: F64 }, "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 }, "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + "fs.readSyncDyn": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: F64 }, "fs.writeSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, "fs.writeStrSync": { argTypes: [F64, STRING, F64, STRING], result: F64 }, // fs.watch's callback func type is program-dependent (zero params, or diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8270-fs-promises-member.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8270-fs-promises-member.cjs.json new file mode 100644 index 000000000..eb3938040 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8270-fs-promises-member.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8270-fs-promises-member.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8271-cjs-top-level-return.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8271-cjs-top-level-return.cjs.json new file mode 100644 index 000000000..2fcecbe67 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8271-cjs-top-level-return.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8271-cjs-top-level-return.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8272-fs-stream-call-without-new.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8272-fs-stream-call-without-new.cjs.json new file mode 100644 index 000000000..7fdb114e4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8272-fs-stream-call-without-new.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8272-fs-stream-call-without-new.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8273-fs-read-write-argument-checks.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8273-fs-read-write-argument-checks.cjs.json new file mode 100644 index 000000000..60e64d369 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8273-fs-read-write-argument-checks.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8273-fs-read-write-argument-checks.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-required-top-level-return/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-required-top-level-return/main.js.json new file mode 100644 index 000000000..d8eeb4f76 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-required-top-level-return/main.js.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/diagnostics/cjs-required-top-level-return/lib.js", + "/tests/diagnostics/cjs-required-top-level-return/main.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index 7c0a935cc..6efb970f8 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -524,6 +524,8 @@ ScrDyn *scr_fs_lchmod_sync_chk(const ScrDyn *path, const ScrDyn *mode); ScrPromise *scr_fsp_lchmod_chk(const ScrDyn *path, const ScrDyn *mode); void scr_fs_read_chk(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, const ScrDyn *length, const ScrDyn *position, const ScrStr *fence); +double scr_fs_read_sync_dyn(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, + const ScrDyn *length, const ScrDyn *position); void scr_fs_stream_opts_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence); /* The fs streams' option pieces (scr_fs_stream.c): the getOptions, * options.fd and path checks above without the fence; the checked path diff --git a/packages/runtime/src/scr_bytes_io.c b/packages/runtime/src/scr_bytes_io.c index 253cad1da..bebc2a479 100644 --- a/packages/runtime/src/scr_bytes_io.c +++ b/packages/runtime/src/scr_bytes_io.c @@ -638,6 +638,8 @@ void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *bu static bool scr_fs_read_validate(const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *); static bool scr_fs_encoding_chk(const ScrDyn *); static bool scr_fs_int_range_chk(const ScrDyn *, const char *, double, double, const char *); +static bool scr_fs_length_int32(const ScrDyn *, double *); +static bool scr_fs_write_validate(const ScrDyn *, const ScrDyn *, const ScrDyn *); static bool scr_fs_mode_chk(const ScrDyn *, const char *); /* Where Node's signature handling finds the callback: a fixed position, or @@ -856,7 +858,7 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc * (below); every other form checks it here, before its path. */ bool late = !strcmp(op, "access") || !strcmp(op, "copyFile") || !strcmp(op, "chmod") || !strcmp(op, "open") || !strcmp(op, "mkdir") || !strcmp(op, "fchmod") || !strcmp(op, "ftruncate") || !strcmp(op, "truncate") || - !strcmp(op, "read"); + !strcmp(op, "read") || !strcmp(op, "write"); if (!late && !scr_fs_cb_chk(callback, legacy ? "callback" : "cb")) { scr_arr_release(vectors); return NULL; } size_t count = timestamp ? argc : slot < argc ? slot : argc; #define ARG(index) scr_fs_cb_arg(args, count, index) @@ -889,7 +891,8 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc const ScrDyn *params = count == 3 ? ARG(2) : scr_dyn_bytes_is(ARG(1), SCR_BYTES_U8) ? NULL : ARG(1); bool object = !params || scr_fs_dyn_absent(params) || params->kind == SCR_DYN_OBJ || params->kind == SCR_DYN_BYTES || - (params->kind == SCR_DYN_TYPED_REF && !scr_dyn_ref_is_array(params)); + (params->kind == SCR_DYN_TYPED_REF && !scr_dyn_ref_is_array(params)) || + (params->kind == SCR_DYN_PROXY && scr_dyn_is_object(params)); if (!object) { scr_dyn_arg_type_fail("options", "of type object", params); return NULL; @@ -902,6 +905,22 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc if (!scr_fs_cb_chk(callback, "cb")) return NULL; } if (!strcmp(op, "read") && count >= 4 && !scr_fs_read_validate(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4))) return NULL; + /* write checks its fd first, then the buffer form's callback, offset and + * length (validateOffsetLengthWrite), or the string form's data and + * callback. */ + if (!strcmp(op, "write")) { + if (!scr_fs_int_range_chk(ARG(0), "fd", 0, 2147483647.0, ">= 0 && <= 2147483647")) return NULL; + if (ARG(1)->kind == SCR_DYN_BYTES) { + if (!scr_fs_cb_chk(callback, "cb") || !scr_fs_write_validate(ARG(1), ARG(2), ARG(3))) return NULL; + } else { + if (ARG(1)->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("buffer", "of type string or an instance of Buffer, TypedArray, or DataView", + ARG(1)); + return NULL; + } + if (!scr_fs_cb_chk(callback, "cb")) return NULL; + } + } bool descriptor = timestamp_fd || vector || newControl || !strcmp(op, "close") || !strcmp(op, "fstat") || !strcmp(op, "fsync") || !strcmp(op, "read") || !strcmp(op, "write") || !strcmp(op, "fchown"); /* truncate checks len, then the callback, before the open its request * starts with. */ @@ -1100,7 +1119,10 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc return NULL; } else { double offset = ownedBuffer ? scr_fs_cb_option_number(options, "offset", 0) : scr_fs_cb_number(ARG(2), "offset", 0); - double length = ownedBuffer ? scr_fs_cb_option_number(options, "length", bytes->len - offset) : scr_fs_cb_number(ARG(3), "length", bytes->len - offset); + double length = bytes->len - offset; + if (ownedBuffer) length = scr_fs_cb_option_number(options, "length", length); + else if (readOp && count >= 4) scr_fs_length_int32(ARG(3), &length); + else length = scr_fs_cb_number(ARG(3), "length", length); double position = ownedBuffer ? scr_fs_cb_option_number(options, "position", -1) : scr_fs_cb_number(ARG(4), "position", -1); if (!scr_exc_pending()) result = scr_dyn_new_num(readOp ? scr_fs_read_sync(fd, bytes, offset, length, position) : scr_fs_write_sync(fd, bytes, offset, length, position)); } @@ -1877,68 +1899,134 @@ static bool scr_fs_int_range_chk(const ScrDyn *v, const char *name, double min, return true; } -static bool scr_fs_read_validate(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, - const ScrDyn *length, const ScrDyn *position) { - if (buffer->kind != SCR_DYN_BYTES) { - scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer); +/* `length |= 0`: ToInt32 of the length argument (a non-numeric value is + * 0). False with the exception pending when ToNumber throws. */ +static bool scr_fs_length_int32(const ScrDyn *length, double *out) { + double n; + if (length->kind == SCR_DYN_NUM) { + n = length->v.num; + } else if (!scr_dyn_number_coerce_js(length, &n)) { return false; } + if (!isfinite(n)) { + *out = 0; + return true; + } + double m = fmod(trunc(n), 4294967296.0); + if (m < 0) m += 4294967296.0; + *out = m >= 2147483648.0 ? m - 4294967296.0 : m; + return true; +} + +/* A bigint read position in [-1n, 2n ** 63n - 1n - BigInt(length)] + * (validatePosition); false with ERR_OUT_OF_RANGE pending otherwise. The + * received value groups the digits past 2^32 in threes, as Node's + * ERR_OUT_OF_RANGE does for bigints. */ +static bool scr_fs_bigint_position_chk(ScrBigInt *position, double length) { + ScrBigInt *top = scr_bigint_from_i64(INT64_MAX); + ScrBigInt *span = scr_bigint_from_f64(length); + ScrBigInt *max = scr_bigint_sub(top, span); + scr_bigint_release(top); + scr_bigint_release(span); + ScrBigInt *low = scr_bigint_from_i64(-1); + bool ok = scr_bigint_cmp_f64(position, low) >= 0 && scr_bigint_cmp_f64(position, max) <= 0; + scr_bigint_release(low); + if (ok) { + scr_bigint_release(max); + return true; + } + ScrStr *limit = scr_bigint_to_string(max, 10); + scr_bigint_release(max); + ScrStr *decimal = scr_bigint_to_string(position, 10); + if (!limit || !decimal) { + if (limit) scr_str_release(limit); + if (decimal) scr_str_release(decimal); + return false; + } + ScrBigInt *bound = scr_bigint_from_i64((int64_t)1 << 32); + ScrBigInt *negBound = scr_bigint_from_i64(-((int64_t)1 << 32)); + bool group = scr_bigint_cmp_f64(position, bound) > 0 || scr_bigint_cmp_f64(position, negBound) < 0; + scr_bigint_release(bound); + scr_bigint_release(negBound); + /* addNumericalSeparator: underscores every three digits from the right, + * after an optional sign. */ + char digits[200]; + size_t len = decimal->len < 120 ? decimal->len : 120, out = 0; + size_t start = len > 0 && decimal->data[0] == '-' ? 1 : 0; + for (size_t i = 0; i < len; i++) { + if (group && i > start && (len - i) % 3 == 0) digits[out++] = '_'; + digits[out++] = (char)decimal->data[i]; + } + digits[out] = '\0'; + char msg[384]; + int n = snprintf(msg, sizeof msg, + "The value of \"position\" is out of range. It must be >= -1 && <= %.*s. Received %sn", + (int)limit->len, (const char *)limit->data, digits); + scr_str_release(limit); + scr_str_release(decimal); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, n < 0 ? 0 : (size_t)n, "ERR_OUT_OF_RANGE"); + return false; +} + + +/* fs.read(fd, buffer, offset, length, position, cb)'s argument checks, in + * Node's order: the fd, the buffer, the offset (an integer >= 0), the + * length as `length |= 0`, the position (validatePosition, against that + * length), then for a non-empty read the empty-buffer and + * validateOffsetLengthRead checks. */ +static bool scr_fs_read_validate(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, + const ScrDyn *length, const ScrDyn *position) { if (fd->kind != SCR_DYN_NUM) { scr_dyn_arg_type_fail("fd", "of type number", fd); return false; } + if (buffer->kind != SCR_DYN_BYTES) { + scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer); + return false; + } double buflen = scr_bytes_byte_len(buffer->v.bytes); + double off = 0; if (!scr_fs_dyn_absent(offset)) { - /* validateInteger's MAX_SAFE range first, the buffer bound second — - * Node renders each with its own max. */ if (!scr_fs_int_range_chk(offset, "offset", 0, 9007199254740991.0, ">= 0 && <= 9007199254740991")) { return false; } - if (offset->v.num > buflen) { - char range[64]; - snprintf(range, sizeof range, ">= 0 && <= %.0f", buflen); - if (!scr_fs_int_range_chk(offset, "offset", 0, buflen, range)) return false; - } + off = offset->v.num; } + double count = 0; + if (!scr_fs_length_int32(length, &count)) return false; if (!scr_fs_dyn_absent(position)) { - if (position->kind != SCR_DYN_NUM) { + if (position->kind == SCR_DYN_BIGINT) { + if (!scr_fs_bigint_position_chk(position->v.bigint, count)) return false; + } else if (position->kind != SCR_DYN_NUM) { scr_dyn_arg_type_fail("position", "of type bigint or integer", position); return false; - } - if (!scr_fs_int_range_chk(position, "position", -1, 9007199254740991.0, - ">= -1 && <= 9007199254740991")) { + } else if (!scr_fs_int_range_chk(position, "position", -1, 9007199254740991.0, + ">= -1 && <= 9007199254740991")) { return false; } } - double off = offset->kind == SCR_DYN_NUM ? offset->v.num : 0; - if (!scr_fs_dyn_absent(length)) { - if (length->kind != SCR_DYN_NUM || !(isfinite(length->v.num) && trunc(length->v.num) == length->v.num) || - length->v.num < 0) { - /* Node renders the bare ">= 0" form here (checkPosition's cousin). */ - if (length->kind == SCR_DYN_NUM) { - char recv[48], msg[160]; - scr_num_received(length->v.num, recv); - const char *shape = (isfinite(length->v.num) && trunc(length->v.num) == length->v.num) - ? "It must be >= 0." - : "It must be an integer."; - int len = snprintf(msg, sizeof msg, - "The value of \"length\" is out of range. %s Received %s", shape, recv); - scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); - return false; - } - scr_dyn_arg_type_fail("length", "of type number", length); - return false; - } - if (length->v.num > buflen - off) { - char recv[48], msg[160]; - scr_num_received(length->v.num, recv); - int len = snprintf(msg, sizeof msg, - "The value of \"length\" is out of range. It must be <= %.0f. Received %s", - buflen - off, recv); - scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); - return false; + if (count == 0) return true; + if (buflen == 0) { + if (scr_bytes_is_buffer(buffer->v.bytes)) { + static const char empty[] = + "The argument 'buffer' is empty and cannot be written. Received "; + scr_throw_error_msg_code(SCR_ERR_TYPE, empty, sizeof empty - 1, "ERR_INVALID_ARG_VALUE"); + } else { + scr_dyn_arg_value_fail("buffer", "is empty and cannot be written", buffer); } + return false; + } + if (count < 0 || off + count > buflen) { + char recv[48], range[64], msg[192]; + scr_num_received(count, recv); + if (count < 0) snprintf(range, sizeof range, ">= 0"); + else snprintf(range, sizeof range, "<= %.0f", buflen - off); + int len = snprintf(msg, sizeof msg, + "The value of \"length\" is out of range. It must be %s. Received %s", range, + recv); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); + return false; } return true; } @@ -1991,3 +2079,69 @@ bool scr_fs_stream_mode(const ScrDyn *value, double *mode) { } return scr_fs_cb_file_mode(value, "mode", mode); } + +/* fs.readSync(fd, buffer, offset, length[, position]) with arguments the + * compiler could not type (JavaScript): Node's checks (validateBuffer + * first, then the fd, the offset, `length |= 0` and the position, which + * may be a bigint), then the typed read. */ +double scr_fs_read_sync_dyn(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, + const ScrDyn *length, const ScrDyn *position) { + if (buffer->kind != SCR_DYN_BYTES) { + scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer); + return 0; + } + if (!scr_fs_read_validate(fd, buffer, offset, length, position)) return 0; + double count = 0; + if (!scr_fs_length_int32(length, &count)) return 0; + if (count == 0) return 0; + double at = scr_fs_dyn_absent(position) ? -1 + : position->kind == SCR_DYN_BIGINT ? scr_bigint_to_f64(position->v.bigint) + : position->v.num; + ScrBytes *bytes = scr_dyn_bytes_unbox(buffer); + double read = scr_fs_read_sync(fd->v.num, bytes, scr_fs_dyn_absent(offset) ? 0 : offset->v.num, + count, at); + scr_bytes_release(bytes); + return read; +} + +/* ERR_OUT_OF_RANGE for the number `n` of argument `name`. Always false. */ +static bool scr_fs_num_range_fail(const char *name, const char *range, double n) { + char recv[48], msg[192]; + scr_num_received(n, recv); + int len = snprintf(msg, sizeof msg, "The value of \"%s\" is out of range. It must be %s. Received %s", + name, range, recv); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); + return false; +} + +/* fs.write(fd, buffer, offset, length, position, cb)'s buffer checks: the + * offset (an integer >= 0 unless omitted, null or the callback), a + * non-number length defaulting to the rest of the buffer, then + * validateOffsetLengthWrite. An options object in the offset slot is read + * by the write itself. */ +static bool scr_fs_write_validate(const ScrDyn *buffer, const ScrDyn *offset, const ScrDyn *length) { + if (offset->kind == SCR_DYN_OBJ || offset->kind == SCR_DYN_TYPED_REF || offset->kind == SCR_DYN_PROXY || + offset->kind == SCR_DYN_NULL) + return true; + double buflen = scr_bytes_byte_len(buffer->v.bytes); + double off = 0; + if (!scr_fs_dyn_absent(offset) && offset->kind != SCR_DYN_FUNC) { + if (!scr_fs_int_range_chk(offset, "offset", 0, 9007199254740991.0, ">= 0 && <= 9007199254740991")) + return false; + off = offset->v.num; + } + double len = length->kind == SCR_DYN_NUM ? length->v.num : buflen - off; + char range[64]; + if (off > buflen) { + snprintf(range, sizeof range, "<= %.0f", buflen); + return scr_fs_num_range_fail("offset", range, off); + } + if (len > buflen - off) { + snprintf(range, sizeof range, "<= %.0f", buflen - off); + return scr_fs_num_range_fail("length", range, len); + } + if (len < 0) return scr_fs_num_range_fail("length", ">= 0", len); + if (!(isfinite(len) && trunc(len) == len)) return scr_fs_num_range_fail("length", "an integer", len); + if (len > 2147483647.0) return scr_fs_num_range_fail("length", ">= 0 && <= 2147483647", len); + return true; +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index fb3524c81..b7deae018 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -4236,27 +4236,35 @@ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length, scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE"); return 0; } - /* Node returns after the offset and position validation when the - * requested length normalizes to zero: buffer-window bounds and fd are - * not consulted. Preserve the existing size_t coercion's [0, 1) case. */ - if (length >= 0 && length < 1) return 0; - if (offset > (double)bytelen) { - numbuf[scr_f64_to_str(offset, numbuf)] = 0; + /* `length |= 0`, then Node's order: a zero length reads nothing after + * the offset and position checks; otherwise the buffer must not be + * empty and the window must fit it (validateOffsetLengthRead). */ + if (!isfinite(length)) { + length = 0; + } else { + double wrapped = fmod(trunc(length), 4294967296.0); + if (wrapped < 0) wrapped += 4294967296.0; + length = wrapped >= 2147483648.0 ? wrapped - 4294967296.0 : wrapped; + } + if (length == 0) return 0; + if (bytelen == 0) { + static const char empty[] = + "The argument 'buffer' is empty and cannot be written. Received "; + scr_throw_error_msg_code(SCR_ERR_TYPE, empty, sizeof empty - 1, "ERR_INVALID_ARG_VALUE"); + return 0; + } + if (length < 0 || offset + length > (double)bytelen) { + char recv[48], range[48]; + scr_num_received(length, recv); + if (length < 0) snprintf(range, sizeof range, ">= 0"); + else snprintf(range, sizeof range, "<= %.0f", (double)bytelen - offset); mlen = snprintf(msg, sizeof msg, - "The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s", - numbuf); + "The value of \"length\" is out of range. It must be %s. Received %s", range, + recv); scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE"); return 0; } size_t off = (size_t)offset; - if (length < 0 || (double)length > (double)(bytelen - off)) { - numbuf[scr_f64_to_str(length, numbuf)] = 0; - mlen = snprintf(msg, sizeof msg, - "The value of \"length\" is out of range. It must be <= %zu. Received %s", - bytelen - off, numbuf); - scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_OUT_OF_RANGE"); - return 0; - } size_t want = (size_t)length; uint8_t *bounce = buf->shared ? malloc(want ? want : 1) : NULL; if (buf->shared && !bounce) scr_trap("scriptc: out of memory\n"); diff --git a/tests/corpus/8270-fs-promises-member.cjs b/tests/corpus/8270-fs-promises-member.cjs new file mode 100644 index 000000000..86448f012 --- /dev/null +++ b/tests/corpus/8270-fs-promises-member.cjs @@ -0,0 +1,25 @@ +'use strict'; +// fs.promises is node:fs/promises, as in Node: through a binding, an +// inline require, destructuring and a direct member call. +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'scriptc-fsp-')); +const file = path.join(dir, 'data.txt'); +const fsPromises = fs.promises; +const { readFile, stat } = require('fs').promises; +const { open } = fs.promises; + +(async () => { + await fsPromises.writeFile(file, 'abc'); + await fs.promises.appendFile(file, 'def'); + console.log(await readFile(file, 'utf8')); + console.log((await stat(file)).size); + const handle = await open(file, 'r'); + const { bytesRead, buffer } = await handle.read(Buffer.alloc(3), 0, 3, 1); + console.log(bytesRead, buffer.toString()); + await handle.close(); + await fsPromises.rm(dir, { recursive: true }); + console.log(fs.existsSync(dir)); +})(); diff --git a/tests/corpus/8271-cjs-top-level-return.cjs b/tests/corpus/8271-cjs-top-level-return.cjs new file mode 100644 index 000000000..9f5cc6d23 --- /dev/null +++ b/tests/corpus/8271-cjs-top-level-return.cjs @@ -0,0 +1,20 @@ +'use strict'; +// A return at the top level of the entry CommonJS module ends its +// evaluation, as in Node's module wrapper: from a block, a loop and a try +// statement, whose finally block still runs. +const fs = require('fs'); + +console.log('start'); +for (const name of ['a', 'b', 'c']) { + if (name === 'c') { + try { + console.log('returning at', name); + process.on('exit', (code) => console.log('exit', code)); + if (!fs.existsSync('/scriptc-missing-path')) return; + } finally { + console.log('finally runs'); + } + } + console.log('visited', name); +} +console.log('not reached'); diff --git a/tests/corpus/8272-fs-stream-call-without-new.cjs b/tests/corpus/8272-fs-stream-call-without-new.cjs new file mode 100644 index 000000000..91fe0a6c0 --- /dev/null +++ b/tests/corpus/8272-fs-stream-call-without-new.cjs @@ -0,0 +1,21 @@ +'use strict'; +// fs.ReadStream and fs.WriteStream called without new construct their +// stream, as in Node. +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'scriptc-streams-')); +const target = path.join(dir, 'out.txt'); +const writer = fs.WriteStream(target); +writer.on('finish', () => { + const reader = fs.ReadStream(target, { encoding: 'utf8' }); + let text = ''; + reader.on('data', (chunk) => (text += chunk)); + reader.on('end', () => { + console.log(text); + fs.rmSync(dir, { recursive: true }); + }); +}); +writer.write('hello '); +writer.end('streams'); diff --git a/tests/corpus/8273-fs-read-write-argument-checks.cjs b/tests/corpus/8273-fs-read-write-argument-checks.cjs new file mode 100644 index 000000000..623d5286f --- /dev/null +++ b/tests/corpus/8273-fs-read-write-argument-checks.cjs @@ -0,0 +1,69 @@ +'use strict'; +// fs.read, fs.readSync and fs.write check their arguments in Node's order +// and with Node's errors: the fd, the buffer, the offset, `length |= 0`, +// number and bigint positions, and the buffer window. +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'scriptc-fs-args-')); +const file = path.join(dir, 'data.txt'); +fs.writeFileSync(file, 'abcdef'); +const fd = fs.openSync(file, 'r+'); +const noop = () => {}; + +function attempt(name, fn) { + try { + fn(); + console.log(name, 'ok'); + } catch (error) { + console.log(name, error.name, error.code, error.message); + } +} + +attempt('read offset -1', () => fs.read(fd, Buffer.alloc(3), -1, 3, 0, noop)); +attempt('read offset NaN', () => fs.read(fd, Buffer.alloc(3), NaN, 3, 0, noop)); +attempt('read length -1', () => fs.read(fd, Buffer.alloc(3), 0, -1, 0, noop)); +attempt('read length 4', () => fs.read(fd, Buffer.alloc(3), 0, 4, 0, noop)); +attempt('read length 1.5', () => fs.read(fd, Buffer.alloc(3), 0, 1.5, 0, noop)); +attempt('read length "x"', () => fs.read(fd, Buffer.alloc(3), 0, 'x', 0, noop)); +attempt('read position 1.5', () => fs.read(fd, Buffer.alloc(3), 0, 3, 1.5, noop)); +attempt('read position -2', () => fs.read(fd, Buffer.alloc(3), 0, 3, -2, noop)); +attempt('read position ""', () => fs.read(fd, Buffer.alloc(3), 0, 3, '', noop)); +attempt('read position 2n ** 63n', () => fs.read(fd, Buffer.alloc(3), 0, 3, 2n ** 63n, noop)); +attempt('read position -2n', () => fs.read(fd, Buffer.alloc(3), 0, 3, -2n, noop)); +attempt('read empty buffer', () => fs.read(fd, Buffer.alloc(0), 0, 1, 0, noop)); +attempt('read fd true', () => fs.read(true, Buffer.alloc(3), 0, 3, 0, noop)); +attempt('read buffer number', () => fs.read(fd, 3, 0, 3, 0, noop)); + +const read = (position) => { + const buffer = Buffer.alloc(3); + const count = fs.readSync(fd, buffer, 0, 3, position); + return `${count} ${buffer.toString()}`; +}; +attempt('readSync position 1n', () => console.log(read(1n))); +attempt('readSync position 0n', () => console.log(read(0n))); +attempt('readSync position 2n ** 63n', () => read(2n ** 63n)); +attempt('readSync offset 4', () => fs.readSync(fd, Buffer.alloc(3), 4, 1, 0)); +attempt('readSync buffer number', () => fs.readSync(fd, 3, 0, 1, 0)); +attempt('readSync fd null', () => fs.readSync(null, Buffer.alloc(3), 0, 1, 0)); +console.log(fs.readSync(fd, Buffer.alloc(3), 0, 0.5, 0), fs.readSync(fd, Buffer.alloc(3), 0, 2.5, 0)); + +attempt('write fd null', () => fs.write(null, Buffer.alloc(1), 0, 1, noop)); +attempt('write fd "1"', () => fs.write('1', 'x', noop)); +attempt('write offset -1', () => fs.write(fd, Buffer.alloc(0), -1, noop)); +attempt('write offset NaN', () => fs.write(fd, Buffer.from('abcd'), NaN, 4, 0, noop)); +attempt('write offset 5', () => fs.write(fd, Buffer.from('abcd'), 5, noop)); +attempt('write length 5', () => fs.write(fd, Buffer.from('abcd'), 0, 5, 0, noop)); +attempt('write length -1', () => fs.write(fd, Buffer.alloc(0), 0, -1, noop)); +attempt('write data number', () => fs.write(fd, 4, noop)); +attempt('write no callback', () => fs.write(fd, Buffer.from('a'), 0, 1, 0)); + +fs.read(fd, Buffer.alloc(2), 0, 2, 2n, (error, count, buffer) => { + console.log('read 2n', error, count, buffer.toString()); + fs.write(fd, Buffer.from('XY'), 0, 2, 0, (error2, written) => { + console.log('wrote', error2, written, fs.readFileSync(file, 'utf8')); + fs.closeSync(fd); + fs.rmSync(dir, { recursive: true }); + }); +}); diff --git a/tests/diagnostics/cjs-required-top-level-return/lib.js b/tests/diagnostics/cjs-required-top-level-return/lib.js new file mode 100644 index 000000000..62e017964 --- /dev/null +++ b/tests/diagnostics/cjs-required-top-level-return/lib.js @@ -0,0 +1,5 @@ +'use strict'; + +exports.ready = true; +if (process.env.SCRIPTC_SKIP_LIB === '1') return; +exports.value = 42; diff --git a/tests/diagnostics/cjs-required-top-level-return/main.js b/tests/diagnostics/cjs-required-top-level-return/main.js new file mode 100644 index 000000000..0b62093c2 --- /dev/null +++ b/tests/diagnostics/cjs-required-top-level-return/main.js @@ -0,0 +1,8 @@ +// A top-level return in a CommonJS module the program requires: the +// exports assigned after it may stay undefined, which the module's typed +// export storage cannot hold, so the return is refused. The entry module's +// own top-level return compiles (tests/corpus/8271-cjs-top-level-return.cjs). +'use strict'; + +const lib = require('./lib.js'); +console.log(lib.ready, lib.value); diff --git a/tests/diagnostics/cjs-required-top-level-return/package.json b/tests/diagnostics/cjs-required-top-level-return/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/diagnostics/cjs-required-top-level-return/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/harness/__snapshots__/cjs-required-top-level-return/main.js.txt b/tests/harness/__snapshots__/cjs-required-top-level-return/main.js.txt new file mode 100644 index 000000000..db3d72b88 --- /dev/null +++ b/tests/harness/__snapshots__/cjs-required-top-level-return/main.js.txt @@ -0,0 +1,6 @@ +cjs-required-top-level-return/lib.js:4:43 - error SC1090: top-level return statements in CommonJS modules the program requires (only the entry module can end its evaluation early) are not supported yet + + 3 | exports.ready = true; + 4 | if (process.env.SCRIPTC_SKIP_LIB === '1') return; + | ^~~~~~~ + 5 | exports.value = 42; \ No newline at end of file diff --git a/tests/harness/corpus-costs/8270-fs-promises-member.cjs.json b/tests/harness/corpus-costs/8270-fs-promises-member.cjs.json new file mode 100644 index 000000000..04488e737 --- /dev/null +++ b/tests/harness/corpus-costs/8270-fs-promises-member.cjs.json @@ -0,0 +1 @@ +{ "plain": 4040, "san": 4008 } diff --git a/tests/harness/corpus-costs/8271-cjs-top-level-return.cjs.json b/tests/harness/corpus-costs/8271-cjs-top-level-return.cjs.json new file mode 100644 index 000000000..33a1c426c --- /dev/null +++ b/tests/harness/corpus-costs/8271-cjs-top-level-return.cjs.json @@ -0,0 +1 @@ +{ "plain": 1907, "san": 2109 } diff --git a/tests/harness/corpus-costs/8272-fs-stream-call-without-new.cjs.json b/tests/harness/corpus-costs/8272-fs-stream-call-without-new.cjs.json new file mode 100644 index 000000000..dab2504e6 --- /dev/null +++ b/tests/harness/corpus-costs/8272-fs-stream-call-without-new.cjs.json @@ -0,0 +1 @@ +{ "plain": 2099, "san": 2373 } diff --git a/tests/harness/corpus-costs/8273-fs-read-write-argument-checks.cjs.json b/tests/harness/corpus-costs/8273-fs-read-write-argument-checks.cjs.json new file mode 100644 index 000000000..1989bfb5f --- /dev/null +++ b/tests/harness/corpus-costs/8273-fs-read-write-argument-checks.cjs.json @@ -0,0 +1 @@ +{ "plain": 2379, "san": 2450 } From 5ad3576c1e71e49750ad854cb12b4a7be36b7d66 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 07:39:14 -0500 Subject: [PATCH 058/395] Hold a Map.get result as the entry's reference until the map can change A local initialized from Map.get of a reference value retained the value and released it at the end of its scope. In the common update const cur = counts.get(key); if (cur === undefined) counts.set(key, new Counter()); else cur.num++; the map is written only on the branch that does not read the local, so the entry keeps the value alive for every read. A stable local whose every use comes before any statement that may change a reference edge (ReferenceEffects) now holds the entry's own reference: no retain on the read and no release at scope exit. An if statement passes its condition's state to both branches and joins them; any other statement that names the local is judged whole. Number keys and inline string keys take this path, for nullable results and for the stack-boxed results of union locals. --- .../src/backend/llvm/emit-function.ts | 18 ++- .../src/backend/llvm/emit-statements.ts | 15 +- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../src/backend/llvm/expr-containers.ts | 59 ++++++++ .../src/backend/llvm/map-number-lookup.ts | 25 ++-- .../backend/llvm/map-read-lifetimes.test.ts | 10 +- .../src/backend/llvm/map-read-lifetimes.ts | 96 ++++++++++++- .../test/map-get-borrow-emission.test.ts | 58 ++++++++ .../tests/corpus/8181-map-get-borrows.ts.json | 6 + tests/corpus/8181-map-get-borrows.ts | 130 ++++++++++++++++++ .../corpus-costs/8181-map-get-borrows.ts.json | 1 + 11 files changed, 405 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/map-get-borrow-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-map-get-borrows.ts.json create mode 100644 tests/corpus/8181-map-get-borrows.ts create mode 100644 tests/harness/corpus-costs/8181-map-get-borrows.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index 8f306035a..4f04df59c 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -20,7 +20,12 @@ import { constructorClass } from "../../ir/final-fields.js"; import { findLoopArrayBorrows } from "./loop-array-borrows.js"; import { findCallArrayReads, findLocalArrayReads } from "./local-array-reads.js"; import { findScalarStringSlices, findScratchStringSlices } from "./string-slices.js"; -import { findMapReadLifetimes, findReadNumberMapWrites } from "./map-read-lifetimes.js"; +import { + findMapReadBorrows, + findMapReadLifetimes, + findReadNumberMapWrites, +} from "./map-read-lifetimes.js"; +import { borrowableMapGet } from "./expr-containers.js"; import { findMapEntryFusions } from "./map-entry-fusion.js"; import { findLocalStackUnions } from "./stack-unions.js"; import { findLocalStackObjects, findReassignedStackObjects } from "./stack-objects.js"; @@ -193,6 +198,17 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { fn, emitter.boxedUnionsById, emitter.callLifetimes, + emitter.referenceEffects, + ); + const stable = emitter.callLifetimes.bindings.get(fn.name); + emitter.borrowedMapGets = findMapReadBorrows( + fn, + emitter.referenceEffects, + (decl) => + decl.init !== null && + stable?.has(decl.localId) === true && + !emitter.mapReadLifetimes.locals.has(decl.localId) && + borrowableMapGet(emitter, decl.init), ); emitter.readNumberMapWrites = findReadNumberMapWrites(fn); emitter.mapEntryFusion = emitter.debug === null ? findMapEntryFusions(fn) : null; diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 55e31219c..fdcc54b55 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -32,7 +32,7 @@ import { allocateLocalUnion, storeLocalUnion } from "./local-union-storage.js"; import { boxAccess, boxNewCall, elemAccess } from "./shapes.js"; import { dynHoleRead } from "./loop-array-borrows.js"; import { endsWithJump, matchStringSelfConcat } from "../../ir/analysis.js"; -import { charAtUnitRead, emitDiscardedSplice } from "./expr-containers.js"; +import { charAtUnitRead, emitBorrowedMapGet, emitDiscardedSplice } from "./expr-containers.js"; import { InternalCompilerError } from "../../errors.js"; import { emitDenseArrayGet, @@ -104,6 +104,19 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { break; } const b = emitter.binding(s.localId); + if ( + emitter.borrowedMapGets.has(s.localId) && + b.kind === "local" && + s.init?.kind === "mapIntrinsic" + ) { + // The map entry's own reference: no retain now, no release at scope + // exit (findMapReadBorrows). + const borrowed = emitBorrowedMapGet(emitter, s.init); + if (borrowed) { + B.line(`store ptr ${borrowed.name}, ptr ${b.slot}`); + break; + } + } if (emitter.walkBorrows.has(s.localId) && b.kind === "local") { // A borrowed walk pointer: no retain now, no release at scope exit. const v = s.init === null ? "null" : emitter.emitReadReceiver(s.init).name; diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6760f3b74..53cb99c9e 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1145,6 +1145,9 @@ export class LlEmitter { readonly checkedNarrows: CheckedNarrows; callArrayReads = new Map(); mapReadLifetimes: MapReadLifetimes = { locals: new Map(), arguments: new Map() }; + /** Locals of the current function that hold a Map.get result as the + * entry's own reference (findMapReadBorrows, borrowableMapGet). */ + borrowedMapGets: ReadonlySet = new Set(); readNumberMapWrites: ReadonlySet = new Set(); /** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */ mapEntryFusion: MapEntryFusion | null = null; diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 279b8c044..906f03b76 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -1127,6 +1127,65 @@ export function emitArrIntrinsic( } } +/** Whether `e` is a Map.get that emitBorrowedMapGet can emit without a + * retain: a typed map with number or inline string keys, a reference value, + * and a nullable result (the value's pointer, or the absent arm's). */ +export function borrowableMapGet(host: LlvmEmitterContext, e: IrExpr): boolean { + if (e.kind !== "mapIntrinsic" || e.method !== "get" || e.args.length !== 1) return false; + if (!borrowsMapReadInputs(e) || e.receiver.type.kind !== "map") return false; + const { key, value } = e.receiver.type; + if (!isRefCounted(value) || value.kind === "union" || key.kind === "union") return false; + if (e.type.kind !== "union" || !host.nullableUnions.has(e.type.unionId)) return false; + const arg = e.args[0]!; + const access = mapKeyAccess(key); + return ( + (access === "f64" && arg.type.kind === "f64") || + (access === "str" && arg.type.kind === "string") + ); +} + +/** A borrowableMapGet read as the entry's own reference: no retain, and + * no owner. The caller proves that nothing can change the map while the + * value is used (findMapReadBorrows). Null, before emitting anything, for + * a key held as a split span, which takes the runtime's owned lookup. */ +export function emitBorrowedMapGet( + host: LlvmEmitterContext, + e: IrExpr & { kind: "mapIntrinsic" }, +): LlValue | null { + const arg = e.args[0]!; + if (arg.kind === "varRef" && host.splitSpans.get(arg.localId) !== undefined) return null; + const B = host.B; + const mapType = e.receiver.type as IrType & { kind: "map" }; + const r = host.emitStableReceiver(e.receiver, e.args); + const k = emitMapLookupKey(host, e.args[0]!, true); + const numberKey = inlineNumberKey(k); + const stringKey = entryStringKey(k); + const value = mapType.value; + let raw: string; + if (numberKey !== null) raw = emitNumberMapGetRef(host, r.name, numberKey, value, true); + else if (stringKey !== null) + raw = emitStringMapGetRef( + host, + r.name, + stringKey, + value, + compactMapValues(host, mapType.key, value), + true, + ); + else throw new InternalCompilerError("llvm emitter bug: borrowed Map.get without an inline key"); + const resultType = e.type as IrType & { kind: "union" }; + const absent = host.unitInstanceRef( + resultType.unionId, + undefinedArmTag(resultType, host.unionsById), + ); + if (absent === "null") return { name: raw, type: e.type }; + const isnull = B.tmp(), + t = B.tmp(); + B.line(`${isnull} = icmp eq ptr ${raw}, null`); + B.line(`${t} = select i1 ${isnull}, ptr ${absent}, ptr ${raw}`); + return { name: t, type: e.type }; +} + export function wrapNullable( host: LlvmEmitterContext, raw: string, diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.ts b/packages/compiler/src/backend/llvm/map-number-lookup.ts index 6d219b50a..64bc3516e 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.ts @@ -122,14 +122,16 @@ export function emitStringMapGetScalar( } /** Map.get of a reference value: a +1 value, or null when absent — the - * contract of scr_map_get_f64_ref. */ + * contract of scr_map_get_f64_ref. `borrow`: the entry's own reference, + * without a retain, for a reader that finishes before the map can change. */ export function emitNumberMapGetRef( host: LlvmEmitterContext, map: string, key: string, value: IrType, + borrow = false, ): string { - return emitMapEntryGetRef(host, emitNumberMapEntry(host, map, key), value); + return emitMapEntryGetRef(host, emitNumberMapEntry(host, map, key), value, false, borrow); } /** Map.get of a reference value under a string key, with scr_map_get_str_ref's @@ -141,16 +143,25 @@ export function emitStringMapGetRef( key: string, value: IrType, compact = false, + borrow = false, ): string { - return emitMapEntryGetRef(host, emitStringMapEntry(host, map, key, compact), value, compact); + return emitMapEntryGetRef( + host, + emitStringMapEntry(host, map, key, compact), + value, + compact, + borrow, + ); } -/** The +1 value of a found entry, or null for a missing (null) entry. */ +/** The +1 value of a found entry (its own reference when `borrow`), or + * null for a missing (null) entry. */ function emitMapEntryGetRef( host: LlvmEmitterContext, entry: string, value: IrType, compact = false, + borrow = false, ): string { const B = host.B; const found = B.tmp(); @@ -160,10 +171,8 @@ function emitMapEntryGetRef( join = B.newLabel("mapn.join"); B.condBr(found, hit, miss); B.startBlock(hit); - const retained = host.retainFrame( - compact ? emitCompactMapSlot(host, entry) : emitNumberMapSlot(host, entry, "ptr"), - value, - ); + const loaded = compact ? emitCompactMapSlot(host, entry) : emitNumberMapSlot(host, entry, "ptr"); + const retained = borrow ? loaded : host.retainFrame(loaded, value); B.br(join); B.startBlock(miss); B.br(join); diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.test.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.test.ts index 3725db060..d91e2b211 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.test.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.test.ts @@ -120,7 +120,12 @@ test("local map results keep scalar and owned reference payloads without heap wr expect(ir).toContain("call ptr @sc_map_entry_str("); expect(ir).not.toContain("@scr_map_get_"); } - if (value === STRING) expect(ir).toContain("@scr_str_release"); + // Nothing can change the map before the tag test, so a reference + // result is the entry's own (findMapReadBorrows): no retain or release. + if (value === STRING) { + expect(ir).not.toContain("@scr_str_retain"); + expect(ir).not.toContain("@scr_str_release"); + } } } }); @@ -132,7 +137,8 @@ test("nullable record results are the looked-up pointer itself on both ABIs", () expect(ir).not.toContain("%ScrUnion"); expect(ir).not.toContain("@scr_union_"); expect(ir).toMatch(/icmp (?:eq|ne) ptr %\w+, null/); - expect(ir).toContain("@sc_rrelease_"); + // The pointer is the entry's own reference until the tag test. + expect(ir).not.toContain("@sc_rrelease_"); } }); diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index 6b58d314a..0f22f9e05 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -9,6 +9,7 @@ import { } from "../../ir/ir.js"; import { everyExpr, everyStmt, everyStmtList } from "../../ir/traverse.js"; import type { CallLifetimes } from "./call-lifetimes.js"; +import type { ReferenceEffects } from "./reference-effects.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { emitNumberMapGetRef, @@ -68,6 +69,10 @@ export function borrowsMapMutationReceiver(expr: IrExpr): boolean { export interface LocalMapRead { /** The Map.get expression itself. */ expr: IrExpr; + /** A local's reference result may stay the entry's own reference: every + * use of the local runs before anything that can change a reference + * edge (findMapReadBorrows), so the entry still holds the value. */ + borrowed?: boolean; type: IrType; receiver: IrExpr; key: IrExpr; @@ -126,6 +131,7 @@ export function findMapReadLifetimes( fn: IrFunction, unions: ReadonlyMap, lifetimes: CallLifetimes, + effects?: ReferenceEffects, ): MapReadLifetimes { const result: MapReadLifetimes = { locals: new Map(), arguments: new Map() }; if (fn.async || fn.generator) return result; @@ -150,6 +156,85 @@ export function findMapReadLifetimes( return true; }, }); + if (effects) { + const borrowed = findMapReadBorrows(fn, effects, (decl) => { + const read = result.locals.get(decl.localId); + return read !== undefined && isRefCounted(read.value); + }); + for (const id of borrowed) result.locals.get(id)!.borrowed = true; + } + return result; +} + +/** The locals, declared by statements `candidate` accepts, that can hold a + * map entry's own reference instead of a +1 copy. The statements after the + * declaration run in order, and every one that names the local must + * preserve reference edges (ReferenceEffects) and run before any statement + * that may not, so no write, deletion or unknown call can release the value + * while the local is read. An `if` passes its condition's state to both + * branches and joins them; any other statement that names the local is + * judged whole. A later pass of an enclosing loop declares the local again, + * so only the rest of its own statement list matters. An update such as + * `const c = m.get(k); if (c === undefined) m.set(k, v); else c.n++` keeps + * the borrow: the write runs only on the branch that does not read `c`. */ +export function findMapReadBorrows( + fn: IrFunction, + effects: ReferenceEffects, + candidate: (decl: IrStmt & { kind: "varDecl" }) => boolean, +): Set { + const result = new Set(); + if (fn.async || fn.generator) return result; + const names = (node: IrStmt | IrExpr, id: string, isStmt: boolean): boolean => { + let found = false; + const visitor = { + stmt: () => !found, + expr: (expr: IrExpr) => { + if (expr.kind === "varRef" && expr.localId === id) found = true; + return !found; + }, + }; + if (isStmt) everyStmt(node as IrStmt, visitor); + else everyExpr(node as IrExpr, visitor); + return found; + }; + // Whether an edge may have changed after `list`, or null when a statement + // names the local after such a change (or changes edges itself). + const walk = (list: readonly IrStmt[], id: string, changed: boolean): boolean | null => { + for (const stmt of list) { + if (stmt.kind === "if") { + const preserving = effects.preserves(stmt.cond); + if (names(stmt.cond, id, false) && (changed || !preserving)) return null; + const condition = changed || !preserving; + const then = walk(stmt.then, id, condition); + const otherwise = stmt.else_ === null ? condition : walk(stmt.else_, id, condition); + if (then === null || otherwise === null) return null; + changed = then || otherwise; + continue; + } + if (stmt.kind === "block") { + const inner = walk(stmt.body, id, changed); + if (inner === null) return null; + changed = inner; + continue; + } + const preserving = effects.preservesScope([stmt]); + if (names(stmt, id, true) && (changed || !preserving)) return null; + changed ||= !preserving; + } + return changed; + }; + const visit = (list: readonly IrStmt[]): void => { + list.forEach((stmt, index) => { + if ( + stmt.kind === "varDecl" && + candidate(stmt) && + walk(list.slice(index + 1), stmt.localId, false) !== null + ) + result.add(stmt.localId); + for (const nested of nestedStmtLists(stmt)) visit(nested); + }); + }; + visit(fn.body); return result; } @@ -303,13 +388,14 @@ export function emitStackMapRead(host: LlvmEmitterContext, read: LocalMapRead): let owner: StackMapRead["owner"] = null; const number = inlineNumberKey(key); const string = entryStringKey(key); + const borrow = read.borrowed === true; if (number !== null) { if (isRefCounted(read.value)) { - const raw = emitNumberMapGetRef(host, receiver.name, number, read.value); + const raw = emitNumberMapGetRef(host, receiver.name, number, read.value, borrow); B.line(`${found} = icmp ne ptr ${raw}, null`); B.line(`store i64 0, ptr ${payload}`); B.line(`store ptr ${raw}, ptr ${payload}`); - owner = { slot: payload, type: read.value }; + if (!borrow) owner = { slot: payload, type: read.value }; } else { // Booleans are stored as 0/1, already the widened union payload. const scalar = emitNumberMapGetScalar(host, receiver.name, number); @@ -326,8 +412,10 @@ export function emitStackMapRead(host: LlvmEmitterContext, read: LocalMapRead): let raw: string; const mapType = read.receiver.type; const compact = mapType.kind === "map" && compactMapValues(host, mapType.key, mapType.value); + // The runtime's other key kinds return +1, which the frame then owns. + const borrowed = borrow && stringKey !== null; if (stringKey !== null) - raw = emitStringMapGetRef(host, receiver.name, stringKey, read.value, compact); + raw = emitStringMapGetRef(host, receiver.name, stringKey, read.value, compact, borrowed); else { raw = B.tmp(); host.declare(`declare ptr @scr_map_get_${access}_ref(ptr, ${keyType})`); @@ -336,7 +424,7 @@ export function emitStackMapRead(host: LlvmEmitterContext, read: LocalMapRead): B.line(`${found} = icmp ne ptr ${raw}, null`); B.line(`store i64 0, ptr ${payload}`); B.line(`store ptr ${raw}, ptr ${payload}`); - owner = { slot: payload, type: read.value }; + if (!borrowed) owner = { slot: payload, type: read.value }; } else { const scalar = read.value.kind === "f64" ? "f64" : "bool"; B.line(`store i64 0, ptr ${payload}`); diff --git a/packages/compiler/test/map-get-borrow-emission.test.ts b/packages/compiler/test/map-get-borrow-emission.test.ts new file mode 100644 index 000000000..950fe35c2 --- /dev/null +++ b/packages/compiler/test/map-get-borrow-emission.test.ts @@ -0,0 +1,58 @@ +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 Map.get local read before any reference write holds the entry's reference", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-map-get-borrow-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile( + entry, + ` +class Counter { + num = 1; +} +function count(m: Map, keys: number[]): void { + for (const key of keys) { + const cur = m.get(key); + if (cur === undefined) m.set(key, new Counter()); + else cur.num++; + } +} +function named(m: Map, key: string): number { + const cur = m.get(key); + return cur === undefined ? 0 : cur.num; +} +function replaced(m: Map): number { + const old = m.get(1); + m.set(1, new Counter()); + return old === undefined ? 0 : old.num; +} +const numbers = new Map(); +count(numbers, [1, 2, 1]); +console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers)); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const mod = deserializeModule(await readFile(outPath, "utf8")); + const ll = emitLlvmModule(mod); + const fn = (name: string): string => + new RegExp(`^define internal [^\\n]*@sc_bf_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec(ll)![0]; + // The update writes the map only where the entry is absent, and a + // string key's lookup is inline: neither read retains the value. + for (const name of ["count", "named"]) { + expect(fn(name)).not.toContain("@sc_retain_Counter"); + expect(fn(name)).not.toContain("@sc_release_Counter"); + } + // A write between the read and its use keeps the read's own reference. + expect(fn("replaced")).toContain("@sc_retain_Counter"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-map-get-borrows.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-map-get-borrows.ts.json new file mode 100644 index 000000000..39f43aa85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-map-get-borrows.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8181-map-get-borrows.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8181-map-get-borrows.ts b/tests/corpus/8181-map-get-borrows.ts new file mode 100644 index 000000000..be546cf73 --- /dev/null +++ b/tests/corpus/8181-map-get-borrows.ts @@ -0,0 +1,130 @@ +// Map.get results held in locals across the operations that can and cannot +// release the entry's value: an update on the absent branch, a write, delete +// or clear before a later read, a call that may change the map, loops, +// string keys, and values that are stored, returned or compared. +class Counter { + num: number; + label: string; + constructor(num: number, label: string) { + this.num = num; + this.label = label; + } +} + +function count(keys: number[]): Map { + const counts = new Map(); + for (const key of keys) { + const cur = counts.get(key); + if (cur === undefined) counts.set(key, new Counter(1, `k${key}`)); + else cur.num++; + } + return counts; +} + +function report(counts: Map): string { + const parts: string[] = []; + for (const [key, value] of counts) parts.push(`${key}:${value.num}:${value.label}`); + return parts.join(" "); +} + +console.log("count", report(count([3, 1, 3, 2, 3, 1, 9]))); + +// A write replaces the entry's value while the old one is still read. +function replaceThenRead(m: Map): string { + const old = m.get(1); + m.set(1, new Counter(100, "new")); + return old === undefined ? "none" : `${old.num}:${old.label}`; +} +const replaced = new Map([[1, new Counter(5, "old")]]); +console.log("replace", replaceThenRead(replaced), replaced.get(1)!.label); + +// Deleting and clearing release the value before the read. +function deleteThenRead(m: Map): string { + const held = m.get(2); + m.delete(2); + return held === undefined ? "none" : held.label; +} +function clearThenRead(m: Map): string { + const held = m.get(2); + m.clear(); + return held === undefined ? "none" : `${held.label}:${m.size}`; +} +console.log("delete", deleteThenRead(new Map([[2, new Counter(1, "two")]]))); +console.log("clear", clearThenRead(new Map([[2, new Counter(1, "two")]]))); + +// A call that may change the map runs between the read and the use. +let shared = new Map([[7, new Counter(7, "seven")]]); +function mutate(): void { + shared.set(7, new Counter(70, "seventy")); + shared = new Map(); +} +function callBetween(): string { + const held = shared.get(7); + mutate(); + return held === undefined ? "none" : `${held.num}:${held.label}`; +} +console.log("call", callBetween()); + +// Reads on both branches, a write on one of them, and a read after the join. +function branches(m: Map, k: number, write: boolean): string { + const held = m.get(k); + if (held === undefined) return "absent"; + if (write) m.set(k, new Counter(0, "zero")); + else held.num += 10; + return `${held.num}:${held.label}`; +} +const branchMap = new Map([[4, new Counter(4, "four")]]); +console.log("branches", branches(branchMap, 4, false), branches(branchMap, 4, true), branches(branchMap, 5, true)); + +// The value escapes into an array and a returned object. +function escape(m: Map): Counter[] { + const kept: Counter[] = []; + for (let k = 0; k < 4; k++) { + const value = m.get(k % 2); + if (value !== undefined) kept.push(value); + } + m.clear(); + return kept; +} +const escapes = escape(new Map([[0, new Counter(0, "zero")], [1, new Counter(1, "one")]])); +console.log("escape", escapes.map((c) => c.label).join(",")); +function returned(m: Map): Counter | undefined { + const value = m.get(3); + return value; +} +const returnMap = new Map([[3, new Counter(3, "three")]]); +const got = returned(returnMap); +returnMap.clear(); +console.log("returned", got?.label); + +// String keys, including keys built at run time. +function words(text: string): string { + const seen = new Map(); + for (const word of text.split(" ")) { + const entry = seen.get(word); + if (entry === undefined) seen.set(word, new Counter(1, word.toUpperCase())); + else entry.num++; + } + const parts: string[] = []; + for (const [word, entry] of seen) parts.push(`${word}=${entry.num}/${entry.label}`); + return parts.join(" "); +} +console.log("words", words("a b a c b a")); +const prefix = "k"; +const byName = new Map([["k1", new Counter(1, "one")]]); +const named = byName.get(prefix + "1"); +byName.set("k1", new Counter(2, "two")); +console.log("named", named?.label, byName.get("k1")?.label); + +// A loop whose body writes the map after the read on every pass. +function loopWrites(m: Map): number { + let total = 0; + for (let i = 0; i < 5; i++) { + const value = m.get(i % 2); + if (value !== undefined) total += value.num; + m.set(i % 2, new Counter(i, "loop")); + if (value !== undefined) total += value.num * 100; + } + return total; +} +console.log("loop", loopWrites(new Map([[0, new Counter(1, "a")], [1, new Counter(2, "b")]]))); diff --git a/tests/harness/corpus-costs/8181-map-get-borrows.ts.json b/tests/harness/corpus-costs/8181-map-get-borrows.ts.json new file mode 100644 index 000000000..d5d1c5f65 --- /dev/null +++ b/tests/harness/corpus-costs/8181-map-get-borrows.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3500 } From ab9cfd7e91cf2844ff6033d60a54ac299ebb5916 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 07:53:04 -0500 Subject: [PATCH 059/395] Speculate integer loops through pure helper calls, powers of two and byte reads - A speculated write may call a function whose body is one return of arithmetic, Math.floor and Math.pow over its number parameters. The fast copy evaluates the expanded body; the double copy still calls the function. Union tests on the parameters (`number | undefined`) fold against the wrapped argument. - `Math.pow(2, n)` and `Math.floor(a / 2^n)` run as checked i64 shifts: with n an integer in [0, 52], both equal the double results for every safe integer a. - Element reads of integer typed arrays are speculative leaves, read before any check, as string and array reads are. - A write's other calls of program functions are hoisted: the fast copy evaluates each once into a local of its own, and a failed check resumes at a copy of the write that re-runs only the arithmetic over those locals. This applies only when no call can write a local the write reads. - k-nucleotide's key chain (`key = bitwiseTrimLeft(key, m) * 4 + seq[i]`) now runs in i64. --- .../src/backend/llvm/emit-statements.ts | 37 +- packages/compiler/src/backend/llvm/emitter.ts | 44 ++ .../backend/llvm/speculative-integers.test.ts | 100 +++++ .../src/backend/llvm/speculative-integers.ts | 383 +++++++++++++++++- .../7757-speculative-key-chains.ts.json | 6 + tests/corpus/7757-speculative-key-chains.ts | 67 +++ .../7757-speculative-key-chains.ts.json | 1 + 7 files changed, 613 insertions(+), 25 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7757-speculative-key-chains.ts.json create mode 100644 tests/corpus/7757-speculative-key-chains.ts create mode 100644 tests/harness/corpus-costs/7757-speculative-key-chains.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 55e31219c..ab87b3439 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2136,6 +2136,7 @@ export function emitLoopCopies( ); }, (id) => emitter.speculativeLocal(id) || emitter.speculativeInvariantGlobal(id), + emitter.speculationContext(), ) : null; if (!plan) { @@ -2193,6 +2194,20 @@ export function emitLoopCopies( B.line(`${both} = and i1 ${ok}, ${trying}`); ok = both; } + // Hoisted leaves live in locals of their own, which the resume copy of + // their write reads (see SpeculativeValue). + for (const { leaves } of plan.values.values()) + for (const leaf of leaves) { + emitter.currentLocals.set(leaf.localId, { + id: leaf.localId, + name: "speculated leaf", + type: leaf.expr.type, + mutable: true, + }); + B.entryAllocas.push( + `${emitter.binding(leaf.localId).slot} = alloca double ; speculated leaf`, + ); + } const fast = B.newLabel("spec.fast"), slow = B.newLabel("spec.slow"); B.condBr(ok, fast, slow); @@ -2218,6 +2233,7 @@ export function emitLoopCopies( throw new InternalCompilerError("llvm emitter bug: speculative loop copy without a header"); B.startBlock(B.newLabel("spec.tail")); emitter.speculationResume = resume; + emitter.speculationResumeValues = plan.values; emitter.speculationHeader = () => { B.br(header!); B.startBlock(B.newLabel("spec.unused")); @@ -2226,6 +2242,7 @@ export function emitLoopCopies( emitCopy(exit); } finally { emitter.speculationResume = null; + emitter.speculationResumeValues = null; emitter.speculationHeader = null; } B.startBlock(fast); @@ -2274,6 +2291,7 @@ export function emitLoopCopies( deferred, bails, ran, + values: plan.values, }; try { const leave = B.newLabel("spec.leave"); @@ -2531,9 +2549,22 @@ export function emitSpeculativeWrite( // at once: a bailing path then owns nothing, and reads have no effect // whose reordering before the arithmetic could be observed. const reads = new Map(); - if (target.value !== null) { + const rewritten = emitter.speculation!.values?.get(s); + const value = rewritten?.value ?? target.value; + if (rewritten !== undefined) { + // Hoisted leaves run first, in the write's order, and keep their + // doubles for the resume copy. emitter.frames.push([]); - for (const read of speculativeReads(target.value)) reads.set(read, emitter.emitExpr(read)); + for (const leaf of rewritten.leaves) { + const evaluated = emitter.emitExpr(leaf.expr); + B.line(`store double ${evaluated.name}, ptr ${emitter.binding(leaf.localId).slot}`); + reads.set(leaf.ref, evaluated); + } + emitter.releaseTemps(emitter.frames.pop()!); + } + if (value !== null) { + emitter.frames.push([]); + for (const read of speculativeReads(value)) reads.set(read, emitter.emitExpr(read)); emitter.releaseTemps(emitter.frames.pop()!); } emitter.frames.push([]); @@ -2548,7 +2579,7 @@ export function emitSpeculativeWrite( B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`); B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`); bail(outside); - } else result = emitSpeculativeInteger(emitter, target.value, bail, reads, resume === undefined); + } else result = emitSpeculativeInteger(emitter, value!, bail, reads, resume === undefined); // Speculable values read only numbers past this point. if (emitter.frames.pop()!.length > 0) throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary"); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6760f3b74..4080084de 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -338,6 +338,13 @@ import { } from "./shapes.js"; import type { ExprOf, LibCallExpr, LlStreamTypedRefAdapter, LlValue } from "./expr-context.js"; import { INLINE_LITERAL_BYTES } from "./string-literal-equality.js"; +import { + inlinableNumberFunction, + withSpeculativeValue, + type InlinableFunction, + type SpeculationContext, + type SpeculativeValue, +} from "./speculative-integers.js"; import { emitFfiCallbackDefs, emitLibDefs, @@ -1435,11 +1442,41 @@ export class LlEmitter { * integer loop has neither. */ bails?: string; ran?: string; + /** Writes that speculate a rewritten value (rewriteSpeculable). */ + values?: ReadonlyMap; } | null = null; /** Speculative loop sites numbered so far in the module. */ speculativeLoopSites = 0; /** The ordinary copy being emitted: resume labels still to place. */ speculationResume: Map | null = null; + /** The rewritten values of the writes `speculationResume` resumes at. */ + speculationResumeValues: ReadonlyMap | null = null; + /** Hoisted speculative leaves numbered so far in the module. */ + private speculativeLeaves = 0; + private readonly inlinableFunctions = new Map(); + + /** What speculative loop planning may assume here (SpeculationContext). */ + speculationContext(): SpeculationContext { + return { + inlinable: (callee) => { + if (!this.inlinableFunctions.has(callee)) { + const fn = this.fnByName.get(callee); + this.inlinableFunctions.set(callee, fn ? inlinableNumberFunction(fn) : undefined); + } + return this.inlinableFunctions.get(callee); + }, + unaliased: (id) => { + const local = this.currentLocals.get(id); + return ( + local !== undefined && + !local.boxed && + !this.captureIds.has(id) && + !this.lazyCaptureBoxes.has(id) + ); + }, + leafId: () => `%spec.leaf.${this.speculativeLeaves++}`, + }; + } /** Run once at the top of the next loop copy's header * (`enterLoopHeader`): records the ordinary copy's header, or sends the * third copy's header there. */ @@ -5591,6 +5628,13 @@ export class LlEmitter { resume.placed = true; this.B.br(resume.label); this.B.startBlock(resume.label); + // A write with hoisted leaves re-executes its arithmetic over the + // leaves' stored doubles: each leaf ran once, in the fast copy. + const rewritten = this.speculationResumeValues?.get(s); + if (rewritten !== undefined && rewritten.leaves.length > 0) { + this.emitStmt(withSpeculativeValue(s, rewritten.value)); + return; + } } if (this.speculation?.writes.has(s)) { this.emitSpeculativeWrite(s, this.speculation.resume.get(s)); diff --git a/packages/compiler/src/backend/llvm/speculative-integers.test.ts b/packages/compiler/src/backend/llvm/speculative-integers.test.ts index 3acfec0fb..86055b1b6 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.test.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.test.ts @@ -1,9 +1,11 @@ import { expect, test } from "vitest"; import { F64, type IrExpr, type IrStmt } from "../../ir/ir.js"; import { + inlinableNumberFunction, isSpeculableValue, planProvenIntegerLoop, planSpeculativeLoop, + rewriteSpeculable, speculativeReads, } from "./speculative-integers.js"; @@ -266,3 +268,101 @@ test("bounded cursors with proven writes are shadowed in a proven integer loop", planProvenIntegerLoop(forLoop([...body, jump]) as never, cursors, all, () => false, all), ).toBeNull(); }); + +const libCall = (fn: string, args: IrExpr[]): IrExpr => + ({ kind: "libCall", fn, args, type: F64, loc }) as never; +const call = (callee: string, args: IrExpr[]): IrExpr => ({ + kind: "call", + callee, + args, + type: F64, + loc, +}); +const pow2 = (n: IrExpr): IrExpr => libCall("math.pow", [num(2), n]); +/** `function trim(v, n) { return v - Math.floor(v / Math.pow(2, n)) * Math.pow(2, n); }` */ +const trim = { + name: "trim", + params: [ + { localId: "v", name: "v", type: F64 }, + { localId: "n", name: "n", type: F64 }, + ], + returnType: F64, + locals: [], + body: [ + { + kind: "return", + value: bin( + "-", + ref("v"), + bin("*", libCall("math.floor", [bin("/", ref("v"), pow2(ref("n")))]), pow2(ref("n"))), + ), + loc, + }, + ], + loc, +} as never; + +test("calls of small pure number functions expand into a speculated write", () => { + const inlinable = inlinableNumberFunction(trim)!; + expect(inlinable.params).toEqual(["v", "n"]); + const context = { inlinable: (callee: string) => (callee === "trim" ? inlinable : undefined) }; + // key = trim(key, m) * 4 + c + const value = bin("+", bin("*", call("trim", [ref("key"), ref("m")]), num(4)), ref("c")); + expect(isSpeculableValue(value)).toBe(false); + const rewritten = rewriteSpeculable(value, context)!; + expect(rewritten.leaves).toEqual([]); + expect(JSON.stringify(rewritten.value)).not.toContain('"call"'); + const write = assign("key", value); + const plan = planSpeculativeLoop( + loop([write, increment("i")]) as never, + all, + () => false, + () => false, + all, + context, + )!; + expect(plan.writes.has(write)).toBe(true); + expect(plan.values.get(write)).toEqual(rewritten); + // `m`, an input of the expanded body, is checked on entry with the others. + expect([...plan.locals.keys()].sort()).toEqual(["c", "i", "key", "m", "n"]); +}); + +test("Math.floor needs a power-of-two divisor, and expansion pure arguments", () => { + const context = { inlinable: () => inlinableNumberFunction(trim) }; + expect(rewriteSpeculable(libCall("math.floor", [bin("/", ref("a"), num(8))]), {})).not.toBeNull(); + expect(rewriteSpeculable(libCall("math.floor", [bin("/", ref("a"), num(6))]), {})).toBeNull(); + expect(rewriteSpeculable(libCall("math.floor", [bin("/", ref("a"), ref("b"))]), {})).toBeNull(); + // An argument that is itself a call is evaluated once: no expansion. + expect(rewriteSpeculable(call("trim", [call("other", []), ref("m")]), context)).toBeNull(); + const effectful = { + ...(trim as object), + body: [{ kind: "exprStmt", expr: call("log", []), loc }], + }; + expect(inlinableNumberFunction(effectful as never)).toBeUndefined(); +}); + +test("other leaves are hoisted only when no call can change the write's locals", () => { + let next = 0; + const leafId = (): string => `%leaf.${next++}`; + const value = bin( + "%", + bin("+", bin("*", ref("acc"), num(7)), call("noisy", [ref("i")])), + num(1000003), + ); + const hoisted = rewriteSpeculable(value, { unaliased: () => true, leafId })!; + expect(hoisted.leaves.map((leaf) => leaf.expr)).toEqual([call("noisy", [ref("i")])]); + expect(rewriteSpeculable(value, { unaliased: (id) => id !== "acc", leafId })).toBeNull(); + // Without leaf ids nothing is hoisted. + expect(rewriteSpeculable(value, { unaliased: () => true })).toBeNull(); + const write = assign("acc", value); + const plan = planSpeculativeLoop( + loop([write, increment("i")]) as never, + (id) => !id.startsWith("%leaf"), + () => false, + () => false, + (id) => !id.startsWith("%leaf"), + { unaliased: () => true, leafId }, + )!; + expect(plan.values.get(write)!.leaves).toHaveLength(1); + expect([...plan.locals.keys()].some((id) => id.startsWith("%leaf"))).toBe(false); +}); diff --git a/packages/compiler/src/backend/llvm/speculative-integers.ts b/packages/compiler/src/backend/llvm/speculative-integers.ts index 00fabb788..69066a5b4 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.ts @@ -1,5 +1,6 @@ -import type { IrExpr, IrStmt } from "../../ir/ir.js"; +import type { IrExpr, IrFunction, IrStmt } from "../../ir/ir.js"; import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; +import { InternalCompilerError } from "../../errors.js"; import { f64Lit } from "./common.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { exactInteger, widenInteger } from "./integer-values.js"; @@ -30,6 +31,273 @@ export interface SpeculativeLoopPlan { * than through its condition, `break` or a failed check: no calls, no * throwing operations, no labeled jumps and no `return`. */ observerFree: boolean; + /** The writes that speculate a rewritten value (rewriteSpeculable). */ + values: Map; +} + +/** A write's value as speculation evaluates it: calls of small pure number + * functions expanded in place, and other number leaves (`leaves`) replaced + * by references to fresh locals. The fast copy evaluates each leaf once, + * stores it to its local and checks it as an integer; a failed check + * resumes at a copy of the write that reads the locals, so a leaf is never + * evaluated twice. */ +export interface SpeculativeValue { + value: IrExpr; + leaves: { localId: string; expr: IrExpr; ref: IrExpr }[]; +} + +/** What the planner may assume about the code around a loop. */ +export interface SpeculationContext { + /** The parameters and the returned expression of `callee` when it is a + * small pure number function (inlinableNumberFunction). */ + inlinable?: (callee: string) => InlinableFunction | undefined; + /** A local that no call can write: not captured, boxed or global. */ + unaliased?: (localId: string) => boolean; + /** A fresh local id for a hoisted leaf. */ + leafId?: () => string; +} + +export interface InlinableFunction { + params: readonly string[]; + body: IrExpr; +} + +/** `s` (a speculated write) writing `value` instead. */ +export function withSpeculativeValue(s: IrStmt, value: IrExpr): IrStmt { + switch (s.kind) { + case "assign": + return { ...s, value }; + case "varDecl": + return { ...s, init: value }; + case "exprStmt": + if (s.expr.kind === "assignExpr") return { ...s, expr: { ...s.expr, value } }; + break; + default: + break; + } + throw new InternalCompilerError(`llvm emitter bug: speculated ${s.kind} with a rewritten value`); +} + +/** Calls nest at most this deep in one expansion. */ +const MAX_INLINE_DEPTH = 4; +/** An expansion larger than this stays a call. */ +const MAX_INLINE_NODES = 64; + +/** A function that only returns arithmetic over its number parameters: + * `function trim(v, n) { return v - Math.floor(v / Math.pow(2, n)) * Math.pow(2, n); }`. + * Its call evaluates nothing but its arguments, so a speculated write may + * expand it in place. */ +export function inlinableNumberFunction(fn: IrFunction): InlinableFunction | undefined { + if ( + fn.async || + fn.generator || + (fn.captures?.length ?? 0) > 0 || + (fn.classCaptures?.length ?? 0) > 0 || + fn.returnType.kind !== "f64" || + fn.params.some((p) => p.type.kind !== "f64" && p.type.kind !== "union") || + fn.body.length !== 1 || + fn.body[0]!.kind !== "return" || + fn.body[0]!.value === undefined + ) + return undefined; + const params = fn.params.map((p) => p.localId); + const known = new Set(params); + let nodes = 0; + // A parameter of a union type (`number | undefined`) is read only + // through its tag test and narrowing, which a wrapped argument folds. + const union = (e: IrExpr): boolean => e.kind === "varRef" && known.has(e.localId); + const pure = (e: IrExpr): boolean => { + if (++nodes > MAX_INLINE_NODES) return false; + if (e.kind === "boolLit") return true; + if (e.kind === "unionIsTag") return union(e.value); + if (e.kind === "unionNarrow") return e.type.kind === "f64" && union(e.value); + if (e.type.kind !== "f64") return false; + switch (e.kind) { + case "numLit": + return true; + case "varRef": + return known.has(e.localId); + case "ternary": + return pure(e.cond) && pure(e.then) && pure(e.else_); + case "bin": + return (INTEGER_OPS.has(e.op) || e.op === "/") && pure(e.left) && pure(e.right); + case "unary": + return e.op === "-" && pure(e.operand); + case "libCall": + return (e.fn === "math.floor" || e.fn === "math.pow") && e.args.every(pure); + case "call": + return e.args.every(pure); + default: + return false; + } + }; + const body = fn.body[0]!.value!; + return pure(body) ? { params, body } : undefined; +} + +/** `e` with each parameter reference replaced by its argument. A tag test + * or narrowing of a wrapped argument folds, and so does a ternary over a + * folded test. */ +function substitute(e: IrExpr, args: ReadonlyMap): IrExpr { + switch (e.kind) { + case "varRef": + return args.get(e.localId) ?? e; + case "unionIsTag": { + const value = substitute(e.value, args); + if (value.kind !== "unionWrap" || value.unionId !== e.unionId) return { ...e, value }; + return { + kind: "boolLit", + value: (value.tag === e.tag) !== (e.negated === true), + type: e.type, + loc: e.loc, + }; + } + case "unionNarrow": { + const value = substitute(e.value, args); + return value.kind === "unionWrap" && value.unionId === e.unionId && value.tag === e.tag + ? value.value + : { ...e, value }; + } + case "ternary": { + const cond = substitute(e.cond, args); + if (cond.kind === "boolLit") return substitute(cond.value ? e.then : e.else_, args); + return { ...e, cond, then: substitute(e.then, args), else_: substitute(e.else_, args) }; + } + case "bin": + return { ...e, left: substitute(e.left, args), right: substitute(e.right, args) }; + case "unary": + return { ...e, operand: substitute(e.operand, args) }; + case "libCall": + case "call": + return { ...e, args: e.args.map((a) => substitute(a, args)) }; + default: + return e; + } +} + +/** Whether `e` is a power of two speculation can shift by: + * `Math.pow(2, n)` or a literal 2^k with k in [0, 52]. */ +function powerOfTwo(e: IrExpr): boolean { + if (e.kind === "numLit") { + const k = Math.log2(e.value); + return Number.isInteger(k) && k >= 0 && k <= 52; + } + return ( + e.kind === "libCall" && + e.fn === "math.pow" && + e.args.length === 2 && + e.args[0]!.kind === "numLit" && + e.args[0]!.value === 2 + ); +} + +/** Typed-array element kinds whose elements are integers. */ +const INTEGER_ELEMENTS = new Set(["u8", "u8c", "i8", "u16", "i16", "u32", "i32"]); + +/** A typed-array element read of an integer kind with no effect: an + * out-of-range read gives NaN, which fails its check. Float elements would + * only bail. */ +function isSpeculativeByteRead(e: IrExpr): boolean { + return ( + e.kind === "bytesIntrinsic" && + e.method === "get" && + e.type.kind === "f64" && + e.receiver.type.kind === "bytes" && + INTEGER_ELEMENTS.has(e.receiver.type.elem) && + e.args.length === 1 && + isEffectFree(e.receiver) && + isEffectFree(e.args[0]!) + ); +} + +/** Rewrite a write's value that isSpeculableValue refuses into one it can + * speculate (SpeculativeValue), or null. Expansion and the new operations + * (`Math.pow(2, n)`, `Math.floor(a / 2^n)`, byte reads) evaluate nothing + * but numbers, so the fast copy may evaluate them in any order. Hoisted + * leaves run in the write's evaluation order before the arithmetic; that + * reorders nothing only when the write reads no heap and every local it + * reads is one no call can write. */ +export function rewriteSpeculable( + value: IrExpr, + context: SpeculationContext, +): SpeculativeValue | null { + const leaves: SpeculativeValue["leaves"] = []; + let reads = false; + const locals: string[] = []; + const rewrite = (e: IrExpr, depth: number): IrExpr | null => { + if (e.type.kind !== "f64") return null; + switch (e.kind) { + case "numLit": + return Number.isSafeInteger(e.value) && !Object.is(e.value, -0) ? e : null; + case "varRef": + locals.push(e.localId); + return e; + case "bin": { + if (!ARITHMETIC_OPS.has(e.op)) break; + const left = rewrite(e.left, depth); + const right = left && rewrite(e.right, depth); + return left && right ? { ...e, left, right } : null; + } + case "unary": { + if (e.op !== "-") break; + const operand = rewrite(e.operand, depth); + return operand ? { ...e, operand } : null; + } + case "libCall": { + if (powerOfTwo(e) && e.kind === "libCall") { + const exponent = rewrite(e.args[1]!, depth); + return exponent ? { ...e, args: [e.args[0]!, exponent] } : null; + } + const quotient = e.args[0]; + if ( + e.fn === "math.floor" && + e.args.length === 1 && + quotient?.kind === "bin" && + quotient.op === "/" && + powerOfTwo(quotient.right) + ) { + const left = rewrite(quotient.left, depth); + const right = left && rewrite(quotient.right, depth); + return left && right ? { ...e, args: [{ ...quotient, left, right }] } : null; + } + break; + } + case "call": { + const fn = depth < MAX_INLINE_DEPTH ? context.inlinable?.(e.callee) : undefined; + // Arguments are evaluated before the body: an expansion may read + // them in another order, or twice, only when they are pure. + const plain = (a: IrExpr): boolean => + isSpeculableValue(a, false) || + (a.kind === "unionWrap" && isSpeculableValue(a.value, false)); + if (fn !== undefined && fn.params.length === e.args.length && e.args.every(plain)) { + const args = new Map(fn.params.map((id, i) => [id, e.args[i]!])); + return rewrite(substitute(fn.body, args), depth + 1); + } + break; + } + default: + if (isSpeculativeRead(e) || isSpeculativeByteRead(e)) { + reads = true; + return e; + } + break; + } + // Only calls of program functions are hoisted: other leaves (a string's + // length, a Map read) are cheap where they stand, and would turn every + // loop that adds one into a second copy. + if (context.leafId === undefined || e.kind !== "call") return null; + const ref: IrExpr = { kind: "varRef", localId: context.leafId(), type: e.type, loc: e.loc }; + leaves.push({ localId: (ref as { localId: string }).localId, expr: e, ref }); + return ref; + }; + const rewritten = rewrite(value, 0); + if (rewritten === null) return null; + if (leaves.length > 0) { + const leafIds = new Set(leaves.map((leaf) => leaf.localId)); + if (reads || !locals.every((id) => leafIds.has(id) || context.unaliased?.(id) === true)) + return null; + } + return { value: rewritten, leaves }; } type SpeculativeLoop = Extract; @@ -170,13 +438,16 @@ function isEffectFree(e: IrExpr): boolean { } } -/** The speculative reads of a speculable value in evaluation order. */ +/** The speculative reads of a speculable value in evaluation order, those + * of a rewritten value (rewriteSpeculable) included. */ export function speculativeReads(e: IrExpr, out: IrExpr[] = []): IrExpr[] { - if (e.kind === "bin" && ARITHMETIC_OPS.has(e.op)) { + if (e.kind === "bin" && (ARITHMETIC_OPS.has(e.op) || e.op === "/")) { speculativeReads(e.left, out); speculativeReads(e.right, out); } else if (e.kind === "unary" && e.op === "-") speculativeReads(e.operand, out); - else if (isSpeculativeRead(e)) out.push(e); + else if (e.kind === "libCall" && (e.fn === "math.floor" || e.fn === "math.pow")) + for (const arg of e.args) speculativeReads(arg, out); + else if (isSpeculativeRead(e) || isSpeculativeByteRead(e)) out.push(e); return out; } @@ -195,6 +466,8 @@ function hasProduct(e: IrExpr): boolean { if (e.kind === "bin") return e.op === "*" || e.op === "%" || hasProduct(e.left) || hasProduct(e.right); if (e.kind === "unary") return hasProduct(e.operand); + // Math.floor(a / 2^n) and Math.pow(2, n) replace a division and a call. + if (e.kind === "libCall") return e.fn === "math.floor" || e.fn === "math.pow"; return false; } @@ -202,6 +475,7 @@ function hasArithmetic(e: IrExpr): boolean { if (e.kind === "bin") return ARITHMETIC_OPS.has(e.op) || hasArithmetic(e.left) || hasArithmetic(e.right); if (e.kind === "unary") return e.op === "-" || hasArithmetic(e.operand); + if (e.kind === "libCall") return e.fn === "math.floor" || e.fn === "math.pow"; return false; } @@ -236,8 +510,11 @@ export function planSpeculativeLoop( integral: (localId: string) => boolean = () => false, proven: (e: IrExpr) => boolean = () => false, invariantEligible: (localId: string) => boolean = eligible, + context: SpeculationContext = {}, ): SpeculativeLoopPlan | null { const writes = new Map(); + const values = new Map(); + const leafIds = new Set(); const unspeculable = new Set(); const reads = new Set(); // Locals with at least one read the static analysis does not prove. @@ -263,11 +540,9 @@ export function planSpeculativeLoop( // make the body's work integer. let inCondition = false; - const expr = (e: IrExpr): boolean => { - if (rejected) return false; - nodes++; - if (!OBSERVER_FREE_EXPRESSIONS.has(e.kind) || (e.kind === "bin" && e.left.type.kind !== "f64")) - observerFree = false; + // What the integer writes read and consume. A rewritten value is noted + // here alone: its original, which `expr` walks, has its effects. + const note = (e: IrExpr): void => { if (e.kind === "varRef") { reads.add(e.localId); if (!proven(e)) unproven.add(e.localId); @@ -281,6 +556,20 @@ export function planSpeculativeLoop( } else if (e.kind === "arrayGet" || e.kind === "arrayHas" || e.kind === "arrayState") consume(e.index); else if (e.kind === "bytesIntrinsic" || e.kind === "strIntrinsic") e.args.forEach(consume); + }; + // An expanded call is emitted in the fast copy: it counts toward the + // duplicated code. + const noteAll = (e: IrExpr): boolean => { + nodes++; + note(e); + return everyExprChild(e, noteAll, () => true); + }; + const expr = (e: IrExpr): boolean => { + if (rejected) return false; + nodes++; + if (!OBSERVER_FREE_EXPRESSIONS.has(e.kind) || (e.kind === "bin" && e.left.type.kind !== "f64")) + observerFree = false; + note(e); return everyExprChild(e, expr, nested); }; // Statements inside expressions (sequence expressions) run mid-expression, @@ -307,7 +596,11 @@ export function planSpeculativeLoop( if (s.kind === "arraySet" || s.kind === "bytesSet") consume(s.index); const target = speculativeWriteTarget(s); if (target) { - if (target.value === null || isSpeculableValue(target.value)) { + const rewritten = + target.value === null || isSpeculableValue(target.value) + ? null + : rewriteSpeculable(target.value, context); + if (target.value === null || isSpeculableValue(target.value) || rewritten !== null) { const list = writes.get(target.localId) ?? []; list.push(s); writes.set(target.localId, list); @@ -315,6 +608,11 @@ export function planSpeculativeLoop( reads.add(target.localId); if (s.kind !== "exprStmt" || !proven(s.expr)) unproven.add(target.localId); } else expr(target.value); + if (rewritten !== null) { + values.set(s, rewritten); + for (const leaf of rewritten.leaves) leafIds.add(leaf.localId); + noteAll(rewritten.value); + } return !rejected; } unspeculable.add(target.localId); @@ -341,25 +639,28 @@ export function planSpeculativeLoop( // write runs: speculated itself, already an integer, or loop-invariant // (checked on entry). A value computed from doubles the loop writes // would only fail its check on every entry. + const valueOf = (s: IrStmt): IrExpr | null => + values.get(s)?.value ?? speculativeWriteTarget(s)!.value; const grounded = (e: IrExpr): boolean => { if (e.kind === "varRef") { const id = e.localId; return ( + leafIds.has(id) || proven(e) || locals.has(id) || integral(id) || (!writes.has(id) && !unspeculable.has(id) && invariantEligible(id)) ); } - if (isSpeculativeRead(e)) return true; + if (isSpeculativeRead(e) || isSpeculativeByteRead(e)) return true; return everyExprChild(e, grounded, () => false); }; for (let changed = true; changed;) { changed = false; for (const id of [...locals.keys()]) { for (const s of writes.get(id)!) { - const target = speculativeWriteTarget(s)!; - if (target.value !== null && !grounded(target.value)) { + const value = valueOf(s); + if (value !== null && !grounded(value)) { locals.delete(id); changed = true; break; @@ -376,6 +677,7 @@ export function planSpeculativeLoop( if (e.kind === "varRef") { const id = e.localId; if ( + !leafIds.has(id) && !proven(e) && !locals.has(id) && !writes.has(id) && @@ -388,11 +690,11 @@ export function planSpeculativeLoop( return everyExprChild(e, invariant, () => true); }; for (const s of speculated) { - const target = speculativeWriteTarget(s)!; - if (target.value === null) arithmetic = true; + const value = valueOf(s); + if (value === null) arithmetic = true; else { - if (hasArithmetic(target.value)) arithmetic = true; - invariant(target.value); + if (hasArithmetic(value)) arithmetic = true; + invariant(value); } } if (!arithmetic) return null; @@ -401,13 +703,12 @@ export function planSpeculativeLoop( [...locals.keys()].some((id) => multiplied.has(id)) || [...speculated].some((s) => { const target = speculativeWriteTarget(s)!; - return ( - target.value !== null && - (hasProduct(target.value) || isVariableStep(target.localId, target.value)) - ); + const value = valueOf(s); + return value !== null && (hasProduct(value) || isVariableStep(target.localId, value)); }); if (!heavy) return null; - return { locals, writes: speculated, observerFree }; + for (const s of values.keys()) if (!speculated.has(s)) values.delete(s); + return { locals, writes: speculated, observerFree, values }; } /** A proven integer loop. The bindings integer-ranges.ts bounds over a for @@ -682,6 +983,19 @@ export function emitSpeculativeInteger( bail(negativeZero); return result; } + case "libCall": { + // A rewritten value's Math.pow(2, n) and Math.floor(a / 2^n) + // (rewriteSpeculable): with n an integer in [0, 52], 2^n is exact and + // so is a / 2^n for a safe integer a, whose floor the arithmetic shift + // computes, also for negative a. No result is -0. + if (e.fn === "math.pow") return shiftOne(host, exponentOf(host, e, bail, reads, integral)); + if (e.fn !== "math.floor" || e.args[0]?.kind !== "bin") break; + const a = emitSpeculativeInteger(host, e.args[0].left, bail, reads, integral); + const n = exponentOf(host, e.args[0].right, bail, reads, integral); + const result = B.tmp(); + B.line(`${result} = ashr i64 ${a}, ${n}`); + return result; + } case "unary": { if (e.op !== "-") break; const a = emitSpeculativeInteger(host, e.operand, bail, reads, integral); @@ -699,6 +1013,31 @@ export function emitSpeculativeInteger( return speculativeLeaf(host, reads.get(e) ?? host.emitExpr(e), e, bail); } +/** The exponent of a power of two (Math.pow(2, n) or a literal 2^k) as an + * i64 in [0, 52]; other exponents bail. */ +function exponentOf( + host: LlvmEmitterContext, + e: IrExpr, + bail: (condition: string) => void, + reads: ReadonlyMap, + integral: boolean, +): string { + if (e.kind === "numLit") return String(Math.log2(e.value)); + if (e.kind !== "libCall") + throw new InternalCompilerError("llvm emitter bug: speculative power of two"); + const n = emitSpeculativeInteger(host, e.args[1]!, bail, reads, integral); + const outside = host.B.tmp(); + host.B.line(`${outside} = icmp ugt i64 ${n}, 52`); + bail(outside); + return n; +} + +function shiftOne(host: LlvmEmitterContext, n: string): string { + const result = host.B.tmp(); + host.B.line(`${result} = shl i64 1, ${n}`); + return result; +} + /** The safe i64 of an evaluated number: its proven integer, or a checked * conversion. */ function speculativeLeaf( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7757-speculative-key-chains.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7757-speculative-key-chains.ts.json new file mode 100644 index 000000000..274151b18 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7757-speculative-key-chains.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7757-speculative-key-chains.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7757-speculative-key-chains.ts b/tests/corpus/7757-speculative-key-chains.ts new file mode 100644 index 000000000..bdbd12e1e --- /dev/null +++ b/tests/corpus/7757-speculative-key-chains.ts @@ -0,0 +1,67 @@ +// Speculative integer loops over writes that call small pure number +// functions, use Math.pow(2, n) and Math.floor(a / 2^n), read bytes, or +// call other functions: every fallback gives Node's numbers. +function trim(value: number, length: number): number { + return value - Math.floor(value / Math.pow(2, length)) * Math.pow(2, length); +} +function maybeTrim(value: number | undefined, length: number): number { + return (value ?? 0) - Math.floor((value ?? 0) / Math.pow(2, length)) * Math.pow(2, length); +} + +function chain(codes: Uint8Array, length: number): number { + const mask = length * 2 - 2; + let key = 0; + let sum = 0; + for (let i = 0; i < codes.length; ++i) { + sum = (sum + key) % 1000000007; + key = trim(key, mask) * 4 + codes[i]!; + } + return sum * 31 + key; +} + +function optionalChain(codes: Uint8Array, length: number): number { + let key = 0; + let sum = 0; + for (let i = 0; i < codes.length; ++i) { + sum = (sum * 3 + key) % 1000003; + key = maybeTrim(key, length * 2) * 4 + codes[i]!; + } + return sum + key; +} + +// Exponents past 52, negative and fractional values, and NaN reads leave +// the integer copy and finish in the double one. +function shifts(start: number, exponent: number, steps: number): number[] { + const out: number[] = []; + let v = start; + for (let i = 0; i < steps; i++) { + v = Math.floor(v / Math.pow(2, exponent)) * 3 + i * 7; + out.push(v); + } + return out; +} + +let calls = 0; +function noisy(x: number): number { + calls++; + return x % 5 === 0 ? x / 2 : x * 2; +} +// A call that the write cannot expand runs once per iteration, also on +// the iteration whose check fails. +function hoisted(n: number): number { + let acc = 1; + for (let i = 0; i < n; i++) acc = (acc * 7 + noisy(i)) % 1000003; + return acc; +} + +const codes = new Uint8Array(5000); +let seed = 7; +for (let i = 0; i < codes.length; i++) { + seed = (seed * 1103515245 + 12345) % 2147483648; + codes[i] = seed % 4; +} +for (const length of [1, 2, 3, 6, 12, 18, 27]) console.log(length, chain(codes, length), optionalChain(codes, length)); +console.log(shifts(1000, 3, 6), shifts(-1000, 2, 6), shifts(5, 53, 3), shifts(7.5, 1, 3), shifts(2 ** 52, 1, 3)); +console.log(hoisted(20), calls); +const short = codes.subarray(0, 3); +console.log(chain(short, 2), chain(new Uint8Array(0), 2)); diff --git a/tests/harness/corpus-costs/7757-speculative-key-chains.ts.json b/tests/harness/corpus-costs/7757-speculative-key-chains.ts.json new file mode 100644 index 000000000..be69de5f7 --- /dev/null +++ b/tests/harness/corpus-costs/7757-speculative-key-chains.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 2900 } From d9f1457cd55dd99cf60f630eb1d610a1545207f3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 08:07:52 -0500 Subject: [PATCH 060/395] Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does --- .../limitations/builtin-apis/assertions.mdx | 2 + .../limitations/builtin-apis/prototypes.mdx | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 4 + .../lowering/expressions/relational.ts | 15 +- .../src/frontend/lowering/lower-assert.ts | 316 +++++++++++++++++- .../src/frontend/lowering/lower-calls.ts | 15 +- .../src/frontend/lowering/lower-stmts.ts | 40 +++ .../compiler/src/frontend/lowering/lowerer.ts | 8 +- .../src/frontend/lowering/surfaces.ts | 6 + packages/compiler/src/frontend/program.ts | 11 + packages/compiler/src/frontend/tsc-codes.ts | 4 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 8 + .../8062-assert-message-values.cjs.json | 6 + .../8065-cjs-top-level-return/main.js.json | 7 + .../corpus/8066-object-hasown-delete.cjs.json | 6 + .../8067-assert-throws-validators.cjs.json | 6 + .../8076-assert-instance-identity.cjs.json | 6 + .../8078-instanceof-object-function.cjs.json | 6 + .../8190-assert-function-values.cjs.json | 6 + .../8191-assert-function-values.ts.json | 6 + packages/runtime/src/scr_assert.c | 314 +++++++++++++++++ packages/runtime/src/scr_assert.h | 13 + packages/runtime/src/scr_json.c | 72 ++++ tests/corpus/8062-assert-message-values.cjs | 41 +++ .../corpus/8065-cjs-top-level-return/early.js | 6 + .../corpus/8065-cjs-top-level-return/main.js | 16 + .../8065-cjs-top-level-return/package.json | 3 + tests/corpus/8066-object-hasown-delete.cjs | 23 ++ .../corpus/8067-assert-throws-validators.cjs | 22 ++ .../corpus/8076-assert-instance-identity.cjs | 50 +++ .../8078-instanceof-object-function.cjs | 55 +++ tests/corpus/8190-assert-function-values.cjs | 30 ++ tests/corpus/8191-assert-function-values.ts | 16 + tests/coverage-fixtures/startup-fences.js | 4 +- tests/diagnostics/assert-fences.ts | 8 +- .../__snapshots__/assert-fences.ts.txt | 8 +- tests/harness/coverage.test.ts | 4 +- 38 files changed, 1121 insertions(+), 44 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8062-assert-message-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8065-cjs-top-level-return/main.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8066-object-hasown-delete.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8067-assert-throws-validators.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8076-assert-instance-identity.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8078-instanceof-object-function.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8190-assert-function-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8191-assert-function-values.ts.json create mode 100644 tests/corpus/8062-assert-message-values.cjs create mode 100644 tests/corpus/8065-cjs-top-level-return/early.js create mode 100644 tests/corpus/8065-cjs-top-level-return/main.js create mode 100644 tests/corpus/8065-cjs-top-level-return/package.json create mode 100644 tests/corpus/8066-object-hasown-delete.cjs create mode 100644 tests/corpus/8067-assert-throws-validators.cjs create mode 100644 tests/corpus/8076-assert-instance-identity.cjs create mode 100644 tests/corpus/8078-instanceof-object-function.cjs create mode 100644 tests/corpus/8190-assert-function-values.cjs create mode 100644 tests/corpus/8191-assert-function-values.ts diff --git a/docs/content/limitations/builtin-apis/assertions.mdx b/docs/content/limitations/builtin-apis/assertions.mdx index f7f5c9354..381fc3802 100644 --- a/docs/content/limitations/builtin-apis/assertions.mdx +++ b/docs/content/limitations/builtin-apis/assertions.mdx @@ -3,6 +3,8 @@ When `strictEqual` compares function values, `actual` and `expected` read as `undefined`; when `assert.throws` or `assert.rejects` reports an error that is not an instance of the expected class, or that does not match an expected object, `expected` reads as `undefined`. Values whose type can be cyclic, such as recursive records, also read as `undefined`. `assert.AssertionError` used as a value outside `instanceof` throws `SC2020`. + The comparison methods, `ok`, `fail` and `ifError` are function values that assert as the methods do; called through a stored value, `ok` without a message reports `value == true`, where Node quotes the call, and `fail` takes at most one argument. + `throws`, `rejects`, `doesNotThrow`, `doesNotReject`, `match` and `doesNotMatch` as values throw `SC2020`. `assert.deepEqual` and `assert.notDeepEqual` follow Node's loose comparison, including Maps, Sets, Dates, regular expressions, typed arrays, and errors; operands whose type can be cyclic or holds functions inside a structure are refused. `assert.partialDeepStrictEqual` follows Node's partial comparison over the same values, with the same refusals. Checked-dynamic values compared deeply have no cycle detection. diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index 170b11793..ce7bec74c 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -6,3 +6,4 @@ The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called. `new` through a stored `Object`, `Date`, `Map`, `Set`, `WeakMap`, or `WeakSet` builds the same value as the `new` expression. `new` through a stored `RegExp` or `Promise`, `new` of a stored `Object` with a primitive argument, and `instanceof` against these constructors are unsupported. + `instanceof Object` and `instanceof Function` on untyped values follow the prototype chain as in Node, so an object built with `Object.create(null)` is not an `Object`. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 7a16c37e8..46798e275 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -37,6 +37,7 @@ export const ASSERT_VERDICT_LIB_FNS: ReadonlyMap = new Ma export const LIB_FN_SYMS: Record = { "assert.expectsErrDyn": "scr_assert_expects_err_dyn", + "assert.throwsValidate": "scr_assert_throws_validate", "insp.jsonDyn": "scr_util_format_j", "http.reqH2StreamOrThrow": "scr_http_req_h2_stream_or_throw", "intl.segmenterNew": "scr_intl_segmenter_new", @@ -399,6 +400,9 @@ export const LIB_FN_SYMS: Record = { "assert.eqBool": "scr_assert_eq_bool", "assert.eqSym": "scr_assert_eq_sym", "assert.eqDyn": "scr_assert_eq_dyn", + "assert.messageValue": "scr_assert_message_value", + "assert.methodValue": "scr_assert_method_value", + "assert.messageClear": "scr_assert_message_clear", "assert.looseResult": "scr_assert_loose_result", "assert.deepResult": "scr_assert_deep_result", "assert.looseDeepResult": "scr_assert_loose_deep_result", diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 1651bf0d9..5cd63013e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -2178,23 +2178,10 @@ function lowerRuntimeKeyIn( return { kind: "call", callee: helper, args: [keyIr, recv], type: BOOL, loc }; } -/** Standard-library constructors the untyped instanceof helper has no - * instance test for: an untyped `x instanceof Object` always refuses. */ -const DYN_INSTANCEOF_REFUSED: ReadonlySet = new Set(["Object", "Function"]); - /** The SC1090 message `x instanceof C` throws, with the site's position, - * when `C` is a runtime constructor with no instance test. A right-hand - * side that names one of DYN_INSTANCEOF_REFUSED always reaches that - * refusal, so the site is listed with the runtime fences. */ + * when `C` is a runtime constructor with no instance test. */ function dynInstanceOfRefusal(lowerer: Lowerer, expr: ts.BinaryExpression): string { const diag = unsupportedDiag("SC1090", locOf(expr), "instanceof against this native constructor"); - const symbol = lowerer.checker.getSymbolAtLocation(expr.right); - if ( - symbol !== undefined && - lowerer.isStdlibSymbol(symbol) && - DYN_INSTANCEOF_REFUSED.has(ts.symbolName(symbol)) - ) - lowerer.runtimeFences.push(diag); const sf = ts.getSourceFileOfNode(expr); const line = ts.getLineAndCharacterOfPosition(sf, diag.loc.start).line + 1; return `${diag.message} [${diag.code} at ${diag.loc.file}:${line}]`; diff --git a/packages/compiler/src/frontend/lowering/lower-assert.ts b/packages/compiler/src/frontend/lowering/lower-assert.ts index b996abed7..39ec9c959 100644 --- a/packages/compiler/src/frontend/lowering/lower-assert.ts +++ b/packages/compiler/src/frontend/lowering/lower-assert.ts @@ -51,11 +51,14 @@ import { STRING, type SrcLoc, VOID, + isDynTypedRefType, isUnitType, typeEquals, typeKey, } from "../../ir/ir.js"; import { boolLit, countedFor, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/build.js"; +import { checkedClassInstanceOf } from "./class-construction.js"; +import { isJsSourceFile } from "../program.js"; /** node:assert/strict binds the loose NAMES to the strict comparisons — * Node's own aliasing (`strict.equal === assert.strictEqual`). */ @@ -73,6 +76,63 @@ function canonicalAssertMember(module: string, member: string): string { return member; } +/** The assert methods that are function values (scr_assert_method_value). */ +const ASSERT_FUNCTION_VALUES: ReadonlySet = new Set([ + "ok", + "fail", + "equal", + "notEqual", + "strictEqual", + "notStrictEqual", + "deepEqual", + "notDeepEqual", + "deepStrictEqual", + "notDeepStrictEqual", + "partialDeepStrictEqual", + "ifError", +]); + +/** The assert method a `const` binding holds (`const ok = assert.ok`, + * `const eq = strictEqual`), or null. */ +export function assertFunctionAliasOf( + lowerer: Lowerer, + ident: ts.Identifier, +): { module: string; member: string } | null { + const decl = lowerer.checker.getSymbolAtLocation(ident)?.valueDeclaration; + if ( + !decl || + !ts.isVariableDeclaration(decl) || + !ts.isIdentifier(decl.name) || + !ts.isVariableDeclarationList(decl.parent) || + (decl.parent.flags & ts.NodeFlags.Const) === 0 || + !decl.initializer + ) + return null; + let init = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + const bi = ts.isPropertyAccessExpression(init) + ? lowerer.builtinMemberOf(init) + : ts.isIdentifier(init) + ? lowerer.builtinImportOf(init) + : null; + if (!bi || (bi.module !== "assert" && bi.module !== "assert/strict")) return null; + return ASSERT_FUNCTION_VALUES.has(canonicalAssertMember(bi.module, bi.member)) ? bi : null; +} + +/** An assert method read as a value (`emitter.on("x", assert.fail)`, + * `const eq = assert.strictEqual`): an untyped function that asserts as + * the method does, the same value for every read. Null for other + * members. */ +export function assertFunctionValue( + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr | null { + if (bi.module !== "assert" && bi.module !== "assert/strict") return null; + const member = canonicalAssertMember(bi.module, bi.member); + if (!ASSERT_FUNCTION_VALUES.has(member)) return null; + return { kind: "libCall", fn: "assert.methodValue", args: [strLit(member, loc)], type: DYN, loc }; +} + /* Node's AssertionError carries actual, expected and operator. Where the * verdict is computed in compiled code, the failure branch records them * (assert.record) before the throwing call, so a passing assertion boxes @@ -166,11 +226,22 @@ function requireStatementPosition(lowerer: Lowerer, call: ts.CallExpression, wha ); } +/** The message the runtime reads from a registered value + * (SCR_ASSERT_VALUE_MESSAGE in scr_assert.h). */ +const VALUE_MESSAGE = "\u0001scriptc:assert-message-value"; + +/** A message argument that is not a string, met while one assertion + * lowers (withValueMessage). */ +let valueMessage: { value: IrExpr; has: string; text: string } | null = null; + /** The optional trailing message argument: Node accepts a string or an * Error (throwing the Error itself) — the STRING form lowers; a literal * `undefined` counts as omitted (Node's `message == null` test). Returns * the libCall's (msg, hasMsg) pair — a "" dummy when omitted, so the - * runtime can distinguish an omitted message from an empty one. */ + * runtime can distinguish an omitted message from an empty one. Any other + * value that crosses into the checked-dynamic tree (an untyped + * `failures.join('')`, an Error) is registered with the runtime at the call + * instead (withValueMessage). */ function lowerMessageArg( lowerer: Lowerer, node: ts.Expression | undefined, @@ -180,14 +251,112 @@ function lowerMessageArg( return { msg: { kind: "strLit", value: "", type: STRING, loc }, hasMsg: boolLit(false, loc) }; } const msg = lowerer.lowerExpr(node); - if (msg.type.kind !== "string") { + if (msg.type.kind === "string") return { msg, hasMsg: boolLit(true, loc) }; + const value = + valueMessage === null && (msg.type.kind === "dyn" || lowerer.dynConvertible(msg.type)) + ? lowerer.coerceToExpected(msg, DYN) + : null; + if (value === null || value.type.kind !== "dyn") { lowerer.noLowering( `assert messages of type '${lowerer.fmt(msg.type)}'`, node, "only string messages lower (Node also accepts an Error to throw — construct and throw it directly)", ); } - return { msg, hasMsg: boolLit(true, loc) }; + const has = lowerer.declareHiddenLocal("assert.hasMessage", BOOL); + const text = lowerer.declareHiddenLocal("assert.messageText", STRING); + valueMessage = { value, has: has.id, text: text.id }; + return { msg: varRef(text.id, STRING, loc), hasMsg: varRef(has.id, BOOL, loc) }; +} + +/** Lowers one assertion; a message value met on the way is evaluated after + * the assertion's other arguments, as Node evaluates them. A null or + * undefined message is omitted (Node's `message == null` test); a truthy + * one is registered with the runtime, which the failure reads; any other + * value is the empty message, which Node's comparison diffs treat as no + * message too (createErrDiff tests the message's truthiness). */ +function withValueMessage( + lowerer: Lowerer, + expr: ts.CallExpression, + loc: SrcLoc, + lower: () => IrExpr | null, +): IrExpr | null { + const outer = valueMessage; + valueMessage = null; + try { + const lowered = lower(); + const message = valueMessage as { value: IrExpr; has: string; text: string } | null; + if (lowered === null || message === null) return lowered; + // assert.ok's statements evaluate the value first; its call is last. + const before = lowered.kind === "seqExpr" ? lowered.stmts : []; + const call = lowered.kind === "seqExpr" ? lowered.result : lowered; + if (call.kind !== "libCall") { + lowerer.noLowering( + "assert messages that are not strings in this assertion form", + expr, + "pass a string message", + ); + } + const stmts: IrStmt[] = [...before]; + const args = call.args.map((arg, index) => + arg.kind === "varRef" && (arg.localId === message.has || arg.localId === message.text) + ? arg + : stableValue(lowerer, stmts, arg, `assert.arg${index}`, loc), + ); + const local = lowerer.declareHiddenLocal("assert.message", DYN); + const value = varRef(local.id, DYN, loc); + stmts.push( + { kind: "varDecl", localId: local.id, init: message.value, loc }, + { + kind: "varDecl", + localId: message.has, + init: { kind: "dynTest", test: "nullish", negated: true, value, type: BOOL, loc }, + loc, + }, + { + kind: "varDecl", + localId: message.text, + init: { + kind: "ternary", + cond: { kind: "dynTest", test: "truthy", value, type: BOOL, loc }, + then: strLit(VALUE_MESSAGE, loc), + else_: strLit("", loc), + type: STRING, + loc, + }, + loc, + }, + { + kind: "if", + cond: { kind: "dynTest", test: "truthy", value, type: BOOL, loc }, + then: [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "assert.messageValue", + args: [value, { kind: "dynTest", test: "error", value, type: BOOL, loc }], + type: VOID, + loc, + }, + loc, + }, + ], + else_: null, + loc, + }, + { kind: "exprStmt", expr: { ...call, args }, loc }, + ); + return { + kind: "seqExpr", + stmts, + result: { kind: "libCall", fn: "assert.messageClear", args: [], type: VOID, loc }, + type: VOID, + loc, + }; + } finally { + valueMessage = outer; + } } /** The module-function dispatch — the spoke's entry, called from both the @@ -201,6 +370,15 @@ export function lowerAssertModuleCall( loc: SrcLoc, ): IrExpr | null { if (bi.module !== "assert" && bi.module !== "assert/strict") return null; + return withValueMessage(lowerer, expr, loc, () => lowerAssertMember(lowerer, expr, bi, loc)); +} + +function lowerAssertMember( + lowerer: Lowerer, + expr: ts.CallExpression, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr | null { switch (canonicalAssertMember(bi.module, bi.member)) { case "ok": return lowerAssertOk(lowerer, expr, loc); @@ -880,7 +1058,7 @@ export function lowerAssertDirectCall( 'use the default import: import assert from "node:assert")', ); } - return lowerAssertOk(lowerer, expr, loc); + return withValueMessage(lowerer, expr, loc, () => lowerAssertOk(lowerer, expr, loc)); } /** assert(value[, message]) / assert.ok(value[, message]): the truthiness @@ -889,7 +1067,24 @@ export function lowerAssertDirectCall( * the same bytes at compile time). */ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { requireStatementPosition(lowerer, expr, ts.getTextOfNode(expr.expression)); - if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { + if (expr.arguments.nodes.length === 0) { + // Node's message for a call without arguments. + const text = "No value argument passed to `assert.ok()`"; + return { + kind: "seqExpr", + stmts: [recordOnFailure(lowerer, boolLit(true, loc), null, boolLit(true, loc), "==", loc)], + result: { + kind: "libCall", + fn: "assert.ok", + args: [boolLit(false, loc), strLit(text, loc), boolLit(true, loc)], + type: VOID, + loc, + }, + type: VOID, + loc, + }; + } + if (expr.arguments.nodes.length > 2) { lowerer.noLowering( `${ts.getTextOfNode(expr.expression)} with ${expr.arguments.nodes.length} arguments`, expr, @@ -1056,9 +1251,22 @@ function lowerAssertEqual( // would answer false where Node can answer true (the value's own // earlier crossing) — a miscompare, never lowered. Bytes fence both // ways (the dyn copy cannot carry the Buffer/Uint8Array brand the - // checker type knows); class instances and everything non-convertible - // keep the fence too. - if (a.type.kind === "dyn" || b.type.kind === "dyn") { + // checker type knows); everything non-convertible keeps the fence too. + // A class instance crosses as a reference to the instance itself, so + // strict compares are honest reference equality, between two instances + // too (assert.strictEqual(e, e.setMaxListeners(1))). + const instanceType = (t: IrType): boolean => + isDynTypedRefType(t) && !lowerer.isSubclassOf(t.className, "%Error"); + const instance = (e: IrExpr): boolean => + !deep && + (instanceType(e.type) || + (e.type.kind === "union" && + (lowerer.unions + .get(e.type.unionId) + ?.arms.every((arm) => instanceType(arm) || isUnitType(arm)) ?? + false))) && + lowerer.dynConvertible(e.type); + if (a.type.kind === "dyn" || b.type.kind === "dyn" || (instance(a) && instance(b))) { const box = (e: IrExpr, node: ts.Expression): IrExpr => { if (e.type.kind === "dyn") return e; const k = e.type.kind; @@ -1067,6 +1275,18 @@ function lowerAssertEqual( k === "f64" || k === "string" || k === "bool" || + // A union of scalars and units crosses by value, like its arms. + (e.type.kind === "union" && + (lowerer.unions + .get(e.type.unionId) + ?.arms.every( + (arm) => + arm.kind === "f64" || + arm.kind === "string" || + arm.kind === "bool" || + isUnitType(arm), + ) ?? + false)) || (k === "func" && lowerer.dynConvertible(e.type)) || // Runtime handles cross by REFERENCE (identity preserved), so the // strict compare is honest: assert.strictEqual(this, server) — @@ -1082,6 +1302,9 @@ function lowerAssertEqual( // same). The deep walk compares the retained error data without // re-encoding and losing identity (SEMANTICS.md). (k === "object" && e.type.kind === "object" && e.type.className === "%Error") || + instance(e) || + // A symbol crosses as itself, so strict compares are identity. + (!deep && k === "symbol") || (deep && k !== "bytes" && k !== "object" && lowerer.dynConvertible(e.type)) || // Under skipPrototype the prototype a class instance or Buffer // brand would carry is not compared. @@ -1470,17 +1693,29 @@ function classifyThrowsExpected( displayName: rec ? rec.lib : info.def.name.replace(/^%/, ""), }; } - if ( - (surface === "assert.doesNotThrow" || surface === "assert.doesNotReject") && - expectedT?.kind === "func" - ) { + if (expectedT?.kind === "func") { const value = lowerer.lowerExpr(node); + // An untyped value there (a dynamic-tier function's result) is checked + // at run time like any untyped expected value. + if (value.type.kind === "jsval" || value.type.kind === "dyn") { + const boxed = lowerer.coerceToExpected(value, DYN); + if (boxed.type.kind === "dyn") return { form: "errValue", value: boxed }; + } + // A JavaScript function accepts any argument; a typed one must take + // unknown values (the caught value need not be an Error). if ( value.type.kind === "func" && - value.type.params.every((param) => param.kind === "dyn") && + (value.type.params.every((param) => param.kind === "dyn") || + isJsSourceFile(ts.getSourceFileOfNode(node))) && lowerer.dynConvertible(value.type) ) { - return { form: "predicate", value: lowerer.coerceToExpected(value, DYN) }; + // The boxed function carries its source name (Node's message names + // a named validation function). + const fnName = jsFuncNameOf(node); + return { + form: "predicate", + value: { kind: "dynFrom", value, ...(fnName !== null ? { fnName } : {}), type: DYN, loc }, + }; } lowerer.noLowering( `${surface} with this validation callback`, @@ -1546,7 +1781,9 @@ function classifyThrowsExpected( // tracingChannel suite's shape): the value crosses as a dyn error and // the runtime compares per Node's expectsError key walk. A // checked-dynamic expected rides directly; a static %Error boxes. - if (expectedT?.kind === "dyn") { + // An untyped JavaScript value (a parameter) too: a function there is + // checked at run time (assertThrowsHelper). + if (expectedT?.kind === "dyn" || expectedT === null) { const value = lowerer.lowerExpr(node); if (value.type.kind === "dyn") return { form: "errValue", value }; } @@ -1943,6 +2180,46 @@ function assertThrowsHelper( return { e: strLit("", loc), has: false }; })(); + // expectedException's function branch: a function with a prototype that + // the caught value is an instance of passes; anything else is called as + // a validation function, whose result must be true. + const validateStmts = (fn: IrExpr): IrStmt[] => { + const actual = (): IrExpr => ({ kind: "caughtToDyn", value: caughtRef(), type: DYN, loc }); + const prototype: IrExpr = { + kind: "dynKeyGet", + value: fn, + key: strLit("prototype", loc), + optional: true, + type: DYN, + loc, + }; + return [ + ifStmt( + { kind: "dynTest", test: "undefined", negated: true, value: prototype, type: BOOL, loc }, + [ + ifStmt( + checkedClassInstanceOf( + lowerer, + actual(), + fn, + "assert.throws against this native constructor is not supported yet", + loc, + ), + [doReturn], + ), + ], + ), + lib("assert.throwsValidate", [ + actual(), + fn, + strLit(mode === "rejects" ? "rejects" : "throws", loc), + m(), + hm(), + ]), + doReturn, + ]; + }; + let catchBody: IrStmt[]; if (mode === "dnr" || mode === "dnt") { const actual = (): IrExpr => ({ kind: "caughtToDyn", value: caughtRef(), type: DYN, loc }); @@ -2116,7 +2393,13 @@ function assertThrowsHelper( // caught value crosses as a dyn value (identity-preserving) and // the runtime walks the expected's keys — a mismatch throws the // deep-equal AssertionError. + // A function there is a validation function (or an error class + // value) as in the predicate form. catchBody = [ + ifStmt( + { kind: "dynTest", test: "function", value: varRef("ev.0", DYN, loc), type: BOOL, loc }, + validateStmts(varRef("ev.0", DYN, loc)), + ), lib("assert.expectsErrDyn", [ { kind: "caughtToDyn", value: caughtRef(), type: DYN, loc }, varRef("ev.0", DYN, loc), @@ -2127,7 +2410,8 @@ function assertThrowsHelper( ]; break; case "predicate": - throw new InternalCompilerError("assert validation callback only enters a no-error helper"); + catchBody = validateStmts(varRef("predicate.0", DYN, loc)); + break; } } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 14565aebc..a75796cba 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,3 +1,4 @@ +import { assertFunctionAliasOf } from "./lower-assert.js"; import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; @@ -1052,6 +1053,13 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { } const bi = lowerer.builtinImportOf(expr.expression); if (bi) return lowerBuiltinCall(lowerer, expr, bi, loc); + // A constant bound to an assert method (`const ok = assert.ok`) called + // directly is that assertion, with the message Node quotes the call for. + { + const alias = assertFunctionAliasOf(lowerer, expr.expression); + const served = alias ? lowerer.lowerAssertModuleCall(expr, alias, loc) : null; + if (served) return served; + } // The assert module binding called DIRECTLY (`assert(x)` — a default // import or the CJS `const assert = require("assert")`): Node's // module object IS assert.ok; namespace-import bindings fence inside @@ -8703,7 +8711,12 @@ function lowerObjectStaticCall( (probed && isDynTypedRefType(probed.type)) || (probed?.type.kind === "object" && lowerer.errorHierarchyClassOf(probed.type.className)) || (probed && constructor && (constructor.callableBase || hasRuntimeStatics(constructor))) || - (probed?.type.kind === "func" && lowerer.dynConvertible(probed.type)) + (probed?.type.kind === "func" && lowerer.dynConvertible(probed.type)) || + // null, undefined and unions of untyped JavaScript values: the same + // probe, whose ToObject throws Node's TypeError on nullish. + (probed && + (isUnitType(probed.type) || probed.type.kind === "union") && + lowerer.dynConvertible(probed.type)) ) { const loc = locOf(call); const receiver = lowerer.coerceToExpected(probed, DYN); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index c50676863..ddb764305 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1626,6 +1626,44 @@ const THROWN_AS_DYN: ReadonlySet = new Set([ "regex", ]); +/** A `return` at the top level of a CommonJS module (preflight admits it + * there only): it ends the module's evaluation, and its value is + * evaluated and discarded, as Node's module wrapper does. Top-level + * bindings and export assignments after it would be left uninitialized + * where Node leaves them in their temporal dead zone or absent, so a + * module with any keeps a refusal. Null for a return inside a function. */ +function lowerModuleLevelReturn(lowerer: Lowerer, stmt: ts.ReturnStatement): IrStmt | null { + let top: ts.Node = stmt; + while (top.parent !== undefined && !ts.isSourceFile(top.parent)) { + if (ts.isFunctionLike(top.parent) || ts.isClassStaticBlockDeclaration(top.parent)) return null; + top = top.parent; + } + const sf = top.parent; + if (sf === undefined || !ts.isSourceFile(sf)) return null; + const stmts = sf.statements.nodes; + const later = stmts.slice(stmts.indexOf(top as ts.Statement) + 1); + if ( + later.some( + (s) => + ts.isVariableStatement(s) || ts.isClassDeclaration(s) || cjsExportAssignmentOf(s) !== null, + ) + ) { + lowerer.unsupported( + "SC1090", + stmt, + "a top-level return in a CommonJS module that declares variables or classes, or assigns exports, after it", + ); + } + const loc = locOf(stmt); + const ret: IrStmt = { kind: "return", value: null, loc }; + if (!stmt.expression) return ret; + return { + kind: "block", + body: [{ kind: "exprStmt", expr: lowerer.lowerExpr(stmt.expression), loc }, ret], + loc, + }; +} + export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null { if (ownPrototypeResetStatement(lowerer, stmt)) return { kind: "block", body: [], loc: locOf(stmt) }; @@ -1735,6 +1773,8 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt if (ts.isForOfStatement(stmt)) return lowerer.lowerForOf(stmt); if (ts.isLabeledStatement(stmt)) return lowerLabeled(lowerer, stmt); if (ts.isReturnStatement(stmt)) { + const moduleReturn = lowerModuleLevelReturn(lowerer, stmt); + if (moduleReturn) return moduleReturn; // Implicit-any instance RETURN INFERENCE (function-returns.ts // resolveInferredReturn): the value lowers BARE and the statement // records itself — the post-pass settles the instance's return type diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 78980d322..8ef5a42a4 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -593,7 +593,11 @@ import { lowerTestMethodCall, lowerTestCtxProperty, } from "./lower-test.js"; -import { lowerAssertModuleCall, lowerAssertDirectCall } from "./lower-assert.js"; +import { + assertFunctionValue, + lowerAssertModuleCall, + lowerAssertDirectCall, +} from "./lower-assert.js"; import type { AssertInstanceOptions } from "./assert-instances.js"; import { lowerUtilModuleCall } from "./lower-inspect.js"; import { lowerSystemErrorMapValue } from "./lower-system-errors.js"; @@ -13985,6 +13989,8 @@ export class Lowerer { * deliberately narrower than table membership because many table rows * are only dispatch sentinels for call-site-specific lowering. */ lowerBuiltinCallableValue(bi: { module: string; member: string }, loc: SrcLoc): IrExpr | null { + const assertion = assertFunctionValue(bi, loc); + if (assertion) return assertion; if (bi.module === "util" && bi.member === "types") return utilTypesModuleValue(this, loc); if (bi.module === "util/types" && builtinModuleFnOf(this, bi.module, bi.member)) return lowerUtilTypeValue(this, bi.member, loc); diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 90acabce6..310d6f50b 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -873,6 +873,12 @@ export const OBJECT_CALLABLE_VALUES: Record result: DYN, valueParams: exactValueParams(DYN, DYN), }, + hasOwn: { + fn: "dyn.hasOwnComputed", + params: [DYN, DYN], + result: BOOL, + valueParams: exactValueParams(DYN, DYN), + }, keys: { fn: "dyn.objKeys", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) }, values: { fn: "dyn.objValues", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) }, entries: { fn: "dyn.objEntries", params: [DYN], result: DYN, valueParams: exactValueParams(DYN) }, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index bf75127c4..664759d54 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -5358,6 +5358,16 @@ function preflight7(load: LoadResult): { return new RegExp(`\\btypeof\\s*\\(?\\s*${name.replace(/\$/g, "\\$")}(?![\\w$])`).test(sf.text); }; + // A `return` at the top level of a CommonJS module (TS1108): Node runs + // the module as a function body, so the return ends the module's + // evaluation. ES modules and TypeScript keep the error. + const cjsTopLevelReturnSuppressed = (p: ts.Program, d: ts.Diagnostic): boolean => { + if (d.code !== 1108 || d.fileName === undefined || !isJsSourceFileName(d.fileName)) + return false; + const sf = p.getSourceFile(d.fileName); + return sf !== undefined && isCjsJsFile7(sf, p); + }; + const workspaceImplicitAnySuppressed = (p: ts.Program, d: ts.Diagnostic): boolean => { if (d.code !== 7016 || d.fileName === undefined || d.pos === undefined || d.end === undefined) return false; @@ -5404,6 +5414,7 @@ function preflight7(load: LoadResult): { !isNpmStaticSubclassArgument(p, d) && !workspaceImplicitAnySuppressed(p, d) && !typeofTestedNameSuppressed(p, d) && + !cjsTopLevelReturnSuppressed(p, d) && !jsdocTypeSuppressed(p, d, commentDup), ); }; diff --git a/packages/compiler/src/frontend/tsc-codes.ts b/packages/compiler/src/frontend/tsc-codes.ts index e6c0be7cd..e8ffc29fb 100644 --- a/packages/compiler/src/frontend/tsc-codes.ts +++ b/packages/compiler/src/frontend/tsc-codes.ts @@ -132,6 +132,10 @@ export const JS_RELAXED_TSC_CODES: ReadonlySet = new Set([ // binding types as any and reached uses fence instead of passing unchecked 2307, 2792, + // `delete` of a property the inference typed required (2790): JS deletes + // any configurable property, and the lowering checks each delete's + // receiver itself + 2790, // useUnknownInCatchVariables' annotation demand ("'e' is of type // 'unknown'", "Object is of type 'unknown'"): a JS catch clause CANNOT // annotate its variable, so the demand is unsatisfiable in source — diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..1924dc7b2 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -976,6 +976,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "assert.noErrorPredicate", "assert.regexDynTest", "assert.expectsErrDyn", + "assert.throwsValidate", "assert.ifErrorErr", "assert.ifErrorF64", "assert.ifErrorStr", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 216e46a4d..25a86a82b 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4410,6 +4410,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ // The equality quartet over checked-dynamic operands (the frontend // boxes a static side into the checked-dynamic tree first). "assert.eqDyn": { argTypes: [DYN, DYN, BOOL, BOOL, STRING, BOOL], result: VOID }, + /** A message argument that is not a string, registered for the next + * failing assertion (scr_assert_message_value). */ + "assert.messageValue": { argTypes: [DYN, BOOL], result: VOID }, + /** An assert method as a function value, by canonical name. */ + "assert.methodValue": { argTypes: [STRING], result: DYN }, + "assert.messageClear": { argTypes: [], result: VOID }, "assert.looseResult": { argTypes: [BOOL, BOOL, STRING, STRING, STRING, BOOL], result: VOID }, // The throws(fn, {shape}) accumulator: begin/slot calls never throw; // shapeEnd throws the Comparison diff. The error slot is the @@ -4435,6 +4441,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * against the caught value's; a mismatch throws the deep-equal * AssertionError (scr_assert.c). MAY THROW by design. */ "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, + /** A validation function against the caught value (Node's expectedException). */ + "assert.throwsValidate": { argTypes: [DYN, DYN, STRING, STRING, BOOL], result: VOID }, // assert.ifError's typed entries (always throw; unit args never // lower). The error slot is program-dependent — any %Error-hierarchy // class, root or subclass (the insp.error precedent, null slot). diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8062-assert-message-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8062-assert-message-values.cjs.json new file mode 100644 index 000000000..78ba99298 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8062-assert-message-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8062-assert-message-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8065-cjs-top-level-return/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8065-cjs-top-level-return/main.js.json new file mode 100644 index 000000000..22edecf3f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8065-cjs-top-level-return/main.js.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/8065-cjs-top-level-return/early.js", + "/tests/corpus/8065-cjs-top-level-return/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8066-object-hasown-delete.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8066-object-hasown-delete.cjs.json new file mode 100644 index 000000000..bf862b8f2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8066-object-hasown-delete.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8066-object-hasown-delete.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8067-assert-throws-validators.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8067-assert-throws-validators.cjs.json new file mode 100644 index 000000000..89f59e97c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8067-assert-throws-validators.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8067-assert-throws-validators.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8076-assert-instance-identity.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8076-assert-instance-identity.cjs.json new file mode 100644 index 000000000..b4212e234 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8076-assert-instance-identity.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8076-assert-instance-identity.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8078-instanceof-object-function.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8078-instanceof-object-function.cjs.json new file mode 100644 index 000000000..90af753d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8078-instanceof-object-function.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8078-instanceof-object-function.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8190-assert-function-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8190-assert-function-values.cjs.json new file mode 100644 index 000000000..730dd6360 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8190-assert-function-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8190-assert-function-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8191-assert-function-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8191-assert-function-values.ts.json new file mode 100644 index 000000000..0837dc2ee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8191-assert-function-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8191-assert-function-values.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_assert.c b/packages/runtime/src/scr_assert.c index 4917b1291..738a7729c 100644 --- a/packages/runtime/src/scr_assert.c +++ b/packages/runtime/src/scr_assert.c @@ -212,7 +212,56 @@ bool scr_assert_skip_prototype(void) { return (g_instance_options & SCR_ASSERT_SKIP_PROTOTYPE) != 0; } +/* A message argument that is not a string (an untyped value, or an Error + * to throw): the compiled call registers it before the assertion and passes + * SCR_ASSERT_VALUE_MESSAGE as its message, which the failure paths compose + * like any custom message. A failure whose message starts with it throws + * the Error itself (innerFail's `message instanceof Error`) or puts + * String(value) in its place with generatedMessage `!value`, as Node's + * AssertionError does. Matching the sentinel keeps a registration that no + * failure consumed from reaching another assertion. */ +static SCR_TL ScrDyn *g_message_value; +static SCR_TL bool g_message_error; + +void scr_assert_message_value(ScrDyn *value, bool error) { + if (g_message_value) scr_dyn_release(g_message_value); + g_message_value = scr_dyn_retain(value); + g_message_error = error; +} + +void scr_assert_message_clear(void) { + if (g_message_value) scr_dyn_release(g_message_value); + g_message_value = NULL; +} + void scr_assert_fail(ScrStr *message, bool generated) { + static const char sentinel[] = SCR_ASSERT_VALUE_MESSAGE; + const size_t n = sizeof sentinel - 1; + if (g_message_value && message->len >= n && memcmp(message->data, sentinel, n) == 0) { + ScrDyn *value = g_message_value; + g_message_value = NULL; + if (g_message_error) { + scr_str_release(message); + scr_assert_record(NULL, NULL, NULL); + g_instance_options = 0; + scr_dyn_throw(value); + return; + } + ScrStr *text = scr_dyn_string_coerce_js(value); + generated = !scr_dyn_truthy(value); + scr_dyn_release(value); + if (!text) { + scr_str_release(message); + return; + } + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_write(&b, text->data, text->len); + scr_jb_write(&b, message->data + n, message->len - n); + scr_str_release(text); + scr_str_release(message); + message = scr_jb_finish(&b); + } ScrError *e = scr_assert_error(message, generated, scr_assert_diff_full() ? "full" : "simple"); g_instance_options = 0; scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); @@ -2703,6 +2752,60 @@ bool scr_assert_no_error_predicate(ScrDyn *predicate, ScrDyn *actual) { return matched; } +/* assert.throws/rejects with a validation function (expectedException's + * function branch, after its instanceof check): the function runs with a + * fresh object as `this` and the caught value, and any result but true + * fails, with Node's generated message unless the call passed one. The + * caught value and the function are the AssertionError's actual and + * expected. Borrows all. */ +void scr_assert_throws_validate(ScrDyn *actual, ScrDyn *validate, ScrStr *op, ScrStr *msg, + bool has_msg) { + ScrDyn *receiver = scr_dyn_new_obj(); + scr_dyn_this_push_dyn(receiver); + ScrDyn *result = scr_dyn_call(validate, &actual, 1, "expected"); + scr_dyn_this_pop(); + scr_dyn_release(receiver); + if (!result) return; + if (result->kind == SCR_DYN_BOOL && result->v.b) { + scr_dyn_release(result); + return; + } + ScrStr *message; + bool generated = !(has_msg && msg->len > 0); + if (!generated) { + message = scr_str_retain(msg); + } else { + ScrAssertBuf b = {0}; + ab_cstr(&b, "The "); + ScrDyn *name = scr_dyn_fn_get(validate, "name", 4); + if (name && name->kind == SCR_DYN_STR && name->v.str->len > 0) { + ab_cstr(&b, "\""); + ab_bytes(&b, name->v.str->data, name->v.str->len); + ab_cstr(&b, "\" "); + } + if (name) scr_dyn_release(name); + ab_cstr(&b, "validation function is expected to return \"true\". Received "); + ScrStr *shown = scr_assert_cf_inspect(result); + ab_bytes(&b, shown->data, shown->len); + scr_str_release(shown); + if (scr_dyn_ref_is_error(actual)) { + ScrStr *text = scr_dyn_string_coerce_js(actual); + if (!text) { + scr_dyn_release(result); + scr_str_release(ab_take(&b)); + return; + } + ab_cstr(&b, "\n\nCaught error:\n\n"); + ab_bytes(&b, text->data, text->len); + scr_str_release(text); + } + message = ab_take(&b); + } + scr_dyn_release(result); + scr_assert_record_dyn(actual, validate, op); + scr_assert_fail(message, generated); +} + /* ── assert.ifError ─────────────────────────────────────────────────── * Node throws for ANY value but null/undefined (falsy included); the * frontend routes null/undefined to a no-op and everything else here. @@ -2879,3 +2982,214 @@ ScrError *scr_assert_error_new(ScrDyn *options) { scr_assert_attach_values(e, generated, actual, expected, op, diff); return e; } + +/* ── assert functions as values ────────────────────────────────────────── + * `assert.fail` stored or passed as a function (`emitter.on("x", + * assert.fail)`): one function per method, which takes its arguments as + * untyped values and asserts as the method does. */ + +/* Node's message argument: absent or null/undefined (a generated message), + * a string, an Error (thrown when the assertion fails), or another value + * (String(value)). The value is registered as scr_assert_fail reads it; the + * caller clears it after the assertion. +1 into *msg. */ +static void scr_av_message(ScrDyn *const *args, size_t argc, size_t i, ScrStr **msg, bool *has) { + const ScrDyn *m = i < argc ? args[i] : NULL; + if (!m || m->kind == SCR_DYN_UNDEF || m->kind == SCR_DYN_NULL) { + *msg = scr_str_new("", 0); + *has = false; + return; + } + *has = true; + if (m->kind == SCR_DYN_STR) { + *msg = scr_str_retain(m->v.str); + return; + } + bool error = scr_dyn_ref_is_error(m) || + (m->kind == SCR_DYN_OBJ && scr_dyn_obj_get(m, "%error", 6) != NULL); + scr_assert_message_value((ScrDyn *)m, error); + *msg = scr_str_new(SCR_ASSERT_VALUE_MESSAGE, sizeof SCR_ASSERT_VALUE_MESSAGE - 1); +} + +static const ScrDyn *scr_av_arg(ScrDyn *const *args, size_t argc, size_t i) { + return i < argc ? args[i] : scr_dyn_undefined(); +} + +static ScrDyn *scr_av_done(ScrStr *msg) { + scr_str_release(msg); + scr_assert_message_clear(); + return scr_dyn_retain(scr_dyn_undefined()); +} + +/* assert(value, message) and assert.ok: Node's generated message quotes + * the call's source, which a function value has no fixed site for; the + * message here is AssertionError's own `value == true`. */ +static ScrDyn *scr_av_ok(ScrClosure *c, ScrDyn *const *args, size_t argc) { + (void)c; + const ScrDyn *value = scr_av_arg(args, argc, 0); + if (scr_dyn_truthy(value)) return scr_dyn_retain(scr_dyn_undefined()); + scr_assert_record(scr_dyn_retain((ScrDyn *)value), scr_dyn_new_bool(true), "=="); + if (argc == 0) { + static const char text[] = "No value argument passed to `assert.ok()`"; + scr_assert_fail(scr_str_new(text, sizeof text - 1), true); + return NULL; + } + ScrStr *msg; + bool has; + scr_av_message(args, argc, 1, &msg, &has); + if (has) { + scr_assert_fail(scr_str_retain(msg), false); + return scr_av_done(msg); + } + ScrStr *shown = scr_assert_cf_inspect(value); + ScrAssertBuf b = {0}; + ab_str(&b, shown); + ab_cstr(&b, " == true"); + scr_str_release(shown); + scr_assert_fail(ab_take(&b), true); + return scr_av_done(msg); +} + +/* assert.fail(message): "Failed" without a message. */ +static ScrDyn *scr_av_fail(ScrClosure *c, ScrDyn *const *args, size_t argc) { + (void)c; + if (argc > 1) { + static const char text[] = "assert.fail with more than one argument through a function value is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, text, sizeof text - 1, "SC2020"); + return NULL; + } + ScrStr *msg; + bool has; + scr_av_message(args, argc, 0, &msg, &has); + scr_assert_record(scr_dyn_retain(scr_dyn_undefined()), scr_dyn_retain(scr_dyn_undefined()), "fail"); + if (has) scr_assert_fail(scr_str_retain(msg), false); + else scr_assert_fail(scr_str_new("Failed", 6), true); + return scr_av_done(msg); +} + +/* equal / notEqual: Node's loose comparison, NaN equal to NaN. */ +static ScrDyn *scr_av_loose(ScrDyn *const *args, size_t argc, bool negated) { + const ScrDyn *a = scr_av_arg(args, argc, 0), *b = scr_av_arg(args, argc, 1); + bool nan = a->kind == SCR_DYN_NUM && b->kind == SCR_DYN_NUM && isnan(a->v.num) && isnan(b->v.num); + bool equal = nan || scr_dyn_abstract_eq(a, b); + if (scr_exc_pending()) return NULL; + if (negated ? !equal : equal) return scr_dyn_retain(scr_dyn_undefined()); + ScrStr *msg; + bool has; + scr_av_message(args, argc, 2, &msg, &has); + scr_assert_record(scr_dyn_retain((ScrDyn *)a), scr_dyn_retain((ScrDyn *)b), negated ? "!=" : "=="); + ScrStr *ia = scr_assert_cf_inspect(a), *ib = scr_assert_cf_inspect(b); + scr_assert_loose_result(equal, negated, ia, ib, msg, has); + scr_str_release(ia); + scr_str_release(ib); + return scr_av_done(msg); +} + +static ScrDyn *scr_av_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_loose(a, n, false); +} +static ScrDyn *scr_av_not_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_loose(a, n, true); +} + +/* The strict and deep comparisons, through the untyped assert paths. */ +enum { SCR_AV_STRICT, SCR_AV_DEEP_STRICT, SCR_AV_DEEP, SCR_AV_PARTIAL }; + +static ScrDyn *scr_av_compare(ScrDyn *const *args, size_t argc, int form, bool negated) { + ScrDyn *a = (ScrDyn *)scr_av_arg(args, argc, 0), *b = (ScrDyn *)scr_av_arg(args, argc, 1); + ScrStr *msg; + bool has; + scr_av_message(args, argc, 2, &msg, &has); + if (form == SCR_AV_DEEP) scr_assert_loose_deep_dyn(a, b, negated, msg, has); + else if (form == SCR_AV_PARTIAL) scr_assert_partial_deep_dyn(a, b, msg, has); + else scr_assert_eq_dyn(a, b, negated, form == SCR_AV_DEEP_STRICT, msg, has); + return scr_av_done(msg); +} + +static ScrDyn *scr_av_strict_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_STRICT, false); +} +static ScrDyn *scr_av_not_strict_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_STRICT, true); +} +static ScrDyn *scr_av_deep_strict_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_DEEP_STRICT, false); +} +static ScrDyn *scr_av_not_deep_strict_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_DEEP_STRICT, true); +} +static ScrDyn *scr_av_deep_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_DEEP, false); +} +static ScrDyn *scr_av_not_deep_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_DEEP, true); +} +static ScrDyn *scr_av_partial_deep_strict_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { + (void)c; + return scr_av_compare(a, n, SCR_AV_PARTIAL, false); +} + +static ScrDyn *scr_av_if_error(ScrClosure *c, ScrDyn *const *args, size_t argc) { + (void)c; + scr_assert_iferror_dyn(scr_av_arg(args, argc, 0)); + return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); +} + +static const struct { + const char *name; + ScrDynThunk thunk; + uint32_t arity; +} scr_av_methods[] = { + {"ok", &scr_av_ok, 0}, + {"fail", &scr_av_fail, 1}, + {"equal", &scr_av_equal, 3}, + {"notEqual", &scr_av_not_equal, 3}, + {"strictEqual", &scr_av_strict_equal, 3}, + {"notStrictEqual", &scr_av_not_strict_equal, 3}, + {"deepEqual", &scr_av_deep_equal, 3}, + {"notDeepEqual", &scr_av_not_deep_equal, 3}, + {"deepStrictEqual", &scr_av_deep_strict_equal, 3}, + {"notDeepStrictEqual", &scr_av_not_deep_strict_equal, 3}, + {"partialDeepStrictEqual", &scr_av_partial_deep_strict_equal, 3}, + {"ifError", &scr_av_if_error, 1}, +}; +#define SCR_AV_COUNT (sizeof scr_av_methods / sizeof scr_av_methods[0]) +static SCR_TL ScrDyn *scr_av_values[SCR_AV_COUNT]; +static SCR_TL bool scr_av_registered; + +static void scr_av_cleanup(void) { + for (size_t i = 0; i < SCR_AV_COUNT; i++) { + scr_dyn_release(scr_av_values[i]); + scr_av_values[i] = NULL; + } + scr_av_registered = false; +} + +/* The function value of an assert method by its canonical name (the frontend + * maps assert/strict's aliases), +1; one value per method, so stored copies + * compare equal. */ +ScrDyn *scr_assert_method_value(ScrStr *name) { + for (size_t i = 0; i < SCR_AV_COUNT; i++) { + if (strlen(scr_av_methods[i].name) != name->len || + memcmp(scr_av_methods[i].name, name->data, name->len) != 0) + continue; + if (!scr_av_values[i]) { + if (!scr_av_registered) { + scr_av_registered = true; + scr_atexit(scr_av_cleanup); + } + scr_av_values[i] = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_av_methods[i].thunk, + scr_av_methods[i].arity, "native:assert", + scr_av_methods[i].name); + } + return scr_dyn_retain(scr_av_values[i]); + } + return scr_dyn_retain(scr_dyn_undefined()); +} diff --git a/packages/runtime/src/scr_assert.h b/packages/runtime/src/scr_assert.h index 8037f08ec..57158a69a 100644 --- a/packages/runtime/src/scr_assert.h +++ b/packages/runtime/src/scr_assert.h @@ -24,6 +24,19 @@ void scr_assert_fail(ScrStr *message, bool generated); /* takes ownership; alway * consumes them; a passing call clears them. */ void scr_assert_set_options(int options); void scr_assert_clear_options(void); +/* A message argument that is not a string, for the next assertion that + * fails with SCR_ASSERT_VALUE_MESSAGE as its message (scr_assert.c): an + * Error (`error`) is thrown as it is, anything else reports String(value). + * Borrows value; clear drops a registration the call did not consume. */ +#define SCR_ASSERT_VALUE_MESSAGE "\x01scriptc:assert-message-value" +void scr_assert_message_value(struct ScrDyn *value, bool error); +void scr_assert_message_clear(void); +/* An assert method as a function value, by canonical name (+1). */ +struct ScrDyn *scr_assert_method_value(ScrStr *name); +/* assert.throws/rejects with a validation function: its result must be + * true (Node's expectedException), else the AssertionError. Borrows all. */ +void scr_assert_throws_validate(struct ScrDyn *actual, struct ScrDyn *validate, ScrStr *op, + ScrStr *msg, bool has_msg); bool scr_assert_diff_full(void); bool scr_assert_skip_prototype(void); void scr_assert_record(ScrDyn *actual, ScrDyn *expected, const char *op); /* values move in */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index eb03ab6cc..ecc49e8cf 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -4665,6 +4665,71 @@ int scr_dyn_has_instance_hook(const ScrDyn *value, const ScrDyn *callee) { return truthy ? 1 : 0; } +/* `value instanceof Object`: whether Object.prototype is on the value's + * prototype chain. Primitives answer false, and so does an object whose + * chain ends in null first (Object.create(null), a null-prototype record). + * Objects without a chain the runtime walks (class instances, native + * handles, errors, promises) inherit Object.prototype. False with the + * exception pending when a prototype read throws. */ +static bool scr_dyn_instanceof_object(const ScrDyn *value) { + switch (value->kind) { + case SCR_DYN_UNDEF: + case SCR_DYN_NULL: + case SCR_DYN_BOOL: + case SCR_DYN_NUM: + case SCR_DYN_STR: + case SCR_DYN_BIGINT: + case SCR_DYN_SYMBOL: + return false; + default: + break; + } + ScrDyn *current = scr_dyn_retain((ScrDyn *)value); + for (;;) { + if (current == scr_builtin_object_prototype) { + scr_dyn_release(current); + return true; + } + ScrDyn *next = NULL; + if (current->kind == SCR_DYN_FUNC) { + /* A function's prototype is Function.prototype, whose own is + * Object.prototype, unless the program set it. */ + if (scr_closure_identity(current->v.fn.clo)->props) { + ScrDyn *table = scr_dyn_fn_properties(current); + next = table->prototype ? scr_dyn_retain(table->prototype) + : table->null_proto ? scr_dyn_new_null() : NULL; + scr_dyn_release(table); + } + } else if (current->kind == SCR_DYN_OBJ || current->kind == SCR_DYN_ARR || + (current->kind == SCR_DYN_TYPED_REF && + strncmp(current->v.typed_ref.type_key, "object:", 7) != 0)) { + next = scr_dyn_get_prototype(current); + if (!next) { + scr_dyn_release(current); + return false; /* the read threw: pending */ + } + } + scr_dyn_release(current); + if (!next) return true; + if (next->kind == SCR_DYN_NULL) { + scr_dyn_release(next); + return false; + } + current = next; + } +} + +/* `value instanceof Function`: every function, unless the program set its + * prototype to null or to a non-function. */ +static bool scr_dyn_instanceof_function(const ScrDyn *value) { + if (value->kind != SCR_DYN_FUNC) return false; + if (!scr_closure_identity(value->v.fn.clo)->props) return true; + ScrDyn *table = scr_dyn_fn_properties((ScrDyn *)value); + bool result = table->prototype ? scr_dyn_instanceof_function(table->prototype) : !table->null_proto; + scr_dyn_release(table); + return result; +} + bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const ScrStr *refusal) { if (!callee || callee->kind != SCR_DYN_FUNC) { static const char message[] = "Right-hand side of 'instanceof' is not callable"; @@ -4673,6 +4738,13 @@ bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const Sc } int hooked = scr_dyn_has_instance_hook(value, callee); if (hooked >= 0) return hooked == 1; + if (callee->v.fn.sig && callee->v.fn.name) { + if (strcmp(callee->v.fn.sig, "native:constructor") == 0 && strcmp(callee->v.fn.name, "Object") == 0) + return scr_dyn_instanceof_object(value); + if (strcmp(callee->v.fn.sig, "native:function-constructor") == 0 && + strcmp(callee->v.fn.name, "Function") == 0) + return scr_dyn_instanceof_function(value); + } /* An error constructor held as a value (`inst instanceof params.Parent` * with Parent === Error): the error kind's interval, and for Error itself * objects inheriting Error.prototype. */ diff --git a/tests/corpus/8062-assert-message-values.cjs b/tests/corpus/8062-assert-message-values.cjs new file mode 100644 index 000000000..7f84ccd37 --- /dev/null +++ b/tests/corpus/8062-assert-message-values.cjs @@ -0,0 +1,41 @@ +'use strict'; +// node:assert messages that are not string literals: an untyped string, an +// Error to throw, null/undefined (omitted) and falsy values, and +// scalar-union operands against untyped values. +const assert = require('assert'); + +function attempt(label, fn) { + try { + fn(); + console.log(label, 'passed'); + } catch (e) { + console.log(label, e.name, JSON.stringify(e.message), e.generatedMessage, e.code); + } +} + +const failures = []; +for (const x of ['a', 'b']) if (x === 'c') failures.push(`\n${x}`); +attempt('joined passes', () => assert.strictEqual(failures.length, 0, failures.join(''))); +const untyped = JSON.parse('"untyped message"'); +attempt('untyped', () => assert.strictEqual(1, 2, untyped)); +attempt('untyped deep', () => assert.deepStrictEqual([1], [2], untyped)); +attempt('untyped notStrict', () => assert.notStrictEqual(1, 1, untyped)); +attempt('error message', () => assert.strictEqual(1, 2, new TypeError('own error'))); +attempt('error deep', () => assert.deepStrictEqual({ a: 1 }, { a: 2 }, new RangeError('own range'))); +const none = JSON.parse('null'); +attempt('null message', () => assert.strictEqual(1, 2, none)); +const zero = JSON.parse('0'); +attempt('zero message deep', () => assert.deepStrictEqual([1], [2], zero)); +attempt('passing error message', () => assert.strictEqual(1, 1, new Error('unused'))); +attempt('later string message', () => assert.strictEqual(1, 2, 'plain')); + +// Operand order: both operands evaluate before the message. +const order = []; +const mark = (label, value) => (order.push(label), value); +attempt('order', () => assert.strictEqual(mark('a', 1), mark('b', 2), mark('m', JSON.parse('"m"')))); +console.log(order.join(',')); + +const row = [['a', 'b', true], ['c', 'd', 'x']]; +for (const [, , expected] of row) { + attempt('union operand', () => assert.strictEqual(JSON.parse(JSON.stringify(expected)), expected)); +} diff --git a/tests/corpus/8065-cjs-top-level-return/early.js b/tests/corpus/8065-cjs-top-level-return/early.js new file mode 100644 index 000000000..8d7de4ba3 --- /dev/null +++ b/tests/corpus/8065-cjs-top-level-return/early.js @@ -0,0 +1,6 @@ +'use strict'; +exports.ready = true; +console.log('early module start', typeof helper); +if (exports.ready) return; +console.log('early module end'); +function helper() {} diff --git a/tests/corpus/8065-cjs-top-level-return/main.js b/tests/corpus/8065-cjs-top-level-return/main.js new file mode 100644 index 000000000..7743eaac5 --- /dev/null +++ b/tests/corpus/8065-cjs-top-level-return/main.js @@ -0,0 +1,16 @@ +'use strict'; +// A CommonJS module may end its evaluation with a top-level return: the +// rest of the module does not run, while timers, exit handlers and the +// modules it already required keep their effects. +const early = require('./early.js'); +console.log('early exports', early.ready); +setTimeout(() => console.log('timer ran'), 1); +process.on('exit', () => console.log('exit handler')); +for (const value of [1, 2, 3]) { + if (value === 2) return 'ignored value'; + console.log('value', value); +} +console.log('not reached'); +function hoisted() { + return 'hoisted'; +} diff --git a/tests/corpus/8065-cjs-top-level-return/package.json b/tests/corpus/8065-cjs-top-level-return/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/corpus/8065-cjs-top-level-return/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/corpus/8066-object-hasown-delete.cjs b/tests/corpus/8066-object-hasown-delete.cjs new file mode 100644 index 000000000..11668b521 --- /dev/null +++ b/tests/corpus/8066-object-hasown-delete.cjs @@ -0,0 +1,23 @@ +'use strict'; +// Object.hasOwn over nullish receivers and as a value, and `delete` of +// properties JavaScript inference types as required. +const o = { a: 1, b: undefined }; +console.log(Object.hasOwn(o, 'a'), Object.hasOwn(o, 'b'), Object.hasOwn(o, 'c')); +console.log(Object.hasOwn([1, 2], 1), Object.hasOwn([1, 2], 2), Object.hasOwn(Object.create({ x: 1 }), 'x')); +for (const value of [null, undefined]) { + try { + Object.hasOwn(value, 'a'); + } catch (e) { + console.log(e.name, e.message); + } +} +const pick = Math.random() < 2 ? { k: 1 } : null; +console.log(Object.hasOwn(pick, 'k')); +const hasOwn = Object.hasOwn; +console.log(hasOwn({ k: 1 }, 'k'), hasOwn({}, 'k'), [{ a: 1 }, {}].map((x) => hasOwn(x, 'a')).join()); +const record = { a: 1, b: 2 }; +delete record.a; +console.log(Object.keys(record).join(), Object.hasOwn(record, 'a'), record.a); +const table = { one: 1, two: 2 }; +for (const key of Object.keys(table)) if (key === 'one') delete table[key]; +console.log(JSON.stringify(table)); diff --git a/tests/corpus/8067-assert-throws-validators.cjs b/tests/corpus/8067-assert-throws-validators.cjs new file mode 100644 index 000000000..189f4221a --- /dev/null +++ b/tests/corpus/8067-assert-throws-validators.cjs @@ -0,0 +1,22 @@ +'use strict'; +// assert.throws and assert.rejects with validation functions, constructor +// functions, and untyped expected values (Node's expectedException). +const assert = require('assert'); +function show(label, fn) { + try { fn(); console.log(label, 'passed'); } catch (e) { console.log(label, e.name, JSON.stringify(e.message), e.generatedMessage, e.operator, e.actual && e.actual.message); } +} +function check(err) { return err.message === 'x'; } +show('named false', () => assert.throws(() => { throw new Error('y'); }, check)); +show('named true', () => assert.throws(() => { throw new Error('x'); }, check)); +show('custom message', () => assert.throws(() => { throw new Error('y'); }, check, 'custom')); +show('returns 1', () => assert.throws(() => { throw new Error('y'); }, () => 1)); +show('non-error thrown', () => assert.throws(() => { throw 'str'; }, (v) => v === 'str')); +show('non-error fails', () => assert.throws(() => { throw 42; }, () => undefined)); +function MyErr(m) { this.message = m; } +show('constructor', () => assert.throws(() => { throw new MyErr('q'); }, MyErr)); +function untyped(expected) { assert.throws(() => { throw new Error('u'); }, expected); } +show('untyped validator', () => untyped((e) => e.message === 'u')); +show('untyped validator fails', () => untyped(function v() { return false; })); +show('untyped object', () => untyped({ message: 'u' })); +assert.rejects(Promise.reject(new Error('r')), (e) => e.message === 'r').then(() => console.log('rejects passed')); +assert.rejects(Promise.reject(new Error('r')), () => false).catch((e) => console.log('rejects', JSON.stringify(e.message), e.operator)); diff --git a/tests/corpus/8076-assert-instance-identity.cjs b/tests/corpus/8076-assert-instance-identity.cjs new file mode 100644 index 000000000..e8cedf3d6 --- /dev/null +++ b/tests/corpus/8076-assert-instance-identity.cjs @@ -0,0 +1,50 @@ +'use strict'; +// assert.strictEqual and notStrictEqual compare class instances by +// reference: two instances, an instance and the same instance read back +// through an untyped value, and an instance a method returns. Symbols +// compare by identity too. +const assert = require('assert'); +const EventEmitter = require('events'); + +class Box { + constructor(v) { + this.v = v; + } + self() { + return this; + } +} + +const b = new Box(1); +const c = new Box(1); +assert.strictEqual(b, b.self()); +assert.notStrictEqual(b, c); +const e = new EventEmitter(); +assert.strictEqual(e, e.setMaxListeners(1)); +const untyped = [e, b][0]; +assert.strictEqual(untyped, e); +assert.strictEqual(e, untyped); +assert.notStrictEqual(untyped, b); + +try { + assert.strictEqual(b, c); +} catch (error) { + console.log(error.code); + console.log(error.message); +} +try { + assert.notStrictEqual(b, b.self(), 'same box'); +} catch (error) { + console.log(error.code, error.message); +} +// A symbol read back through an untyped value is the same symbol. +const symbol = Symbol('s'); +const read = Reflect.get({ k: symbol }, 'k'); +assert.strictEqual(read, symbol); +assert.notStrictEqual(read, Symbol('s')); +try { + assert.strictEqual(read, Symbol('other')); +} catch (error) { + console.log(error.code, error.message.split('\n')[0]); +} +console.log('done'); diff --git a/tests/corpus/8078-instanceof-object-function.cjs b/tests/corpus/8078-instanceof-object-function.cjs new file mode 100644 index 000000000..9a72de00f --- /dev/null +++ b/tests/corpus/8078-instanceof-object-function.cjs @@ -0,0 +1,55 @@ +'use strict'; +// `x instanceof Object` and `x instanceof Function` on untyped values follow +// Node: objects are instances of Object unless their prototype chain ends +// in null first, and functions (classes and arrows included) are instances +// of Function. +class Shape {} +class Square extends Shape {} + +const nullProto = Object.create(null); +const underNull = Object.create(nullProto); +const regrown = Object.create(null); +Object.setPrototypeOf(regrown, {}); +const orphan = function orphan() {}; +Object.setPrototypeOf(orphan, null); +function declared() {} + +const values = { + 'plain object': {}, + array: [1, 2], + 'Object.create(null)': nullProto, + 'object under a null-prototype object': underNull, + 'Object.create(null) given a prototype': regrown, + 'Object.create(plain object)': Object.create({ a: 1 }), + 'parsed JSON': JSON.parse('{"a":[1]}'), + 'function declaration': declared, + 'bound function': declared.bind(null), + 'arrow function': () => 1, + 'async function': async () => {}, + class: Square, + 'class instance': new Square(), + 'function with a null prototype': orphan, + map: new Map(), + date: new Date(0), + 'regular expression': /x/, + error: new TypeError('t'), + 'typed array': new Uint8Array(2), + promise: Promise.resolve(1), + number: 1, + string: 's', + boolean: false, + null: null, + undefined: undefined, + symbol: Symbol('s'), + bigint: 10n, +}; + +for (const [label, value] of Object.entries(values)) { + console.log(`${label}: Object ${value instanceof Object}, Function ${value instanceof Function}`); +} + +function describe(value) { + if (!(value instanceof Object)) return 'primitive or null-prototype'; + return value instanceof Function ? 'function' : 'object'; +} +console.log([nullProto, {}, describe, 0].map(describe).join(', ')); diff --git a/tests/corpus/8190-assert-function-values.cjs b/tests/corpus/8190-assert-function-values.cjs new file mode 100644 index 000000000..b91434899 --- /dev/null +++ b/tests/corpus/8190-assert-function-values.cjs @@ -0,0 +1,30 @@ +'use strict'; +// assert methods are function values: stored, passed and called through +// aliases, they assert as the methods do, and every read is the same +// function. A constant bound to a method and called directly reports +// the call as Node quotes it. +const assert = require('assert'); +const { strictEqual, deepStrictEqual } = require('assert'); +const strict = require('assert/strict'); +const EventEmitter = require('events'); +console.log(typeof assert.fail, assert.fail === assert.fail, strict.equal === assert.strictEqual, strictEqual === assert.strictEqual); +const ok = assert.ok; +ok(1); +const tryIt = (label, fn) => { try { fn(); console.log(label, 'passed'); } catch (e) { console.log(label, e.name, e.code, JSON.stringify(e.message), e.generatedMessage); } }; +tryIt('ok', () => ok(0)); +tryIt('ok none', () => ok()); +tryIt('ok msg', () => ok(false, 'nope')); +tryIt('ok err', () => ok(false, new TypeError('own'))); +tryIt('fail', () => assert.fail.call(null)); +tryIt('fail msg', () => (0, assert.fail)('boom')); +tryIt('equal', () => assert.equal.call(null, 1, '1')); +tryIt('equal fail', () => [assert.equal][0](1, 2)); +tryIt('notEqual fail', () => [assert.notEqual][0](1, '1')); +tryIt('strict', () => strictEqual(1, 2)); +tryIt('deep', () => deepStrictEqual({ a: 1 }, { a: 1 })); +tryIt('deep fail', () => [deepStrictEqual][0]({ a: 1 }, { a: 2 }, 'differs')); +tryIt('ifError', () => [assert.ifError][0](null)); +const checks = [assert.ok, assert.fail, assert.strictEqual]; +checks.forEach((check, i) => console.log(i, typeof check, check === checks[i], check.name)); +[1, 'x', true].forEach((value) => assert.ok(value)); +console.log('done'); diff --git a/tests/corpus/8191-assert-function-values.ts b/tests/corpus/8191-assert-function-values.ts new file mode 100644 index 000000000..8ef24153b --- /dev/null +++ b/tests/corpus/8191-assert-function-values.ts @@ -0,0 +1,16 @@ +// assert methods read as values in TypeScript: a constant alias called +// directly, a stored value, and a method passed as a callback. +import assert from "node:assert"; +const eq = assert.strictEqual; +eq(1, 1); +const fail = assert.fail; +const checks: unknown[] = [assert.ok, assert.fail]; +console.log(typeof checks[0], checks[1] === fail); +try { + eq(1, 2, "one is not two"); +} catch (error) { + console.log((error as Error).message); +} +const run = (fn: (v: unknown) => void) => fn(true); +run(assert.ok); +console.log("done"); diff --git a/tests/coverage-fixtures/startup-fences.js b/tests/coverage-fixtures/startup-fences.js index 22aad4e0a..5e1d00b62 100644 --- a/tests/coverage-fixtures/startup-fences.js +++ b/tests/coverage-fixtures/startup-fences.js @@ -2,8 +2,8 @@ // a fence inside a function runs when the function is called. "use strict"; var value = JSON.parse("{}"); -console.log(value instanceof Object); +console.log(value instanceof WeakRef); function check(other) { - return other instanceof Object; + return other instanceof WeakRef; } console.log(check([])); diff --git a/tests/diagnostics/assert-fences.ts b/tests/diagnostics/assert-fences.ts index 823c64c40..7cf96bc5b 100644 --- a/tests/diagnostics/assert-fences.ts +++ b/tests/diagnostics/assert-fences.ts @@ -5,8 +5,8 @@ // (typed arrays, class instances), namespace-object calls, unsupported // throws/rejects expectations (properties outside the static error // surface, empty shapes, non-literal objects, doesNotReject shapes — -// Node's own TypeError), ifError over uncomparable types, and assert -// functions as values. Every reached site terminates in a named +// Node's own TypeError), ifError over uncomparable types, and assert.throws +// as a value. Every reached site terminates in a named // diagnostic — never a miscompare, never silence. import assert from "node:assert"; import * as assertNs from "node:assert"; @@ -47,8 +47,8 @@ assert.throws(() => { throw new Error("x"); }, {}); // Node rejects the empty sh void assert.doesNotReject(async () => {}, { name: "TypeError" }); // Node's own TypeError (function or RegExp only) assert.ifError(new Map()); // no honest ifError rendering for this type -const eq = assert.strictEqual; // assert functions as values -eq(1, 1); +const throwsFn = assert.throws; // assert.throws as a value: it takes the function to run +throwsFn(() => {}); interface Tree { label: string; kids: Tree[] } const tree: Tree = { label: "t", kids: [] }; diff --git a/tests/harness/__snapshots__/assert-fences.ts.txt b/tests/harness/__snapshots__/assert-fences.ts.txt index 2ab78f117..041c20e9b 100644 --- a/tests/harness/__snapshots__/assert-fences.ts.txt +++ b/tests/harness/__snapshots__/assert-fences.ts.txt @@ -104,12 +104,12 @@ assert-fences.ts:48:16 - error SC2020: 'assert.ifError over 'Map hint: numbers, strings, booleans, Errors, null/undefined, and unions of those are the lowered forms -assert-fences.ts:50:12 - error SC2020: 'assert.strictEqual' is part of the standard library types but has no scriptc lowering yet +assert-fences.ts:50:18 - error SC2020: 'assert.throws' is part of the standard library types but has no scriptc lowering yet 49 | - 50 | const eq = assert.strictEqual; // assert functions as values - | ^~~~~~~~~~~~~~~~~~ - 51 | eq(1, 1); + 50 | const throwsFn = assert.throws; // assert.throws as a value: it takes the function to run + | ^~~~~~~~~~~~~ + 51 | throwsFn(() => {}); hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index e1831c609..1046ca6af 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -221,10 +221,10 @@ test("deferred fences in a module's top-level code report as running at startup" ]); const out = report(fixture("startup-fences.js")); expect(out).toMatch( - /runs at startup {3}1 site .*\n.*instanceof against this native constructor\n\s+at tests\/coverage-fixtures\/startup-fences\.js:5:13/, + /runs at startup {3}1 site .*\n.*'instanceof' right-hand sides other than classes declared in the program\n\s+at tests\/coverage-fixtures\/startup-fences\.js:5:30/, ); expect(out).toMatch( - /deferred to runtime {3}1 site .*\n.*instanceof against this native constructor\n\s+at tests\/coverage-fixtures\/startup-fences\.js:7:10/, + /deferred to runtime {3}1 site .*\n.*'instanceof' right-hand sides other than classes declared in the program\n\s+at tests\/coverage-fixtures\/startup-fences\.js:7:27/, ); const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }); expect(envelope.diagnostics.map((d) => [d.scope, d.line, d.startup])).toEqual([ From cd3c48d2b0d7dff2a3b910770e65b6d05df40980 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 08:19:25 -0500 Subject: [PATCH 061/395] Fence fs.readSyncDyn with fs.readSync The readSync form whose arguments are checked at run time lowers to fs.readSyncDyn, which the determinism attestation demotes on. List it as an alias of fs.readSync so the readSync fence witnesses it. --- packages/compiler/src/frontend/lowering/surfaces.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 90acabce6..15606cc3a 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2127,6 +2127,8 @@ export const BUILTIN_MODULE_FN_ALIASES: Record< ], // The utf8 string overload; the table row is the Buffer-window form. writeSync: ["fs.writeStrSync"], + // The form whose arguments Node checks at run time. + readSync: ["fs.readSyncDyn"], // The { recursive, mode } option lowerings. mkdirSync: ["fs.mkdirModeSync", "fs.mkdirRecursiveSync", "fs.mkdirRecursiveModeSync"], // The { recursive, force } and maxRetries/retryDelay option lowerings. From 878f03288630b2097c7102de0146e016214d74f9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 08:35:30 -0500 Subject: [PATCH 062/395] Crypto parity: RSA key generation lengths, RSA-PSS keys and encrypted private keys --- .../limitations/builtin-apis/crypto.mdx | 9 +- .../ambient/scriptc-node-fallback.d.ts | 31 +- packages/compiler/src/ir/runtime-features.ts | 11 +- .../corpus/8165-rsa-keygen-lengths.cjs.json | 6 + .../tests/corpus/8166-rsa-pss-keys.cjs.json | 6 + .../8167-encrypted-private-keys.cjs.json | 6 + packages/runtime/src/scr_cipher.c | 45 +- packages/runtime/src/scr_cipher_names.h | 100 ++ packages/runtime/src/scr_lib.c | 5 + packages/runtime/src/scr_lib.h | 3 + packages/runtime/src/scr_pki.c | 1387 ++++++++++++++++- packages/runtime/src/scr_webcrypto_pki.c | 3 +- tests/corpus/8165-rsa-keygen-lengths.cjs | 40 + tests/corpus/8166-rsa-pss-keys.cjs | 104 ++ tests/corpus/8167-encrypted-private-keys.cjs | 114 ++ .../8165-rsa-keygen-lengths.cjs.json | 1 + .../corpus-costs/8166-rsa-pss-keys.cjs.json | 1 + .../8167-encrypted-private-keys.cjs.json | 1 + 18 files changed, 1787 insertions(+), 86 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json create mode 100644 packages/runtime/src/scr_cipher_names.h create mode 100644 tests/corpus/8165-rsa-keygen-lengths.cjs create mode 100644 tests/corpus/8166-rsa-pss-keys.cjs create mode 100644 tests/corpus/8167-encrypted-private-keys.cjs create mode 100644 tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json create mode 100644 tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json create mode 100644 tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json diff --git a/docs/content/limitations/builtin-apis/crypto.mdx b/docs/content/limitations/builtin-apis/crypto.mdx index 329aa6809..bbe9cca21 100644 --- a/docs/content/limitations/builtin-apis/crypto.mdx +++ b/docs/content/limitations/builtin-apis/crypto.mdx @@ -31,18 +31,21 @@ `checkPrime`, `checkPrimeSync`, `generatePrime`, and `generatePrimeSync` support bigint, ArrayBuffer, typed-array, Buffer, and DataView values and the `checks`, `safe`, `add`, `rem`, and `bigint` options on native targets, with Node's errors; the callback forms compute before they return and call back in a later macrotask. Candidates and sizes longer than 262144 bits throw a coded SC2020 error, and wasm32-wasi builds are refused. Node calls their callbacks with `undefined` as the error, which a callback whose error parameter is typed `Error | null`, as @types/node declares it, receives as `null`. - RSA, EC, Ed25519, and X25519 private and public KeyObjects come from `generateKeyPairSync`, `generateKeyPair`, `createPrivateKey`, and `createPublicKey`, on native targets. + RSA, RSA-PSS, EC, Ed25519, and X25519 private and public KeyObjects come from `generateKeyPairSync`, `generateKeyPair`, `createPrivateKey`, and `createPublicKey`, on native targets. `generateKeyPairSync` and `generateKeyPair` check their arguments in Node's order, with Node's errors, including the OpenSSL errors for RSA modulus lengths below 512 or above 2147483647 bits and for public exponents of 0, 1, or an even number, which `generateKeyPair` passes to its callback. - RSA keys generate with even modulus lengths from 1024 to 8192 bits and public exponents up to 2147483647; other lengths and exponents that Node accepts, and `paramEncoding: 'explicit'`, throw a coded SC2020 error. + RSA keys generate with modulus lengths from 512 to 8192 bits, an odd length from 2048 bits yielding one bit less as in Node, and odd public exponents up to 4294967295; longer moduli that Node accepts, and `paramEncoding: 'explicit'`, throw a coded SC2020 error. The EC curves are prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521), and secp256k1. Keys read and write PEM and DER as PKCS#1, SEC1, PKCS#8, and SubjectPublicKeyInfo, and JWK (Ed25519 and X25519 keys use PKCS#8, SubjectPublicKeyInfo, and JWK); `createPublicKey` also takes a private key or an X.509 certificate. EC, Ed25519, and X25519 keys also read and write the `raw-public` and `raw-private` formats, including compressed EC points. Raw X448, Ed448, and post-quantum keys, EC scalars of 0 or at least the curve order, and the EC point at infinity, which Node accepts, throw a coded SC2020 error. They support `type`, `asymmetricKeyType`, `asymmetricKeyDetails`, `export()`, `equals()`, and `instanceof KeyObject`. `crypto.sign`, `crypto.verify`, `createSign`, and `createVerify` support RSA PKCS#1 v1.5 and PSS padding and ECDSA with DER and IEEE P1363 encodings over SHA-1 and SHA-2 digests, and Ed25519 through `crypto.sign` and `crypto.verify` with a `null` or `undefined` algorithm. + RSA-PSS keys follow their RSASSA-PSS-params as Node does: PSS padding only, the key's digest and MGF1 digest, and salts of at least the key's length, with OpenSSL's errors; their details name the parameters, they export as PKCS#8 and SubjectPublicKeyInfo only, and digests other than SHA-1 and SHA-2 in their options throw a coded SC2020 error. Ed25519 signing, verification, and private keys link when a string literal in the program names `ed25519` (in any case); without one they throw a coded SC2020 error, while Ed25519 public keys still import and export. Sign and Verify values accumulate `update()` data and are not writable streams. - Encrypted private keys (`passphrase`, `cipher`), the other key types (`rsa-pss`, `dsa`, `ed448`, `x448`, and the rest), and wasm32-wasi builds throw a coded SC2020 error or are refused. + Private keys export encrypted (`cipher`, `passphrase`) with aes-128, aes-192, and aes-256 in CBC and ECB modes and with des-ede3-cbc, PKCS#8 as PBES2 with PBKDF2-HMAC-SHA256 and PKCS#1 and SEC1 as traditional PEM, and the ciphers OpenSSL refuses for a form throw its errors; a cipher option can also be an OID; other ciphers Node encrypts with throw a coded SC2020 error. + Encrypted private keys import with a `passphrase` from PBES2 with HMAC-SHA PRFs and the CBC and ECB ciphers, and from traditional PEM with the CBC ciphers, with Node's errors for a missing or wrong passphrase; other schemes and ciphers throw a coded SC2020 error once a passphrase is given. + The other key types (`dsa`, `ed448`, `x448`, and the rest) and wasm32-wasi builds throw a coded SC2020 error or are refused. Comparing keys of different types with `equals()` answers false; Node also leaves an OpenSSL error queued, which a later key operation can report. `publicEncrypt`/`privateDecrypt` remain unsupported. The WebCrypto `crypto` global, which node:crypto also exports as `webcrypto`, supports `randomUUID()` and `getRandomValues()`, also read as values from untyped JavaScript. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 1fa787e35..2c056c148 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -2914,6 +2914,8 @@ declare module "crypto" { export interface AsymmetricKeyExportOptions { type: "pkcs1" | "spki" | "pkcs8" | "sec1"; format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; } export interface AsymmetricKeyDetails { modulusLength?: number; @@ -2938,6 +2940,7 @@ declare module "crypto" { format?: "pem" | "der" | undefined; type?: "pkcs1" | "pkcs8" | "sec1" | undefined; encoding?: string | undefined; + passphrase?: string | Buffer | undefined; } export interface PublicKeyInput { key: string | Buffer; @@ -2974,12 +2977,22 @@ declare module "crypto" { modulusLength: number; publicExponent?: number | undefined; publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs1" | "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export interface ECKeyPairOptions { namedCurve: string; publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { type: "sec1" | "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "sec1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "rsa", @@ -3007,7 +3020,12 @@ declare module "crypto" { ): KeyPairKeyObjectResult; export interface ED25519KeyPairOptions { publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "ed25519", @@ -3020,7 +3038,12 @@ declare module "crypto" { export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; export interface X25519KeyPairOptions { publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "x25519", diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85b21af7b..1cae265d5 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -875,10 +875,11 @@ const RUNTIME_FEATURE_RULES = { mod.functions.some( (fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn", ), - // The legacy url functions, crypto.createCipheriv and - // ecdh.setPublicKey emit their deprecation warnings through the - // dyn-async unit (WebCrypto's encrypt and decrypt link scr_cipher.c - // too); scr_wasm.c boxes its handles and promises through it. + // The legacy url functions, crypto.createCipheriv, + // ecdh.setPublicKey and generateKeyPair's rsa-pss options emit their + // deprecation warnings through the dyn-async unit (WebCrypto's + // encrypt and decrypt link scr_cipher.c too); scr_wasm.c boxes its + // handles and promises through it. fns: [ ...DYN_ASYNC_LIB_FNS, ...READABLE_DYN_ASYNC_LIB_FNS, @@ -889,6 +890,8 @@ const RUNTIME_FEATURE_RULES = { "webcrypto.wrapKey", "webcrypto.unwrapKey", "crypto.ecdhNew", + "crypto.generateKeyPairCb", + "crypto.generateKeyPairSync", ], prefixes: [...READABLE_DYN_ASYNC_PREFIXES, "wasm."], expr: (node, tables) => diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json new file mode 100644 index 000000000..45346fc86 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8165-rsa-keygen-lengths.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json new file mode 100644 index 000000000..623419ef3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8166-rsa-pss-keys.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json new file mode 100644 index 000000000..e5e03f8e9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8167-encrypted-private-keys.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_cipher.c b/packages/runtime/src/scr_cipher.c index a2db11434..33780fdfc 100644 --- a/packages/runtime/src/scr_cipher.c +++ b/packages/runtime/src/scr_cipher.c @@ -12,6 +12,7 @@ * compared in constant time. A cipher used as a stream reads and writes * through its own native Transform, which the stream unit builds on first * use, as Node's LazyTransform is. All arguments are borrowed. */ +#include "scr_cipher_names.h" #include "scr_runtime.h" #include @@ -114,52 +115,12 @@ static const ScCipherSpec sc_specs[] = { static const struct { const char *name; unsigned iv_len; -} sc_legacy[] = { - {"des", 8}, {"des-cbc", 8}, {"des-ecb", 0}, {"des-cfb", 8}, {"des-cfb1", 8}, {"des-cfb8", 8}, - {"des-ofb", 8}, {"desx", 8}, {"desx-cbc", 8}, {"bf", 8}, {"bf-cbc", 8}, {"bf-ecb", 0}, - {"bf-cfb", 8}, {"bf-ofb", 8}, {"blowfish", 8}, {"cast", 8}, {"cast-cbc", 8}, {"cast5-cbc", 8}, - {"cast5-ecb", 0}, {"cast5-cfb", 8}, {"cast5-ofb", 8}, {"rc2", 8}, {"rc2-cbc", 8}, {"rc2-ecb", 0}, - {"rc2-cfb", 8}, {"rc2-ofb", 8}, {"rc2-40-cbc", 8}, {"rc2-64-cbc", 8}, {"rc4", 0}, {"rc4-40", 0}, - {"rc4-hmac-md5", 0}, {"seed", 16}, {"seed-cbc", 16}, {"seed-ecb", 0}, {"seed-cfb", 16}, - {"seed-ofb", 16}, {"idea", 8}, {"idea-cbc", 8}, {"idea-ecb", 0}, {"idea-cfb", 8}, {"idea-ofb", 8}, -}; +} sc_legacy[] = {SCR_CIPHER_LEGACY_ENTRIES}; /* crypto.getCiphers() on Node 24.22 (OpenSSL 3.5). Names in this list that * have no implementation throw a coded SC2020 error instead of Node's * ERR_CRYPTO_UNKNOWN_CIPHER. */ -static const char *const sc_known[] = { - "aes-128-cbc", "aes-128-cbc-cts", "aes-128-ccm", "aes-128-cfb", "aes-128-cfb1", "aes-128-cfb8", - "aes-128-ctr", "aes-128-ecb", "aes-128-gcm", "aes-128-gcm-siv", "aes-128-ocb", "aes-128-ofb", - "aes-128-siv", "aes-128-wrap", "aes-128-wrap-inv", "aes-128-wrap-pad", "aes-128-wrap-pad-inv", - "aes-128-xts", "aes-192-cbc", "aes-192-cbc-cts", "aes-192-ccm", "aes-192-cfb", "aes-192-cfb1", - "aes-192-cfb8", "aes-192-ctr", "aes-192-ecb", "aes-192-gcm", "aes-192-gcm-siv", "aes-192-ocb", - "aes-192-ofb", "aes-192-siv", "aes-192-wrap", "aes-192-wrap-inv", "aes-192-wrap-pad", - "aes-192-wrap-pad-inv", "aes-256-cbc", "aes-256-cbc-cts", "aes-256-ccm", "aes-256-cfb", - "aes-256-cfb1", "aes-256-cfb8", "aes-256-ctr", "aes-256-ecb", "aes-256-gcm", "aes-256-gcm-siv", - "aes-256-ocb", "aes-256-ofb", "aes-256-siv", "aes-256-wrap", "aes-256-wrap-inv", - "aes-256-wrap-pad", "aes-256-wrap-pad-inv", "aes-256-xts", "aes128", "aes128-wrap", - "aes128-wrap-inv", "aes128-wrap-pad", "aes128-wrap-pad-inv", "aes192", "aes192-wrap", - "aes192-wrap-inv", "aes192-wrap-pad", "aes192-wrap-pad-inv", "aes256", "aes256-wrap", - "aes256-wrap-inv", "aes256-wrap-pad", "aes256-wrap-pad-inv", "aria-128-cbc", "aria-128-ccm", - "aria-128-cfb", "aria-128-cfb1", "aria-128-cfb8", "aria-128-ctr", "aria-128-ecb", "aria-128-gcm", - "aria-128-ofb", "aria-192-cbc", "aria-192-ccm", "aria-192-cfb", "aria-192-cfb1", "aria-192-cfb8", - "aria-192-ctr", "aria-192-ecb", "aria-192-gcm", "aria-192-ofb", "aria-256-cbc", "aria-256-ccm", - "aria-256-cfb", "aria-256-cfb1", "aria-256-cfb8", "aria-256-ctr", "aria-256-ecb", "aria-256-gcm", - "aria-256-ofb", "aria128", "aria192", "aria256", "camellia-128-cbc", "camellia-128-cbc-cts", - "camellia-128-cfb", "camellia-128-cfb1", "camellia-128-cfb8", "camellia-128-ctr", - "camellia-128-ecb", "camellia-128-ofb", "camellia-192-cbc", "camellia-192-cbc-cts", - "camellia-192-cfb", "camellia-192-cfb1", "camellia-192-cfb8", "camellia-192-ctr", - "camellia-192-ecb", "camellia-192-ofb", "camellia-256-cbc", "camellia-256-cbc-cts", - "camellia-256-cfb", "camellia-256-cfb1", "camellia-256-cfb8", "camellia-256-ctr", - "camellia-256-ecb", "camellia-256-ofb", "camellia128", "camellia192", "camellia256", "chacha20", - "chacha20-poly1305", "des-ede", "des-ede-cbc", "des-ede-cfb", "des-ede-ecb", "des-ede-ofb", - "des-ede3", "des-ede3-cbc", "des-ede3-cfb", "des-ede3-cfb1", "des-ede3-cfb8", "des-ede3-ecb", - "des-ede3-ofb", "des3", "des3-wrap", "id-aes128-ccm", "id-aes128-gcm", "id-aes128-wrap", - "id-aes128-wrap-pad", "id-aes192-ccm", "id-aes192-gcm", "id-aes192-wrap", "id-aes192-wrap-pad", - "id-aes256-ccm", "id-aes256-gcm", "id-aes256-wrap", "id-aes256-wrap-pad", - "id-smime-alg-cms3deswrap", "sm4", "sm4-cbc", "sm4-ccm", "sm4-cfb", "sm4-cfb128", "sm4-ctr", - "sm4-ecb", "sm4-gcm", "sm4-ofb", "sm4-ofb128", "sm4-xts", -}; +static const char *const sc_known[] = {SCR_CIPHER_KNOWN_NAMES}; enum { SC_NO_TAG = -1 }; enum { SC_TAG_UNKNOWN, SC_TAG_SET_BY_USER, SC_TAG_COMPUTED }; diff --git a/packages/runtime/src/scr_cipher_names.h b/packages/runtime/src/scr_cipher_names.h new file mode 100644 index 000000000..38b31e06e --- /dev/null +++ b/packages/runtime/src/scr_cipher_names.h @@ -0,0 +1,100 @@ +/* crypto.getCiphers() on Node 24.22 (OpenSSL 3.5), the names a cipher + * option can give. The cipher and pki units each include it: names + * without an implementation there throw a coded SC2020 error instead of + * Node's ERR_CRYPTO_UNKNOWN_CIPHER. */ +#ifndef SCR_CIPHER_NAMES_H +#define SCR_CIPHER_NAMES_H + +#define SCR_CIPHER_KNOWN_NAMES \ + "aes-128-cbc", "aes-128-cbc-cts", "aes-128-ccm", "aes-128-cfb", "aes-128-cfb1", "aes-128-cfb8", \ + "aes-128-ctr", "aes-128-ecb", "aes-128-gcm", "aes-128-gcm-siv", "aes-128-ocb", "aes-128-ofb", \ + "aes-128-siv", "aes-128-wrap", "aes-128-wrap-inv", "aes-128-wrap-pad", "aes-128-wrap-pad-inv", \ + "aes-128-xts", "aes-192-cbc", "aes-192-cbc-cts", "aes-192-ccm", "aes-192-cfb", "aes-192-cfb1", \ + "aes-192-cfb8", "aes-192-ctr", "aes-192-ecb", "aes-192-gcm", "aes-192-gcm-siv", "aes-192-ocb", \ + "aes-192-ofb", "aes-192-siv", "aes-192-wrap", "aes-192-wrap-inv", "aes-192-wrap-pad", \ + "aes-192-wrap-pad-inv", "aes-256-cbc", "aes-256-cbc-cts", "aes-256-ccm", "aes-256-cfb", \ + "aes-256-cfb1", "aes-256-cfb8", "aes-256-ctr", "aes-256-ecb", "aes-256-gcm", "aes-256-gcm-siv", \ + "aes-256-ocb", "aes-256-ofb", "aes-256-siv", "aes-256-wrap", "aes-256-wrap-inv", \ + "aes-256-wrap-pad", "aes-256-wrap-pad-inv", "aes-256-xts", "aes128", "aes128-wrap", \ + "aes128-wrap-inv", "aes128-wrap-pad", "aes128-wrap-pad-inv", "aes192", "aes192-wrap", \ + "aes192-wrap-inv", "aes192-wrap-pad", "aes192-wrap-pad-inv", "aes256", "aes256-wrap", \ + "aes256-wrap-inv", "aes256-wrap-pad", "aes256-wrap-pad-inv", "aria-128-cbc", "aria-128-ccm", \ + "aria-128-cfb", "aria-128-cfb1", "aria-128-cfb8", "aria-128-ctr", "aria-128-ecb", "aria-128-gcm", \ + "aria-128-ofb", "aria-192-cbc", "aria-192-ccm", "aria-192-cfb", "aria-192-cfb1", "aria-192-cfb8", \ + "aria-192-ctr", "aria-192-ecb", "aria-192-gcm", "aria-192-ofb", "aria-256-cbc", "aria-256-ccm", \ + "aria-256-cfb", "aria-256-cfb1", "aria-256-cfb8", "aria-256-ctr", "aria-256-ecb", "aria-256-gcm", \ + "aria-256-ofb", "aria128", "aria192", "aria256", "camellia-128-cbc", "camellia-128-cbc-cts", \ + "camellia-128-cfb", "camellia-128-cfb1", "camellia-128-cfb8", "camellia-128-ctr", \ + "camellia-128-ecb", "camellia-128-ofb", "camellia-192-cbc", "camellia-192-cbc-cts", \ + "camellia-192-cfb", "camellia-192-cfb1", "camellia-192-cfb8", "camellia-192-ctr", \ + "camellia-192-ecb", "camellia-192-ofb", "camellia-256-cbc", "camellia-256-cbc-cts", \ + "camellia-256-cfb", "camellia-256-cfb1", "camellia-256-cfb8", "camellia-256-ctr", \ + "camellia-256-ecb", "camellia-256-ofb", "camellia128", "camellia192", "camellia256", "chacha20", \ + "chacha20-poly1305", "des-ede", "des-ede-cbc", "des-ede-cfb", "des-ede-ecb", "des-ede-ofb", \ + "des-ede3", "des-ede3-cbc", "des-ede3-cfb", "des-ede3-cfb1", "des-ede3-cfb8", "des-ede3-ecb", \ + "des-ede3-ofb", "des3", "des3-wrap", "id-aes128-ccm", "id-aes128-gcm", "id-aes128-wrap", \ + "id-aes128-wrap-pad", "id-aes192-ccm", "id-aes192-gcm", "id-aes192-wrap", "id-aes192-wrap-pad", \ + "id-aes256-ccm", "id-aes256-gcm", "id-aes256-wrap", "id-aes256-wrap-pad", \ + "id-smime-alg-cms3deswrap", "sm4", "sm4-cbc", "sm4-ccm", "sm4-cfb", "sm4-cfb128", "sm4-ctr", \ + "sm4-ecb", "sm4-gcm", "sm4-ofb", "sm4-ofb128", "sm4-xts", + +/* The ciphers OpenSSL 3 keeps in its legacy provider, which Node 24 does + * not load, with their IV lengths: OpenSSL knows their names, and using + * them fails with its "unsupported" error. */ +#define SCR_CIPHER_LEGACY_ENTRIES \ + {"des", 8}, {"des-cbc", 8}, {"des-ecb", 0}, {"des-cfb", 8}, {"des-cfb1", 8}, {"des-cfb8", 8}, \ + {"des-ofb", 8}, {"desx", 8}, {"desx-cbc", 8}, {"bf", 8}, {"bf-cbc", 8}, {"bf-ecb", 0}, \ + {"bf-cfb", 8}, {"bf-ofb", 8}, {"blowfish", 8}, {"cast", 8}, {"cast-cbc", 8}, {"cast5-cbc", 8}, \ + {"cast5-ecb", 0}, {"cast5-cfb", 8}, {"cast5-ofb", 8}, {"rc2", 8}, {"rc2-cbc", 8}, {"rc2-ecb", 0}, \ + {"rc2-cfb", 8}, {"rc2-ofb", 8}, {"rc2-40-cbc", 8}, {"rc2-64-cbc", 8}, {"rc4", 0}, {"rc4-40", 0}, \ + {"rc4-hmac-md5", 0}, {"seed", 16}, {"seed-cbc", 16}, {"seed-ecb", 0}, {"seed-cfb", 16}, \ + {"seed-ofb", 16}, {"idea", 8}, {"idea-cbc", 8}, {"idea-ecb", 0}, {"idea-cfb", 8}, {"idea-ofb", 8}, + +/* The OID texts OpenSSL's providers register as cipher aliases, with the + * name each fetches (crypto.getCipherInfo(oid).name on Node 24.22). */ +#define SCR_CIPHER_OID_ALIASES \ + {"2.16.840.1.101.3.4.1.1", "aes-128-ecb"}, {"2.16.840.1.101.3.4.1.2", "aes-128-cbc"}, \ + {"2.16.840.1.101.3.4.1.3", "aes-128-ofb"}, {"2.16.840.1.101.3.4.1.4", "aes-128-cfb"}, \ + {"2.16.840.1.101.3.4.1.5", "id-aes128-wrap"}, {"2.16.840.1.101.3.4.1.6", "id-aes128-gcm"}, \ + {"2.16.840.1.101.3.4.1.7", "id-aes128-ccm"}, {"2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad"}, \ + {"2.16.840.1.101.3.4.1.21", "aes-192-ecb"}, {"2.16.840.1.101.3.4.1.22", "aes-192-cbc"}, \ + {"2.16.840.1.101.3.4.1.23", "aes-192-ofb"}, {"2.16.840.1.101.3.4.1.24", "aes-192-cfb"}, \ + {"2.16.840.1.101.3.4.1.25", "id-aes192-wrap"}, {"2.16.840.1.101.3.4.1.26", "id-aes192-gcm"}, \ + {"2.16.840.1.101.3.4.1.27", "id-aes192-ccm"}, {"2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad"}, \ + {"2.16.840.1.101.3.4.1.41", "aes-256-ecb"}, {"2.16.840.1.101.3.4.1.42", "aes-256-cbc"}, \ + {"2.16.840.1.101.3.4.1.43", "aes-256-ofb"}, {"2.16.840.1.101.3.4.1.44", "aes-256-cfb"}, \ + {"2.16.840.1.101.3.4.1.45", "id-aes256-wrap"}, {"2.16.840.1.101.3.4.1.46", "id-aes256-gcm"}, \ + {"2.16.840.1.101.3.4.1.47", "id-aes256-ccm"}, {"2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad"}, \ + {"1.2.410.200046.1.1.1", "aria-128-ecb"}, {"1.2.410.200046.1.1.2", "aria-128-cbc"}, \ + {"1.2.410.200046.1.1.3", "aria-128-cfb"}, {"1.2.410.200046.1.1.4", "aria-128-ofb"}, \ + {"1.2.410.200046.1.1.5", "aria-128-ctr"}, {"1.2.410.200046.1.1.6", "aria-192-ecb"}, \ + {"1.2.410.200046.1.1.7", "aria-192-cbc"}, {"1.2.410.200046.1.1.8", "aria-192-cfb"}, \ + {"1.2.410.200046.1.1.9", "aria-192-ofb"}, {"1.2.410.200046.1.1.10", "aria-192-ctr"}, \ + {"1.2.410.200046.1.1.11", "aria-256-ecb"}, {"1.2.410.200046.1.1.12", "aria-256-cbc"}, \ + {"1.2.410.200046.1.1.13", "aria-256-cfb"}, {"1.2.410.200046.1.1.14", "aria-256-ofb"}, \ + {"1.2.410.200046.1.1.15", "aria-256-ctr"}, {"1.2.410.200046.1.1.34", "aria-128-gcm"}, \ + {"1.2.410.200046.1.1.35", "aria-192-gcm"}, {"1.2.410.200046.1.1.36", "aria-256-gcm"}, \ + {"1.2.410.200046.1.1.37", "aria-128-ccm"}, {"1.2.410.200046.1.1.38", "aria-192-ccm"}, \ + {"1.2.410.200046.1.1.39", "aria-256-ccm"}, {"0.3.4401.5.3.1.9.1", "camellia-128-ecb"}, \ + {"0.3.4401.5.3.1.9.3", "camellia-128-ofb"}, {"0.3.4401.5.3.1.9.4", "camellia-128-cfb"}, \ + {"0.3.4401.5.3.1.9.9", "camellia-128-ctr"}, {"0.3.4401.5.3.1.9.21", "camellia-192-ecb"}, \ + {"0.3.4401.5.3.1.9.23", "camellia-192-ofb"}, {"0.3.4401.5.3.1.9.24", "camellia-192-cfb"}, \ + {"0.3.4401.5.3.1.9.29", "camellia-192-ctr"}, {"0.3.4401.5.3.1.9.41", "camellia-256-ecb"}, \ + {"0.3.4401.5.3.1.9.43", "camellia-256-ofb"}, {"0.3.4401.5.3.1.9.44", "camellia-256-cfb"}, \ + {"0.3.4401.5.3.1.9.49", "camellia-256-ctr"}, {"1.2.156.10197.1.104.1", "sm4-ecb"}, \ + {"1.2.156.10197.1.104.2", "sm4-cbc"}, {"1.2.156.10197.1.104.3", "sm4-ofb"}, \ + {"1.2.156.10197.1.104.4", "sm4-cfb"}, {"1.2.156.10197.1.104.7", "sm4-ctr"}, \ + {"1.2.156.10197.1.104.8", "sm4-gcm"}, {"1.2.156.10197.1.104.9", "sm4-ccm"}, \ + {"1.2.156.10197.1.104.10", "sm4-xts"}, {"1.2.392.200011.61.1.1.1.2", "camellia-128-cbc"}, \ + {"1.2.392.200011.61.1.1.1.3", "camellia-192-cbc"}, \ + {"1.2.392.200011.61.1.1.1.4", "camellia-256-cbc"}, {"1.2.840.113549.3.2", "rc2-cbc"}, \ + {"1.2.840.113549.3.4", "rc4"}, {"1.2.840.113549.3.7", "des-ede3-cbc"}, \ + {"1.2.840.113549.1.9.16.3.6", "id-smime-alg-cms3deswrap"}, {"1.3.14.3.2.6", "des-ecb"}, \ + {"1.3.14.3.2.7", "des-cbc"}, {"1.3.14.3.2.8", "des-ofb"}, {"1.3.14.3.2.9", "des-cfb"}, \ + {"1.3.14.3.2.17", "des-ede"}, {"1.3.111.2.1619.0.1.1", "aes-128-xts"}, \ + {"1.3.111.2.1619.0.1.2", "aes-256-xts"}, {"1.3.6.1.4.1.3029.1.2", "bf-cbc"}, \ + {"1.3.6.1.4.1.188.7.1.1.2", "idea-cbc"}, {"1.2.410.200004.1.3", "seed-ecb"}, \ + {"1.2.410.200004.1.4", "seed-cbc"}, {"1.2.410.200004.1.5", "seed-cfb"}, \ + {"1.2.410.200004.1.6", "seed-ofb"}, {"1.2.840.113533.7.66.10", "cast5-cbc"}, + +#endif diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index fb3524c81..b7736bc35 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -8137,6 +8137,11 @@ static bool scr_digest_alg(const char *name, size_t len, ScrDigestAlg *out) { return false; } +bool scr_digest_known(const char *name, size_t len) { + ScrDigestAlg alg; + return scr_digest_alg(name, len, &alg); +} + /* createHash() and crypto.hash() also accept "dss1" in any case. */ static bool scr_hash_alg(const char *name, size_t len, ScrDigestAlg *out) { if (scr_digest_alg(name, len, out)) return true; diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index fa447ff41..7ba9e2907 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -63,6 +63,9 @@ bool scr_lib_fork_info(double *target, uintptr_t *read_handle, * direct startup never filters user arguments. */ bool scr_lib_should_collapse_reexec_arg(ScrStr *cmd, ScrArr *args); ScrStr *scr_process_platform(void); /* +1 interned ("darwin", "linux", ...) */ +/* Whether OpenSSL, as Node 24 links it, names a digest `name` (its names, + * aliases and object identifiers, case-insensitively). */ +bool scr_digest_known(const char *name, size_t len); ScrStr *scr_process_cwd(void); /* +1 fresh (getcwd) */ bool scr_stdio_write(int fd, const void *data, size_t len); diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index a1d5b98a6..908129243 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -1,7 +1,7 @@ /* node:crypto asymmetric keys and signatures over the vendored mbedTLS * public-key layer: createPrivateKey, createPublicKey, generateKeyPairSync - * and generateKeyPair for "rsa", "ec", "ed25519" and "x25519" keys, - * crypto.diffieHellman (ECDH and X25519), the PrivateKeyObject and + * and generateKeyPair for "rsa", "rsa-pss", "ec", "ed25519" and "x25519" + * keys, crypto.diffieHellman (ECDH and X25519), the PrivateKeyObject and * PublicKeyObject handles (type, asymmetricKeyType, * asymmetricKeyDetails, export(), equals()), crypto.sign and crypto.verify, * and the Sign and Verify objects of createSign and createVerify. Keys read @@ -10,7 +10,8 @@ * prime256v1, secp384r1, secp521r1 and secp256k1, and the Ed25519 * arithmetic is scr_ed25519.c. Argument checks * and errors follow Node 24's lib/internal/crypto/keys.js, keygen.js and - * sig.js. Encrypted keys and other key types throw a coded SC2020 error. + * sig.js. Private keys export and import encrypted as PBES2 PKCS#8 and as + * traditional PEM. Other key types throw a coded SC2020 error. * All arguments are borrowed. */ #include "scr_runtime.h" @@ -23,6 +24,7 @@ #include "mbedtls/asn1.h" #include "mbedtls/asn1write.h" #include "mbedtls/base64.h" +#include "mbedtls/cipher.h" #include "mbedtls/bignum.h" #include "mbedtls/ecdh.h" #include "mbedtls/ecdsa.h" @@ -30,11 +32,13 @@ #include "mbedtls/md.h" #include "mbedtls/oid.h" #include "mbedtls/pk.h" +#include "mbedtls/pkcs5.h" #include "mbedtls/platform_util.h" #include "mbedtls/rsa.h" #include "mbedtls/x509_crt.h" #include "scr_pki.h" +#include "scr_cipher_names.h" /* ── errors ───────────────────────────────────────────────────────────── */ @@ -187,6 +191,13 @@ typedef struct SpKey { * bytes as given, before clamping, and the public key */ uint8_t ed_seed[32]; uint8_t ed_pub[32]; + /* An RSA-PSS key (id-RSASSA-PSS): signatures only, with PSS padding. + * With RSASSA-PSS-params it signs only with pss_md and MGF1 over + * pss_mgf1, and salts of at least pss_salt bytes. */ + bool rsa_pss; + bool pss_params; + mbedtls_md_type_t pss_md, pss_mgf1; + int pss_salt; } SpKey; static void *sp_key_retain(void *h) { @@ -245,6 +256,27 @@ static bool sp_is_ed(const SpKey *k) { return k->ed25519; } static bool sp_is_x(const SpKey *k) { return k->x25519; } +/* The PSS fields of `from` on `to`. */ +static void sp_pss_copy(SpKey *to, const SpKey *from) { + to->rsa_pss = from->rsa_pss; + to->pss_params = from->pss_params; + to->pss_md = from->pss_md; + to->pss_mgf1 = from->pss_mgf1; + to->pss_salt = from->pss_salt; +} + +/* Node's name for a digest RSASSA-PSS-params can name. */ +static const char *sp_pss_md_name(mbedtls_md_type_t md) { + switch (md) { + case MBEDTLS_MD_SHA1: return "sha1"; + case MBEDTLS_MD_SHA224: return "sha224"; + case MBEDTLS_MD_SHA256: return "sha256"; + case MBEDTLS_MD_SHA384: return "sha384"; + case MBEDTLS_MD_SHA512: return "sha512"; + default: return NULL; + } +} + /* An Ed25519 or X25519 key: 32-byte private and public keys, no details. */ static bool sp_is_okp(const SpKey *k) { return k->ed25519 || k->x25519; } @@ -341,10 +373,102 @@ static int sp_write_ed_der(const uint8_t *prefix, size_t prefix_len, const uint8 return (int)n; } +/* A digest AlgorithmIdentifier with NULL parameters, written backwards + * at *c; the written length or a negative error. */ +static int sp_write_md_alg(unsigned char **c, unsigned char *start, mbedtls_md_type_t md) { + const char *oid; + size_t oid_len; + int r, len = 0; + if ((r = mbedtls_oid_get_oid_by_md(md, &oid, &oid_len)) != 0) return r; + if ((r = mbedtls_asn1_write_null(c, start)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_oid(c, start, oid, oid_len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; +} + +/* An RSA-PSS key's AlgorithmIdentifier, as OpenSSL writes it: id-RSASSA-PSS + * with no parameters for a key without restrictions, or RSASSA-PSS-params + * omitting the fields at their defaults (SHA-1, MGF1 with SHA-1, salt 20). */ +static int sp_write_pss_alg(unsigned char **c, unsigned char *start, const SpKey *k) { + int r, len = 0; + if (k->pss_params) { + int params = 0; + if (k->pss_salt != 20) { + int field = 0; + if ((r = mbedtls_asn1_write_int(c, start, k->pss_salt)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) < 0) + return r; + params += field + r; + } + if (k->pss_mgf1 != MBEDTLS_MD_SHA1) { + int field = 0; + if ((r = sp_write_md_alg(c, start, k->pss_mgf1)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_oid(c, start, MBEDTLS_OID_MGF1, MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1))) < 0) + return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) < 0) + return r; + params += field + r; + } + if (k->pss_md != MBEDTLS_MD_SHA1) { + int field = 0; + if ((r = sp_write_md_alg(c, start, k->pss_md)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) < 0) + return r; + params += field + r; + } + if ((r = mbedtls_asn1_write_len(c, start, (size_t)params)) < 0) return r; + params += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + len += params + r; + } + if ((r = mbedtls_asn1_write_oid(c, start, MBEDTLS_OID_RSASSA_PSS, MBEDTLS_OID_SIZE(MBEDTLS_OID_RSASSA_PSS))) < 0) + return r; + len += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; +} + +static int sp_write_pkcs1_public_der(SpKey *k, unsigned char *buf, size_t size); + static int sp_write_spki_der(SpKey *k, unsigned char *buf, size_t size) { if (sp_is_okp(k)) return sp_write_ed_der(sp_is_x(k) ? sp_x_spki_prefix : sp_ed_spki_prefix, 12, k->ed_pub, buf, size); - return mbedtls_pk_write_pubkey_der(&k->pk, buf, size); + if (!k->rsa_pss) return mbedtls_pk_write_pubkey_der(&k->pk, buf, size); + /* SubjectPublicKeyInfo ::= SEQUENCE { AlgorithmIdentifier, BIT STRING + * RSAPublicKey } */ + unsigned char inner[SP_DER_MAX]; + int n = sp_write_pkcs1_public_der(k, inner, sizeof inner); + if (n < 0) return n; + unsigned char *c = buf + size; + int r, len = 0; + if ((r = mbedtls_asn1_write_bitstring(&c, buf, inner + sizeof inner - n, (size_t)n * 8)) < 0) return r; + len += r; + if ((r = sp_write_pss_alg(&c, buf, k)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_len(&c, buf, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; } /* RSAPublicKey (PKCS#1) DER. */ @@ -414,6 +538,11 @@ static int sp_write_pkcs8_der(SpKey *k, unsigned char *buf, size_t size) { const char *oid; size_t oid_len; size_t alg_len = 0; + if (k->rsa_pss) { + if ((r = sp_write_pss_alg(&c, buf, k)) < 0) goto done; + len += (size_t)r; + goto version; + } if (sp_is_rsa(k)) { if ((r = mbedtls_asn1_write_null(&c, buf)) < 0) goto done; alg_len += (size_t)r; @@ -439,6 +568,7 @@ static int sp_write_pkcs8_der(SpKey *k, unsigned char *buf, size_t size) { alg_len += (size_t)r; len += alg_len; } +version: if ((r = mbedtls_asn1_write_int(&c, buf, 0)) < 0) goto done; len += (size_t)r; if ((r = mbedtls_asn1_write_len(&c, buf, len)) < 0) goto done; @@ -476,6 +606,622 @@ static ScrStr *sp_pem(const char *label, const unsigned char *der, size_t len) { return s; } +/* ── encrypted private keys ─────────────────────────────────────────── + * PKCS#8 EncryptedPrivateKeyInfo as OpenSSL 3 writes it (PBES2 with + * PBKDF2-HMAC-SHA256 over a 16-byte salt and 2048 iterations), and the + * traditional PEM encryption of PKCS#1 and SEC1 keys (Proc-Type and + * DEK-Info headers, the key from EVP_BytesToKey with MD5), for the ciphers + * below. Reading takes PBES2 with any HMAC-SHA PRF and these ciphers, and + * traditional PEM with the CBC ciphers through mbedTLS. */ + +typedef struct { + const char *name; /* OpenSSL's name */ + const char *pem; /* its DEK-Info name */ + mbedtls_cipher_type_t type; + const char *oid; + size_t oid_len, key_len, iv_len; + bool ecb; + /* What OpenSSL does when PKCS#8 or traditional PEM encryption takes + * the cipher, an SpKeyCipherError. */ + unsigned char pkcs8_error, traditional_error; +} SpKeyCipher; + +typedef enum { + SP_KC_OK, /* it encrypts */ + SP_KC_NO_OID, + SP_KC_EVP_CIPHER, + SP_KC_PARAMETER, + SP_KC_INPUT_LENGTH, + SP_KC_ZERO_CONTENT, + SP_KC_OPERATION, + SP_KC_PEM_CIPHER, + SP_KC_UNSUPPORTED, +} SpKeyCipherError; + +static const struct { + const char *packed, *library, *reason, *code; +} sp_key_cipher_errors[] = { + [SP_KC_NO_OID] = {"0680006C", "asn1 encoding routines", "cipher has no object identifier", + "ERR_OSSL_ASN1_CIPHER_HAS_NO_OBJECT_IDENTIFIER"}, + [SP_KC_EVP_CIPHER] = {"0300006B", "digital envelope routines", "unsupported cipher", + "ERR_OSSL_EVP_UNSUPPORTED_CIPHER"}, + [SP_KC_PARAMETER] = {"0300007A", "digital envelope routines", "cipher parameter error", + "ERR_OSSL_EVP_CIPHER_PARAMETER_ERROR"}, + [SP_KC_INPUT_LENGTH] = {"1C8000E6", "Provider routines", "invalid input length", "ERR_OSSL_INVALID_INPUT_LENGTH"}, + [SP_KC_ZERO_CONTENT] = {"068000DE", "asn1 encoding routines", "illegal zero content", + "ERR_OSSL_ASN1_ILLEGAL_ZERO_CONTENT"}, + [SP_KC_OPERATION] = {"1C800066", "Provider routines", "cipher operation failed", + "ERR_OSSL_CIPHER_OPERATION_FAILED"}, + [SP_KC_PEM_CIPHER] = {"04800071", "PEM routines", "unsupported cipher", "ERR_OSSL_PEM_UNSUPPORTED_CIPHER"}, + [SP_KC_UNSUPPORTED] = {"0308010C", "digital envelope routines", "unsupported", "ERR_OSSL_EVP_UNSUPPORTED"}, +}; + +#define SP_AES_OID(last) "\x60\x86\x48\x01\x65\x03\x04\x01" last, 9 +static const SpKeyCipher sp_key_ciphers[] = { + {"aes-128-cbc", "AES-128-CBC", MBEDTLS_CIPHER_AES_128_CBC, SP_AES_OID("\x02"), 16, 16, false}, + {"aes-192-cbc", "AES-192-CBC", MBEDTLS_CIPHER_AES_192_CBC, SP_AES_OID("\x16"), 24, 16, false}, + {"aes-256-cbc", "AES-256-CBC", MBEDTLS_CIPHER_AES_256_CBC, SP_AES_OID("\x2a"), 32, 16, false}, + {"aes-128-ecb", "AES-128-ECB", MBEDTLS_CIPHER_AES_128_ECB, SP_AES_OID("\x01"), 16, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"aes-192-ecb", "AES-192-ECB", MBEDTLS_CIPHER_AES_192_ECB, SP_AES_OID("\x15"), 24, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"aes-256-ecb", "AES-256-ECB", MBEDTLS_CIPHER_AES_256_ECB, SP_AES_OID("\x29"), 32, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"des-ede3-cbc", "DES-EDE3-CBC", MBEDTLS_CIPHER_DES_EDE3_CBC, "\x2a\x86\x48\x86\xf7\x0d\x03\x07", 8, 24, 8, false}, +}; +static const struct { + const char *alias, *name; +} sp_key_cipher_aliases[] = { + {"aes128", "aes-128-cbc"}, {"aes192", "aes-192-cbc"}, {"aes256", "aes-256-cbc"}, {"des3", "des-ede3-cbc"}, +}; + +/* The names OpenSSL 3.5 refuses to encrypt a private key with in one form + * or both, from Node 24 (the legacy-provider names fail in both). Other + * names Node knows encrypt there and are refused here. */ +#define SP_KC_FAIL(name, pkcs8, traditional) \ + {name, NULL, MBEDTLS_CIPHER_NONE, NULL, 0, 0, 0, false, SP_KC_##pkcs8, SP_KC_##traditional} +static const SpKeyCipher sp_key_cipher_failures[] = { + SP_KC_FAIL("aes-128-cbc-cts", NO_OID, OK), SP_KC_FAIL("aes-128-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aes-128-ctr", NO_OID, OK), SP_KC_FAIL("aes-128-gcm", PARAMETER, OK), + SP_KC_FAIL("aes-128-gcm-siv", NO_OID, OK), SP_KC_FAIL("aes-128-ocb", NO_OID, OK), + SP_KC_FAIL("aes-128-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-128-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-128-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-128-xts", EVP_CIPHER, OK), + SP_KC_FAIL("aes-192-cbc-cts", NO_OID, OK), SP_KC_FAIL("aes-192-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aes-192-ctr", NO_OID, OK), SP_KC_FAIL("aes-192-gcm", PARAMETER, OK), + SP_KC_FAIL("aes-192-gcm-siv", NO_OID, OK), SP_KC_FAIL("aes-192-ocb", NO_OID, OK), + SP_KC_FAIL("aes-192-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-192-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-192-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-256-cbc-cts", NO_OID, OK), + SP_KC_FAIL("aes-256-ccm", EVP_CIPHER, OK), SP_KC_FAIL("aes-256-ctr", NO_OID, OK), + SP_KC_FAIL("aes-256-gcm", PARAMETER, OK), SP_KC_FAIL("aes-256-gcm-siv", NO_OID, OK), + SP_KC_FAIL("aes-256-ocb", NO_OID, OK), SP_KC_FAIL("aes-256-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-256-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-256-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-256-xts", EVP_CIPHER, OK), + SP_KC_FAIL("aes128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes128-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes128-wrap-pad-inv", NO_OID, OK), + SP_KC_FAIL("aes192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes192-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes192-wrap-pad-inv", NO_OID, OK), + SP_KC_FAIL("aes256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes256-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes256-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aria-128-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aria-128-cfb1", NO_OID, OK), SP_KC_FAIL("aria-128-cfb8", NO_OID, OK), + SP_KC_FAIL("aria-128-ecb", OK, PEM_CIPHER), SP_KC_FAIL("aria-128-gcm", PARAMETER, OK), + SP_KC_FAIL("aria-192-ccm", EVP_CIPHER, OK), SP_KC_FAIL("aria-192-cfb1", NO_OID, OK), + SP_KC_FAIL("aria-192-cfb8", NO_OID, OK), SP_KC_FAIL("aria-192-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("aria-192-gcm", PARAMETER, OK), SP_KC_FAIL("aria-256-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aria-256-cfb1", NO_OID, OK), SP_KC_FAIL("aria-256-cfb8", NO_OID, OK), + SP_KC_FAIL("aria-256-ecb", OK, PEM_CIPHER), SP_KC_FAIL("aria-256-gcm", PARAMETER, OK), + SP_KC_FAIL("camellia-128-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-128-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-128-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-128-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("camellia-192-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-192-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-192-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-192-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("camellia-256-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-256-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-256-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-256-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("chacha20", NO_OID, OK), SP_KC_FAIL("chacha20-poly1305", NO_OID, OK), + SP_KC_FAIL("des-ede", OK, PEM_CIPHER), SP_KC_FAIL("des-ede-cbc", NO_OID, OK), + SP_KC_FAIL("des-ede-cfb", NO_OID, OK), SP_KC_FAIL("des-ede-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("des-ede-ofb", NO_OID, OK), SP_KC_FAIL("des-ede3", NO_OID, PEM_CIPHER), + SP_KC_FAIL("des-ede3-cfb", ZERO_CONTENT, OK), SP_KC_FAIL("des-ede3-cfb1", ZERO_CONTENT, OK), + SP_KC_FAIL("des-ede3-cfb8", ZERO_CONTENT, OK), SP_KC_FAIL("des-ede3-ecb", NO_OID, PEM_CIPHER), + SP_KC_FAIL("des-ede3-ofb", NO_OID, OK), SP_KC_FAIL("des3-wrap", OPERATION, PEM_CIPHER), + SP_KC_FAIL("id-aes128-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes128-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-aes192-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes192-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-aes256-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes256-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-smime-alg-cms3deswrap", OPERATION, PEM_CIPHER), SP_KC_FAIL("sm4-ccm", NO_OID, OK), + SP_KC_FAIL("sm4-ecb", OK, PEM_CIPHER), SP_KC_FAIL("sm4-gcm", NO_OID, OK), + SP_KC_FAIL("sm4-xts", NO_OID, OK), SP_KC_FAIL("des", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("des-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb1", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb8", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("desx", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("desx-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("bf-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("bf-cfb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf-ofb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("blowfish", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("cast", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-cfb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2-ofb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-40-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-64-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc4", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc4-40", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc4-hmac-md5", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("seed-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("idea", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-ofb", UNSUPPORTED, UNSUPPORTED), +}; +#undef SP_KC_FAIL + +static bool sp_ascii_eq_ci(const char *a, size_t len, const char *b) { + if (strlen(b) != len) return false; + for (size_t i = 0; i < len; i++) { + char c = a[i]; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + if (c != b[i]) return false; + } + return true; +} + +/* The cipher an export names, by a name or the OID text of one: true with + * *out set (an entry of sp_key_ciphers, or of sp_key_cipher_failures); + * false with Node's + * ERR_CRYPTO_UNKNOWN_CIPHER pending, or the SC2020 refusal for a cipher + * Node encrypts with that keys here do not. */ +static bool sp_key_cipher(const ScrStr *name, const SpKeyCipher **out) { + const char *data = name->data; + size_t len = name->len; + static const struct { + const char *oid, *name; + } oids[] = {SCR_CIPHER_OID_ALIASES}; + for (size_t i = 0; i < sizeof oids / sizeof oids[0]; i++) { + if (strlen(oids[i].oid) == len && memcmp(data, oids[i].oid, len) == 0) { + data = oids[i].name; + len = strlen(data); + } + } + const char *wanted = NULL; + for (size_t i = 0; i < sizeof sp_key_cipher_aliases / sizeof sp_key_cipher_aliases[0]; i++) + if (sp_ascii_eq_ci(data, len, sp_key_cipher_aliases[i].alias)) wanted = sp_key_cipher_aliases[i].name; + for (size_t i = 0; i < sizeof sp_key_ciphers / sizeof sp_key_ciphers[0]; i++) { + if (wanted ? strcmp(wanted, sp_key_ciphers[i].name) == 0 : sp_ascii_eq_ci(data, len, sp_key_ciphers[i].name)) { + *out = &sp_key_ciphers[i]; + return true; + } + } + for (size_t i = 0; i < sizeof sp_key_cipher_failures / sizeof sp_key_cipher_failures[0]; i++) { + if (sp_ascii_eq_ci(data, len, sp_key_cipher_failures[i].name)) { + *out = &sp_key_cipher_failures[i]; + return true; + } + } + static const char *const known[] = {SCR_CIPHER_KNOWN_NAMES}; + for (size_t i = 0; i < sizeof known / sizeof known[0]; i++) { + if (sp_ascii_eq_ci(data, len, known[i])) { + sp_not_supported("private keys encrypted with this cipher"); + return false; + } + } + sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_UNKNOWN_CIPHER", "Unknown cipher"); + return false; +} + +/* Whether encryption in the form (PKCS#8, or traditional PEM) can take + * the cipher for `len` bytes of key: false with OpenSSL's error pending, + * or the SC2020 refusal for a cipher OpenSSL encrypts with that keys here + * do not. The key wrap ciphers fail only on a length that is not a + * multiple of 8; AES key wrap then encrypts, and Node's 3DES key wrap + * corrupts its heap. */ +static bool sp_key_cipher_ready(const SpKeyCipher *ci, bool pkcs8, size_t len) { + unsigned char error = pkcs8 ? ci->pkcs8_error : ci->traditional_error; + if ((error == SP_KC_INPUT_LENGTH || error == SP_KC_OPERATION) && len % 8 == 0) error = SP_KC_OK; + if (error != SP_KC_OK) { + sp_throw_openssl(sp_key_cipher_errors[error].packed, sp_key_cipher_errors[error].library, + sp_key_cipher_errors[error].reason, sp_key_cipher_errors[error].code, true); + return false; + } + if (ci->type == MBEDTLS_CIPHER_NONE) { + sp_not_supported("private keys encrypted with this cipher"); + return false; + } + return true; +} + +/* `in` through the cipher with PKCS#7 padding, as OpenSSL's EVP layer + * pads CBC and ECB: the output (+1 malloc'd, *out_len), or NULL when + * decryption finds bad padding. */ +static unsigned char *sp_key_cipher_run(const SpKeyCipher *ci, bool encrypt, const unsigned char *key, + const unsigned char *iv, const unsigned char *in, size_t in_len, + size_t *out_len) { + const mbedtls_cipher_info_t *info = mbedtls_cipher_info_from_type(ci->type); + size_t block = mbedtls_cipher_info_get_block_size(info); + size_t padded = encrypt ? (in_len / block + 1) * block : in_len; + if (!encrypt && (in_len == 0 || in_len % block != 0)) return NULL; + unsigned char *src = malloc(padded ? padded : 1), *out = malloc(padded ? padded : 1); + if (!src || !out) scr_trap("scriptc: out of memory\n"); + memcpy(src, in, in_len); + if (encrypt) memset(src + in_len, (int)(padded - in_len), padded - in_len); + mbedtls_cipher_context_t ctx; + mbedtls_cipher_init(&ctx); + int r = mbedtls_cipher_setup(&ctx, info); + if (r == 0) r = mbedtls_cipher_setkey(&ctx, key, (int)ci->key_len * 8, encrypt ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT); + if (r == 0 && !ci->ecb) r = mbedtls_cipher_set_padding_mode(&ctx, MBEDTLS_PADDING_NONE); + if (r == 0 && !ci->ecb) r = mbedtls_cipher_set_iv(&ctx, iv, ci->iv_len); + size_t written = 0; + for (size_t i = 0; r == 0 && i < padded; i += block) { + size_t n = 0; + r = mbedtls_cipher_update(&ctx, src + i, block, out + written, &n); + written += n; + } + if (r == 0 && !ci->ecb) { + size_t n = 0; + r = mbedtls_cipher_finish(&ctx, out + written, &n); + written += n; + } + mbedtls_cipher_free(&ctx); + mbedtls_platform_zeroize(src, padded); + free(src); + if (r == 0 && !encrypt) { + unsigned char pad = written ? out[written - 1] : 0; + bool ok = pad >= 1 && pad <= block && pad <= written; + for (size_t i = 0; ok && i < pad; i++) ok = out[written - 1 - i] == pad; + if (ok) written -= pad; + else r = -1; + } + if (r != 0) { + mbedtls_platform_zeroize(out, padded); + free(out); + return NULL; + } + *out_len = written; + return out; +} + +/* EncryptedPrivateKeyInfo DER of the PKCS#8 `plain` (+1 malloc'd, + * *out_len), as OpenSSL 3 writes it; NULL on failure. */ +static unsigned char *sp_encrypt_pkcs8(const unsigned char *plain, size_t plain_len, const SpKeyCipher *ci, + const unsigned char *pass, size_t pass_len, size_t *out_len) { + unsigned char salt[16], iv[16], key[32]; + sp_random(NULL, salt, sizeof salt); + sp_random(NULL, iv, ci->iv_len); + if (mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256, pass, pass_len, salt, sizeof salt, 2048, + (uint32_t)ci->key_len, key) != 0) + return NULL; + size_t enc_len; + unsigned char *enc = sp_key_cipher_run(ci, true, key, iv, plain, plain_len, &enc_len); + mbedtls_platform_zeroize(key, sizeof key); + if (!enc) return NULL; + size_t size = enc_len + 256; + unsigned char *buf = malloc(size); + if (!buf) scr_trap("scriptc: out of memory\n"); + unsigned char *c = buf + size; + int r, len = 0, w; +#define SP_W(call) do { if ((w = (call)) < 0) { r = w; goto fail; } } while (0) + SP_W(mbedtls_asn1_write_octet_string(&c, buf, enc, enc_len)); + len += w; + /* encryptionScheme: the cipher with its IV (empty for ECB) */ + int scheme = 0; + SP_W(mbedtls_asn1_write_octet_string(&c, buf, iv, ci->iv_len)); + scheme += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, ci->oid, ci->oid_len)); + scheme += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)scheme)); + scheme += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + scheme += w; + /* keyDerivationFunc: PBKDF2 { salt, 2048, hmacWithSHA256 } */ + int kdf = 0, prf = 0; + SP_W(mbedtls_asn1_write_null(&c, buf)); + prf += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_HMAC_SHA256, MBEDTLS_OID_SIZE(MBEDTLS_OID_HMAC_SHA256))); + prf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)prf)); + prf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + prf += w; + kdf += prf; + SP_W(mbedtls_asn1_write_int(&c, buf, 2048)); + kdf += w; + SP_W(mbedtls_asn1_write_octet_string(&c, buf, salt, sizeof salt)); + kdf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)kdf)); + kdf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + kdf += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_PKCS5_PBKDF2, MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBKDF2))); + kdf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)kdf)); + kdf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + kdf += w; + int params = kdf + scheme; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)params)); + params += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + params += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_PKCS5_PBES2, MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBES2))); + params += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)params)); + params += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + params += w; + len += params; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)len)); + len += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + len += w; +#undef SP_W + memmove(buf, c, (size_t)len); + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + *out_len = (size_t)len; + return buf; +fail: + (void)r; + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + free(buf); + return NULL; +} + +/* OpenSSL's EVP_BytesToKey with MD5 and one round over the first 8 IV + * bytes, as traditional PEM encryption derives its key. */ +static void sp_bytes_to_key(const unsigned char *pass, size_t pass_len, const unsigned char *salt, unsigned char *key, + size_t key_len) { + const mbedtls_md_info_t *md5 = mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + unsigned char digest[16]; + size_t done = 0; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, md5, 0); + for (bool first = true; done < key_len; first = false) { + mbedtls_md_starts(&ctx); + if (!first) mbedtls_md_update(&ctx, digest, sizeof digest); + mbedtls_md_update(&ctx, pass, pass_len); + mbedtls_md_update(&ctx, salt, 8); + mbedtls_md_finish(&ctx, digest); + size_t n = key_len - done < sizeof digest ? key_len - done : sizeof digest; + memcpy(key + done, digest, n); + done += n; + } + mbedtls_md_free(&ctx); + mbedtls_platform_zeroize(digest, sizeof digest); +} + +/* The traditional encrypted PEM of a PKCS#1 or SEC1 key (+1), or NULL. */ +static ScrStr *sp_encrypted_pem(const char *label, const unsigned char *der, size_t len, const SpKeyCipher *ci, + const unsigned char *pass, size_t pass_len) { + unsigned char iv[16], key[32]; + sp_random(NULL, iv, ci->iv_len); + sp_bytes_to_key(pass, pass_len, iv, key, ci->key_len); + size_t enc_len; + unsigned char *enc = sp_key_cipher_run(ci, true, key, iv, der, len, &enc_len); + mbedtls_platform_zeroize(key, sizeof key); + if (!enc) return NULL; + ScrStr *body = sp_pem(label, enc, enc_len); + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + /* The headers follow the BEGIN line, then a blank line. */ + const char *nl = memchr(body->data, '\n', body->len); + size_t head = (size_t)(nl - body->data) + 1; + ScrJsonBuf out; + scr_jb_init(&out); + for (size_t i = 0; i < head; i++) scr_jb_putc(&out, body->data[i]); + scr_jb_puts(&out, "Proc-Type: 4,ENCRYPTED\nDEK-Info: "); + scr_jb_puts(&out, ci->pem); + scr_jb_putc(&out, ','); + static const char hex[] = "0123456789ABCDEF"; + for (size_t i = 0; i < ci->iv_len; i++) { + scr_jb_putc(&out, hex[iv[i] >> 4]); + scr_jb_putc(&out, hex[iv[i] & 15]); + } + scr_jb_puts(&out, "\n\n"); + for (size_t i = head; i < body->len; i++) scr_jb_putc(&out, body->data[i]); + scr_str_release(body); + return scr_jb_finish(&out); +} + +/* OpenSSL's errors for an encrypted private key: the message, Node's + * code, and the error stack OpenSSL leaves for the input's form. */ +static void sp_throw_key_decrypt(const char *message, const char *library, const char *reason, const char *code, + const char *const *stack, size_t stack_len) { + ScrStr *text = scr_str_new(message, strlen(message)); + ScrError *e = scr_error_new(SCR_ERR_ERROR, text); + scr_str_release(text); + ScrDyn *list = scr_dyn_new_arr(); + for (size_t i = 0; i < stack_len; i++) { + ScrStr *entry = scr_str_new(stack[i], strlen(stack[i])); + scr_dyn_arr_push(list, scr_dyn_new_str(entry)); + scr_str_release(entry); + } + scr_error_set_prop(e, "opensslErrorStack", 17, list); + const char *keys[] = {"library", "reason", "code"}; + const char *values[] = {library, reason, code}; + for (size_t i = 0; i < 3; i++) { + if (i == 2) scr_error_set_code(e, code); + ScrStr *value = scr_str_new(values[i], strlen(values[i])); + scr_error_set_prop(e, keys[i], strlen(keys[i]), scr_dyn_new_str(value)); + scr_str_release(value); + } + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +static const char sp_err_interrupted[] = "error:07880109:common libcrypto routines::interrupted or cancelled"; +static const char sp_err_bad_decrypt[] = "error:1C800064:Provider routines::bad decrypt"; + +/* No passphrase for an encrypted PEM key (pkcs8: whether it is PKCS#8). */ +static void sp_throw_passphrase_missing(bool pkcs8) { + static const char *const pkcs8_stack[] = {"error:04800068:PEM routines::bad password read", sp_err_interrupted, + "error:1C80009F:Provider routines::unable to get passphrase", + sp_err_interrupted, sp_err_interrupted}; + static const char *const pem_stack[] = {"error:04800068:PEM routines::bad password read", sp_err_interrupted, + sp_err_interrupted}; + sp_throw_key_decrypt(sp_err_interrupted, "common libcrypto routines", "interrupted or cancelled", + "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED", pkcs8 ? pkcs8_stack : pem_stack, pkcs8 ? 5 : 3); +} + +/* A passphrase that does not decrypt the key: PKCS#8 in PEM, PKCS#8 in + * DER, or traditional PEM. */ +static void sp_throw_bad_decrypt(int form) { + static const char cipherfinal[] = "error:11800074:PKCS12 routines::pkcs12 cipherfinal error"; + static const char *const stacks[][4] = { + {cipherfinal, sp_err_bad_decrypt, cipherfinal, sp_err_bad_decrypt}, + {cipherfinal, sp_err_bad_decrypt}, + {"error:04800065:PEM routines::bad decrypt", sp_err_bad_decrypt}, + }; + sp_throw_key_decrypt(sp_err_bad_decrypt, "Provider routines", "bad decrypt", "ERR_OSSL_BAD_DECRYPT", stacks[form], + form == 0 ? 4 : 2); +} + +/* The cipher name of an encrypted traditional PEM key's DEK-Info header + * into `name` (NUL-terminated), or false for other input. */ +static bool sp_pem_dek_info(const unsigned char *data, size_t len, char *name, size_t cap) { + const char *text = (const char *)data; + const char *begin = NULL; + for (size_t i = 0; i + 11 <= len && !begin; i++) + if (memcmp(text + i, "-----BEGIN ", 11) == 0) begin = text + i; + if (!begin) return false; + const char *end = text + len, *line = memchr(begin, '\n', (size_t)(end - begin)); + bool encrypted = false; + while (line && ++line < end) { + const char *eol = memchr(line, '\n', (size_t)(end - line)); + size_t n = (size_t)((eol ? eol : end) - line); + if (n && line[n - 1] == '\r') n--; + if (n >= 10 && memcmp(line, "Proc-Type:", 10) == 0) { + for (size_t i = 10; i + 9 <= n; i++) + if (memcmp(line + i, "ENCRYPTED", 9) == 0) encrypted = true; + } else if (n >= 9 && memcmp(line, "DEK-Info:", 9) == 0) { + if (!encrypted) return false; + size_t i = 9; + while (i < n && line[i] == ' ') i++; + size_t o = 0; + while (i < n && line[i] != ',' && o + 1 < cap) name[o++] = line[i++]; + name[o] = 0; + return true; + } else if (n == 0 || memchr(line, ':', n) == NULL) { + return false; + } + line = eol; + } + return false; +} + +/* Before mbedTLS reads an encrypted traditional PEM key: false with + * OpenSSL's error pending for a DEK-Info cipher it does not know, a + * missing passphrase, or a legacy-provider cipher, or the SC2020 refusal + * for a cipher mbedTLS does not decrypt; true for other input. */ +static bool sp_pem_dek_check(const unsigned char *data, size_t len, const ScrBytes *pass) { + char name[64]; + if (!sp_pem_dek_info(data, len, name, sizeof name)) return true; + size_t n = strlen(name); + static const char *const known[] = {SCR_CIPHER_KNOWN_NAMES}; + static const struct { + const char *name; + unsigned iv_len; + } legacy[] = {SCR_CIPHER_LEGACY_ENTRIES}; + bool is_known = false, is_legacy = false; + for (size_t i = 0; i < sizeof known / sizeof known[0]; i++) + if (sp_ascii_eq_ci(name, n, known[i])) is_known = true; + for (size_t i = 0; i < sizeof legacy / sizeof legacy[0]; i++) + if (sp_ascii_eq_ci(name, n, legacy[i].name)) is_legacy = true; + if (!is_known && !is_legacy) { + static const char *const stack[] = {"error:04800072:PEM routines::unsupported encryption"}; + sp_throw_key_decrypt(stack[0], "PEM routines", "unsupported encryption", "ERR_OSSL_PEM_UNSUPPORTED_ENCRYPTION", + stack, 1); + return false; + } + if (!pass) { + sp_throw_passphrase_missing(false); + return false; + } + if (is_legacy) { + static const char unsupported[] = "error:0308010C:digital envelope routines::unsupported"; + static const char *const stack[] = {"error:04800065:PEM routines::bad decrypt", unsupported}; + sp_throw_key_decrypt(unsupported, "digital envelope routines", "unsupported", "ERR_OSSL_EVP_UNSUPPORTED", stack, + 2); + return false; + } + /* mbedTLS decrypts these names, as OpenSSL writes them. */ + static const char *const read[] = {"AES-128-CBC", "AES-192-CBC", "AES-256-CBC", "DES-EDE3-CBC"}; + for (size_t i = 0; i < sizeof read / sizeof read[0]; i++) + if (strcmp(name, read[i]) == 0) return true; + sp_not_supported("private keys encrypted with this cipher"); + return false; +} + +/* The PKCS#8 inside a PBES2 EncryptedPrivateKeyInfo `der` (+1 malloc'd, + * *out_len): 1 decrypted, 0 another scheme or cipher, -1 a passphrase + * that does not decrypt it. */ +static int sp_decrypt_pkcs8(const unsigned char *der, size_t len, const unsigned char *pass, size_t pass_len, + unsigned char **out, size_t *out_len) { + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + mbedtls_asn1_buf alg, params; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return 0; + end = p + n; + if (mbedtls_asn1_get_alg(&p, end, &alg, ¶ms) != 0 || alg.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBES2) || + memcmp(alg.p, MBEDTLS_OID_PKCS5_PBES2, alg.len) != 0 || + params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) + return 0; + unsigned char *q = params.p; + const unsigned char *params_end = params.p + params.len; + mbedtls_asn1_buf kdf, kdf_params, scheme, scheme_params; + if (mbedtls_asn1_get_alg(&q, params_end, &kdf, &kdf_params) != 0 || + kdf.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBKDF2) || memcmp(kdf.p, MBEDTLS_OID_PKCS5_PBKDF2, kdf.len) != 0 || + mbedtls_asn1_get_alg(&q, params_end, &scheme, &scheme_params) != 0) + return 0; + const SpKeyCipher *ci = NULL; + for (size_t i = 0; i < sizeof sp_key_ciphers / sizeof sp_key_ciphers[0]; i++) + if (scheme.len == sp_key_ciphers[i].oid_len && memcmp(scheme.p, sp_key_ciphers[i].oid, scheme.len) == 0) + ci = &sp_key_ciphers[i]; + if (!ci || scheme_params.tag != MBEDTLS_ASN1_OCTET_STRING || scheme_params.len != ci->iv_len) return 0; + /* PBKDF2-params ::= SEQUENCE { salt OCTET STRING, iterationCount + * INTEGER, keyLength INTEGER OPTIONAL, prf AlgorithmIdentifier DEFAULT + * hmacWithSHA1 } */ + unsigned char *k = kdf_params.p; + const unsigned char *k_end = kdf_params.p + kdf_params.len; + size_t salt_len; + int iterations, key_len = (int)ci->key_len; + if (kdf_params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) || + mbedtls_asn1_get_tag(&k, k_end, &salt_len, MBEDTLS_ASN1_OCTET_STRING) != 0) + return 0; + const unsigned char *salt = k; + k += salt_len; + if (mbedtls_asn1_get_int(&k, k_end, &iterations) != 0 || iterations < 1) return 0; + if (k < k_end && *k == MBEDTLS_ASN1_INTEGER && (mbedtls_asn1_get_int(&k, k_end, &key_len) != 0 || + key_len != (int)ci->key_len)) + return 0; + mbedtls_md_type_t prf = MBEDTLS_MD_SHA1; + if (k < k_end) { + mbedtls_asn1_buf prf_oid, prf_params; + if (mbedtls_asn1_get_alg(&k, k_end, &prf_oid, &prf_params) != 0 || mbedtls_oid_get_md_hmac(&prf_oid, &prf) != 0) + return 0; + } + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING) != 0) return 0; + unsigned char key[32]; + if (mbedtls_pkcs5_pbkdf2_hmac_ext(prf, pass, pass_len, salt, salt_len, (unsigned)iterations, + (uint32_t)ci->key_len, key) != 0) + return 0; + *out = sp_key_cipher_run(ci, false, key, scheme_params.p, p, n, out_len); + mbedtls_platform_zeroize(key, sizeof key); + return *out ? 1 : -1; +} + static ScrDyn *sp_dyn_buffer(const unsigned char *data, size_t len) { ScrBytes *bytes = scr_bytes_new(SCR_BYTES_U8, (double)len); bytes->is_buffer = true; @@ -761,12 +1507,13 @@ static SpKey *sp_public_of(SpKey *k) { return pub; } unsigned char der[SP_DER_MAX]; - int n = sp_write_spki_der(k, der, sizeof der); + int n = k->rsa_pss ? mbedtls_pk_write_pubkey_der(&k->pk, der, sizeof der) : sp_write_spki_der(k, der, sizeof der); SpKey *pub = sp_key_alloc(false); if (n < 0 || mbedtls_pk_parse_public_key(&pub->pk, der + sizeof der - n, (size_t)n) != 0) { sp_key_release(pub); return NULL; } + sp_pss_copy(pub, k); return pub; } @@ -888,7 +1635,158 @@ static SpKey *sp_ed_from_der(const unsigned char *der, size_t len, bool private_ return k; } +/* RSASSA-PSS-params at *p (RFC 4055): true with the restrictions set on + * `k`, false when they are malformed or unsupported, which leaves the key + * without restrictions, as OpenSSL reads a negative or oversized salt. */ +static bool sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k) { + size_t n; + if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + const unsigned char *params_end = *p + n; + mbedtls_md_type_t md = MBEDTLS_MD_SHA1, mgf1 = MBEDTLS_MD_SHA1; + int salt = 20; + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { + mbedtls_asn1_buf oid, params; + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) != 0 || + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || mbedtls_oid_get_md_alg(&oid, &md) != 0 || + !sp_pss_md_name(md)) + return false; + } + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) { + mbedtls_asn1_buf oid, params; + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1) != 0 || + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || oid.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1) || + memcmp(oid.p, MBEDTLS_OID_MGF1, oid.len) != 0) + return false; + /* MGF1's parameters are the hash's AlgorithmIdentifier, whose + * content starts with the hash's OID. */ + if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return false; + unsigned char *alg = params.p; + mbedtls_asn1_buf hash_oid = {.tag = MBEDTLS_ASN1_OID}; + if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return false; + hash_oid.p = alg; + if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) return false; + } + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) { + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2) != 0 || + mbedtls_asn1_get_int(p, *p + n, &salt) != 0 || salt < 0) + return false; + } + *p = (unsigned char *)params_end; + k->pss_params = true; + k->pss_md = md; + k->pss_mgf1 = mgf1; + k->pss_salt = salt; + return true; +} + +/* An RSA-PSS key from PKCS#8 or SubjectPublicKeyInfo DER whose algorithm + * is id-RSASSA-PSS; NULL when the DER is neither, or with *failed set and + * Node's decoder error pending. A private key wanted as public answers its + * public key. */ +static SpKey *sp_public_of(SpKey *k); + +static SpKey *sp_pss_from_der(const unsigned char *der, size_t len, bool private_key, bool *failed) { + *failed = false; + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return NULL; + end = p + n; + bool pkcs8 = p < end && *p == MBEDTLS_ASN1_INTEGER; + if (pkcs8) { + int version; + if (mbedtls_asn1_get_int(&p, end, &version) != 0 || version != 0) return NULL; + } + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return NULL; + const unsigned char *alg_end = p + n; + size_t oid_len; + if (mbedtls_asn1_get_tag(&p, alg_end, &oid_len, MBEDTLS_ASN1_OID) != 0 || + oid_len != MBEDTLS_OID_SIZE(MBEDTLS_OID_RSASSA_PSS) || memcmp(p, MBEDTLS_OID_RSASSA_PSS, oid_len) != 0) + return NULL; + p += oid_len; + SpKey *k = sp_key_alloc(pkcs8); + k->rsa_pss = true; + if (p < alg_end && !sp_pss_params_read(&p, alg_end, k)) k->pss_params = false; + p = (unsigned char *)alg_end; + int r; + if (pkcs8) { + r = mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING); + if (r == 0) r = mbedtls_pk_parse_key(&k->pk, p, n, NULL, 0, sp_random, NULL); + } else { + r = private_key ? -1 : mbedtls_asn1_get_bitstring_null(&p, end, &n); + if (r == 0) r = mbedtls_pk_parse_public_key(&k->pk, p, n); + } + if (r != 0 || !sp_is_rsa(k)) { + sp_key_release(k); + *failed = true; + sp_unsupported_input(); + return NULL; + } + if (pkcs8 && !private_key) { + SpKey *pub = sp_public_of(k); + sp_key_release(k); + return pub; + } + return k; +} + +/* Whether `der` is a PKCS#8 EncryptedPrivateKeyInfo: a SEQUENCE of a PKCS#5 + * or PKCS#12 password-based AlgorithmIdentifier and an OCTET STRING. */ +static bool sp_is_encrypted_pkcs8(const unsigned char *der, size_t len) { + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + mbedtls_asn1_buf alg, params; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + end = p + n; + if (mbedtls_asn1_get_alg(&p, end, &alg, ¶ms) != 0) return false; + static const char pkcs5[] = MBEDTLS_OID_PKCS5, pkcs12[] = MBEDTLS_OID_PKCS12_PBE; + bool password = (alg.len > sizeof pkcs5 - 1 && memcmp(alg.p, pkcs5, sizeof pkcs5 - 1) == 0) || + (alg.len > sizeof pkcs12 - 1 && memcmp(alg.p, pkcs12, sizeof pkcs12 - 1) == 0); + return password && mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING) == 0 && p + n == end; +} + +static SpKey *sp_parse_with(const unsigned char *data, size_t len, bool private_key, const ScrBytes *pass); + static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) { + return sp_parse_with(data, len, private_key, NULL); +} + +/* sp_parse with a passphrase for an encrypted private key (NULL: none). */ +static SpKey *sp_parse_with(const unsigned char *data, size_t len, bool private_key, const ScrBytes *pass) { + { + unsigned char *decoded; + const unsigned char *der; + size_t der_len; + if (sp_der_of(data, len, &decoded, &der, &der_len) && sp_is_encrypted_pkcs8(der, der_len)) { + bool from_pem = decoded != NULL; + SpKey *k = NULL; + unsigned char *plain = NULL; + size_t plain_len = 0; + int r = 0; + if (!pass) { + if (from_pem) sp_throw_passphrase_missing(true); + else sp_throw(SCR_ERR_TYPE, "ERR_MISSING_PASSPHRASE", "Passphrase required for encrypted key"); + } else { + r = sp_decrypt_pkcs8(der, der_len, pass->data, (size_t)scr_bytes_byte_len(pass), &plain, &plain_len); + if (r < 0) sp_throw_bad_decrypt(from_pem ? 0 : 1); + } + if (r > 0) { + k = sp_parse_with(plain, plain_len, private_key, NULL); + mbedtls_platform_zeroize(plain, plain_len); + free(plain); + } + if (decoded) { + mbedtls_platform_zeroize(decoded, der_len); + free(decoded); + } + if (r == 0 && pass) sp_not_supported("private keys encrypted with this scheme or cipher"); + return k; + } else if (decoded) { + mbedtls_platform_zeroize(decoded, der_len); + free(decoded); + } + } { unsigned char *decoded; const unsigned char *der; @@ -896,6 +1794,7 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) if (sp_der_of(data, len, &decoded, &der, &der_len)) { bool failed; SpKey *ed = sp_ed_from_der(der, der_len, private_key, &failed); + if (!ed && !failed) ed = sp_pss_from_der(der, der_len, private_key, &failed); if (decoded) { mbedtls_platform_zeroize(decoded, der_len); free(decoded); @@ -912,6 +1811,7 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) break; } } + if (pem && !sp_pem_dek_check(data, len, pass)) return NULL; unsigned char *buf = malloc(len + 1); if (!buf) scr_trap("scriptc: out of memory\n"); memcpy(buf, data, len); @@ -919,15 +1819,19 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) size_t n = pem ? len + 1 : len; SpKey *k = sp_key_alloc(private_key); bool ok; + const unsigned char *pwd = pass ? pass->data : NULL; + size_t pwd_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; + int private_rc = 0; if (private_key) { - ok = mbedtls_pk_parse_key(&k->pk, buf, n, NULL, 0, sp_random, NULL) == 0; + private_rc = mbedtls_pk_parse_key(&k->pk, buf, n, pwd, pwd_len, sp_random, NULL); + ok = private_rc == 0; } else { ok = mbedtls_pk_parse_public_key(&k->pk, buf, n) == 0; if (!ok) { SpKey *priv = sp_key_alloc(true); mbedtls_pk_free(&k->pk); mbedtls_pk_init(&k->pk); - if (mbedtls_pk_parse_key(&priv->pk, buf, n, NULL, 0, sp_random, NULL) == 0) { + if ((private_rc = mbedtls_pk_parse_key(&priv->pk, buf, n, pwd, pwd_len, sp_random, NULL)) == 0) { sp_key_release(k); k = sp_public_of(priv); ok = k != NULL; @@ -949,7 +1853,10 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) free(buf); if (!ok) { sp_key_release(k); - sp_unsupported_input(); + /* An encrypted traditional PEM key mbedTLS read. */ + if (private_rc == MBEDTLS_ERR_PK_PASSWORD_REQUIRED) sp_throw_passphrase_missing(false); + else if (private_rc == MBEDTLS_ERR_PK_PASSWORD_MISMATCH) sp_throw_bad_decrypt(2); + else sp_unsupported_input(); return NULL; } if (!sp_key_supported(k)) { @@ -1011,13 +1918,16 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { const ScrDyn *format = scr_dyn_undefined(); const ScrDyn *type = scr_dyn_undefined(); const ScrDyn *encoding = NULL; + const ScrDyn *passphrase = NULL; if (sp_is_object(input) && !scr_buffer_storage_is(input)) { data = sp_opt(input, "key"); format = sp_opt(input, "format"); type = sp_opt(input, "type"); encoding = sp_opt(input, "encoding"); - if (!sp_absent(sp_opt(input, "passphrase"))) { - sp_not_supported("encrypted private keys"); + passphrase = sp_opt(input, "passphrase"); + if (!sp_absent(passphrase) && passphrase->kind != SCR_DYN_STR && passphrase->kind != SCR_DYN_BYTES && + !scr_buffer_storage_is(passphrase)) { + sp_property_invalid("key.passphrase", passphrase); return NULL; } SpKey *inner = sp_key_of(data); @@ -1055,7 +1965,16 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { scr_dyn_arg_type_fail(data == input ? "key" : "key.key", sp_key_input_types, data); return NULL; } - SpKey *k = sp_parse(bytes->data, (size_t)scr_bytes_byte_len(bytes), private_key); + ScrBytes *pass = NULL; + if (passphrase && !sp_absent(passphrase)) { + pass = sp_source_bytes(passphrase, NULL); + if (!pass) { + scr_bytes_release(bytes); + return NULL; + } + } + SpKey *k = sp_parse_with(bytes->data, (size_t)scr_bytes_byte_len(bytes), private_key, pass); + if (pass) scr_bytes_release(pass); scr_bytes_release(bytes); return k ? sp_key_box(k) : NULL; } @@ -1296,7 +2215,13 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { scr_dyn_arg_type_fail("options", "of type object", options ? options : scr_dyn_undefined()); return NULL; } - if (sp_is_str(format, "jwk")) return sp_export_jwk(k); + if (sp_is_str(format, "jwk")) { + if (k->rsa_pss) { + sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE", "Unsupported JWK Key Type."); + return NULL; + } + return sp_export_jwk(k); + } bool pem = sp_is_str(format, "pem"); if (!pem && !sp_is_str(format, "der")) { sp_property_invalid("options.format", format); @@ -1311,11 +2236,7 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { sp_property_invalid("options.type", type); return NULL; } - if (!sp_absent(sp_opt(options, "cipher")) || !sp_absent(sp_opt(options, "passphrase"))) { - sp_not_supported("encrypted private key export"); - return NULL; - } - if (pkcs1 && !sp_is_rsa(k)) { + if (pkcs1 && (!sp_is_rsa(k) || k->rsa_pss)) { sp_incompatible("pkcs1", "can only be used for RSA keys"); return NULL; } @@ -1323,6 +2244,30 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { sp_incompatible("sec1", "can only be used for EC keys"); return NULL; } + /* parseKeyEncoding's cipher and passphrase checks, then the cipher. */ + const ScrDyn *cipher = sp_opt(options, "cipher"), *passphrase = sp_opt(options, "passphrase"); + const SpKeyCipher *ci = NULL; + if (k->private_key) { + bool encrypted = !sp_absent(cipher) && cipher->kind != SCR_DYN_NULL; + if (encrypted) { + if (cipher->kind != SCR_DYN_STR) { + sp_property_invalid("options.cipher", cipher); + return NULL; + } + if (!pem && (pkcs1 || sec1)) { + sp_incompatible(pkcs1 ? "pkcs1" : "sec1", "does not support encryption"); + return NULL; + } + if (passphrase->kind != SCR_DYN_STR && passphrase->kind != SCR_DYN_BYTES && !scr_buffer_storage_is(passphrase)) { + sp_property_invalid("options.passphrase", passphrase); + return NULL; + } + if (!sp_key_cipher(cipher->v.str, &ci)) return NULL; + } else if (!sp_absent(passphrase)) { + sp_property_invalid_because("options.cipher", cipher, "is required when a passphrase is specified"); + return NULL; + } + } unsigned char *der = malloc(SP_DER_MAX); if (!der) scr_trap("scriptc: out of memory\n"); int n; @@ -1343,6 +2288,26 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { ScrDyn *result = NULL; if (n < 0) { sp_unsupported_input(); + } else if (ci && !sp_key_cipher_ready(ci, pkcs8, (size_t)n)) { + /* OpenSSL's error, raised once the key is encoded. */ + } else if (ci) { + const unsigned char *start = der + SP_DER_MAX - n; + ScrBytes *pass = sp_source_bytes(passphrase, NULL); + const unsigned char *pass_data = pass ? pass->data : (const unsigned char *)""; + size_t pass_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; + if (pkcs8) { + size_t enc_len; + unsigned char *enc = sp_encrypt_pkcs8(start, (size_t)n, ci, pass_data, pass_len, &enc_len); + if (enc) { + result = pem ? sp_dyn_str(sp_pem("ENCRYPTED PRIVATE KEY", enc, enc_len)) : sp_dyn_buffer(enc, enc_len); + free(enc); + } + } else { + ScrStr *text = sp_encrypted_pem(label, start, (size_t)n, ci, pass_data, pass_len); + if (text) result = sp_dyn_str(text); + } + if (pass) scr_bytes_release(pass); + if (!result) sp_unsupported_input(); } else { const unsigned char *start = der + SP_DER_MAX - n; result = pem ? sp_dyn_str(sp_pem(label, start, (size_t)n)) : sp_dyn_buffer(start, (size_t)n); @@ -1372,6 +2337,12 @@ static ScrDyn *sp_key_details(SpKey *k) { ScrBigInt *big = scr_bigint_from_u64(exponent); sp_set(details, "publicExponent", scr_dyn_new_bigint(big)); scr_bigint_release(big); + if (k->pss_params) { + const char *md = sp_pss_md_name(k->pss_md), *mgf1 = sp_pss_md_name(k->pss_mgf1); + sp_set(details, "hashAlgorithm", sp_dyn_str(scr_str_new(md, strlen(md)))); + sp_set(details, "mgf1HashAlgorithm", sp_dyn_str(scr_str_new(mgf1, strlen(mgf1)))); + sp_set(details, "saltLength", scr_dyn_new_num(k->pss_salt)); + } } else { const SpCurve *curve = sp_curve_of(k); sp_set(details, "namedCurve", sp_dyn_str(scr_str_new(curve->name, strlen(curve->name)))); @@ -1429,6 +2400,7 @@ static ScrDyn *sp_key_get(void *h, const char *name, size_t len) { if (len == 17 && memcmp(name, "asymmetricKeyType", 17) == 0) return sp_is_ed(k) ? sp_dyn_str(scr_str_new("ed25519", 7)) : sp_is_x(k) ? sp_dyn_str(scr_str_new("x25519", 6)) + : k->rsa_pss ? sp_dyn_str(scr_str_new("rsa-pss", 7)) : sp_is_rsa(k) ? sp_dyn_str(scr_str_new("rsa", 3)) : sp_dyn_str(scr_str_new("ec", 2)); if (len == 20 && memcmp(name, "asymmetricKeyDetails", 20) == 0) return sp_key_details(k); @@ -1640,7 +2612,7 @@ static bool sp_check_encoding_plain(const ScrDyn *enc, const char *name, const S return false; } } - if (!*refusal && encrypted) *refusal = "encrypted private key export"; + (void)encrypted; return true; } @@ -1648,10 +2620,122 @@ static bool sp_check_encoding_plain(const ScrDyn *enc, const char *name, const S typedef struct { enum { SP_GEN_RSA, SP_GEN_EC, SP_GEN_ED25519, SP_GEN_X25519 } kind; double bits, exponent; /* RSA */ + bool pss; /* an RSA-PSS key, restricted when pss_params */ + bool pss_params, md_set, mgf1_set, salt_set; + mbedtls_md_type_t md, mgf1; + int salt; const SpCurve *curve; /* EC */ } SpKeyGen; +static bool sp_digest_of(const ScrStr *name, mbedtls_md_type_t *md); + +/* A digest name of rsa-pss's options: true with *md set, or false with + * Node's ERR_CRYPTO_INVALID_DIGEST pending ("Invalid digest: x" or "Invalid + * MGF1 digest: x"), or the SC2020 refusal for a digest OpenSSL has and + * RSASSA-PSS-params here cannot name. */ +static bool sp_pss_digest(const ScrDyn *name, bool mgf1, mbedtls_md_type_t *md) { + if (sp_digest_of(name->v.str, md) && sp_pss_md_name(*md)) return true; + if (scr_digest_known(name->v.str->data, name->v.str->len)) { + sp_not_supported("generateKeyPair rsa-pss with this digest"); + return false; + } + scr_pki_throw_fmt(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_DIGEST", "Invalid %sdigest: %.*s", mgf1 ? "MGF1 " : "", + (int)name->v.str->len, name->v.str->data); + return false; +} + +/* rsa-pss's options after the modulus and exponent, in Node's order + * (lib/internal/crypto/keygen.js): saltLength, hashAlgorithm, + * mgf1HashAlgorithm, and the deprecated hash and mgf1Hash with their + * DEP0154 warnings, then the digest lookups of RsaKeyGenTraits. */ +static bool sp_pss_keygen_options(const ScrDyn *options, SpKeyGen *gen) { + const ScrDyn *salt = sp_opt(options, "saltLength"); + const ScrDyn *hash_alg = sp_opt(options, "hashAlgorithm"), *mgf1_alg = sp_opt(options, "mgf1HashAlgorithm"); + const ScrDyn *hash = sp_opt(options, "hash"), *mgf1_hash = sp_opt(options, "mgf1Hash"); + if (!sp_absent(salt)) { + if (salt->kind != SCR_DYN_NUM) { + scr_dyn_prop_type_fail("options.saltLength", "of type number", salt); + return false; + } + double x = salt->v.num; + bool integer = isfinite(x) && trunc(x) == x; + if (!integer || x < 0 || x > 2147483647.0) { + ScrStr *received = sp_received(salt, true); + scr_pki_throw_fmt(SCR_ERR_RANGE, "ERR_OUT_OF_RANGE", + "The value of \"options.saltLength\" is out of range. It must be %s. Received %.*s", + integer ? ">= 0 && <= 2147483647" : "an integer", (int)received->len, received->data); + scr_str_release(received); + return false; + } + gen->salt_set = true; + gen->salt = (int)x; + } + if (!sp_absent(hash_alg) && hash_alg->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.hashAlgorithm", "of type string", hash_alg); + return false; + } + if (!sp_absent(mgf1_alg) && mgf1_alg->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.mgf1HashAlgorithm", "of type string", mgf1_alg); + return false; + } + const struct { + const ScrDyn *value, *preferred; + const char *name, *preferred_name; + } deprecated[] = { + {hash, hash_alg, "options.hash", "options.hashAlgorithm"}, + {mgf1_hash, mgf1_alg, "options.mgf1Hash", "options.mgf1HashAlgorithm"}, + }; + for (size_t i = 0; i < 2; i++) { + if (sp_absent(deprecated[i].value)) continue; + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "\""); + scr_jb_puts(&text, deprecated[i].name); + scr_jb_puts(&text, "\" is deprecated, use \""); + scr_jb_puts(&text, deprecated[i].preferred_name); + scr_jb_puts(&text, "\" instead."); + /* process.emitWarning: reported on the next tick, dropped under + * process.noDeprecation, and thrown from its own tick under + * process.throwDeprecation. */ + ScrStr *message = scr_jb_finish(&text); + ScrStr *code = scr_str_new("DEP0154", 7); + ScrDyn *warning = scr_warning_new(message, "DeprecationWarning", code); + scr_str_release(code); + scr_str_release(message); + scr_warning_emit_deferred(warning); + scr_dyn_release(warning); + if (deprecated[i].value->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail(deprecated[i].name, "of type string", deprecated[i].value); + return false; + } + const ScrDyn *preferred = deprecated[i].preferred; + if (preferred->kind == SCR_DYN_STR && preferred->v.str->len > 0 && + !scr_str_eq(preferred->v.str, deprecated[i].value->v.str)) { + sp_property_invalid(deprecated[i].name, deprecated[i].value); + return false; + } + } + /* `hashAlgorithm || hash`: an empty string reads as absent. */ + const ScrDyn *md = hash_alg->kind == SCR_DYN_STR && hash_alg->v.str->len > 0 ? hash_alg : hash; + const ScrDyn *mgf1 = mgf1_alg->kind == SCR_DYN_STR && mgf1_alg->v.str->len > 0 ? mgf1_alg : mgf1_hash; + if (md->kind == SCR_DYN_STR) { + if (!sp_pss_digest(md, false, &gen->md)) return false; + gen->md_set = true; + } + if (mgf1->kind == SCR_DYN_STR) { + if (!sp_pss_digest(mgf1, true, &gen->mgf1)) return false; + gen->mgf1_set = true; + } + /* OpenSSL's defaults: MGF1 over the digest and a salt of the digest's + * length once a digest is given, SHA-1 and 20 otherwise. */ + gen->pss_params = gen->md_set || gen->mgf1_set || gen->salt_set; + if (!gen->md_set) gen->md = MBEDTLS_MD_SHA1; + if (!gen->mgf1_set) gen->mgf1 = gen->md; + if (!gen->salt_set) gen->salt = gen->md_set ? (int)mbedtls_md_get_size(mbedtls_md_info_from_type(gen->md)) : 20; + return true; +} + /* createJob's checks for generateKeyPair(Sync)(type, options) in Node's * order: the type, the options object, the public and private key * encodings, then the options of the type. False with the error pending. @@ -1677,7 +2761,8 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen if (!sp_absent(exponent) && exponent->kind != SCR_DYN_NULL && !sp_check_uint32(exponent, "options.publicExponent", &gen->exponent)) return false; - if (pss && !refusal) refusal = "generateKeyPairSync for this key type"; + gen->pss = pss; + if (pss && !sp_pss_keygen_options(options, gen)) return false; } else if (sp_is_str(type, "dsa") || sp_is_str(type, "dh")) { if (!sp_check_object(options, "options")) return false; if (!refusal) refusal = "generateKeyPairSync for this key type"; @@ -1723,6 +2808,23 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen } if (!refusal) refusal = "generateKeyPairSync for this key type"; } + /* The job's private key cipher, which Node looks up once the options + * of the type are read. */ + { + const ScrDyn *enc = sp_opt(options, "privateKeyEncoding"); + const ScrDyn *cipher = sp_is_object(enc) ? sp_opt(enc, "cipher") : scr_dyn_undefined(); + const SpKeyCipher *ci; + if (cipher->kind == SCR_DYN_STR) { + if (!sp_key_cipher(cipher->v.str, &ci)) return false; + /* OpenSSL's errors for the cipher come from the export once the + * key is generated; a cipher it encrypts with and keys here do not + * is refused first. */ + bool pkcs8 = sp_is_str(sp_opt(enc, "type"), "pkcs8"); + if (ci->type == MBEDTLS_CIPHER_NONE && (pkcs8 ? ci->pkcs8_error : ci->traditional_error) == SP_KC_OK && + !refusal) + refusal = "private keys encrypted with this cipher"; + } + } if (refusal) { sp_not_supported(refusal); return false; @@ -1730,6 +2832,54 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen return gen->kind != SP_GEN_ED25519 || sp_ed() != NULL; } +/* An RSA key of `bits` bits with public exponent `exponent`, within + * OpenSSL's limits rather than mbedtls_rsa_gen_key's (even lengths from + * 1024 bits, exponents up to INT_MAX): any length from 512 bits and odd + * exponents up to 2^32 - 1. From 2048 bits OpenSSL generates two primes + * of equal length (SP 800-56B), so an odd length yields one bit less. + * The primes have (bits + 1) / 2 and bits / 2 bits, each with its top + * bits set (mbedtls_mpi_gen_prime's FIPS 186-4 bound), so their product + * has exactly `bits` bits; each prime minus one is coprime to the + * exponent. 0 on success. */ +static int sp_rsa_generate(mbedtls_rsa_context *rsa, unsigned bits, uint64_t exponent) { + if (bits >= 2048) bits &= ~1u; + mbedtls_mpi P, Q, E, N, T, G; + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&T); + mbedtls_mpi_init(&G); + unsigned char e_bytes[8]; + for (int i = 0; i < 8; i++) e_bytes[i] = (unsigned char)(exponent >> (56 - 8 * i)); + int r = mbedtls_mpi_read_binary(&E, e_bytes, sizeof e_bytes); + const int flags = MBEDTLS_MPI_GEN_PRIME_FLAG_LOW_ERR; + while (r == 0) { + r = mbedtls_mpi_gen_prime(&P, (bits + 1) / 2, flags, sp_random, NULL); + if (r == 0) r = mbedtls_mpi_sub_int(&T, &P, 1); + if (r == 0) r = mbedtls_mpi_gcd(&G, &E, &T); + if (r != 0 || mbedtls_mpi_cmp_int(&G, 1) != 0) continue; + r = mbedtls_mpi_gen_prime(&Q, bits / 2, flags, sp_random, NULL); + if (r == 0) r = mbedtls_mpi_sub_int(&T, &Q, 1); + if (r == 0) r = mbedtls_mpi_gcd(&G, &E, &T); + if (r != 0 || mbedtls_mpi_cmp_int(&G, 1) != 0 || mbedtls_mpi_cmp_mpi(&P, &Q) == 0) continue; + r = mbedtls_mpi_mul_mpi(&N, &P, &Q); + if (r == 0 && mbedtls_mpi_bitlen(&N) == bits) break; + } + /* The larger prime is p, as OpenSSL orders them. */ + if (r == 0 && mbedtls_mpi_cmp_mpi(&P, &Q) < 0) mbedtls_mpi_swap(&P, &Q); + if (r == 0) r = mbedtls_rsa_import(rsa, &N, &P, &Q, NULL, &E); + if (r == 0) r = mbedtls_rsa_complete(rsa); + if (r == 0) r = mbedtls_rsa_check_privkey(rsa); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&T); + mbedtls_mpi_free(&G); + return r; +} + /* OpenSSL's error for an RSA modulus length or public exponent it does not * generate, as Node reports it, or NULL. */ static const char *sp_rsa_keygen_failure(double bits, double exponent) { @@ -1773,16 +2923,22 @@ static SpKey *sp_keygen_run(const SpKeyGen *gen, bool *job_error) { scr_pki_throw_fmt(SCR_ERR_ERROR, NULL, "%s", failure); return NULL; } - /* mbedTLS generates even modulus lengths from 1024 to 8192 bits and, - * past the checks above, odd exponents up to INT_MAX. */ + /* Moduli past mbedTLS's 8192-bit numbers stay unsupported (OpenSSL + * generates up to 16384 bits). */ double b = gen->bits, e = gen->exponent; - if (b < MBEDTLS_RSA_GEN_KEY_MIN_BITS || b > MBEDTLS_MPI_MAX_BITS || fmod(b, 2) != 0 || e > 2147483647.0 || - mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || - mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, (unsigned)b, (int)e) != 0) { + if (b > MBEDTLS_MPI_MAX_BITS || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || + sp_rsa_generate(mbedtls_pk_rsa(k->pk), (unsigned)b, (uint64_t)e) != 0) { sp_key_release(k); sp_not_supported("generateKeyPairSync with this RSA modulus length or exponent"); return NULL; } + if (gen->pss) { + k->rsa_pss = true; + k->pss_params = gen->pss_params; + k->pss_md = gen->md; + k->pss_mgf1 = gen->mgf1; + k->pss_salt = gen->salt; + } return k; } if (mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) != 0 || @@ -1799,7 +2955,7 @@ static SpKey *sp_keygen_run(const SpKeyGen *gen, bool *job_error) { * pending, and *job_error as sp_keygen_run sets it. */ static ScrDyn *sp_key_pair(const ScrDyn *type, const ScrDyn *options, bool *job_error) { *job_error = false; - SpKeyGen gen; + SpKeyGen gen = {0}; if (!sp_keygen_check(type, options, &gen)) return NULL; SpKey *k = sp_keygen_run(&gen, job_error); if (!k) return NULL; @@ -1910,8 +3066,11 @@ static bool sp_algorithm(const ScrDyn *alg, mbedtls_md_type_t *md) { typedef struct { SpKey *key; /* +1 */ int padding; /* RSA_PKCS1_PADDING (1) or RSA_PKCS1_PSS_PADDING (6) */ - int salt; /* -1 digest length, -2 maximum (sign) or auto (verify) */ + int salt; /* -1 digest length, -2 maximum (sign) or auto (verify), + * -4 the digest length or the maximum, if smaller */ bool p1363; /* dsaEncoding: "ieee-p1363" */ + bool padding_given, salt_given; + mbedtls_md_type_t mgf1; /* PSS's MGF1 digest; NONE for the message's */ } SpSigKey; /* The key argument of a signing operation: a KeyObject, key material, or @@ -1932,6 +3091,9 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { out->padding = 1; out->salt = sign ? -2 : -2; out->p1363 = false; + out->padding_given = false; + out->salt_given = false; + out->mgf1 = MBEDTLS_MD_NONE; const ScrDyn *options = NULL; if (sp_is_object(value) && !sp_key_of(value) && !scr_buffer_storage_is(value)) { options = value; @@ -1948,6 +3110,7 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { return false; } out->padding = (int)padding->v.num; + out->padding_given = true; } const ScrDyn *salt = sp_opt(value, "saltLength"); if (!sp_absent(salt)) { @@ -1956,6 +3119,7 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { return false; } out->salt = (int)salt->v.num; + out->salt_given = true; } } SpKey *direct = sp_key_of(options ? sp_opt(options, "key") : value); @@ -2022,6 +3186,40 @@ static int sp_p1363_to_der(const unsigned char *sig, size_t len, unsigned char * static size_t sp_ec_size(SpKey *k) { return (mbedtls_pk_ec(k->pk)->MBEDTLS_PRIVATE(grp).nbits + 7) / 8; } +/* An RSA-PSS key's restrictions on a signature over a `md` digest, as + * OpenSSL's provider applies them: PSS padding only (the default), and + * for a key with RSASSA-PSS-params its digest, its MGF1 digest and salts + * of at least its own (the default). Without parameters a signature + * salts with the digest's length, or the most the key holds when that is + * less (RSA_PSS_SALTLEN_AUTO_DIGEST_MAX). False with OpenSSL's error + * pending. */ +static bool sp_pss_key_rules(SpSigKey *key, mbedtls_md_type_t md, size_t hash_len, bool sign) { + const SpKey *k = key->key; + if (!k->rsa_pss) return true; + if (key->padding_given && key->padding != 6) { + sp_throw_openssl("1C8000A5", "Provider routines", "illegal or unsupported padding mode", + "ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", true); + return false; + } + key->padding = 6; + if (!k->pss_params) { + if (!key->salt_given) key->salt = sign ? -4 : -2; + return true; + } + if (md != k->pss_md) { + sp_throw_openssl("1C8000AE", "Provider routines", "digest not allowed", "ERR_OSSL_DIGEST_NOT_ALLOWED", true); + return false; + } + key->mgf1 = k->pss_mgf1; + if (!key->salt_given) { + key->salt = k->pss_salt; + } else if (key->salt != -2 && (key->salt == -1 ? (int)hash_len : key->salt) < k->pss_salt) { + sp_throw_openssl("1C8000AC", "Provider routines", "pss saltlen too small", "ERR_OSSL_PSS_SALTLEN_TOO_SMALL", true); + return false; + } + return true; +} + /* Applies the RSA padding of a signature operation. */ static bool sp_rsa_padding(SpSigKey *key, mbedtls_md_type_t md) { if (!sp_is_rsa(key->key)) return true; @@ -2030,11 +3228,119 @@ static bool sp_rsa_padding(SpSigKey *key, mbedtls_md_type_t md) { return mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE) == 0; } +/* MGF1 over `mgf1` (RFC 8017 B.2.1), XORed into mask[0, len). */ +static int sp_mgf1_xor(mbedtls_md_type_t mgf1, const unsigned char *seed, size_t seed_len, unsigned char *mask, + size_t len) { + const mbedtls_md_info_t *info = mbedtls_md_info_from_type(mgf1); + size_t h = mbedtls_md_get_size(info); + unsigned char block[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + int r = mbedtls_md_setup(&ctx, info, 0); + for (uint32_t counter = 0; r == 0 && (size_t)counter * h < len; counter++) { + unsigned char c[4] = {(unsigned char)(counter >> 24), (unsigned char)(counter >> 16), + (unsigned char)(counter >> 8), (unsigned char)counter}; + if ((r = mbedtls_md_starts(&ctx)) == 0 && (r = mbedtls_md_update(&ctx, seed, seed_len)) == 0 && + (r = mbedtls_md_update(&ctx, c, 4)) == 0 && (r = mbedtls_md_finish(&ctx, block)) == 0) { + for (size_t i = 0; i < h && (size_t)counter * h + i < len; i++) mask[(size_t)counter * h + i] ^= block[i]; + } + } + mbedtls_md_free(&ctx); + mbedtls_platform_zeroize(block, sizeof block); + return r; +} + +/* An RSASSA-PSS signature whose MGF1 digest differs from the message's + * (RFC 8017 9.1.1 and 8.1.1), which mbedtls_rsa_rsassa_pss_sign_ext + * cannot make: the encoding here, then the private-key operation. */ +static int sp_pss_sign_mgf1(mbedtls_rsa_context *rsa, mbedtls_md_type_t md, mbedtls_md_type_t mgf1, + const unsigned char *hash, size_t hash_len, int salt_len, unsigned char *sig) { + size_t bytes = mbedtls_rsa_get_len(rsa); + size_t em_bits = mbedtls_rsa_get_bitlen(rsa) - 1; + size_t em_len = (em_bits + 7) / 8; + size_t salt = (size_t)salt_len; + if (em_len < hash_len + salt + 2) return MBEDTLS_ERR_RSA_BAD_INPUT_DATA; + unsigned char em[MBEDTLS_MPI_MAX_SIZE] = {0}; + unsigned char *db = em, *h = em + em_len - hash_len - 1; + size_t db_len = em_len - hash_len - 1; + unsigned char salt_bytes[MBEDTLS_MPI_MAX_SIZE]; + int r = sp_random(NULL, salt_bytes, salt); + /* H = Hash(0x00 * 8 || mHash || salt) */ + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + static const unsigned char zeros[8] = {0}; + if (r == 0 && (r = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md), 0)) == 0 && + (r = mbedtls_md_starts(&ctx)) == 0 && (r = mbedtls_md_update(&ctx, zeros, 8)) == 0 && + (r = mbedtls_md_update(&ctx, hash, hash_len)) == 0 && (r = mbedtls_md_update(&ctx, salt_bytes, salt)) == 0) + r = mbedtls_md_finish(&ctx, h); + mbedtls_md_free(&ctx); + if (r == 0) { + /* DB = PS || 0x01 || salt, masked with MGF1(H). */ + db[db_len - salt - 1] = 0x01; + memcpy(db + db_len - salt, salt_bytes, salt); + r = sp_mgf1_xor(mgf1, h, hash_len, db, db_len); + } + if (r == 0) { + db[0] &= (unsigned char)(0xff >> (8 * em_len - em_bits)); + em[em_len - 1] = 0xbc; + unsigned char block[MBEDTLS_MPI_MAX_SIZE] = {0}; + memcpy(block + bytes - em_len, em, em_len); + r = mbedtls_rsa_private(rsa, sp_random, NULL, block, sig); + mbedtls_platform_zeroize(block, sizeof block); + } + mbedtls_platform_zeroize(em, sizeof em); + mbedtls_platform_zeroize(salt_bytes, sizeof salt_bytes); + return r; +} + +/* Verifies an RSASSA-PSS signature whose MGF1 digest differs from the + * message's (RFC 8017 9.1.2), which mbedtls_rsa_rsassa_pss_verify_ext + * cannot: it hashes M' with the MGF1 digest too. `salt_len` -2 accepts any + * salt length. */ +static bool sp_pss_verify_mgf1(mbedtls_rsa_context *rsa, mbedtls_md_type_t md, mbedtls_md_type_t mgf1, + const unsigned char *hash, size_t hash_len, int salt_len, + const unsigned char *sig, size_t sig_len) { + size_t bytes = mbedtls_rsa_get_len(rsa); + if (sig_len != bytes) return false; + unsigned char decoded[MBEDTLS_MPI_MAX_SIZE]; + if (mbedtls_rsa_public(rsa, sig, decoded) != 0) return false; + size_t em_bits = mbedtls_rsa_get_bitlen(rsa) - 1; + size_t em_len = (em_bits + 7) / 8; + if (bytes > em_len && decoded[0] != 0) return false; + unsigned char *em = decoded + (bytes - em_len); + if (em_len < hash_len + 2 || em[em_len - 1] != 0xbc) return false; + size_t db_len = em_len - hash_len - 1; + unsigned char *h = em + db_len; + unsigned char top = (unsigned char)(0xff >> (8 * em_len - em_bits)); + if (em[0] & (unsigned char)~top) return false; + unsigned char db[MBEDTLS_MPI_MAX_SIZE]; + memcpy(db, em, db_len); + if (sp_mgf1_xor(mgf1, h, hash_len, db, db_len) != 0) return false; + db[0] &= top; + size_t i = 0; + while (i < db_len && db[i] == 0) i++; + if (i == db_len || db[i] != 0x01) return false; + size_t salt = db_len - i - 1; + if (salt_len >= 0 && salt != (size_t)salt_len) return false; + static const unsigned char zeros[8] = {0}; + unsigned char check[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + int r; + if ((r = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md), 0)) == 0 && (r = mbedtls_md_starts(&ctx)) == 0 && + (r = mbedtls_md_update(&ctx, zeros, 8)) == 0 && (r = mbedtls_md_update(&ctx, hash, hash_len)) == 0 && + (r = mbedtls_md_update(&ctx, db + i + 1, salt)) == 0) + r = mbedtls_md_finish(&ctx, check); + mbedtls_md_free(&ctx); + return r == 0 && memcmp(check, h, hash_len) == 0; +} + /* Signs a digest: +1 Buffer or NULL with the error pending. */ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len) { unsigned char sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE]; size_t sig_len = 0; int r; + if (!sp_pss_key_rules(key, md, hash_len, true)) return NULL; if (!sp_rsa_padding(key, md)) { sp_unsupported_input(); return NULL; @@ -2047,12 +3353,15 @@ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned size_t max = bytes - hash_len - 2 - ((bits - 1) % 8 == 0 ? 1 : 0); if (salt == -1) salt = (int)hash_len; else if (salt == -2) salt = (int)max; + else if (salt == -4) salt = (int)(hash_len < max ? hash_len : max); if (salt < 0 || (size_t)salt > max) { sp_throw(SCR_ERR_ERROR, "ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE", "error:1C8000A5:Provider routines::data too large for key size"); return NULL; } - r = mbedtls_rsa_rsassa_pss_sign_ext(rsa, sp_random, NULL, md, (unsigned)hash_len, hash, salt, sig); + r = key->mgf1 != MBEDTLS_MD_NONE && key->mgf1 != md + ? sp_pss_sign_mgf1(rsa, md, key->mgf1, hash, hash_len, salt, sig) + : mbedtls_rsa_rsassa_pss_sign_ext(rsa, sp_random, NULL, md, (unsigned)hash_len, hash, salt, sig); sig_len = bytes; } else { r = mbedtls_pk_sign(&key->key->pk, md, hash, hash_len, sig, sizeof sig, &sig_len, sp_random, NULL); @@ -2077,6 +3386,7 @@ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned * not verify or does not parse. */ static bool sp_verify_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len) { + if (!sp_pss_key_rules(key, md, hash_len, false)) return false; if (!sp_rsa_padding(key, md)) return false; if (sp_is_rsa(key->key) && key->padding == 6) { mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key->key->pk); @@ -2084,6 +3394,9 @@ static bool sp_verify_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned c int salt = key->salt; if (salt == -1) salt = (int)hash_len; else if (salt == -2) salt = MBEDTLS_RSA_SALT_LEN_ANY; + if (key->mgf1 != MBEDTLS_MD_NONE && key->mgf1 != md) + return sp_pss_verify_mgf1(rsa, md, key->mgf1, hash, hash_len, salt == MBEDTLS_RSA_SALT_LEN_ANY ? -2 : salt, + sig, sig_len); return mbedtls_rsa_rsassa_pss_verify_ext(rsa, md, (unsigned)hash_len, hash, md, salt, sig) == 0; } if (sp_is_ec(key->key) && key->p1363) { @@ -2127,6 +3440,9 @@ ScrDyn *scr_crypto_sign_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key) { SpSigKey k; if (!sp_sig_key(key, true, &k)) return NULL; if (sp_reject_x25519(&k)) return NULL; + /* A null algorithm signs with an RSA-PSS key's own digest. */ + if ((algorithm->kind == SCR_DYN_UNDEF || algorithm->kind == SCR_DYN_NULL) && k.key->pss_params) + md = k.key->pss_md; ScrBytes *bytes = scr_bytes_buffer_source(data); if (sp_is_ed(k.key)) { ScrDyn *result = NULL; @@ -2165,6 +3481,8 @@ ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, return NULL; } if (sp_reject_x25519(&k)) return NULL; + if ((algorithm->kind == SCR_DYN_UNDEF || algorithm->kind == SCR_DYN_NULL) && k.key->pss_params) + md = k.key->pss_md; ScrBytes *bytes = scr_bytes_buffer_source(data); ScrBytes *sig = scr_bytes_buffer_source(signature); if (sp_is_ed(k.key)) { @@ -2186,7 +3504,7 @@ ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, scr_bytes_release(bytes); scr_bytes_release(sig); sp_key_release(k.key); - return scr_dyn_new_bool(ok); + return scr_exc_pending() ? NULL : scr_dyn_new_bool(ok); } /* ── Sign and Verify objects ──────────────────────────────────────────── */ @@ -2305,7 +3623,7 @@ static ScrDyn *sp_signer_invoke(void *h, ScrDyn *self, const char *method, ScrDy sig->data, (size_t)scr_bytes_byte_len(sig)); scr_bytes_release(sig); sp_key_release(k.key); - return scr_dyn_new_bool(ok); + return scr_exc_pending() ? NULL : scr_dyn_new_bool(ok); } scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what); return NULL; @@ -2466,7 +3784,11 @@ static const char sp_dh_public_types[] = /* Node's message names: a KeyObject's asymmetricKeyType. */ static const char *sp_type_name(const SpKey *k) { - return sp_is_ed(k) ? "ed25519" : sp_is_x(k) ? "x25519" : sp_is_rsa(k) ? "rsa" : "ec"; + return sp_is_ed(k) ? "ed25519" + : sp_is_x(k) ? "x25519" + : k->rsa_pss ? "rsa-pss" + : sp_is_rsa(k) ? "rsa" + : "ec"; } /* A key input of diffieHellman's options: a primitive other than a string @@ -2683,8 +4005,9 @@ ScrPkiKey *scr_pki_key_public_of(ScrPkiKey *key) { return sp_public_of(key); } ScrPkiKey *scr_pki_generate_rsa(unsigned bits, uint64_t exponent) { SpKey *k = sp_key_alloc(true); - if (exponent > 2147483647 || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || - mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, bits, (int)exponent) != 0) { + if (bits < 512 || bits > MBEDTLS_MPI_MAX_BITS || exponent < 3 || exponent % 2 == 0 || + exponent > 4294967295u || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || + sp_rsa_generate(mbedtls_pk_rsa(k->pk), bits, exponent) != 0) { sp_key_release(k); return NULL; } diff --git a/packages/runtime/src/scr_webcrypto_pki.c b/packages/runtime/src/scr_webcrypto_pki.c index 7439a12db..458cfeae8 100644 --- a/packages/runtime/src/scr_webcrypto_pki.c +++ b/packages/runtime/src/scr_webcrypto_pki.c @@ -380,7 +380,8 @@ static ScrPromise *sw_generate_key(const ScrDyn *args) { scr_throw_domex("OperationError", "algorithm.publicExponent must be odd"); goto done; } - if (bits > 16384 || value < 3) { + /* The pki unit's numbers hold moduli up to 8192 bits. */ + if (bits > 8192 || value < 3) { scr_wc_unsupported("generateKey", "this RSA modulus length or public exponent"); goto done; } diff --git a/tests/corpus/8165-rsa-keygen-lengths.cjs b/tests/corpus/8165-rsa-keygen-lengths.cjs new file mode 100644 index 000000000..35e2cb28f --- /dev/null +++ b/tests/corpus/8165-rsa-keygen-lengths.cjs @@ -0,0 +1,40 @@ +// RSA key generation within OpenSSL's limits: any modulus length from +// 512 bits (an odd length from 2048 bits yields one bit less, as +// OpenSSL's SP 800-56B generation does) and odd exponents up to 2^32 - 1. +"use strict"; +const crypto = require("crypto"); + +const cases = [ + [512, 65537], + [513, 65537], + [1023, 3], + [1024, 0x10001], + [2049, 65537], + [768, 4294967295], + [520, 17], +]; +for (const [modulusLength, publicExponent] of cases) { + const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength, publicExponent }); + const details = privateKey.asymmetricKeyDetails; + const data = Buffer.from("rsa key lengths"); + const signature = crypto.sign("sha256", data, privateKey); + console.log( + modulusLength, + details.modulusLength, + details.publicExponent, + publicKey.asymmetricKeyDetails.modulusLength, + signature.length, + crypto.verify("sha256", data, publicKey, signature), + ); +} +for (const [modulusLength, publicExponent] of [[511, 65537], [1024, 1], [1024, 4], [1024, 0]]) { + try { + crypto.generateKeyPairSync("rsa", { modulusLength, publicExponent }); + console.log(modulusLength, publicExponent, "generated"); + } catch (error) { + console.log(modulusLength, publicExponent, error.message); + } +} +crypto.generateKeyPair("rsa", { modulusLength: 515, publicExponent: 3 }, (error, publicKey) => { + console.log("async", error, publicKey.asymmetricKeyDetails.modulusLength); +}); diff --git a/tests/corpus/8166-rsa-pss-keys.cjs b/tests/corpus/8166-rsa-pss-keys.cjs new file mode 100644 index 000000000..aad90f6aa --- /dev/null +++ b/tests/corpus/8166-rsa-pss-keys.cjs @@ -0,0 +1,104 @@ +// RSA-PSS keys (id-RSASSA-PSS): generation with and without +// RSASSA-PSS-params, their details, SubjectPublicKeyInfo and PKCS#8 in +// OpenSSL's encoding, import of both, and the restrictions on signatures: +// PSS padding only, the key's digest and MGF1 digest, and at least its +// salt length. The deprecated hash and mgf1Hash options run in a child +// process, whose DEP0154 reports are printed with process ids normalized. +"use strict"; +const { spawnSync } = require("child_process"); +const c = require("crypto"); +const fmt = (v) => JSON.stringify(v, (k, x) => (typeof x === "bigint" ? `${x}n` : x)); +const tryIt = (label, fn) => { + try { + const r = fn(); + console.log(label, typeof r === "string" ? r.split("\n")[0] : Buffer.isBuffer(r) ? `buffer ${r.length > 0}` : fmt(r)); + } catch (e) { + console.log(label, e.name, e.code, e.message); + } +}; +const algOf = (spki) => { + // The AlgorithmIdentifier: the SEQUENCE after the outer header. + const start = spki[1] & 0x80 ? 2 + (spki[1] & 0x7f) : 2; + const len = spki[start + 1]; + return spki.subarray(start, start + 2 + len).toString("hex"); +}; +const report = (label, options) => tryIt(label, () => c.generateKeyPairSync("rsa-pss", { modulusLength: 512, ...options }).publicKey.asymmetricKeyDetails); + +function main() { + const cases = { + none: {}, + sha256: { hashAlgorithm: "sha256" }, + full: { hashAlgorithm: "sha256", mgf1HashAlgorithm: "sha384", saltLength: 16 }, + sha1: { hashAlgorithm: "sha1" }, + salt: { saltLength: 10 }, + s512: { hashAlgorithm: "sha512", saltLength: 0 }, + mgf: { mgf1HashAlgorithm: "sha256" }, + }; + for (const [name, options] of Object.entries(cases)) { + const { publicKey, privateKey } = c.generateKeyPairSync("rsa-pss", { modulusLength: ["full", "sha1", "salt", "mgf"].includes(name) ? 512 : 1024, ...options }); + const spki = publicKey.export({ format: "der", type: "spki" }); + const pkcs8 = privateKey.export({ format: "der", type: "pkcs8" }); + console.log(name, publicKey.asymmetricKeyType, fmt(publicKey.asymmetricKeyDetails), fmt(privateKey.asymmetricKeyDetails)); + console.log(name, "spki alg", algOf(spki), "pkcs8 alg", pkcs8.indexOf(Buffer.from(algOf(spki), "hex")) > 0); + tryIt(`${name} pkcs1`, () => privateKey.export({ format: "pem", type: "pkcs1" })); + tryIt(`${name} jwk`, () => publicKey.export({ format: "jwk" })); + tryIt(`${name} pem`, () => privateKey.export({ format: "pem", type: "pkcs8" })); + const reimported = c.createPublicKey(publicKey.export({ format: "pem", type: "spki" })); + const fromPrivate = c.createPublicKey(privateKey.export({ format: "pem", type: "pkcs8" })); + const privateAgain = c.createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" }); + console.log(name, reimported.equals(publicKey), fromPrivate.equals(publicKey), privateAgain.asymmetricKeyType); + const data = Buffer.from("rsa-pss"); + const signature = c.sign(null, data, privateAgain); + console.log(name, c.verify(null, data, publicKey, signature), c.verify(null, data, reimported, signature)); + } + + const data = Buffer.from("restricted"); + const { publicKey, privateKey } = c.generateKeyPairSync("rsa-pss", { + modulusLength: 512, + hashAlgorithm: "sha256", + saltLength: 16, + }); + tryIt("sign sha1", () => c.sign("sha1", data, privateKey)); + tryIt("sign pkcs1", () => c.sign("sha256", data, { key: privateKey, padding: c.constants.RSA_PKCS1_PADDING })); + tryIt("sign salt 8", () => c.sign("sha256", data, { key: privateKey, saltLength: 8 })); + tryIt("verify salt 8", () => c.verify("sha256", data, { key: publicKey, saltLength: 8 }, Buffer.alloc(64))); + for (const saltLength of [16, 20, 30]) { + const signature = c.sign("sha256", data, { key: privateKey, saltLength }); + console.log( + saltLength, + c.verify("sha256", data, publicKey, signature), + c.verify("sha256", data, { key: publicKey, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + } + const signer = c.createSign("sha256").update(data); + console.log("createVerify", c.createVerify("sha256").update(data).verify(publicKey, signer.sign(privateKey))); + const free = c.generateKeyPairSync("rsa-pss", { modulusLength: 1024 }); + const freeSignature = c.sign("sha512", data, free.privateKey); + console.log("free sha512", c.verify("sha512", data, free.publicKey, freeSignature)); + + report("bad digest", { hashAlgorithm: "nope" }); + report("bad mgf1", { mgf1HashAlgorithm: "nope" }); + report("negative salt", { saltLength: -1 }); + report("upper", { hashAlgorithm: "SHA256" }); + report("alias", { hashAlgorithm: "RSA-SHA256" }); +} + +if (process.argv[2] === "deprecated") { + report("hash", { hash: "sha256" }); + report("conflict", { hashAlgorithm: "sha256", hash: "sha1" }); + report("mgf1Hash", { hashAlgorithm: "sha256", mgf1Hash: "sha384" }); + report("hash type", { hash: 5 }); + // Node drops deprecations while noDeprecation is set when it reports + // them, so the flag is set once the reports above are out. + setTimeout(() => { + process.noDeprecation = true; + report("quiet", { hash: "sha512" }); + console.log("end of turn"); + }); +} else { + main(); + const child = spawnSync(process.execPath, [process.argv[1], "deprecated"], { encoding: "utf8" }); + console.log(`status: ${child.status}`); + console.log(child.stdout); + console.log(child.stderr.replace(/\(node:\d+\)/g, "(node:)")); +} diff --git a/tests/corpus/8167-encrypted-private-keys.cjs b/tests/corpus/8167-encrypted-private-keys.cjs new file mode 100644 index 000000000..561a0aec3 --- /dev/null +++ b/tests/corpus/8167-encrypted-private-keys.cjs @@ -0,0 +1,114 @@ +// Encrypted private keys: PKCS#8 as PBES2 and PKCS#1 and SEC1 as +// traditional PEM with the CBC and ECB ciphers, import with a passphrase, +// generateKeyPair with a cipher, and OpenSSL's errors for missing and +// wrong passphrases, unknown ciphers, and ciphers a form refuses. +"use strict"; +const c = require("crypto"); +const show = (e) => + `${e.name} ${e.code} ${e.message}${e.library ? ` [${e.library}: ${e.reason}]` : ""}${ + e.opensslErrorStack ? ` ${JSON.stringify(e.opensslErrorStack)}` : "" + }`; +const tryIt = (label, fn) => { + try { + console.log(label, fn()); + } catch (e) { + console.log(label, show(e)); + } +}; +// The PEM label and headers, without the random IV. +const head = (pem) => + pem + .split("\n") + .filter((l) => l.startsWith("-----BEGIN") || l.includes(":")) + .map((l) => l.replace(/,[0-9A-F]+$/, "")) + .join(" | "); + +const ec = c.generateKeyPairSync("ec", { namedCurve: "P-256" }); +const rsa = c.generateKeyPairSync("rsa", { modulusLength: 1024 }); +const same = (key, pair) => c.createPublicKey(key).equals(pair.publicKey); + +for (const cipher of ["aes-128-cbc", "aes-192-cbc", "aes-256-cbc", "aes-128-ecb", "aes-256-ecb", "des-ede3-cbc", "aes256", "AES-128-CBC", "2.16.840.1.101.3.4.1.42"]) { + for (const format of ["pem", "der"]) { + tryIt(`pkcs8 ${format} ${cipher}`, () => { + const out = ec.privateKey.export({ type: "pkcs8", format, cipher, passphrase: "top secret" }); + const key = c.createPrivateKey({ key: out, format, type: format === "der" ? "pkcs8" : undefined, passphrase: "top secret" }); + return `${format === "pem" ? head(out) : Buffer.isBuffer(out)} ${key.asymmetricKeyType} ${same(key, ec)}`; + }); + } +} +for (const cipher of ["aes-128-cbc", "aes-256-cbc", "des-ede3-cbc", "des3"]) { + tryIt(`sec1 ${cipher}`, () => { + const out = ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: Buffer.from("pw") }); + return `${head(out)} ${same(c.createPrivateKey({ key: out, passphrase: "pw" }), ec)}`; + }); + tryIt(`pkcs1 ${cipher}`, () => { + const out = rsa.privateKey.export({ type: "pkcs1", format: "pem", cipher, passphrase: "pw" }); + return `${head(out)} ${same(c.createPrivateKey({ key: out, passphrase: Buffer.from("pw") }), rsa)}`; + }); +} +tryIt("empty passphrase", () => { + const out = rsa.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-128-cbc", passphrase: "" }); + return same(c.createPrivateKey({ key: out, passphrase: "" }), rsa); +}); + +// Reading encrypted keys. +const p8pem = ec.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase: "pw" }); +const p8der = ec.privateKey.export({ type: "pkcs8", format: "der", cipher: "aes-256-cbc", passphrase: "pw" }); +const sec1 = String(ec.privateKey.export({ type: "sec1", format: "pem", cipher: "aes-128-cbc", passphrase: "pw" })); +tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(ec.publicKey)); +tryIt("public from traditional", () => c.createPublicKey({ key: sec1, passphrase: "pw" }).type); +tryIt("pkcs8 pem no passphrase", () => c.createPrivateKey(p8pem)); +tryIt("pkcs8 der no passphrase", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8" })); +tryIt("sec1 no passphrase", () => c.createPrivateKey(sec1)); +tryIt("public no passphrase", () => c.createPublicKey(p8pem)); +tryIt("pkcs8 pem wrong", () => c.createPrivateKey({ key: p8pem, passphrase: "nope" })); +tryIt("pkcs8 der wrong", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8", passphrase: "nope" })); +tryIt("sec1 wrong", () => c.createPrivateKey({ key: sec1, passphrase: "nope" })); +tryIt("passphrase type", () => c.createPrivateKey({ key: p8pem, passphrase: 5 })); +tryIt("dek unknown", () => c.createPrivateKey({ key: sec1.replace(/DEK-Info: AES-128-CBC/, "DEK-Info: NOPE-CBC"), passphrase: "pw" })); +tryIt("dek legacy", () => + c.createPrivateKey({ key: sec1.replace(/DEK-Info: AES-128-CBC,([0-9A-F]{16})[0-9A-F]+/, "DEK-Info: DES-CBC,$1"), passphrase: "pw" }), +); +tryIt("dek legacy no passphrase", () => + c.createPrivateKey(sec1.replace(/DEK-Info: AES-128-CBC,([0-9A-F]{16})[0-9A-F]+/, "DEK-Info: DES-CBC,$1")), +); + +// Export options. +const opts = (extra) => ({ type: "pkcs8", format: "pem", ...extra }); +tryIt("unknown cipher", () => ec.privateKey.export(opts({ cipher: "nope", passphrase: "x" }))); +tryIt("cipher without passphrase", () => ec.privateKey.export(opts({ cipher: "aes-128-cbc" }))); +tryIt("passphrase without cipher", () => ec.privateKey.export(opts({ passphrase: "x" }))); +tryIt("cipher type", () => ec.privateKey.export(opts({ cipher: 5, passphrase: "x" }))); +tryIt("sec1 der", () => ec.privateKey.export({ type: "sec1", format: "der", cipher: "aes-128-cbc", passphrase: "x" })); +tryIt("pkcs1 der", () => rsa.privateKey.export({ type: "pkcs1", format: "der", cipher: "aes-128-cbc", passphrase: "x" })); +// OpenSSL refuses these ciphers for PKCS#8, and the last ones for +// traditional PEM too (the P-256 key's PKCS#8 and SEC1 lengths are not +// multiples of 8, which key wrap needs). +for (const cipher of ["aes-128-gcm", "aes-256-ccm", "aes-128-ctr", "chacha20-poly1305", "aes-128-xts", "des-ede3-cfb", "2.16.840.1.101.3.4.1.6"]) + tryIt(`refused pkcs8 ${cipher}`, () => ec.privateKey.export(opts({ cipher, passphrase: "x" })).length > 0); +for (const cipher of ["aes-128-wrap", "id-aes256-wrap", "des-cbc", "rc4"]) { + tryIt(`refused pkcs8 ${cipher}`, () => ec.privateKey.export(opts({ cipher, passphrase: "x" })).length > 0); + tryIt(`refused sec1 ${cipher}`, () => ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: "x" })); +} +for (const cipher of ["aes-128-ecb", "aes-256-ecb", "des-ede3"]) + tryIt(`refused sec1 ${cipher}`, () => ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: "x" })); + +// generateKeyPair with a cipher. +const encoding = (cipher, type = "pkcs8") => ({ + namedCurve: "P-256", + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type, format: "pem", cipher, passphrase: "gen" }, +}); +tryIt("keygen sync", () => { + const { publicKey, privateKey } = c.generateKeyPairSync("ec", encoding("aes-256-cbc")); + return `${head(privateKey)} ${c.createPublicKey({ key: privateKey, passphrase: "gen" }).equals(c.createPublicKey(publicKey))}`; +}); +tryIt("keygen unknown", () => c.generateKeyPairSync("ec", encoding("nope"))); +tryIt("keygen gcm", () => c.generateKeyPairSync("ec", encoding("aes-128-gcm"))); +tryIt("keygen sec1 ecb", () => c.generateKeyPairSync("ec", encoding("aes-128-ecb", "sec1"))); +// One job at a time: Node's jobs finish in either order. +c.generateKeyPair("ec", encoding("des-ede3-cbc", "sec1"), (err, publicKey, privateKey) => { + console.log("keygen async", err, head(privateKey)); + console.log("keygen async key", c.createPublicKey({ key: privateKey, passphrase: "gen" }).equals(c.createPublicKey(publicKey))); + c.generateKeyPair("ec", encoding("aes-128-ccm"), (err) => console.log("keygen async ccm", show(err))); +}); diff --git a/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json b/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json new file mode 100644 index 000000000..bbf64dca5 --- /dev/null +++ b/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json @@ -0,0 +1 @@ +{ "plain": 11640, "san": 77506 } diff --git a/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json b/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json new file mode 100644 index 000000000..436e9ed51 --- /dev/null +++ b/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json @@ -0,0 +1 @@ +{ "plain": 11459, "san": 74658 } diff --git a/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json b/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json new file mode 100644 index 000000000..0f258ddbb --- /dev/null +++ b/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json @@ -0,0 +1 @@ +{ "plain": 3144, "san": 8504 } From f2e710d65f4233424be8a0c01db56520b440828b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 09:05:16 -0500 Subject: [PATCH 063/395] Answer stream, StringDecoder and handle-object members in untyped code as Node does --- packages/compiler/src/backend/llvm/dyn.ts | 45 +++--- .../compiler/src/backend/llvm/expr-dynamic.ts | 2 +- .../src/backend/llvm/expr-stream-bridges.ts | 5 +- .../frontend/lowering/expressions/typeof.ts | 7 +- .../src/frontend/lowering/f64-handles.ts | 15 ++ packages/compiler/src/ir/ir.ts | 26 +++- packages/compiler/src/ir/runtime-features.ts | 7 +- .../8232-native-streams-untyped.cjs.json | 6 + .../8233-string-decoder-values.cjs.json | 6 + .../tests/corpus/8234-handle-typeof.ts.json | 6 + packages/runtime/src/scr_bytes.h | 6 + packages/runtime/src/scr_dyn.h | 1 + packages/runtime/src/scr_stream.c | 96 ++++++++++++- packages/runtime/src/scr_text_codec_dyn.c | 128 +++++++++++++++++- tests/corpus/8232-native-streams-untyped.cjs | 56 ++++++++ tests/corpus/8233-string-decoder-values.cjs | 33 +++++ tests/corpus/8234-handle-typeof.ts | 24 ++++ .../8232-native-streams-untyped.cjs.json | 1 + .../8233-string-decoder-values.cjs.json | 1 + .../corpus-costs/8234-handle-typeof.ts.json | 1 + 20 files changed, 437 insertions(+), 35 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/f64-handles.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-native-streams-untyped.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8233-string-decoder-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8234-handle-typeof.ts.json create mode 100644 tests/corpus/8232-native-streams-untyped.cjs create mode 100644 tests/corpus/8233-string-decoder-values.cjs create mode 100644 tests/corpus/8234-handle-typeof.ts create mode 100644 tests/harness/corpus-costs/8232-native-streams-untyped.cjs.json create mode 100644 tests/harness/corpus-costs/8233-string-decoder-values.cjs.json create mode 100644 tests/harness/corpus-costs/8234-handle-typeof.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index da31ffdbb..c7b80b71d 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -43,7 +43,7 @@ import { isDynTypedRefType, isRefCounted, isUnitType, - recordTextCodecClass, + recordHandleCodecClass, textCodecRecordClass, typeKey, } from "../../ir/ir.js"; @@ -98,14 +98,16 @@ export const DYN_HANDLE_TAG_NUM: Record = { /** The handle tags of TextEncoder and TextDecoder records boxed as dyn * (scr_dyn.h; scr_text_codec_dyn.c boxes and unboxes them). */ -export const TEXT_CODEC_TAG_NUM: Record<"TextEncoder" | "TextDecoder", number> = { +export const TEXT_CODEC_TAG_NUM: Record<"TextEncoder" | "TextDecoder" | "StringDecoder", number> = { TextDecoder: 76, TextEncoder: 77, + StringDecoder: 78, }; /** The decoder record fields scr_text_decoder_to_dyn reads, in its * parameter order. */ const TEXT_DECODER_FIELDS = ["%TextDecoder", "%fatal", "%ignoreBOM", "%state"] as const; +const STRING_DECODER_FIELDS = ["%enc", "%pending"] as const; /** Handle kinds whose runtime unit boxes them through its own function, * which installs the tag's ops on first use (scr_crypto.h). Programs that @@ -1825,11 +1827,18 @@ export class LlDyn { const codec = textCodecRecordClass(t, (id) => host.recordsById.get(id)); if (codec !== null) { // The record the codec's handle box holds (+1), or the path TypeError. - host.declare(`declare ptr @scr_text_codec_from_dyn(ptr, i1 zeroext, ptr, ptr)`); const checked = B.tmp(); - B.line( - `${checked} = call ptr @scr_text_codec_from_dyn(ptr %d, i1 ${codec === "TextDecoder"}, ptr %path, ptr ${want})`, - ); + if (codec === "StringDecoder") { + host.declare(`declare ptr @scr_string_decoder_from_dyn(ptr, ptr, ptr)`); + B.line( + `${checked} = call ptr @scr_string_decoder_from_dyn(ptr %d, ptr %path, ptr ${want})`, + ); + } else { + host.declare(`declare ptr @scr_text_codec_from_dyn(ptr, i1 zeroext, ptr, ptr)`); + B.line( + `${checked} = call ptr @scr_text_codec_from_dyn(ptr %d, i1 ${codec === "TextDecoder"}, ptr %path, ptr ${want})`, + ); + } B.terminate(`ret ptr ${checked}`); this.defs.push( `define internal ptr @${name}(ptr %d, ptr %path) ${FN_ATTRS} {`, @@ -2917,14 +2926,14 @@ export class LlDyn { return matched; } - /** A TextEncoder or TextDecoder record boxes as its handle (+1), which - * holds the record by reference. A decoder passes the byte offsets of the - * fields its boxed decode reads. */ + /** A TextEncoder, TextDecoder or StringDecoder record boxes as its + * handle (+1), which holds the record by reference. The decoders pass + * the byte offsets of the fields their boxed methods read. */ private textCodecToDyn( B: BlockBuilder, t: IrType & { kind: "record" }, shape: IrRecordShape, - codec: "TextEncoder" | "TextDecoder", + codec: "TextEncoder" | "TextDecoder" | "StringDecoder", ): void { const host = this.host; const rc = vAdapters(host, t); @@ -2939,10 +2948,12 @@ export class LlDyn { const base = host.nullableUnions.fieldBase(t.shapeId); const start = B.tmp(); B.line(`${start} = ptrtoint ptr %v to ${this.S}`); - const offsets = TEXT_DECODER_FIELDS.map((field) => { + const fields: readonly string[] = + codec === "StringDecoder" ? STRING_DECODER_FIELDS : TEXT_DECODER_FIELDS; + const offsets = fields.map((field) => { const index = shape.fields.findIndex((f) => f.name === field); if (index < 0) - throw new InternalCompilerError(`llvm emitter bug: TextDecoder record lacks ${field}`); + throw new InternalCompilerError(`llvm emitter bug: ${codec} record lacks ${field}`); const p = B.tmp(); const at = B.tmp(); const offset = B.tmp(); @@ -2953,11 +2964,11 @@ export class LlDyn { B.line(`${offset} = sub ${this.S} ${at}, ${start}`); return `${this.S} ${offset}`; }); - host.declare( - `declare ptr @scr_text_decoder_to_dyn(ptr, ptr, ptr, ${this.S}, ${this.S}, ${this.S}, ${this.S})`, - ); + const fn = + codec === "StringDecoder" ? "scr_string_decoder_to_dyn" : "scr_text_decoder_to_dyn"; + host.declare(`declare ptr @${fn}(ptr, ptr, ptr, ${fields.map(() => this.S).join(", ")})`); B.line( - `${r} = call ptr @scr_text_decoder_to_dyn(ptr %v, ptr ${rc.retain}, ptr ${rc.release}, ${offsets.join(", ")})`, + `${r} = call ptr @${fn}(ptr %v, ptr ${rc.retain}, ptr ${rc.release}, ${offsets.join(", ")})`, ); } B.terminate(`ret ptr ${r}`); @@ -3137,7 +3148,7 @@ export class LlDyn { const shape = host.recordsById.get(t.shapeId); if (!shape) throw new InternalCompilerError(`llvm emitter bug: to-dyn of unknown shape ${t.shapeId}`); - const codec = recordTextCodecClass(shape); + const codec = recordHandleCodecClass(shape); if (codec !== null) { this.textCodecToDyn(B, t, shape, codec); break; diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index b7a5c7736..3d9aec3cc 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -89,7 +89,7 @@ export function emitDynamicExpr( // Bytes already box their shared mutable storage directly. A second // capsule would split identity between checked and native views. const errorRef = v.type.kind === "object" && host.shapeHost.isErrorClass(v.type.className); - // A TextEncoder or TextDecoder record boxes as its own handle, which + // A TextEncoder, TextDecoder or StringDecoder record boxes as its own handle, which // keeps the record's identity (toDynHelper). const codecRef = textCodecRecordClass(v.type, (id) => host.recordsById.get(id)) !== null; if (!errorRef && !codecRef && ((e.liveRef && v.type.kind !== "bytes") || identityRef)) { diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 675932d47..28f5ae5bc 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -1,7 +1,7 @@ import { typedRefConstructor } from "./shapes.js"; /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; -import { streamTypedRefEligible, undefinedArmTag } from "../../ir/analysis.js"; +import { streamRooted, streamTypedRefEligible, undefinedArmTag } from "../../ir/analysis.js"; import { type IrRecordShape, type IrType, @@ -843,8 +843,7 @@ export function streamTypedRefMaterializeAdapter( host.declare(`declare ptr @scr_error_dyn_fields(ptr)`); B.line(`${out} = call ptr @scr_error_dyn_fields(ptr %p)`); } else if ( - meta.def.runtime && - RUNTIME_STREAM_CLASSES.has(t.className) && + ((meta.def.runtime && RUNTIME_STREAM_CLASSES.has(t.className)) || streamRooted(meta)) && !meta.def.instancePrototypeHelper && !meta.def.prototypeDataHelper ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index fbbc068b9..ba1aba68e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -35,6 +35,7 @@ import { isExecExceptionType, refuseExecExceptionCode } from "../builtins/errors import { InternalCompilerError } from "../../../errors.js"; import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; import { isTemporalGlobalRoot } from "../lower-namespaces.js"; +import { isF64HandleTyped } from "../f64-handles.js"; export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc: SrcLoc): IrExpr { const webAssembly = lowerWebAssemblyTypeof(lowerer, expr.expression); @@ -287,7 +288,11 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc undefinedT: "undefined", nullT: "object", }; - const folded = FOLD[operand.type.kind]; + // An f64 handle (a readline Interface, a Timeout) is an object. + const folded = + operand.type.kind === "f64" && isF64HandleTyped(lowerer, expr.expression) + ? "object" + : FOLD[operand.type.kind]; if (folded) { const result: IrExpr = { kind: "strLit", value: folded, type: STRING, loc }; return isSafeToDiscard(operand) diff --git a/packages/compiler/src/frontend/lowering/f64-handles.ts b/packages/compiler/src/frontend/lowering/f64-handles.ts new file mode 100644 index 000000000..0d7da0ff9 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/f64-handles.ts @@ -0,0 +1,15 @@ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; + +/** Whether `node`'s value is an f64 handle: an object whose class + * type-mapper.ts represents as an f64 id into a runtime registry (a + * readline Interface, a Timeout or Immediate, an AsyncLocalStorage, a + * diagnostics_channel Channel). Its checker type is an object type that + * maps to f64; the Number wrapper interface maps to f64 for primitives. */ +export function isF64HandleTyped(lowerer: Lowerer, node: ts.Node): boolean { + const type = lowerer.checker.getTypeAtLocation(node); + if ((type.flags & ts.TypeFlags.Object) === 0) return false; + const symbol = type.getSymbol(); + if (symbol !== undefined && ts.symbolName(symbol) === "Number") return false; + return lowerer.mapTypeOf(type)?.kind === "f64"; +} diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 3818433f8..c6dcb6e08 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1781,16 +1781,32 @@ export function recordTextCodecClass(shape: IrRecordShape): "TextEncoder" | "Tex return null; } -/** The codec class of a TextEncoder or TextDecoder record type, else null. - * Such a record crosses an `unknown` slot as a native handle that holds the - * record itself (scr_text_codec_dyn.c), never as a copy or a field view. */ +/** The class of a record that crosses checked-dynamic slots as a native + * handle: the codecs, and a StringDecoder (type-mapper.ts: its canonical + * encoding and the packing of its pending bytes). */ +export function recordHandleCodecClass( + shape: IrRecordShape, +): "TextEncoder" | "TextDecoder" | "StringDecoder" | null { + const codec = recordTextCodecClass(shape); + if (codec !== null) return codec; + return shape.fields.length === 2 && + shape.fields[0]!.name === "%enc" && + shape.fields[1]!.name === "%pending" + ? "StringDecoder" + : null; +} + +/** The codec class of a TextEncoder, TextDecoder or StringDecoder record + * type, else null. Such a record crosses an `unknown` slot as a native + * handle that holds the record itself (scr_text_codec_dyn.c), never as a + * copy or a field view. */ export function textCodecRecordClass( t: IrType, getRecord: (shapeId: string) => IrRecordShape | undefined, -): "TextEncoder" | "TextDecoder" | null { +): "TextEncoder" | "TextDecoder" | "StringDecoder" | null { if (t.kind !== "record") return null; const shape = getRecord(t.shapeId); - return shape ? recordTextCodecClass(shape) : null; + return shape ? recordHandleCodecClass(shape) : null; } /** Object-literal ACCESSOR properties (`{ get x() {...}, set x(v) {...} }`) diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85b21af7b..917406d6c 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -2,6 +2,7 @@ import type { IrClassDef, IrExpr, IrModule, IrRecordShape, IrType, IrUnionDef } import type { IrLibFn } from "./builtin-signatures.js"; import { isDirentShape, + recordHandleCodecClass, recordTextCodecClass, RUNTIME_EMITTER_CLASS, RUNTIME_EVENT_CLASS, @@ -586,7 +587,7 @@ function mentionsDgramSocket(t: IrType, tables: TypeTables): boolean { const textCodecLeaf: CarrierLeaf = (t, tables) => { if (t.kind !== "record") return false; const shape = tables.records.get(t.shapeId); - return shape !== undefined && recordTextCodecClass(shape) !== null; + return shape !== undefined && recordHandleCodecClass(shape) !== null; }; const textDecoderLeaf: CarrierLeaf = (t, tables) => { @@ -596,7 +597,7 @@ const textDecoderLeaf: CarrierLeaf = (t, tables) => { }; /** Whether converting a value of type `t` to or from dyn converts a - * TextEncoder or TextDecoder record, which boxes it as a handle of + * TextEncoder, TextDecoder or StringDecoder record, which boxes it as a handle of * scr_text_codec_dyn.c (the same carrier walk as dgram sockets). */ function mentionsTextCodec(t: IrType, tables: TypeTables): boolean { tables.textCodecCarriers ??= leafCarriers(tables, textCodecLeaf); @@ -858,7 +859,7 @@ const RUNTIME_FEATURE_RULES = { (node.kind === "dynFrom" && mentionsDgramSocket(node.value.type, tables())) || (node.kind === "dynCheck" && mentionsDgramSocket(node.type, tables())), }), - /** A TextEncoder or TextDecoder record converts to or from an untyped + /** A TextEncoder, TextDecoder or StringDecoder record converts to or from an untyped * value (its handle box, or a boxed function, record or collection that * carries one): scr_text_codec_dyn.c. */ textCodecDyn: rule({ diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-native-streams-untyped.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-native-streams-untyped.cjs.json new file mode 100644 index 000000000..2b07cde52 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-native-streams-untyped.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8232-native-streams-untyped.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8233-string-decoder-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8233-string-decoder-values.cjs.json new file mode 100644 index 000000000..a734fb48c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8233-string-decoder-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8233-string-decoder-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8234-handle-typeof.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8234-handle-typeof.ts.json new file mode 100644 index 000000000..7f37dbc5d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8234-handle-typeof.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8234-handle-typeof.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index 7c0a935cc..c7ea68988 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -369,6 +369,12 @@ ScrDyn *scr_text_decoder_to_dyn(void *rec, void *(*retain)(void *), void (*relea void *scr_text_codec_from_dyn(const ScrDyn *d, bool decoder, const struct ScrDynPath *path, const char *want); bool scr_text_codec_is(const ScrDyn *d, bool decoder); +/* StringDecoder records as checked-dynamic handles (the same unit): the + * byte offsets of the encoding (string) and pending (f64) fields. */ +ScrDyn *scr_string_decoder_to_dyn(void *rec, void *(*retain)(void *), void (*release)(void *), + size_t encoding, size_t pending); +void *scr_string_decoder_from_dyn(const ScrDyn *d, const struct ScrDynPath *path, + const char *want); bool scr_buffer_is_ascii(const ScrDyn *input); bool scr_buffer_is_utf8(const ScrDyn *input); ScrBytes *scr_buffer_transcode(const ScrDyn *source, const ScrDyn *from, const ScrDyn *to); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 82fe1a6d9..1c7a9a0e0 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -200,6 +200,7 @@ typedef enum { X(SHARED_ARRAY_BUFFER, 37) \ X(SIGN, 67) /* node:crypto Sign (scr_pki.c) */ \ X(STDIO, 27) /* stable process stdin/stdout/stderr values */ \ + X(STRING_DECODER, 78) /* compiled StringDecoder record (scr_text_codec_dyn.c) */ \ X(TEXT_DECODER, 76) /* compiled TextDecoder record (scr_text_codec_dyn.c) */ \ X(TEXT_ENCODER, 77) /* compiled TextEncoder record (scr_text_codec_dyn.c) */ \ X(URL, 28) /* native WHATWG URL */ \ diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index ec061e10c..33382363b 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6634,6 +6634,12 @@ ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once, return (ScrStream *)scr_emitter_on_dyn((ScrEmitter *)s, name, cb, adapter, error ? scr_ee_inv_fixed1 : scr_ee_inv_fixed0, once, prepend); } + /* The stream emits 'pipe' and 'unpipe' with its source stream; other + * names reach the listener only through emit calls, with every argument + * they pass. */ + bool piping = (name->len == 4 && memcmp(name->data, "pipe", 4) == 0) || + (name->len == 6 && memcmp(name->data, "unpipe", 6) == 0); + if (!piping) return (ScrStream *)scr_emitter_on_flex((ScrEmitter *)s, name, cb, once, prepend); static const char message[] = "this stream event has no checked-dynamic listener adapter"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); return NULL; @@ -8058,10 +8064,68 @@ static ScrStream *scr_stream_view_receiver(const ScrDyn *value) { /* The members a native stream answers to checked-dynamic calls. */ static const char *const scr_stream_dyn_methods[] = { "on", "once", "addListener", "prependListener", "prependOnceListener", - "off", "removeListener", "pipe", "unpipe", "read", "pause", "resume", "isPaused", - "setEncoding", "write", "end", "destroy", "cork", "uncork", + "off", "removeListener", "removeAllListeners", "listenerCount", "eventNames", "pipe", + "unpipe", "read", "push", "unshift", "pause", "resume", "isPaused", "setEncoding", "write", + "end", "destroy", "cork", "uncork", "setDefaultEncoding", }; +/* An event name argument: a string, +1; other values throw the "not + * supported yet" Error. */ +static ScrStr *scr_stream_dyn_event_name(const ScrDyn *value) { + if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str); + static const char message[] = "event names other than strings on a native stream are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return NULL; +} + +/* push(chunk, encoding): a string with an encoding pushes the string's + * bytes in that encoding, undefined pushes an empty chunk outside object + * mode, and other chunks push as push(chunk) does. */ +static ScrDyn *scr_stream_dyn_push(ScrStream *stream, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = argc ? args[0] : scr_dyn_undefined(); + ScrDyn *encoding = argc > 1 ? args[1] : scr_dyn_undefined(); + if (chunk->kind == SCR_DYN_STR && encoding->kind == SCR_DYN_STR) { + ScrStr *canonical = scr_bytes_normalize_encoding(encoding->v.str); + if (!canonical) return NULL; + bool more = scr_stream_push_str_enc(stream, chunk->v.str, canonical); + scr_str_release(canonical); + return scr_dyn_new_bool(more); + } + if (chunk->kind == SCR_DYN_UNDEF && !(stream->st->has_r && stream->st->r.object_entries)) { + ScrStr *empty = scr_str_new("", 0); + bool more = scr_stream_push_str(stream, empty); + scr_str_release(empty); + return scr_dyn_new_bool(more); + } + bool more = scr_stream_push_dyn(stream, chunk); + return scr_exc_pending() ? NULL : scr_dyn_new_bool(more); +} + +/* unshift(chunk): whether the stream takes more data afterwards, Node's + * canPushMore. */ +static ScrDyn *scr_stream_dyn_unshift(ScrStream *stream, const ScrDyn *chunk) { + scr_stream_unshift_dyn(stream, chunk); + if (scr_exc_pending()) return NULL; + ScrStreamState *st = stream->st; + bool more = chunk->kind != SCR_DYN_NULL && st->has_r && !st->destroyed && !st->errored && + !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0); + return scr_dyn_new_bool(more); +} + +/* eventNames(): the stream's event names as a string array, +1. */ +static ScrDyn *scr_stream_dyn_event_names(ScrStream *stream) { + ScrArr *names = scr_emitter_event_names((ScrEmitter *)stream); + ScrDyn *result = scr_dyn_new_arr(); + double length = scr_arr_len(names); + for (double i = 0; i < length; i++) { + ScrStr *name = scr_arr_get_ref(names, i); + scr_dyn_arr_push(result, scr_dyn_new_str(name)); + scr_str_release(name); + } + scr_arr_release(names); + return result; +} + /* A checked-dynamic call of one of a native stream's members (borrowed * stream): the result, +1, with `this` results answering `self`, or the * current `this` when self is NULL. */ @@ -8086,6 +8150,29 @@ static ScrDyn *scr_stream_dyn_call(ScrStream *stream, ScrDyn *self, const char * argc > 1 ? args[1] : scr_dyn_undefined()); scr_emitter_release(registered); } + } else if (!strcmp(method, "removeAllListeners")) { + bool all = first->kind == SCR_DYN_UNDEF; + ScrStr *name = all ? scr_str_new("", 0) : scr_stream_dyn_event_name(first); + if (name) { + scr_emitter_release(scr_emitter_remove_all((ScrEmitter *)stream, name, all)); + scr_str_release(name); + } + } else if (!strcmp(method, "listenerCount")) { + ScrStr *name = scr_stream_dyn_event_name(first); + if (name) { + ScrDyn *only = argc > 1 ? args[1] : scr_dyn_undefined(); + double count = only->kind == SCR_DYN_UNDEF || only->kind == SCR_DYN_NULL + ? scr_emitter_listener_count((ScrEmitter *)stream, name) + : only->kind == SCR_DYN_FUNC ? scr_emitter_listener_count_dyn((ScrEmitter *)stream, name, only) : 0; + scr_str_release(name); + if (!scr_exc_pending()) result = scr_dyn_new_num(count); + } + } else if (!strcmp(method, "eventNames")) result = scr_stream_dyn_event_names(stream); + else if (!strcmp(method, "push")) result = scr_stream_dyn_push(stream, args, argc); + else if (!strcmp(method, "unshift")) result = scr_stream_dyn_unshift(stream, first); + else if (!strcmp(method, "setDefaultEncoding")) { + ScrStream *returned = scr_stream_set_default_encoding(stream, first); + if (returned) scr_stream_release(returned); } else if (!strcmp(method, "pipe") || !strcmp(method, "unpipe")) { ScrStream *destination = first->kind == SCR_DYN_UNDEF && !strcmp(method, "unpipe") ? NULL : scr_stream_view_receiver(first); @@ -8220,6 +8307,11 @@ ScrDyn *scr_stream_dyn_view(ScrStream *stream) { const char *name = scr_stream_dyn_properties[i]; scr_dyn_obj_set(view, name, strlen(name), scr_stream_dyn_property(stream, name)); } + if (stream->st->has_r) { + int flowing = stream->st->r.flowing; + scr_dyn_obj_set(view, "readableFlowing", 15, + flowing < 0 ? scr_dyn_new_null() : scr_dyn_new_bool(flowing == 1)); + } if (stream->st->native && stream->st->native_ops && stream->st->native_ops->view) { ScrClosure *cb = scr_closure_new((void *)scr_stream_view_call, 2); cb->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); diff --git a/packages/runtime/src/scr_text_codec_dyn.c b/packages/runtime/src/scr_text_codec_dyn.c index 5102bc558..37fdfb27b 100644 --- a/packages/runtime/src/scr_text_codec_dyn.c +++ b/packages/runtime/src/scr_text_codec_dyn.c @@ -1,9 +1,10 @@ -/* WHATWG TextEncoder and TextDecoder instances as checked-dynamic values - * (SCR_DYNH_TEXT_ENCODER, SCR_DYNH_TEXT_DECODER). A codec is a compiled +/* WHATWG TextEncoder and TextDecoder instances, and node:string_decoder + * StringDecoder instances, as checked-dynamic values (SCR_DYNH_TEXT_ENCODER, + * SCR_DYNH_TEXT_DECODER, SCR_DYNH_STRING_DECODER). A codec is a compiled * record (type-mapper.ts): the handle holds that record by reference, so * a codec stored in an untyped slot and read back keeps its identity and * a decoder keeps its streaming state. The compiler passes the record's - * reference-count entry points and, for decoders, the byte offsets of the + * reference-count entry points and, for a TextDecoder, the byte offsets of the * encoding, fatal, ignoreBOM and state fields when it boxes one. A unit * of its own so programs that never box a codec link none of it * (native-toolchain gating: the textCodecDyn runtime feature). */ @@ -213,3 +214,124 @@ bool scr_text_codec_is(const ScrDyn *d, bool decoder) { return d->kind == SCR_DYN_HANDLE && d->v.handle.tag == (decoder ? SCR_DYNH_TEXT_DECODER : SCR_DYNH_TEXT_ENCODER); } + +/* node:string_decoder StringDecoder instances (SCR_DYNH_STRING_DECODER). + * The decoder is a compiled record of its canonical encoding (a string) + * and its pending partial sequence (an f64 packing, scr_bytes.c's + * scr_strdec_* helpers); the compiler passes both fields' byte offsets + * when it boxes one. */ +typedef struct { + void *(*retain)(void *); + void (*release)(void *); + size_t encoding, pending; +} ScrStringDecoderLayout; + +static SCR_TL ScrStringDecoderLayout scr_sdd_layout; + +static void *scr_sdd_retain(void *rec) { return scr_sdd_layout.retain(rec); } +static void scr_sdd_release(void *rec) { scr_sdd_layout.release(rec); } + +static ScrStr *scr_sdd_encoding(const void *rec) { + ScrStr *encoding; + memcpy(&encoding, (const char *)rec + scr_sdd_layout.encoding, sizeof encoding); + return encoding; +} + +static double scr_sdd_pending(const void *rec) { + double pending; + memcpy(&pending, (const char *)rec + scr_sdd_layout.pending, sizeof pending); + return pending; +} + +static void scr_sdd_set_pending(void *rec, double pending) { + memcpy((char *)rec + scr_sdd_layout.pending, &pending, sizeof pending); +} + +/* write(buf): a string answers itself; an ArrayBuffer view decodes with + * the pending bytes before it; other values throw Node's + * ERR_INVALID_ARG_TYPE. +1 or NULL. */ +static ScrStr *scr_sdd_write(void *rec, const ScrDyn *buf) { + if (buf->kind == SCR_DYN_STR) return scr_str_retain(buf->v.str); + if (buf->kind != SCR_DYN_BYTES) { + scr_dyn_arg_type_fail("buf", "an instance of Buffer, TypedArray, or DataView", buf); + return NULL; + } + ScrBytes *bytes = scr_bytes_raw_view(buf->v.bytes); + ScrStr *encoding = scr_sdd_encoding(rec); + double pending = scr_sdd_pending(rec); + ScrStr *text = scr_strdec_write(encoding, pending, bytes); + scr_sdd_set_pending(rec, scr_strdec_next(encoding, pending, bytes)); + scr_bytes_release(bytes); + return text; +} + +static ScrDyn *scr_sdd_invoke(void *rec, ScrDyn *self, const char *method, ScrDyn *const *args, + size_t argc, const char *what) { + (void)self; + ScrDyn *buf = scr_tcd_codec_arg(args, argc, 0); + if (strcmp(method, "write") == 0) { + ScrStr *text = scr_sdd_write(rec, buf); + return text ? scr_tcd_str(text) : NULL; + } + if (strcmp(method, "end") == 0) { + /* end(buf): write(buf) when buf is not undefined, then the pending + * bytes' replacement text. */ + ScrStr *head = buf->kind == SCR_DYN_UNDEF ? scr_str_new("", 0) : scr_sdd_write(rec, buf); + if (!head) return NULL; + ScrStr *tail = scr_strdec_end(scr_sdd_encoding(rec), scr_sdd_pending(rec)); + scr_sdd_set_pending(rec, 0); + ScrStr *text = scr_str_concat(head, tail); + scr_str_release(head); + scr_str_release(tail); + return scr_tcd_str(text); + } + if (strcmp(method, "text") == 0) { + static const char message[] = "StringDecoder.prototype.text is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return NULL; + } + scr_tcd_codec_not_a_function(what); + return NULL; +} + +static ScrDyn *scr_sdd_get(void *rec, const char *key, size_t len) { + if (len == 8 && memcmp(key, "encoding", 8) == 0) + return scr_dyn_new_str(scr_sdd_encoding(rec)); + if ((len == 8 && memcmp(key, "lastChar", 8) == 0) || + (len == 8 && memcmp(key, "lastNeed", 8) == 0) || + (len == 9 && memcmp(key, "lastTotal", 9) == 0)) { + static const char message[] = "StringDecoder lastChar, lastNeed and lastTotal are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + } + return NULL; +} + +static const ScrDynHandleMethod scr_sdd_methods[] = { + {.name = "write", .length = 1}, + {.name = "end", .length = 1}, + {.name = "text", .length = 2}, + {.name = NULL}, +}; + +static const ScrDynHandleOps scr_sdd_ops = { + .cls = "StringDecoder", + .retain = scr_sdd_retain, + .release = scr_sdd_release, + .invoke = scr_sdd_invoke, + .get = scr_sdd_get, + .set = scr_tcd_codec_set, + .methods = scr_sdd_methods, +}; + +ScrDyn *scr_string_decoder_to_dyn(void *rec, void *(*retain)(void *), void (*release)(void *), + size_t encoding, size_t pending) { + if (!scr_sdd_layout.retain) { + scr_sdd_layout = (ScrStringDecoderLayout){retain, release, encoding, pending}; + scr_dyn_handle_install(SCR_DYNH_STRING_DECODER, &scr_sdd_ops); + } + return scr_dyn_new_handle(rec, SCR_DYNH_STRING_DECODER); +} + +void *scr_string_decoder_from_dyn(const ScrDyn *d, const ScrDynPath *path, const char *want) { + return scr_dyn_handle_unbox(d, SCR_DYNH_STRING_DECODER, path, want); +} diff --git a/tests/corpus/8232-native-streams-untyped.cjs b/tests/corpus/8232-native-streams-untyped.cjs new file mode 100644 index 000000000..b70017b5a --- /dev/null +++ b/tests/corpus/8232-native-streams-untyped.cjs @@ -0,0 +1,56 @@ +// Native streams passed to untyped helpers: push, unshift, the listener +// members, setDefaultEncoding and readableFlowing answer through the +// untyped value as on the typed one, also for a program subclass and in +// object mode. +const { Readable, Writable } = require("stream"); + +function feed(s) { + console.log("push", s.push("a"), s.push("b", "utf8"), s.unshift("z")); + console.log("counts", s.listenerCount("data"), s.eventNames().join(",")); + s.on("custom", (x, y) => { + console.log("custom", x, y); + }); + s.emit("custom", 1, 2); + s.removeAllListeners("custom"); + console.log("after removeAll", s.listenerCount("custom"), s.readableFlowing); + s.push(null); +} + +const r = new Readable({ read() {} }); +const chunks = []; +r.on("data", (c) => chunks.push(String(c))); +r.on("end", () => console.log("end", chunks.join("|"))); +feed(r); + +class Source extends Readable { + _read() {} +} +const sub = new Source(); +sub.on("data", (c) => console.log("source data", String(c))); +console.log("source push", sub instanceof Readable); +(function (s) { + console.log(s.push("from untyped"), s.readableLength); + s.push(null); +})(sub); + +const objects = new Readable({ objectMode: true, read() {} }); +console.log("flowing before", objects.readableFlowing); +objects.on("data", (c) => console.log("object data", c)); +(function (s) { + console.log(s.push({ n: 1 }), s.push(undefined), s.push(2), s.readableFlowing); + s.push(null); +})(objects); + +function drain(w) { + w.setDefaultEncoding("latin1"); + w.write("é"); + w.end(); +} +const w = new Writable({ + write(c, e, cb) { + console.log("wrote", c.length, e); + cb(); + }, +}); +w.on("finish", () => console.log("finish")); +drain(w); diff --git a/tests/corpus/8233-string-decoder-values.cjs b/tests/corpus/8233-string-decoder-values.cjs new file mode 100644 index 000000000..475a31d57 --- /dev/null +++ b/tests/corpus/8233-string-decoder-values.cjs @@ -0,0 +1,33 @@ +// A StringDecoder passed to untyped code keeps its identity and its +// pending bytes, and answers write, end and encoding as in Node. +const { StringDecoder } = require("string_decoder"); + +function feed(decoder, chunks) { + let out = ""; + for (const chunk of chunks) out += decoder.write(chunk); + return out + decoder.end(); +} + +const euro = Buffer.from("€ and 😀"); +const utf8 = new StringDecoder("utf-8"); +const parts = []; +for (let i = 0; i < euro.length; i++) parts.push(euro.subarray(i, i + 1)); +console.log(feed(utf8, parts), utf8.encoding); + +const hex = new StringDecoder("hex"); +console.log(feed(hex, [Buffer.from([1, 2]), Buffer.from([255])]), hex.encoding); + +const utf16 = new StringDecoder("ucs2"); +const pending = [Buffer.from([0x61]), Buffer.from([0x00, 0x62]), Buffer.from([0x00])]; +console.log(JSON.stringify(feed(utf16, pending)), utf16.encoding); + +function passThrough(d) { + return d; +} +const kept = passThrough(utf8); +console.log(kept === utf8, typeof kept, kept.write("text"), kept.end(Buffer.from([0xf0, 0x9f]))); +try { + kept.write(null); +} catch (e) { + console.log(e.code, e.message); +} diff --git a/tests/corpus/8234-handle-typeof.ts b/tests/corpus/8234-handle-typeof.ts new file mode 100644 index 000000000..f18240130 --- /dev/null +++ b/tests/corpus/8234-handle-typeof.ts @@ -0,0 +1,24 @@ +// typeof of the objects the runtime keeps as numeric handles answers +// "object", as Node's does: readline interfaces, timers, async-local +// stores, diagnostics channels and event-loop histograms. +import * as readline from "node:readline"; +import { PassThrough } from "node:stream"; +import { AsyncLocalStorage } from "node:async_hooks"; +import * as dc from "node:diagnostics_channel"; +import { monitorEventLoopDelay } from "node:perf_hooks"; + +const rl = readline.createInterface({ input: new PassThrough(), terminal: false }); +const timeout = setTimeout(() => {}, 1); +const interval = setInterval(() => {}, 1000); +const immediate = setImmediate(() => {}); +const store = new AsyncLocalStorage(); +const channel = dc.channel("handles"); +const tracing = dc.tracingChannel("handles"); +const histogram = monitorEventLoopDelay(); +const wrapped: Number = 5; +console.log(typeof rl, typeof timeout, typeof interval, typeof immediate); +console.log(typeof store, typeof channel, typeof tracing, typeof histogram, typeof wrapped); +clearTimeout(timeout); +clearInterval(interval); +clearImmediate(immediate); +rl.close(); diff --git a/tests/harness/corpus-costs/8232-native-streams-untyped.cjs.json b/tests/harness/corpus-costs/8232-native-streams-untyped.cjs.json new file mode 100644 index 000000000..bec4e42a8 --- /dev/null +++ b/tests/harness/corpus-costs/8232-native-streams-untyped.cjs.json @@ -0,0 +1 @@ +{ "plain": 4000, "san": 5000 } diff --git a/tests/harness/corpus-costs/8233-string-decoder-values.cjs.json b/tests/harness/corpus-costs/8233-string-decoder-values.cjs.json new file mode 100644 index 000000000..bb380e241 --- /dev/null +++ b/tests/harness/corpus-costs/8233-string-decoder-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3000 } diff --git a/tests/harness/corpus-costs/8234-handle-typeof.ts.json b/tests/harness/corpus-costs/8234-handle-typeof.ts.json new file mode 100644 index 000000000..4c7ad07f1 --- /dev/null +++ b/tests/harness/corpus-costs/8234-handle-typeof.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 5500 } From 48cbf3a89eef85d456b06a24df7d83ee8099d98a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 09:20:05 -0500 Subject: [PATCH 064/395] Verify runtime pack artifacts once per file identity, and store pack selections Every native exe build that the executable cache does not restore hashed each selected runtime artifact before staging it, and every exe build parsed and validated the whole runtime-pack.json manifest to select its artifacts. - Staging still copies each artifact. It skips the SHA-256 of an artifact when the file's identity (device, inode, size, mtime, ctime) is the one recorded the last time that path matched the same size and digest, and the identity does not change while the file is read. Other artifacts are hashed as before, and their identities are recorded after they match. - A pack's selection for a target, compiler version, feature set, flavor and mode is stored, keyed by the file identities of package.json and runtime-pack.json. A stored selection skips parsing and validating the manifest; the manifest is parsed only when a caller needs it, through runtimePackManifest. - The native compiler keeps both in its cache, in the runtime-pack family, which prune covers. --- .../src/backend/runtime-pack-native.test.ts | 155 ++++++++++- .../src/backend/runtime-pack-native.ts | 255 ++++++++++++++++-- packages/compiler/src/native/cache.ts | 1 + packages/compiler/src/native/compiler.ts | 32 ++- .../src/native/executable-cache-open.test.ts | 2 +- .../compiler/src/native/executable-cache.ts | 4 +- .../compiler/src/native/sanitizer.test.ts | 1 + packages/compiler/src/native/sanitizer.ts | 11 +- 8 files changed, 432 insertions(+), 29 deletions(-) diff --git a/packages/compiler/src/backend/runtime-pack-native.test.ts b/packages/compiler/src/backend/runtime-pack-native.test.ts index 20214818f..723deba39 100644 --- a/packages/compiler/src/backend/runtime-pack-native.test.ts +++ b/packages/compiler/src/backend/runtime-pack-native.test.ts @@ -1,12 +1,26 @@ +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, renameSync, statSync, utimesSync, writeFileSync } from "node:fs"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, expect, test } from "vitest"; +import { afterEach, expect, test, vi } from "vitest"; import { nativeCodegenDiag } from "../diagnostics/diagnostic.js"; +import { compilerReleaseVersion } from "../library/sidecar.js"; import type { NativeLinkFeatures } from "./native-link-info.js"; -import { RuntimePackError } from "./runtime-pack-core.js"; -import { selectNativeRuntimePack } from "./runtime-pack-native.js"; -import { WASM32_WASI_TARGET } from "./targets.js"; +import { RuntimePackError, type RuntimePackManifest } from "./runtime-pack-core.js"; +import { + runtimePackManifest, + selectNativeRuntimePack, + stageNativeRuntimeSelection, + type NativeRuntimeSelection, + type RuntimePackStore, +} from "./runtime-pack-native.js"; +import { MACOS_ARM64_TARGET, WASM32_WASI_TARGET } from "./targets.js"; + +vi.mock("node:crypto", async (original) => { + const crypto = await original(); + return { ...crypto, createHash: vi.fn(crypto.createHash) }; +}); const dirs: string[] = []; afterEach(async () => @@ -45,3 +59,136 @@ test("a missing native helper keeps the reinstall hint", () => { "reinstall scriptc with optional dependencies enabled for this host", ); }); + +/** A runtime pack with one runtime object and a license, and a store that + * keeps entries in memory. */ +async function packFixture() { + const root = await mkdtemp(join(tmpdir(), "scriptc-runtime-pack-store-")); + dirs.push(root); + const target = MACOS_ARM64_TARGET; + const version = compilerReleaseVersion(); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ name: target.runtimePackPackage, version }), + ); + mkdirSync(join(root, "artifacts")); + const object = join(root, "artifacts", "base.o"); + writeFileSync(object, "base object"); + writeFileSync(join(root, "license.txt"), "license"); + const unit = { + source: "scr_base.c", + predicate: true, + variants: [ + { + id: "default", + when: {}, + defines: [], + path: "artifacts/base.o", + sha256: createHash("sha256").update("base object").digest("hex"), + size: Buffer.byteLength("base object"), + }, + ], + }; + const manifest: RuntimePackManifest = { + schema: "scriptc.runtime-pack.v1", + format: 1, + package: target.runtimePackPackage, + version, + target: { + name: target.name, + llvm_triple: target.llvmTriple, + architecture: target.architecture, + object_format: target.objectFormat, + minimum_os: target.minimumOs, + }, + runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + compiler: { command: "clang", identity: "fixture clang", target: target.llvmTriple }, + macros: { + executable: [], + excluded: ["SCR_LIB", "SCR_THREAD_INSTANCES", "SCR_RC_AUDIT"], + sanitizer: "external-toolchain-required", + }, + flavors: { + release: { optimization: "-O2", runtime_units: [unit] }, + dev: { optimization: "-O0", runtime_units: [unit] }, + }, + archives: [], + system_libraries: [{ name: "System", predicate: true }], + licenses: [{ path: "license.txt", license: "fixture" }], + }; + const manifestText = JSON.stringify(manifest); + writeFileSync(join(root, "runtime-pack.json"), manifestText); + const entries = new Map(); + const store: RuntimePackStore = { + read: (key) => entries.get(key) ?? null, + write: (key, text) => void entries.set(key, text), + }; + const select = () => + selectNativeRuntimePack( + root, + target, + version, + {} as NativeLinkFeatures, + "release", + "executable", + store, + ); + let stages = 0; + const stage = (selection: NativeRuntimeSelection) => { + const directory = join(root, `stage-${stages++}`); + mkdirSync(directory); + return stageNativeRuntimeSelection(selection, directory, store); + }; + return { root, object, manifestText, entries, select, stage }; +} + +test("a stored selection answers while the pack's files keep their identities", async () => { + const f = await packFixture(); + const first = f.select(); + expect(first.selected.systemLibraries).toEqual(["System"]); + expect(f.entries.size).toBe(1); + // The next selection reads the stored answer instead of the manifest. + const [key, text] = [...f.entries][0]!; + const stored = JSON.parse(text); + stored.selected.systemLibraries = ["Stored"]; + f.entries.set(key, JSON.stringify(stored)); + const second = f.select(); + expect(second.selected.systemLibraries).toEqual(["Stored"]); + expect(second.licenses).toEqual(first.licenses); + expect(second.manifest).toBeUndefined(); + expect(runtimePackManifest(second).flavors.release).toEqual(first.manifest!.flavors.release); + // Writing the manifest again, with the same text, gives it a new identity. + writeFileSync(join(f.root, "runtime-pack.json"), f.manifestText); + expect(f.select().selected.systemLibraries).toEqual(["System"]); +}); + +test.each(["replaced", "rewritten in place"])( + "staging hashes an artifact again when it is %s, even at the same size and mtime", + async (change) => { + const f = await packFixture(); + const selection = f.select(); + f.stage(selection); + const before = statSync(f.object); + const changed = change === "replaced" ? f.object + ".new" : f.object; + writeFileSync(changed, "x".repeat(before.size)); + utimesSync(changed, before.atime, before.mtime); + if (changed !== f.object) renameSync(changed, f.object); + expect(() => f.stage(selection)).toThrow( + "runtime pack artifact hash mismatch: artifacts/base.o", + ); + }, +); + +test("staging does not hash an unchanged artifact again", async () => { + const f = await packFixture(); + const selection = f.select(); + const hashes = () => vi.mocked(createHash).mock.calls.length; + let start = hashes(); + f.stage(selection); + const firstStage = hashes() - start; + start = hashes(); + const staged = f.stage(selection); + // Only the store key is hashed; the staged copy has the verified bytes. + expect(hashes() - start).toBe(firstStage - 1); + expect(readFileSync(staged.runtimeObjects[0]!, "utf8")).toBe("base object"); +}); diff --git a/packages/compiler/src/backend/runtime-pack-native.ts b/packages/compiler/src/backend/runtime-pack-native.ts index bd38e2ef0..f51b24221 100644 --- a/packages/compiler/src/backend/runtime-pack-native.ts +++ b/packages/compiler/src/backend/runtime-pack-native.ts @@ -1,6 +1,8 @@ /** Synchronous runtime-pack host for the statically compiled driver. Each - * selected artifact is hashed and copied from the same read into private - * storage. This host has no persistent cache or mutable installed link inputs. */ + * selected artifact is verified and copied from the same read into private + * storage. With a store, a pack's selections and the identities of the + * artifacts it verified are kept between builds: an artifact whose file + * identity is unchanged since it was hashed is not hashed again. */ import type { NativeOptimization } from "./optimization.js"; import { createHash } from "node:crypto"; import { @@ -10,6 +12,7 @@ import { mkdirSync, openSync, readFileSync, + statSync, writeSync, } from "node:fs"; import { dirname, join } from "node:path"; @@ -34,13 +37,63 @@ export interface NativeRuntimePack { export interface NativeRuntimeSelection { root: string; - manifest: RuntimePackManifest; + /** The parsed manifest; a selection read from a store has none until + * runtimePackManifest parses it. */ + manifest?: RuntimePackManifest; + licenses: RuntimePackManifest["licenses"]; packageText: string; manifestText: string; selected: RuntimePackArtifacts; flavor: NativeOptimization; } +/** Text entries kept between builds (the native compiler's cache). */ +export interface RuntimePackStore { + read(key: string): string | null; + write(key: string, text: string): void; +} + +interface FileIdentity { + dev: number; + ino: number; + size: number; + mtime: number; + ctime: number; +} + +function fileIdentity(path: string): FileIdentity { + const info = statSync(path); + return { + dev: info.dev, + ino: info.ino, + size: info.size, + mtime: info.mtimeMs, + ctime: info.ctimeMs, + }; +} + +function sameIdentity(a: FileIdentity, b: FileIdentity): boolean { + return ( + a.dev === b.dev && + a.ino === b.ino && + a.size === b.size && + a.mtime === b.mtime && + a.ctime === b.ctime + ); +} + +function storeKey(text: string): string { + return createHash("sha256").update(text).digest("hex"); +} + +/** A stored selection: what selecting from the pack's files, with these + * identities, returned. */ +interface StoredSelection { + schema: 1; + licenses: RuntimePackManifest["licenses"]; + selected: RuntimePackArtifacts; +} + export function selectNativeRuntimePack( root: string, target: NativeTargetSpec, @@ -48,38 +101,197 @@ export function selectNativeRuntimePack( features: NativeLinkFeatures, flavor: NativeOptimization, mode: RuntimePackMode = "executable", + store?: RuntimePackStore, ): NativeRuntimeSelection { - if (!existsSync(join(root, "package.json"))) { + const packagePath = join(root, "package.json"); + const manifestPath = join(root, "runtime-pack.json"); + if (!existsSync(packagePath)) { throw new RuntimePackError( `runtime pack for ${target.name} is not installed; install ${target.runtimePackPackage}@${compilerVersion} in your project or set SCRIPTC_RUNTIME_PACK to its directory`, "missing", "scriptc includes the runtime pack for its own platform only; other targets need their runtime pack package in the project", ); } - const packageText = readFileSync(join(root, "package.json"), "utf8"); - const manifestText = readFileSync(join(root, "runtime-pack.json"), "utf8"); + // The pack's files by identity, with everything else selection reads. + let key: string | null = null; + let identities: FileIdentity[] = []; + if (store !== undefined) { + try { + identities = [fileIdentity(packagePath), fileIdentity(manifestPath)]; + key = storeKey( + JSON.stringify({ + schema: 1, + root, + identities, + target: target.name, + compilerVersion, + features, + flavor, + mode, + }), + ); + const stored = readStoredSelection(store.read(key)); + if (stored !== null) { + const packageText = readFileSync(packagePath, "utf8"); + const manifestText = readFileSync(manifestPath, "utf8"); + // The texts read are the ones with the stored identities. + if ( + sameIdentity(fileIdentity(packagePath), identities[0]!) && + sameIdentity(fileIdentity(manifestPath), identities[1]!) + ) { + return { + root, + licenses: stored.licenses, + packageText, + manifestText, + selected: stored.selected, + flavor, + }; + } + } + } catch { + key = null; + } + } + const packageText = readFileSync(packagePath, "utf8"); + const manifestText = readFileSync(manifestPath, "utf8"); const identity = JSON.parse(packageText) as { name?: string; version?: string }; const manifest = parseRuntimePackManifest(JSON.parse(manifestText)); validateRuntimePackIdentity(manifest, identity.name, identity.version, target, compilerVersion); const selected = selectRuntimePackArtifacts(manifest, features, flavor, process.env, mode); - return { root, manifest, packageText, manifestText, selected, flavor }; + if (store !== undefined && key !== null) { + try { + // Stored only when the files selected from still have the identities + // in the key. + if ( + sameIdentity(fileIdentity(packagePath), identities[0]!) && + sameIdentity(fileIdentity(manifestPath), identities[1]!) + ) { + const entry: StoredSelection = { schema: 1, licenses: manifest.licenses, selected }; + store.write(key, JSON.stringify(entry)); + } + } catch { + /* A failed write is a later miss. */ + } + } + return { + root, + manifest, + licenses: manifest.licenses, + packageText, + manifestText, + selected, + flavor, + }; } -function stageArtifact(root: string, stage: string, artifact: RuntimePackArtifact): string { +/** The selection's parsed manifest. */ +export function runtimePackManifest(selection: NativeRuntimeSelection): RuntimePackManifest { + return (selection.manifest ??= parseRuntimePackManifest(JSON.parse(selection.manifestText))); +} + +function readStoredSelection(text: string | null): StoredSelection | null { + if (text === null) return null; + const value: unknown = JSON.parse(text); + if (value === null || typeof value !== "object") return null; + const entry = value as Partial; + if ( + entry.schema !== 1 || + !Array.isArray(entry.licenses) || + entry.selected === undefined || + entry.selected === null || + typeof entry.selected !== "object" || + !Array.isArray(entry.selected.runtime) || + !Array.isArray(entry.selected.archives) || + !Array.isArray(entry.selected.systemLibraries) + ) + return null; + return entry as StoredSelection; +} + +/** An artifact path's identity when its bytes last matched the manifest's + * size and digest. */ +interface VerifiedArtifact { + path: string; + sha256: string; + dev: number; + ino: number; + size: number; + mtime: number; + ctime: number; +} + +interface StoredVerification { + schema: number; + artifacts: VerifiedArtifact[]; +} + +function readVerified( + store: RuntimePackStore | undefined, + key: string, +): Map { + const verified = new Map(); + try { + const text = store?.read(key) ?? null; + if (text === null) return verified; + const stored = JSON.parse(text) as StoredVerification; + if (stored.schema === 1) + for (const artifact of stored.artifacts) verified.set(artifact.path, artifact); + } catch { + /* A malformed entry verifies everything again. */ + } + return verified; +} + +function stageArtifact( + root: string, + stage: string, + artifact: RuntimePackArtifact, + verified: Map, + newlyVerified: VerifiedArtifact[], +): string { const source = join(root, artifact.path); const destination = join(stage, artifact.path); let bytes: Buffer; + let before: FileIdentity; + let after: FileIdentity; try { + before = fileIdentity(source); bytes = readFileSync(source); + after = fileIdentity(source); } catch { throw new RuntimePackError(`runtime pack artifact is missing: ${artifact.path}`, "invalid"); } + // The bytes are those last hashed when the file kept the identity it had + // then for the whole read. + const known = verified.get(artifact.path); + const unchanged = sameIdentity(before, after); + const trusted = + known !== undefined && + known.sha256 === artifact.sha256 && + known.size === artifact.size && + unchanged && + known.dev === before.dev && + known.ino === before.ino && + known.size === before.size && + known.mtime === before.mtime && + known.ctime === before.ctime; if ( bytes.length !== artifact.size || - createHash("sha256").update(bytes).digest("hex") !== artifact.sha256 + (!trusted && createHash("sha256").update(bytes).digest("hex") !== artifact.sha256) ) { throw new RuntimePackError(`runtime pack artifact hash mismatch: ${artifact.path}`, "invalid"); } + if (!trusted && unchanged) + newlyVerified.push({ + path: artifact.path, + sha256: artifact.sha256, + dev: before.dev, + ino: before.ino, + size: before.size, + mtime: before.mtime, + ctime: before.ctime, + }); mkdirSync(dirname(destination), { recursive: true }); const fd = openSync( destination, @@ -123,15 +335,19 @@ export function stageNativeRuntimePack( export function stageNativeRuntimeSelection( selection: NativeRuntimeSelection, stageRoot: string, + store?: RuntimePackStore, ): NativeRuntimePack { - const { root, manifest, selected, packageText, manifestText } = selection; + const { root, licenses, selected, packageText, manifestText } = selection; const packagePath = join(root, "package.json"); const manifestPath = join(root, "runtime-pack.json"); - const runtimeObjects = selected.runtime.map((artifact) => - stageArtifact(root, stageRoot, artifact), - ); - const archives = selected.archives.map((artifact) => stageArtifact(root, stageRoot, artifact)); - for (const license of manifest.licenses) { + const key = storeKey(JSON.stringify({ schema: 1, verified: root })); + const verified = readVerified(store, key); + const newlyVerified: VerifiedArtifact[] = []; + const stage = (artifact: RuntimePackArtifact): string => + stageArtifact(root, stageRoot, artifact, verified, newlyVerified); + const runtimeObjects = selected.runtime.map(stage); + const archives = selected.archives.map(stage); + for (const license of licenses) { try { readFileSync(join(root, license.path)); } catch { @@ -144,5 +360,14 @@ export function stageNativeRuntimeSelection( ) { throw new RuntimePackError("runtime pack changed while staging verified artifacts", "invalid"); } + if (store !== undefined && newlyVerified.length > 0) { + for (const artifact of newlyVerified) verified.set(artifact.path, artifact); + const entry: StoredVerification = { schema: 1, artifacts: [...verified.values()] }; + try { + store.write(key, JSON.stringify(entry)); + } catch { + /* A failed write only means hashing again next time. */ + } + } return { runtimeObjects, archives, systemLibraries: selected.systemLibraries }; } diff --git a/packages/compiler/src/native/cache.ts b/packages/compiler/src/native/cache.ts index 937b4b109..f48410d1d 100644 --- a/packages/compiler/src/native/cache.ts +++ b/packages/compiler/src/native/cache.ts @@ -179,6 +179,7 @@ export class NativeCache { "sanitizer", "executable", "linked", + "runtime-pack", "binary", "dsym", "coverage", diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index 63769fba6..bcd5c1935 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -31,7 +31,9 @@ import { } from "../backend/native-tools.js"; import { selectNativeRuntimePack, + runtimePackManifest, stageNativeRuntimeSelection, + type RuntimePackStore, } from "../backend/runtime-pack-native.js"; import { RuntimePackError } from "../backend/runtime-pack-core.js"; import { @@ -160,6 +162,17 @@ export class NativeCompiler { return result; } + /** Runtime pack selections and verified artifact identities, kept in the + * cache. */ + private runtimePackStore(): RuntimePackStore | undefined { + const cache = this.cache; + if (cache === null) return undefined; + return { + read: (key) => cache.read("runtime-pack", key)?.toString("utf8") ?? null, + write: (key, text) => cache.write("runtime-pack", key, text), + }; + } + private emitObject( input: string, output: string, @@ -394,6 +407,8 @@ export class NativeCompiler { toolchain.compilerVersion, features, optimization, + "executable", + this.runtimePackStore(), ) : null; const executableCache = @@ -449,16 +464,24 @@ export class NativeCompiler { toolchain.compilerVersion, features, optimization, + "executable", + this.runtimePackStore(), ) : null; - if (pack !== null) stageNativeRuntimeSelection(pack, join(stage, "runtime")); + if (pack !== null) + stageNativeRuntimeSelection(pack, join(stage, "runtime"), this.runtimePackStore()); const info = pack === null ? undefined : formatNativeLinkInfo( { programObject: output, target, ffi }, toolchain.compilerVersion, - pack, + { + root: pack.root, + manifest: runtimePackManifest(pack), + selected: pack.selected, + flavor: pack.flavor, + }, ); renameSync(stagedOutput, output); return { @@ -473,7 +496,7 @@ export class NativeCompiler { const pack = executablePack!; const runtime = options.sanitize ? buildSanitizedRuntime(toolchain, pack, join(stage, "runtime"), "executable", this.cache) - : stageNativeRuntimeSelection(pack, join(stage, "runtime")); + : stageNativeRuntimeSelection(pack, join(stage, "runtime"), this.runtimePackStore()); timing("runtime-stage"); const plan = executableLinkInputs({ target, @@ -617,6 +640,7 @@ export class NativeCompiler { features, profile.optimization, profile.instancePerThread ? "library-thread" : "library", + this.runtimePackStore(), ); const runtime = options.sanitize ? buildSanitizedRuntime( @@ -626,7 +650,7 @@ export class NativeCompiler { profile.instancePerThread ? "library-thread" : "library", this.cache, ) - : stageNativeRuntimeSelection(pack, join(stage, "runtime")); + : stageNativeRuntimeSelection(pack, join(stage, "runtime"), this.runtimePackStore()); timing("runtime-stage"); const stagedArchive = join(stage, wasm ? "output.wasm" : "output.a"); const localizeSymbols = libraryLocalizeSymbols(profile); diff --git a/packages/compiler/src/native/executable-cache-open.test.ts b/packages/compiler/src/native/executable-cache-open.test.ts index 05e57148d..09a8b9cc9 100644 --- a/packages/compiler/src/native/executable-cache-open.test.ts +++ b/packages/compiler/src/native/executable-cache-open.test.ts @@ -52,7 +52,7 @@ function fixture(platform: string, driver: string) { } as unknown as NativeToolchain; const pack = { root: runtime, - manifest: { licenses: [] }, + licenses: [], packageText: "{}", manifestText: "{}", selected: { runtime: [], archives: [] }, diff --git a/packages/compiler/src/native/executable-cache.ts b/packages/compiler/src/native/executable-cache.ts index 1dfbf0b00..74c935d88 100644 --- a/packages/compiler/src/native/executable-cache.ts +++ b/packages/compiler/src/native/executable-cache.ts @@ -331,8 +331,8 @@ export function openNativeExecutableCache( join(toolchain.helperPackageRoot, "package.json"), join(toolchain.runtimePackRoot, "package.json"), join(toolchain.runtimePackRoot, "runtime-pack.json"), - ...[...pack.selected.runtime, ...pack.selected.archives, ...pack.manifest.licenses].map( - (artifact) => join(pack.root, artifact.path), + ...[...pack.selected.runtime, ...pack.selected.archives, ...pack.licenses].map((artifact) => + join(pack.root, artifact.path), ), ]; if (platform === "linux") paths.push(join(pack.root, "runtime-pack.json")); diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 1415f2971..31a8c3355 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -64,6 +64,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( { root: directory, manifest, + licenses: manifest.licenses, packageText: "", manifestText: "", flavor: "dev", diff --git a/packages/compiler/src/native/sanitizer.ts b/packages/compiler/src/native/sanitizer.ts index cb3728ec0..5d228a615 100644 --- a/packages/compiler/src/native/sanitizer.ts +++ b/packages/compiler/src/native/sanitizer.ts @@ -4,7 +4,11 @@ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; import { basename, join } from "node:path"; import { NativeCodegenError } from "../backend/native-codegen-core.js"; import { requireNativeArtifact, runNativeTool } from "../backend/native-tools.js"; -import type { NativeRuntimePack, NativeRuntimeSelection } from "../backend/runtime-pack-native.js"; +import { + runtimePackManifest, + type NativeRuntimePack, + type NativeRuntimeSelection, +} from "../backend/runtime-pack-native.js"; import { runtimePackFlavorKey, type RuntimePackMode } from "../backend/runtime-pack-core.js"; import { LRE_SOURCES, ZLIB_SOURCES } from "../backend/vendor-inputs.js"; import { contentDigest, type NativeCache } from "./cache.js"; @@ -119,8 +123,9 @@ export function buildSanitizedRuntime( } return output; }; - const key = runtimePackFlavorKey(selection.manifest, selection.flavor, mode); - const flavor = selection.manifest.flavors[key]; + const manifest = runtimePackManifest(selection); + const key = runtimePackFlavorKey(manifest, selection.flavor, mode); + const flavor = manifest.flavors[key]; if (flavor === undefined) throw new NativeCodegenError("SC3003", `runtime pack lacks ${key}`); const wanted = new Set(selection.selected.runtime.map((artifact) => artifact.path)); const common = [ From 537dfbeb687f1022e68978c68a524e65cf468043 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 09:37:24 -0500 Subject: [PATCH 065/395] Evaluate assert.fail's value messages after declaring them, with Node's defaults assert.fail with a message that is not a string read the message's hidden locals before they were declared: the call stored the message and its generated flag in temporaries ahead of the message's declarations, so `assert.fail(new RangeError('re'))` crashed. Arguments computed from the message now read it after the declarations. assert.fail also follows Node's defaults for such values: null and undefined report "Failed" with generatedMessage true, an Error is thrown as is, and any other value, falsy ones included, becomes the message as its string, with generatedMessage set to !value. --- .../src/frontend/lowering/lower-assert.ts | 76 ++++++++++++------- tests/corpus/8062-assert-message-values.cjs | 7 ++ 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-assert.ts b/packages/compiler/src/frontend/lowering/lower-assert.ts index 39ec9c959..02faa7ade 100644 --- a/packages/compiler/src/frontend/lowering/lower-assert.ts +++ b/packages/compiler/src/frontend/lowering/lower-assert.ts @@ -57,6 +57,7 @@ import { typeKey, } from "../../ir/ir.js"; import { boolLit, countedFor, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/build.js"; +import { everyExpr } from "../../ir/traverse.js"; import { checkedClassInstanceOf } from "./class-construction.js"; import { isJsSourceFile } from "../program.js"; @@ -231,8 +232,9 @@ function requireStatementPosition(lowerer: Lowerer, call: ts.CallExpression, wha const VALUE_MESSAGE = "\u0001scriptc:assert-message-value"; /** A message argument that is not a string, met while one assertion - * lowers (withValueMessage). */ -let valueMessage: { value: IrExpr; has: string; text: string } | null = null; + * lowers (withValueMessage). `fail` marks assert.fail's message, which + * Node uses for every value but null and undefined. */ +let valueMessage: { value: IrExpr; has: string; text: string; fail: boolean } | null = null; /** The optional trailing message argument: Node accepts a string or an * Error (throwing the Error itself) — the STRING form lowers; a literal @@ -246,6 +248,7 @@ function lowerMessageArg( lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc, + fail = false, ): { msg: IrExpr; hasMsg: IrExpr } { if (!node || (ts.isIdentifier(node) && node.text === "undefined")) { return { msg: { kind: "strLit", value: "", type: STRING, loc }, hasMsg: boolLit(false, loc) }; @@ -265,7 +268,7 @@ function lowerMessageArg( } const has = lowerer.declareHiddenLocal("assert.hasMessage", BOOL); const text = lowerer.declareHiddenLocal("assert.messageText", STRING); - valueMessage = { value, has: has.id, text: text.id }; + valueMessage = { value, has: has.id, text: text.id, fail }; return { msg: varRef(text.id, STRING, loc), hasMsg: varRef(has.id, BOOL, loc) }; } @@ -285,7 +288,12 @@ function withValueMessage( valueMessage = null; try { const lowered = lower(); - const message = valueMessage as { value: IrExpr; has: string; text: string } | null; + const message = valueMessage as { + value: IrExpr; + has: string; + text: string; + fail: boolean; + } | null; if (lowered === null || message === null) return lowered; // assert.ok's statements evaluate the value first; its call is last. const before = lowered.kind === "seqExpr" ? lowered.stmts : []; @@ -298,13 +306,25 @@ function withValueMessage( ); } const stmts: IrStmt[] = [...before]; + // An argument computed from the message (assert.fail's generated flag) + // reads it after the declarations below; the others are evaluated first. + const readsMessage = (arg: IrExpr): boolean => + !everyExpr(arg, { + stmt: () => true, + expr: (e) => + !(e.kind === "varRef" && (e.localId === message.has || e.localId === message.text)), + }); const args = call.args.map((arg, index) => - arg.kind === "varRef" && (arg.localId === message.has || arg.localId === message.text) - ? arg - : stableValue(lowerer, stmts, arg, `assert.arg${index}`, loc), + readsMessage(arg) ? arg : stableValue(lowerer, stmts, arg, `assert.arg${index}`, loc), ); const local = lowerer.declareHiddenLocal("assert.message", DYN); const value = varRef(local.id, DYN, loc); + // The runtime reports a registered value: a truthy one for the other + // methods, any value but null and undefined for assert.fail (Node's + // `actual == null` test), whose other values read "Failed". + const registered: IrExpr = message.fail + ? varRef(message.has, BOOL, loc) + : { kind: "dynTest", test: "truthy", value, type: BOOL, loc }; stmts.push( { kind: "varDecl", localId: local.id, init: message.value, loc }, { @@ -318,9 +338,9 @@ function withValueMessage( localId: message.text, init: { kind: "ternary", - cond: { kind: "dynTest", test: "truthy", value, type: BOOL, loc }, + cond: registered, then: strLit(VALUE_MESSAGE, loc), - else_: strLit("", loc), + else_: strLit(message.fail ? "Failed" : "", loc), type: STRING, loc, }, @@ -328,7 +348,7 @@ function withValueMessage( }, { kind: "if", - cond: { kind: "dynTest", test: "truthy", value, type: BOOL, loc }, + cond: registered, then: [ { kind: "exprStmt", @@ -1148,22 +1168,26 @@ function lowerAssertFail(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc) generated = boolLit(true, loc); } else { // Node's AssertionError sets generatedMessage to !message, so an - // empty custom message counts as generated. - msg = stableValue( - lowerer, - stmts, - lowerMessageArg(lowerer, msgNode, loc).msg, - "%assertFailMessage", - loc, - ); - generated = { - kind: "strEq", - negated: false, - left: msg, - right: strLit("", loc), - type: BOOL, - loc, - }; + // empty custom message counts as generated. A message that is not a + // string is read from the hidden local that withValueMessage declares + // after these statements, so it is used as is. + const { msg: lowered, hasMsg } = lowerMessageArg(lowerer, msgNode, loc, true); + if (lowered.kind === "varRef" && hasMsg.kind === "varRef") { + // A value message: generated when Node reads "Failed" for null or + // undefined; the runtime sets the flag for a registered value. + msg = lowered; + generated = { kind: "unary", op: "!", operand: hasMsg, type: BOOL, loc }; + } else { + msg = stableValue(lowerer, stmts, lowered, "%assertFailMessage", loc); + generated = { + kind: "strEq", + negated: false, + left: msg, + right: strLit("", loc), + type: BOOL, + loc, + }; + } } const falseLit: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; stmts.push(recordOnFailure(lowerer, boolLit(true, loc), null, null, "fail", loc)); diff --git a/tests/corpus/8062-assert-message-values.cjs b/tests/corpus/8062-assert-message-values.cjs index 7f84ccd37..fc37a71e4 100644 --- a/tests/corpus/8062-assert-message-values.cjs +++ b/tests/corpus/8062-assert-message-values.cjs @@ -39,3 +39,10 @@ const row = [['a', 'b', true], ['c', 'd', 'x']]; for (const [, , expected] of row) { attempt('union operand', () => assert.strictEqual(JSON.parse(JSON.stringify(expected)), expected)); } + +// assert.fail with a message that is not a string: an Error is thrown as +// is; other values become the message, generated when falsy. +attempt('fail error', () => assert.fail(new RangeError('re'))); +attempt('fail value', () => assert.fail(JSON.parse('{"a":1}'))); +attempt('fail zero', () => assert.fail(zero)); +attempt('fail null', () => assert.fail(JSON.parse('null'))); From e792cf47713ad7c0e23b2ab10e4f089be894bc69 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 09:49:01 -0500 Subject: [PATCH 066/395] Test RSA-PSS verification with an automatic salt length on restricted keys Node recovers the salt length under RSA_PSS_SALTLEN_AUTO without enforcing the minimum in an RSA-PSS key's parameters, while an explicit saltLength below it throws and the default saltLength requires the key's. The corpus program pins that behavior for MGF1 digests equal to and different from the message digest, through crypto.verify and createVerify. --- .../8178-rsa-pss-auto-min-salt.cjs.json | 6 ++ tests/corpus/8178-rsa-pss-auto-min-salt.cjs | 57 +++++++++++++++++++ .../8178-rsa-pss-auto-min-salt.cjs.json | 1 + 3 files changed, 64 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json create mode 100644 tests/corpus/8178-rsa-pss-auto-min-salt.cjs create mode 100644 tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json new file mode 100644 index 000000000..7b3e5eef7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8178-rsa-pss-auto-min-salt.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs new file mode 100644 index 000000000..97b9d1c05 --- /dev/null +++ b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs @@ -0,0 +1,57 @@ +// Verifying with RSA_PSS_SALTLEN_AUTO against an RSA-PSS key whose +// parameters set a minimum salt length still enforces that minimum, as in +// Node: a valid signature with a shorter salt does not verify. The keys use +// an MGF1 digest equal to the message digest and a different one. The +// stricter public key is the generated one with its minimum salt length +// raised from 16 to 32 in its DER. +const c = require("node:crypto"); + +const data = Buffer.from("restricted salt"); + +function raiseMinimumSalt(publicKey) { + const der = publicKey.export({ format: "der", type: "spki" }); + const from = Buffer.from([0xa2, 0x03, 0x02, 0x01, 0x10]); + const at = der.indexOf(from); + if (at < 0) throw new Error("no saltLength field"); + der[at + 4] = 0x20; + return c.createPublicKey({ key: der, format: "der", type: "spki" }); +} + +function attempt(label, fn) { + try { + console.log(label, fn()); + } catch (e) { + console.log(label, "throws", e.code, e.message); + } +} + +for (const mgf1HashAlgorithm of ["sha256", "sha384"]) { + const { privateKey, publicKey } = c.generateKeyPairSync("rsa-pss", { + modulusLength: 1024, + hashAlgorithm: "sha256", + mgf1HashAlgorithm, + saltLength: 16, + }); + const strict = raiseMinimumSalt(publicKey); + const { mgf1HashAlgorithm: mgf1, saltLength: minimum } = strict.asymmetricKeyDetails; + console.log(mgf1HashAlgorithm, "strict key", mgf1, minimum); + for (const saltLength of [16, 20, 32, 40]) { + const signature = c.sign("sha256", data, { key: privateKey, saltLength }); + const label = `${mgf1HashAlgorithm} salt ${saltLength}`; + attempt(`${label} auto, key minimum 16:`, () => + c.verify("sha256", data, { key: publicKey, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + attempt(`${label} auto, key minimum 32:`, () => + c.verify("sha256", data, { key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + attempt(`${label} default, key minimum 32:`, () => c.verify("sha256", data, strict, signature)); + attempt(`${label} digest length, key minimum 32:`, () => + c.verify("sha256", data, { key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_DIGEST }, signature), + ); + const v = c.createVerify("sha256"); + v.update(data); + attempt(`${label} createVerify auto, key minimum 32:`, () => + v.verify({ key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + } +} diff --git a/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json b/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json new file mode 100644 index 000000000..b1711dea1 --- /dev/null +++ b/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json @@ -0,0 +1 @@ +{ "plain": 5740, "san": 60808 } From e35621323dfdceb4b56a01bcbe8c9523591e495a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 10:06:35 -0500 Subject: [PATCH 067/395] Trust no certificate for an empty ca in TLS clients tls.connect, https.request and http2.connect verified the server against the default CAs when ca was an empty Buffer, because the runtime read a zero-length ca as none. The compiled calls now pass a null ca for none (an omitted ca, or an empty string, which Node reads as no ca) and a non-null one for any Buffer, and runtime option records read ca the same way, so an empty Buffer or array trusts no certificate, as in Node. The connection pool keys tell an empty ca from none. An empty array, which threw, is an empty trust store too. A runtime-valued empty string ca in http2.connect is refused instead of being read as an empty store. --- docs/content/limitations/tls-certificates.mdx | 3 + packages/compiler/src/backend/llvm/lib-abi.ts | 20 ++++ .../compiler/src/backend/llvm/lib-dispatch.ts | 7 +- .../compiler/src/backend/llvm/lib-http2.ts | 3 +- packages/compiler/src/backend/llvm/lib-tls.ts | 5 +- .../corpus/8176-tls-empty-context-ca.ts.json | 6 + .../corpus/8177-tls-empty-client-ca.ts.json | 6 + packages/runtime/src/scr_http2.c | 4 +- packages/runtime/src/scr_http2.h | 6 +- packages/runtime/src/scr_tls.c | 99 ++++++++++++----- packages/runtime/src/scr_tls.h | 2 +- tests/corpus/8176-tls-empty-context-ca.ts | 73 ++++++++++++ tests/corpus/8177-tls-empty-client-ca.ts | 104 ++++++++++++++++++ .../8176-tls-empty-context-ca.ts.json | 1 + .../8177-tls-empty-client-ca.ts.json | 1 + 15 files changed, 300 insertions(+), 40 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8176-tls-empty-context-ca.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8177-tls-empty-client-ca.ts.json create mode 100644 tests/corpus/8176-tls-empty-context-ca.ts create mode 100644 tests/corpus/8177-tls-empty-client-ca.ts create mode 100644 tests/harness/corpus-costs/8176-tls-empty-context-ca.ts.json create mode 100644 tests/harness/corpus-costs/8177-tls-empty-client-ca.ts.json diff --git a/docs/content/limitations/tls-certificates.mdx b/docs/content/limitations/tls-certificates.mdx index bbe4ceaf9..639dd3d83 100644 --- a/docs/content/limitations/tls-certificates.mdx +++ b/docs/content/limitations/tls-certificates.mdx @@ -2,6 +2,9 @@ On a TLS socket held in an `unknown` or `any` value, or in an untyped JavaScript parameter, they throw an error that names the call as not supported. Client certificates follow Node: `tls.createServer`, `https.createServer`, and `new tls.TLSSocket` take `requestCert`, `rejectUnauthorized`, and `ca`, and `tls.connect` and `https.request` take `cert` and `key`. A server's `authorized` and `authorizationError` report the client certificate's verification as Node does, and a server, client, or `tls.createSecureContext` given a `ca` also sends the chain it completes, as OpenSSL's automatic chain does. +A server verifies client certificates against the `ca` of the context it serves, including contexts from `addContext()` and `setSecureContext()`: an empty `ca` (an empty Buffer or array) trusts no certificate, and an omitted `ca` trusts the default CAs. +The same holds for a client's `ca` in `tls.connect`, `https.request`, and `http2.connect`, where an empty string is no `ca`, as in Node. +In `http2.connect`, a `ca` held in an `unknown` or `any` value that is an empty string is refused. `requestCert` beside a TLSSocket's `secureContext` or an `SNICallback`, client certificates on http2 secure servers, and a `cert` without a `key` (or the reverse) are refused. `server.addContext()` and `server.setSecureContext()` follow Node on tls and https servers, including wildcard names, which match one label without regard to case. A name with regular-expression groups or alternation, or one outside ASCII, is refused, as are both methods on a server that is not a tls or https server. diff --git a/packages/compiler/src/backend/llvm/lib-abi.ts b/packages/compiler/src/backend/llvm/lib-abi.ts index 6917d8256..ea5ab82a2 100644 --- a/packages/compiler/src/backend/llvm/lib-abi.ts +++ b/packages/compiler/src/backend/llvm/lib-abi.ts @@ -73,6 +73,26 @@ export function rawBytes(host: LlvmEmitterContext, value: LlValue): AbiArgument[ return [ptr(data), { type: host.sizeType, name: length }]; } +/** A TLS client's `ca` as rawBytes, with a null pointer for none: an empty + * string, which Node reads as no ca (and which an omitted ca lowers to). + * A Buffer's pointer is never null, so an empty one is an empty trust + * store. */ +export function caBytes(host: LlvmEmitterContext, value: LlValue): AbiArgument[] { + const B = host.B; + const [data, length] = rawBytes(host, value); + const test = B.tmp(), + pointer = B.tmp(); + if (value.type.kind === "string") { + B.line(`${test} = icmp eq ${host.sizeType} ${length!.name}, 0`); + B.line(`${pointer} = select i1 ${test}, ptr null, ptr ${data!.name}`); + } else { + // An empty Buffer's data pointer may be null; its object stands in. + B.line(`${test} = icmp eq ptr ${data!.name}, null`); + B.line(`${pointer} = select i1 ${test}, ptr ${value.name}, ptr ${data!.name}`); + } + return [ptr(pointer), length!]; +} + export function callbackType(value: LlValue): IrType & { kind: "func" } { if (value.type.kind !== "func") throw new InternalCompilerError("runtime callback must be a function"); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..e371b10a4 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -22,7 +22,7 @@ import { } from "./lib-shared.js"; import { isDynUndefined } from "../../ir/runtime-features.js"; import { DYN_KIND } from "./dyn.js"; -import { rawBytes } from "./lib-abi.js"; +import { caBytes } from "./lib-abi.js"; import { emitInlineAtomicsOp } from "./expr-bytes.js"; import { regexLiteralCaptures } from "./regex-matchers.js"; @@ -595,7 +595,8 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll // The https URL row is the http one with the TLS entry point — same // three arguments, same response-callback adapter. The https options // row is wider: rejectUnauthorized stays an i1, while its ScrStr or - // ScrBytes CA value expands to the runtime's raw pointer + length. + // ScrBytes CA value expands to the runtime's raw pointer + length + // (caBytes: a null pointer for none). const isTls = e.fn.startsWith("https."); const isUrl = e.fn.includes("requestUrl"); const isTlsOptions = isTls && !isUrl; @@ -629,7 +630,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll if (isTlsOptions) { callArgs = [ ...callArgs.slice(0, 8), - ...rawBytes(host, args[8]!).map((arg) => `${arg.type} ${arg.name}`), + ...caBytes(host, args[8]!).map((arg) => `${arg.type} ${arg.name}`), ]; host.declare( `declare ptr @scr_https_request(ptr, double, ptr, ptr, double, ptr, i1 zeroext, i1 zeroext, ptr, ${host.sizeType}, ptr, ptr)`, diff --git a/packages/compiler/src/backend/llvm/lib-http2.ts b/packages/compiler/src/backend/llvm/lib-http2.ts index 321f1d005..d750fb7c5 100644 --- a/packages/compiler/src/backend/llvm/lib-http2.ts +++ b/packages/compiler/src/backend/llvm/lib-http2.ts @@ -9,6 +9,7 @@ import { callbackType, finishRuntimeCall, ptr, + caBytes, rawBytes, requestHandler, } from "./lib-abi.js"; @@ -204,7 +205,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa ); return finishRuntimeCall(host, e, "scr_http2_connect", [ ...a.slice(0, 2), - ...rawBytes(host, args[2]!), + ...caBytes(host, args[2]!), ...handler, ]); } diff --git a/packages/compiler/src/backend/llvm/lib-tls.ts b/packages/compiler/src/backend/llvm/lib-tls.ts index 307aeb3dd..dbe492180 100644 --- a/packages/compiler/src/backend/llvm/lib-tls.ts +++ b/packages/compiler/src/backend/llvm/lib-tls.ts @@ -8,6 +8,7 @@ import { finishRuntimeCall, nullableReference, ptr, + caBytes, rawBytes, requestHandler, } from "./lib-abi.js"; @@ -184,7 +185,7 @@ export function emitTlsLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu ); return finishRuntimeCall(host, e, "scr_https_request_agent", [ ...a.slice(0, 8), - ...rawBytes(host, args[8]!), + ...caBytes(host, args[8]!), a[9]!, ...handler, ]); @@ -204,7 +205,7 @@ export function emitTlsLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu ["ptr", "ptr"], ); // Evaluate arguments once before selecting which transport consumes the callback. - const pem = rawBytes(host, args[9]!); + const pem = caBytes(host, args[9]!); const B = host.B, tls = B.newLabel("request.tls"), plain = B.newLabel("request.plain"), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8176-tls-empty-context-ca.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8176-tls-empty-context-ca.ts.json new file mode 100644 index 000000000..536027066 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8176-tls-empty-context-ca.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8176-tls-empty-context-ca.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8177-tls-empty-client-ca.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8177-tls-empty-client-ca.ts.json new file mode 100644 index 000000000..b28587594 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8177-tls-empty-client-ca.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8177-tls-empty-client-ca.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_http2.c b/packages/runtime/src/scr_http2.c index 55d880c01..489946a12 100644 --- a/packages/runtime/src/scr_http2.c +++ b/packages/runtime/src/scr_http2.c @@ -2617,11 +2617,11 @@ static void scr_h2_on_established(void *ctx) { * TLS client transport with ALPN ["h2"] (scr_tls.c — linked whenever * this unit is): the same preface/SETTINGS buffer through the handshake * and everything after establishment is protocol-identical. `ca` - * replaces the client trust anchors (len 0 = none) and + * replaces the client trust anchors (NULL = none) and * reject_unauthorized false disables the verify gate — the local-CA * self-connect shapes. Throws catchably on unparsable input. */ ScrH2Session *scr_http2_connect(ScrStr *url /*borrowed*/, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, ScrClosure *cb /*moves, nullable*/, ScrH2ConnectFn fn) { const char *p = url->data; const char *sep = strstr(p, "://"); diff --git a/packages/runtime/src/scr_http2.h b/packages/runtime/src/scr_http2.h index 25e236e6f..c4ce850d0 100644 --- a/packages/runtime/src/scr_http2.h +++ b/packages/runtime/src/scr_http2.h @@ -69,7 +69,7 @@ void scr_http2_server_on_session(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH * reject_unauthorized false disables the verify gate — the local-CA * self-connect shapes); throws catchably on bad input */ ScrH2Session *scr_http2_connect(ScrStr *url /*borrowed*/, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, ScrClosure *cb /*moves, nullable*/, ScrH2ConnectFn fn); /* +1 */ /* end_stream: -1 = the method's payload-meaningless default, 0/1 explicit */ @@ -173,7 +173,7 @@ ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port, ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */ /* The URL-string form (https.get('https://host/path')): scr_http.c's * shared parse, then the options form's dial with Node's no-options @@ -187,7 +187,7 @@ ScrHttpClientReq *scr_https_request_agent(ScrStr *host /*borrowed*/, double port ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, const struct ScrDyn *agent /*borrowed*/, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */ /* The fetch unit's https leg (defined in scr_tls.c): a client transport diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index 2f448a417..e764926b2 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -253,7 +253,10 @@ struct ScrSecureCtx { mbedtls_x509_crt cert; mbedtls_pk_context pk; /* the ca option: a socket created from this context (a TLSSocket's - * secureContext) sends the chain it completes */ + * secureContext) sends the chain it completes, and a server that serves + * the context verifies client certificates against it. has_ca tells an + * empty ca (an empty trust store) from none (the default store). */ + bool has_ca; mbedtls_x509_crt ca; }; @@ -573,6 +576,9 @@ static void scr_tls_srv_client_auth(ScrTlsSrv *s, const ScrTlsClientAuth *auth) typedef struct ScrTlsCli { mbedtls_ssl_config conf; + /* the ca option, which replaces the default store; has_ca tells an + * empty ca (an empty trust store) from none */ + bool has_ca; mbedtls_x509_crt ca; char *hostname; /* the SNI name; NULL sends none (an IP literal, servername: "") */ char *verify_name; /* the name the leaf is checked against; NULL fails the check */ @@ -696,6 +702,7 @@ static ScrTlsCli *scr_tls_cli_new2(ScrStr *host, bool reject_unauthorized, const mbedtls_ssl_conf_rng(&c->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg); c->verify_recorded = reject_unauthorized || verify_optional; c->reject = reject_unauthorized; + c->has_ca = ca != NULL; if (ca_len > 0) { int r = scr_tls_parse_crt(&c->ca, ca, ca_len); if (r != 0) scr_tls_bad_material("ca certificate", r); @@ -705,7 +712,7 @@ static ScrTlsCli *scr_tls_cli_new2(ScrStr *host, bool reject_unauthorized, const } else { mbedtls_ssl_conf_authmode(&c->conf, reject_unauthorized ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_OPTIONAL); - mbedtls_ssl_conf_ca_chain(&c->conf, ca_len > 0 ? &c->ca : scr_tls_system_ca(), NULL); + mbedtls_ssl_conf_ca_chain(&c->conf, c->has_ca ? &c->ca : scr_tls_system_ca(), NULL); } /* The host is the verified name; it is also the SNI name unless it is * an IP literal. */ @@ -799,6 +806,9 @@ typedef struct ScrTlsSock { unsigned char *hello; /* the raw ClientHello flight, replayed at DONE */ size_t hello_len, hello_cap; ScrSecureCtx *sni_ctx; /* owned; the answer (NULL = default cert) */ + /* scr_tls_f_cert verified client certificates against sni_ctx's ca + * (or the default store without one), not the server's */ + bool ctx_verifies; /* new tls.TLSSocket(socket, { isServer: true }): the program holds this * socket before the handshake, so a failed handshake is its 'error' * (tls.createServer's sockets fail without one) */ @@ -1889,8 +1899,8 @@ static ScrStr *scr_tls_pool_key(ScrStr *host, bool reject_unauthorized, const ch uint64_t h = 1469598103934665603ull; for (size_t i = 0; i < ca_len; i++) h = (h ^ (unsigned char)ca[i]) * 1099511628211ull; char buf[64]; - int n = snprintf(buf, sizeof buf, "%d:%zu:%016llx:", reject_unauthorized ? 1 : 0, ca_len, - (unsigned long long)h); + int n = snprintf(buf, sizeof buf, "%d:%s%zu:%016llx:", reject_unauthorized ? 1 : 0, + ca != NULL ? "" : "-", ca_len, (unsigned long long)h); ScrJsonBuf b; scr_jb_init(&b); scr_jb_write(&b, buf, (size_t)n); @@ -2148,7 +2158,7 @@ ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port, ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn) { ScrStr *servername = scr_https_take_servername(); ScrTlsCli *cli = scr_tls_cli_for(host, reject_unauthorized, ca, ca_len, servername); @@ -2189,7 +2199,7 @@ ScrHttpClientReq *scr_https_request_agent(ScrStr *host /*borrowed*/, double port ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, bool reject_unauthorized, - const char *ca /*borrowed, len 0 = none*/, size_t ca_len, + const char *ca /*borrowed, NULL = none*/, size_t ca_len, const ScrDyn *agent /*borrowed*/, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn) { ScrStr *servername = scr_https_take_servername(); @@ -2242,6 +2252,14 @@ static bool scr_tls_dyn_truthy(const ScrDyn *v) { * the exception pending. `what` names the fence ("a tls.createServer * 'cert' option"). */ static ScrBytes *scr_tls_pem_dyn(const ScrDyn *v, const char *what, bool concat) { + if (v->kind == SCR_DYN_TYPED_REF) { + /* a typed array (an array literal in a typed record): its entries */ + ScrDyn *plain = scr_dyn_typed_ref_materialize(v); + if (plain == NULL) return NULL; + ScrBytes *b = scr_tls_pem_dyn(plain, what, concat); + scr_dyn_release(plain); + return b; + } if (v->kind == SCR_DYN_STR) { ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)v->v.str->len); memcpy(b->data, v->v.str->data, v->v.str->len); @@ -2292,6 +2310,33 @@ static ScrBytes *scr_tls_pem_dyn(const ScrDyn *v, const char *what, bool concat) return NULL; } +/* A `ca` option from a runtime value: NULL for none (an empty string, + * which Node reads as no ca), empty bytes for an empty Buffer or array + * (an empty trust store), else the PEM bundle (+1). False with the + * exception pending. */ +static bool scr_tls_ca_dyn(const ScrDyn *v, const char *what, ScrBytes **out) { + *out = NULL; + ScrDyn *plain = v->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_materialize(v) : NULL; + if (v->kind == SCR_DYN_TYPED_REF && plain == NULL) return false; + const ScrDyn *ca = plain != NULL ? plain : v; + bool ok = true; + if (ca->kind == SCR_DYN_ARR && ca->v.arr.len == 0) { + *out = scr_bytes_new(SCR_BYTES_U8, 0); + } else if (ca->kind != SCR_DYN_STR || ca->v.str->len > 0) { + *out = scr_tls_pem_dyn(ca, what, true); + ok = *out != NULL; + } + scr_dyn_release(plain); + return ok; +} + +/* A client's ca for scr_tls_cli_new2: NULL for none, and never NULL for a + * given ca, even an empty one. */ +static const char *scr_tls_ca_data(const ScrBytes *ca) { + if (ca == NULL) return NULL; + return ca->data != NULL ? (const char *)ca->data : ""; +} + /* https.request/get with a runtime options record: the http walk, then * the TLS options the literal https path lowers (rejectUnauthorized, ca, * servername) and a client certificate (cert and key). Other tls.connect @@ -2330,8 +2375,7 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au if (r != NULL && r->kind == SCR_DYN_BOOL && !r->v.b) reject = false; const ScrDyn *c = ok ? scr_dyn_obj_get(rec, "ca", 2) : NULL; if (c != NULL && c->kind != SCR_DYN_UNDEF && c->kind != SCR_DYN_NULL) { - ca = scr_tls_pem_dyn(c, "an https.request 'ca' option", true); - if (ca == NULL) ok = false; + ok = scr_tls_ca_dyn(c, "an https.request 'ca' option", &ca); } const ScrDyn *oc = ok ? scr_dyn_obj_get(rec, "cert", 4) : NULL; if (oc != NULL && oc->kind != SCR_DYN_UNDEF && oc->kind != SCR_DYN_NULL) { @@ -2366,7 +2410,7 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au if (cb) scr_closure_release(cb); return NULL; } - const char *ca_data = ca ? (const char *)ca->data : NULL; + const char *ca_data = scr_tls_ca_data(ca); size_t ca_len = ca ? ca->len : 0; ScrTlsCli *cli = scr_tls_cli_for(o.host, reject, ca_data, ca_len, servername); ScrStr *key = scr_tls_pool_key(o.host, reject, ca_data, ca_len, servername); @@ -2397,13 +2441,17 @@ ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool au /* The literal path's runtime-valued cert/key extraction (the compiler's * tls.pemDyn libCall): `what` arrives precomposed. undefined throws — the - * literal walk required the member's presence. */ + * literal walk required the member's presence — and so does an empty + * string, which Node reads as no material while empty bytes here are an + * empty ca. */ ScrBytes *scr_tls_pem_from_dyn(const ScrDyn *v, const char *what) { - if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL) { + bool empty = v->kind == SCR_DYN_STR && v->v.str->len == 0; + if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL || empty) { ScrJsonBuf b; scr_jb_init(&b); scr_jb_puts(&b, what); - scr_jb_puts(&b, " holding undefined has no static lowering — pass PEM material (a string or a Buffer)"); + scr_jb_puts(&b, empty ? " holding an empty string" : " holding undefined"); + scr_jb_puts(&b, " has no static lowering — pass PEM material (a string or a Buffer)"); scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); return NULL; } @@ -2565,14 +2613,7 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt char what[96]; snprintf(what, sizeof what, "a %s 'ca' option", api); scr_bytes_release(auth->ca); - /* An empty string is no ca, as Node reads it; an empty Buffer is - * an empty trust store. */ - if (v->kind == SCR_DYN_STR && v->v.str->len == 0) { - auth->ca = NULL; - } else { - auth->ca = scr_tls_pem_dyn(v, what, true); - if (auth->ca == NULL) ok = false; - } + if (!scr_tls_ca_dyn(v, what, &auth->ca)) ok = false; } continue; } @@ -2658,6 +2699,7 @@ ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts /*borrowed*/) int r = scr_tls_parse_crt(&c->ca, (const char *)auth.ca->data, auth.ca->len); if (r != 0) scr_tls_bad_material("ca certificate", r); } + c->has_ca = true; scr_bytes_release(auth.ca); } scr_bytes_release(cert); @@ -2810,8 +2852,10 @@ static int scr_tls_f_cert(mbedtls_ssl_context *ssl) { scr_tls_chained_copy(&t->served_chain, &pick->cert, store); own = &t->served_chain; } - if (s->request_cert) - mbedtls_ssl_set_hs_ca_chain(ssl, pick->ca.raw.len > 0 ? &pick->ca : scr_tls_system_ca(), NULL); + if (s->request_cert) { + t->ctx_verifies = true; + mbedtls_ssl_set_hs_ca_chain(ssl, pick->has_ca ? &pick->ca : scr_tls_system_ca(), NULL); + } return mbedtls_ssl_set_hs_own_cert(ssl, own, &pick->pk); } @@ -3261,8 +3305,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed, } if (strcmp(e->key, "ca") == 0) { scr_bytes_release(ca); - ca = scr_tls_pem_dyn(v, "a tls.connect 'ca' option", true); - if (ca == NULL) ok = false; + if (!scr_tls_ca_dyn(v, "a tls.connect 'ca' option", &ca)) ok = false; continue; } if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) { @@ -3386,8 +3429,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed, if (dial_addr) scr_str_release(dial_addr); } ScrTlsCli *cli = scr_tls_cli_new2(verify_host, reject, - ca != NULL ? (const char *)ca->data : NULL, - ca != NULL ? ca->len : 0, !reject); + scr_tls_ca_data(ca), ca != NULL ? ca->len : 0, !reject); if (servername != NULL) scr_tls_cli_set_servername(cli, servername); if (own_cert != NULL) scr_tls_cli_set_own(cli, own_cert, own_key); scr_bytes_release(own_cert); @@ -3441,7 +3483,7 @@ void scr_tls_server_wrap_h2(ScrNetServer *s, const char *cert, size_t cert_len, /* The h2-over-TLS CLIENT wrap (http2.connect with an https authority): * a client transport offering ALPN ["h2"] — SNI/verification against - * the authority host; `ca` replaces the trust anchors ("" = none) and + * the authority host; `ca` replaces the trust anchors (NULL = none) and * reject_unauthorized false records like tls.connect's. The session's * preface/SETTINGS ride the socket's write buffer until establishment. */ void scr_tls_h2_client_wrap(ScrNetSocket *sock, ScrStr *host /*borrowed*/, @@ -3595,7 +3637,8 @@ void scr_tls_sock_each_trusted(ScrNetSocket *s, bool (*visit)(void *ctx, const u void *ctx) { ScrTlsSock *t = scr_tls_sock_of(s); if (t == NULL || (t->cli == NULL && t->srv == NULL)) return; - const mbedtls_x509_crt *c = t->cli != NULL ? (t->cli->ca.raw.len > 0 ? &t->cli->ca : scr_tls_system_ca()) + const mbedtls_x509_crt *c = t->cli != NULL ? (t->cli->has_ca ? &t->cli->ca : scr_tls_system_ca()) + : t->ctx_verifies ? (t->sni_ctx->has_ca ? &t->sni_ctx->ca : scr_tls_system_ca()) : t->srv->has_ca ? &t->srv->ca : scr_tls_system_ca(); for (; c != NULL && c->raw.len > 0; c = c->next) diff --git a/packages/runtime/src/scr_tls.h b/packages/runtime/src/scr_tls.h index 94bf41d33..8e044f12d 100644 --- a/packages/runtime/src/scr_tls.h +++ b/packages/runtime/src/scr_tls.h @@ -85,7 +85,7 @@ void scr_tls_sock_on_secure(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once * every http2-using binary links the TLS unit (the moduleUsesTls * switch), so no registration indirection is needed. */ void scr_tls_server_wrap_h2(ScrNetServer *s, const char *cert, size_t cert_len, const char *key, size_t key_len, ScrNetNativeConnFn h2_conn, void *h2_ctx /*moves*/, void (*h2_ctx_free)(void *), ScrClosure *sni_cb /*moves, nullable*/, void *sni_answer_fn); -void scr_tls_h2_client_wrap(ScrNetSocket *sock, ScrStr *host /*borrowed*/, bool reject_unauthorized, const char *ca /*borrowed, len 0 = none*/, size_t ca_len); +void scr_tls_h2_client_wrap(ScrNetSocket *sock, ScrStr *host /*borrowed*/, bool reject_unauthorized, const char *ca /*borrowed, NULL = none*/, size_t ca_len); /* ── tls.SecureContext + the SNI callback (scr_tls.c) ───────────────── * A SecureContext is an opaque refcounted parsed cert/key pair diff --git a/tests/corpus/8176-tls-empty-context-ca.ts b/tests/corpus/8176-tls-empty-context-ca.ts new file mode 100644 index 000000000..510c9881a --- /dev/null +++ b/tests/corpus/8176-tls-empty-context-ca.ts @@ -0,0 +1,73 @@ +// An explicitly empty ca is an empty trust store for client certificates, +// in the server's options, in setSecureContext and in addContext, even when +// the client certificate's issuer is a default CA +// (tls.setDefaultCACertificates); an omitted ca trusts the default CAs. +// Each server has requestCert, with rejectUnauthorized false (the server's +// verdict) and true (the client is dropped). +import { readFileSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const alt = r("alt.pem"), altKey = r("alt-key.pem"); +const client = r("client.pem"), clientKey = r("client-key.pem"); + +tls.setDefaultCACertificates([ca.toString()]); + +function listen(server: tls.Server): Promise { + return new Promise((res) => server.listen(0, () => res((server.address() as AddressInfo).port))); +} + +type Handler = (s: tls.TLSSocket) => void; + +async function run(label: string, make: (h: Handler) => tls.Server, servername: string): Promise { + const events: string[] = []; + const server = make((s) => { + events.push(`server authorized=${s.authorized} error=${s.authorizationError}`); + s.end("ok"); + }); + const port = await listen(server); + await new Promise((done) => { + const c = tls.connect({ port, servername, ca, cert: client, key: clientKey }); + c.on("secureConnect", () => events.push("client secureConnect")); + c.on("data", (d: Buffer) => events.push(`client data ${d}`)); + c.on("error", (e: NodeJS.ErrnoException) => events.push(`client error ${e.code}`)); + c.on("close", () => setTimeout(done, 50)); + }); + console.log(label, "|", events.join(" ; ")); + await new Promise((res) => server.close(() => res())); +} + +for (const reject of [false, true]) { + const opts = { cert, key, requestCert: true, rejectUnauthorized: reject }; + const tag = reject ? "reject" : "request"; + await run(`${tag} omitted`, (h) => tls.createServer(opts, h), "localhost"); + await run(`${tag} empty array`, (h) => tls.createServer({ ...opts, ca: [] }, h), "localhost"); + await run(`${tag} setSecureContext empty`, (h) => { + const server = tls.createServer(opts, h); + server.setSecureContext({ cert, key, ca: [] }); + return server; + }, "localhost"); + await run(`${tag} setSecureContext omitted`, (h) => { + const server = tls.createServer({ ...opts, ca: [] }, h); + server.setSecureContext({ cert, key }); + return server; + }, "localhost"); + await run(`${tag} addContext empty array`, (h) => { + const server = tls.createServer(opts, h); + server.addContext("alt.localhost", { cert: alt, key: altKey, ca: [] }); + return server; + }, "alt.localhost"); + await run(`${tag} addContext empty Buffer`, (h) => { + const server = tls.createServer(opts, h); + server.addContext("alt.localhost", tls.createSecureContext({ cert: alt, key: altKey, ca: Buffer.alloc(0) })); + return server; + }, "alt.localhost"); + await run(`${tag} addContext omitted`, (h) => { + const server = tls.createServer({ ...opts, ca: [] }, h); + server.addContext("alt.localhost", { cert: alt, key: altKey }); + return server; + }, "alt.localhost"); +} diff --git a/tests/corpus/8177-tls-empty-client-ca.ts b/tests/corpus/8177-tls-empty-client-ca.ts new file mode 100644 index 000000000..0fb596ddb --- /dev/null +++ b/tests/corpus/8177-tls-empty-client-ca.ts @@ -0,0 +1,104 @@ +// An explicitly empty ca is an empty trust store for a client's view of +// the server certificate, in tls.connect, https.request and http2.connect, +// even when the server certificate's issuer is a default CA +// (tls.setDefaultCACertificates); an omitted ca, or an empty string, trusts +// the default CAs. +import { readFileSync } from "node:fs"; +import * as http2 from "node:http2"; +import * as https from "node:https"; +import type { AddressInfo } from "node:net"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f: string): Buffer => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), key = r("localhost-key.pem"); +// The servers send their issuer too, so each client sees the same chain. +const cert = Buffer.concat([r("localhost.pem"), ca]); + +tls.setDefaultCACertificates([ca.toString()]); + +function listen(server: tls.Server | https.Server | http2.Http2SecureServer): Promise { + return new Promise((res) => server.listen(0, () => res((server.address() as AddressInfo).port))); +} + +async function watch(label: string, c: tls.TLSSocket): Promise { + const events: string[] = []; + await new Promise((done) => { + c.on("secureConnect", () => { + events.push(`secureConnect authorized=${c.authorized} error=${c.authorizationError}`); + c.end(); + }); + c.on("error", (e: NodeJS.ErrnoException) => events.push(`error ${e.code}`)); + c.on("close", () => done()); + }); + console.log("tls.connect", label, "|", events.join(" ; ")); +} + +{ + const server = tls.createServer({ cert, key }, (s) => s.end()); + const port = await listen(server); + const host = "localhost"; + await watch("omitted", tls.connect({ port, host })); + await watch("empty string", tls.connect({ port, host, ca: "" })); + await watch("empty Buffer", tls.connect({ port, host, ca: Buffer.alloc(0) })); + await watch("empty array", tls.connect({ port, host, ca: [] })); + await watch( + "empty Buffer, rejectUnauthorized false", + tls.connect({ port, host, ca: Buffer.alloc(0), rejectUnauthorized: false }), + ); + await watch("empty array, rejectUnauthorized false", tls.connect({ port, host, ca: [], rejectUnauthorized: false })); + server.close(); +} + +function report(label: string, req: ReturnType, done: () => void): void { + req.on("response", (res) => { + console.log("https.request", label, "| status", res.statusCode); + res.resume(); + res.on("end", done); + }); + req.on("error", (e: NodeJS.ErrnoException) => { + console.log("https.request", label, "| error", e.code); + done(); + }); + req.end(); +} + +{ + const server = https.createServer({ cert, key }, (_req, res) => res.end("ok")); + const port = await listen(server); + await new Promise((done) => + report("omitted", https.request({ host: "localhost", port, path: "/", agent: false }), done), + ); + await new Promise((done) => + report("empty string", https.request({ host: "localhost", port, path: "/", agent: false, ca: "" }), done), + ); + await new Promise((done) => + report( + "empty Buffer", + https.request({ host: "localhost", port, path: "/", agent: false, ca: Buffer.alloc(0) }), + done, + ), + ); + const options: unknown = { host: "localhost", port, path: "/", agent: false, ca: [] }; + await new Promise((done) => + report("empty array in a runtime record", https.request(options as https.RequestOptions), done), + ); + server.close(); +} + +{ + const server = http2.createSecureServer({ cert, key }, (_req, res) => res.end("ok")); + const port = await listen(server); + for (const [label, ca] of [["omitted", undefined], ["empty Buffer", Buffer.alloc(0)]] as const) { + await new Promise((done) => { + const session = ca === undefined ? http2.connect(`https://localhost:${port}`) : http2.connect(`https://localhost:${port}`, { ca }); + session.on("connect", () => { + console.log("http2.connect", label, "| connect"); + session.close(); + }); + session.on("error", (e: NodeJS.ErrnoException) => console.log("http2.connect", label, "| error", e.code)); + session.on("close", () => done()); + }); + } + server.close(); +} diff --git a/tests/harness/corpus-costs/8176-tls-empty-context-ca.ts.json b/tests/harness/corpus-costs/8176-tls-empty-context-ca.ts.json new file mode 100644 index 000000000..2f8507d0f --- /dev/null +++ b/tests/harness/corpus-costs/8176-tls-empty-context-ca.ts.json @@ -0,0 +1 @@ +{ "plain": 7322, "san": 94064 } diff --git a/tests/harness/corpus-costs/8177-tls-empty-client-ca.ts.json b/tests/harness/corpus-costs/8177-tls-empty-client-ca.ts.json new file mode 100644 index 000000000..d7df308c8 --- /dev/null +++ b/tests/harness/corpus-costs/8177-tls-empty-client-ca.ts.json @@ -0,0 +1 @@ +{ "plain": 4335, "san": 61880 } From 9e4f1253c620270543a190b4f2f24e29c9744c28 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 10:11:29 -0500 Subject: [PATCH 068/395] Keep unchanged partitions cached across edits --- native/llvm-codegen/README.md | 2 +- native/llvm-codegen/src/emit.cpp | 39 ++++++++++++++++++- packages/compiler/src/backend/llvm/emitter.ts | 28 ++++++++----- .../src/backend/llvm/expr-callbacks.ts | 30 +++++++------- .../src/backend/llvm/expr-containers.ts | 3 +- .../src/backend/llvm/expr-primitives.ts | 2 +- .../src/backend/llvm/expr-stream-callbacks.ts | 12 +++--- .../compiler/src/backend/llvm/lib-connect.ts | 2 +- .../compiler/src/backend/llvm/lib-datagram.ts | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 2 +- .../compiler/src/backend/llvm/lib-http2.ts | 2 +- .../compiler/src/backend/llvm/lib-lookup.ts | 2 +- .../compiler/src/backend/llvm/lib-process.ts | 2 +- packages/compiler/src/backend/mangle.ts | 9 ----- .../lowering/class-dynamic-dispatch.ts | 3 +- 15 files changed, 89 insertions(+), 53 deletions(-) diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index 0e8abbc47..d72aebdfd 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -37,7 +37,7 @@ Repeating `--output` divides the program into that many partitions, which are co WebAssembly code generation differs from LLVM's default pipeline in two ways that do not change its output. The pass that gives multiple-entry loops a single entry runs only on functions whose control flow is irreducible; its reachability analysis grows with the square of a function's block count even when it has nothing to change. Object emission also leaves out the module pass that collects external symbols for assembly declarations, so each function's machine code is freed once it is printed instead of when the whole module is compiled. -`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. +`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. The locals ThinLTO promotes for importing are named after their partition rather than a hash of its bitcode, so an edit to one partition leaves the partitions that import from it unchanged. `--import-bitcode `, given once per runtime-pack unit that ships bitcode, imports runtime functions for inlining; scriptc's program builds do not pass it. Small runtime functions reachable from the program's runtime calls are imported as `available_externally` so LLVM can inline them; the runtime objects remain the only definitions. The helper never runs a sanitizer pass, so with import it also drops the emitter's inert `sanitize_address` attributes, which would otherwise block that inlining. Without `--import-bitcode` the module is optimized exactly as emitted. diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 9cae164d3..4e8c7a30c 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -170,6 +170,38 @@ void limitProfileImports() { (void)Limited; } +// ThinLTO renames the locals it promotes so that the code other partitions +// import can refer to them: switch tables, constants and the other private +// symbols optimization creates. By default the new name carries a hash of +// the defining partition's whole bitcode, so an edit anywhere in a partition +// renames them in every partition that imports from it, and those partitions +// miss the object cache although their own code is unchanged. Named after +// the partition instead (PartitionSourceName), they keep their names while +// the partition changes. The option is process-wide; the helper sets it once. +void stablePromotedNames() { + static const bool Set = [] { + StringRef Name = "use-source-filename-for-promoted-locals"; + if (cl::Option *Option = cl::getRegisteredOptions().lookup(Name)) + Option->addOccurrence(0, Name, "true"); + return true; + }(); + (void)Set; +} + +// Gives a partition's module its own source file name, which the names of +// the locals ThinLTO promotes derive from (stablePromotedNames). It runs +// after the pre-link optimization, so profile annotation still matches the +// program's local functions under their source path. +struct PartitionSourceName : PassInfoMixin { + explicit PartitionSourceName(std::string Name) : Name(std::move(Name)) {} + std::string Name; + PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { + M.setSourceFileName(Name); + return PreservedAnalyses::all(); + } + static bool isRequired() { return true; } +}; + // Reads the profile's header once, before any partition needs it: it must // hold IR-level counts. std::optional inspectProfile(const EmitOptions &Options) { @@ -559,8 +591,12 @@ compileOptimizedPartitions(const EmitOptions &Options, size_t Count = Plan.Modules.size(); std::vector> Summaries(Count); SharedFailure Failures; + stablePromotedNames(); forEachPartition(Count, [&](size_t I) { - std::string Key = hashParts({Salt, "summary", Plan.Modules[I]}); + // The partition's index names its promoted locals, so it keys the + // summary with the partition's source. + std::string Key = + hashParts({Salt, "summary", std::to_string(I), Plan.Modules[I]}); if ((Summaries[I] = Cache.lookup(Key, "bc"))) return; std::string LookupError; @@ -593,6 +629,7 @@ compileOptimizedPartitions(const EmitOptions &Options, // link; the post-link optimization reads the annotated weights. Pipeline P(*Machine, Level, profileOptions(Options)); ModulePassManager MPM = P.PB.buildThinLTOPreLinkDefaultPipeline(Level); + MPM.addPass(PartitionSourceName("scriptc-partition-" + std::to_string(I))); MPM.addPass(BitcodeWriterPass(Stream, /*ShouldPreserveUseListOrder=*/false, /*EmitSummaryIndex=*/true, /*EmitModuleHash=*/true)); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 59b4b64a4..69ca09e71 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -2746,12 +2746,13 @@ export class LlEmitter { // ── plumbing (frame and scope ownership) ────── - /** A symbol derived from the constant's text rather than its first-use - * index, so an edit that interns one more string leaves every other - * constant's symbol, and the code referencing it, unchanged. This lets - * native builds reuse the compiled partitions an edit does not touch. A - * collision retries with a counter, still in first-use order. */ - private contentSymbol(prefix: string, text: string): string { + /** A symbol derived from a constant's text, or a helper's key, rather + * than its first-use index, so an edit that interns one more string or + * helper leaves every other symbol, and the code referencing it, + * unchanged. This lets native builds reuse the compiled partitions an + * edit does not touch. A collision retries with a counter, still in + * first-use order. */ + contentSymbol(prefix: string, text: string): string { for (let attempt = 0; ; attempt++) { const digest = createHash("sha256") .update(attempt === 0 ? text : `${attempt}\0${text}`) @@ -2877,7 +2878,7 @@ export class LlEmitter { const key = `${unionId}:${tag}`; let sym = this.unitInstances.get(key); if (!sym) { - sym = `sc_unit_${this.unitInstances.size}`; + sym = this.contentSymbol("sc_unit_", key); this.unitInstances.set(key, sym); } this.journal?.entry("unit", key, sym); @@ -4997,8 +4998,15 @@ export class LlEmitter { case "retainWrapper": addEntry(this.nullableUnions.retainWrappers, key, value as string); return; - case "unit": - return addNumbered(this.unitInstances, key, value as string, "sc_unit_", names); + case "unit": { + let own = this.unitInstances.get(key); + if (own === undefined) { + own = this.contentSymbol("sc_unit_", key); + this.unitInstances.set(key, own); + } + if (own !== value) names.set(value as string, own); + return; + } case "absent": return addNumbered(this.absentInstances, key, value as string, "sc_absent_", names); case "retype": @@ -5006,7 +5014,7 @@ export class LlEmitter { case "slots": return addNumbered(this.threadSlotsBlocks, key, value as string, "@sc_slots_", names); case "regex": - return addNumberedSymbol(this.regexInstances, key, value as never, "sc_re_", names); + return this.addContentEntry(this.regexInstances, key, value as never, "sc_re_", names); case "templateStrings": return addNumberedSymbol( this.templateStringsInstances, diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index 398aebb21..3d5a53402 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -8,7 +8,7 @@ import { typeEquals, typeKey, } from "../../ir/ir.js"; -import { mangleGenResThunk, mangleRecordNew, mangleRecordStruct } from "../mangle.js"; +import { mangleRecordNew, mangleRecordStruct } from "../mangle.js"; import { FN_ATTRS, llFieldType, releaseSym, retainSym, traceArg, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext } from "./expr-context.js"; @@ -54,7 +54,7 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp const key = `race:${typeKey(from)}=>${typeKey(to)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_race_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_race_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "raceAdapterFor", [from, to]); const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : []; @@ -217,7 +217,7 @@ export function genResultThunkFor( const key = `gr:${typeKey(genT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = mangleGenResThunk(host.resolveThunks.size); + sym = host.contentSymbol("sc_gres_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "genResultThunkFor", [genT, recT]); const shape = host.recordsById.get(recT.shapeId); @@ -474,7 +474,7 @@ export function childExitThunkFor(host: LlvmEmitterContext, param: IrType): stri const key = `cx:${param.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cx_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cx_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childExitThunkFor", [param]); const def = host.unionsById.get(param.unionId); @@ -519,7 +519,7 @@ export function childExitSignalThunkFor( const key = `cx2:${codeParam.unionId}+${sigParam.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cx_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cx_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childExitSignalThunkFor", [codeParam, sigParam]); const codeDef = host.unionsById.get(codeParam.unionId); @@ -580,7 +580,7 @@ export function execFileThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `exec:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_exec_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_exec_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "execFileThunkFor", [cbT]); const errorParam = cbT.params[0]; @@ -652,7 +652,7 @@ export function ipcMessageThunkFor( const key = `ipcmsg:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ipc_msg_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ipc_msg_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "ipcMessageThunkFor", [cbT]); const param = cbT.params[0]; @@ -703,7 +703,7 @@ export function ipcSendThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `ipcsend:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ipc_send_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ipc_send_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "ipcSendThunkFor", [cbT]); const param = cbT.params[0]; @@ -764,7 +764,7 @@ export function childDataThunkFor(host: LlvmEmitterContext, param: IrType): stri const key = `cd:${param.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childDataThunkFor", [param]); const def = host.unionsById.get(param.unionId); @@ -799,7 +799,7 @@ export function childDataDynThunkFor(host: LlvmEmitterContext, chunk: "bytes" | const key = `cdd:${chunk}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cdd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cdd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childDataDynThunkFor", [chunk]); const box = chunk === "bytes" ? "scr_dyn_new_buffer" : "scr_dyn_new_str"; @@ -834,7 +834,7 @@ export function emitterFixedAdapter( const key = `ee:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; - sym = `sc_ee_ad_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_ad_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "emitterFixedAdapter", [cbT]); host.declare(`declare ptr @scr_box_get_ref(ptr)`); @@ -905,7 +905,7 @@ export function emitterOnceAdapter( : `eeOnce:${fire}:${typeKey(boxerT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; - sym = `sc_ee_once_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_once_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "emitterOnceAdapter", [boxerT, fire]); host.declare(`declare ptr @scr_box_get_ref(ptr)`); @@ -1015,7 +1015,7 @@ export function closeBindThunkFor( const key = `ncb:${cbUnion.unionId}:${retServer ? "srv" : "void"}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ncb_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ncb_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "closeBindThunkFor", [cbUnion, retServer]); const def = host.unionsById.get(cbUnion.unionId); @@ -1121,7 +1121,7 @@ export function closeOverrideWrapFor( const key = `ncw:${cbUnion.unionId}:${retServer ? "srv" : "void"}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ncw_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ncw_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "closeOverrideWrapFor", [cbUnion, retServer]); const def = host.unionsById.get(cbUnion.unionId); @@ -1169,7 +1169,7 @@ export function settledStoreFor( const key = `settled:${typeKey(from)}=>${typeKey(to)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_settled_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_settled_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "settledStoreFor", [from, to]); const def = host.unionsById.get(to.unionId); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 279b8c044..8ffc8727e 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -3,7 +3,6 @@ import { arraySearchHelperName, arraySearchKind } from "./array-search.js"; import { InternalCompilerError } from "../../errors.js"; import { isStableReceiverOperand, undefinedArmTag } from "../../ir/analysis.js"; import { F64, type IrExpr, type IrType, isRefCounted, typeEquals, typeKey } from "../../ir/ir.js"; -import { mangleResolveThunk } from "../mangle.js"; import { arrOps, elemAccess, @@ -71,7 +70,7 @@ export function resolveThunkFor(host: LlvmEmitterContext, inner: IrType): string const key = typeKey(inner); let sym = host.resolveThunks.get(key); if (!sym) { - sym = mangleResolveThunk(host.resolveThunks.size); + sym = host.contentSymbol("sc_rslv_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "resolveThunkFor", [inner]); const v = vAdapters(host.shapeHost, inner); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index ccbe3edcc..8ecf147f6 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -589,7 +589,7 @@ export function regexLiteralTemplate( let re = host.regexInstances.get(key); if (!re) { re = { - sym: `sc_re_${host.regexInstances.size}`, + sym: host.contentSymbol("sc_re_", key), src: host.internLiteral(e.pattern), fl: host.internLiteral(e.flags), pattern: e.pattern, diff --git a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts index b368c91f4..285f9ca9a 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts @@ -13,7 +13,7 @@ export function streamDataAdapter( const key = `eed:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ee_dad_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_dad_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamDataAdapter", [cbT]); const p = cbT.params[0]; @@ -221,7 +221,7 @@ export function streamDoneFnFor( const key = `sd:${kind}:${typeKey(doneT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_sd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_sd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamDoneFnFor", [kind, doneT]); const errT = doneT.params[0]; @@ -365,7 +365,7 @@ export function fsRenameThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `fsren:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_fsren_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_fsren_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "fsRenameThunkFor", [cbT]); const param = cbT.params[0]!; @@ -454,7 +454,7 @@ export function cryptoIntThunkFor( const key = `cryptoint:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cryptoint_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cryptoint_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "cryptoIntThunkFor", [cbT]); const d: string[] = [ @@ -516,7 +516,7 @@ export function zlibBytesThunkFor( const key = `${family}bytes:${arrayBuffer ? "arraybuffer:" : ""}${mode}${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_${family}bytes_${host.resolveThunks.size}`; + sym = host.contentSymbol(`sc_${family}bytes_`, key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "zlibBytesThunkFor", [cbT, arrayBuffer, family, valueMode]); const d: string[] = [ @@ -644,7 +644,7 @@ export function streamCbThunkFor( const key = `scb:${kind}:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_scb_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_scb_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamCbThunkFor", [kind, cbT]); const runtimeParams = diff --git a/packages/compiler/src/backend/llvm/lib-connect.ts b/packages/compiler/src/backend/llvm/lib-connect.ts index 4d151fc7e..2ab72c188 100644 --- a/packages/compiler/src/backend/llvm/lib-connect.ts +++ b/packages/compiler/src/backend/llvm/lib-connect.ts @@ -17,7 +17,7 @@ function connectAdapter(host: LlvmEmitterContext, cb: LlValue, h2: boolean): str const key = `connect:${h2}:${typeKey(type)}`; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_connect_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_connect_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "connectAdapter", [cb, h2]); const retain = h2 ? "scr_http_res_retain_v" : "scr_net_sock_retain_v"; diff --git a/packages/compiler/src/backend/llvm/lib-datagram.ts b/packages/compiler/src/backend/llvm/lib-datagram.ts index 07de4e54e..7a83f1d30 100644 --- a/packages/compiler/src/backend/llvm/lib-datagram.ts +++ b/packages/compiler/src/backend/llvm/lib-datagram.ts @@ -19,7 +19,7 @@ function messageAdapter(host: LlvmEmitterContext, type: IrType): string { const key = "dgram:" + type.shapeId; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_dgram_message_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_dgram_message_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "messageAdapter", [type]); const shape = host.recordsById.get(type.shapeId); @@ -68,7 +68,7 @@ function lookupAdapter(host: LlvmEmitterContext, cb: LlValue): string { const key = `dns:${error.unionId}:${arity}`; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_dns_lookup_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_dns_lookup_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "lookupAdapter", [cb]); host.declare("declare ptr @scr_error_new(i32, ptr)"); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..6ab83016d 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1085,7 +1085,7 @@ function emitPromiseAllTuple(host: LlvmEmitterContext, e: LibCallExpr): LlValue const key = `allTuple:${build.fnName}`; let adapter = host.resolveThunks.get(key); if (adapter === undefined) { - adapter = `sc_all_tuple_${host.resolveThunks.size}`; + adapter = host.contentSymbol("sc_all_tuple_", key); host.resolveThunks.set(key, adapter); const rc = vAdapters(host.shapeHost, tuple); host.declare(`declare ptr @scr_promise_payload_ref(ptr)`); diff --git a/packages/compiler/src/backend/llvm/lib-http2.ts b/packages/compiler/src/backend/llvm/lib-http2.ts index 321f1d005..1808200a0 100644 --- a/packages/compiler/src/backend/llvm/lib-http2.ts +++ b/packages/compiler/src/backend/llvm/lib-http2.ts @@ -21,7 +21,7 @@ function sniAnswer(host: LlvmEmitterContext, type: IrType): string { const key = "sni:" + typeKey(type); const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_sni_answer_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_sni_answer_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "sniAnswer", [type]); const params = ["ptr %self"], diff --git a/packages/compiler/src/backend/llvm/lib-lookup.ts b/packages/compiler/src/backend/llvm/lib-lookup.ts index 56e94461f..620833819 100644 --- a/packages/compiler/src/backend/llvm/lib-lookup.ts +++ b/packages/compiler/src/backend/llvm/lib-lookup.ts @@ -37,7 +37,7 @@ function lookupAnswer(host: LlvmEmitterContext, answer: IrType & { kind: "func" const key = "lookup:" + typeKey(answer); let symbol = host.resolveThunks.get(key); if (symbol === undefined) { - symbol = `sc_lookup_answer_${host.resolveThunks.size}`; + symbol = host.contentSymbol("sc_lookup_answer_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "lookupAnswer", [answer]); for (const declaration of [ diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 1a3973b07..21205c9f6 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -489,7 +489,7 @@ function boundCallbackThunk(host: LlvmEmitterContext, cbT: IrType & { kind: "fun const key = `alsbound:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_alsbound_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_alsbound_", key); host.resolveThunks.set(key, sym); host.declare("declare ptr @scr_als_bound_target(ptr)"); host.declare("declare ptr @scr_als_bound_enter(ptr)"); diff --git a/packages/compiler/src/backend/mangle.ts b/packages/compiler/src/backend/mangle.ts index 14736ead7..8542a1c64 100644 --- a/packages/compiler/src/backend/mangle.ts +++ b/packages/compiler/src/backend/mangle.ts @@ -221,15 +221,6 @@ export function mangleGenDrop(fnName: string): string { export function mangleGenSourceClose(fnName: string): string { return `sc_gc_${sanitize(fnName)}`; } -/** Emitted ref-kind resolve thunk, interned per inner-type key. */ -export function mangleResolveThunk(n: number): string { - return `sc_rslv_${n}`; -} -/** Emitted generator-resume result builder (the IteratorResult record), - * interned per generator-type key. */ -export function mangleGenResThunk(n: number): string { - return `sc_gres_${n}`; -} /** Emitted Promise.race fulfillment adapter (entry inner type → result * inner type), interned per type pair. */ export function mangleRaceThunk(n: number): string { diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index fc6d0a922..0789b6d4f 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -80,6 +80,7 @@ import { isClassCallback } from "./class-callbacks.js"; import { CaughtValueDispatch } from "./caught-value-dispatch.js"; import { refreshDescriptorGuards } from "./class-descriptors.js"; import { ClassConstructionDispatch } from "./class-construction.js"; +import { keyedHelperName } from "./stable-names.js"; import { DispatchSites } from "./dispatch-sites.js"; import { emitterRooted, @@ -3909,7 +3910,7 @@ export class ClassDynamicDispatch { if (!dispatch.fn.params.some((param) => param.localId === dispatch.keyLocal)) params.push({ localId: dispatch.keyLocal, name: "key", type: STRING }); const helper: IrFunction = { - name: `%dyn.class.keys.${lowerer.liftedFns.length}`, + name: keyedHelperName(lowerer, "%dyn.class.keys", helperKey), params, returnType: dispatch.fn.returnType, locals: params.map((p) => ({ From 53baad4ebee91f746f0e4c310c889cfa1cff4dde Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 10:12:06 -0500 Subject: [PATCH 069/395] Pump untyped iterable pipeline sources A pipeline whose first stage is a checked-dynamic value now checks it at run time, as Node's stage ladder does: a stream is piped as before, and a string or a value with a Symbol.asyncIterator or Symbol.iterator method is pumped into the next stage through an async generator over its iterator. A typed Set, Map, generator or async generator object as the first stage takes the same path. This covers the callback and stream/promises forms, with or without function stages and pipeline options. A write whose callback fails synchronously no longer sets needDrain, so no 'drain' follows the error and a pumped source stops at the failed write, as in Node. RUNTIME_ABI_VERSION goes from 37 to 42 for scr_stream_dyn_iterable_source. --- .../builtin-apis/streams-and-readline.mdx | 5 +- packages/cli/test/native-link-info.test.ts | 4 +- .../compiler/src/backend/llvm/lib-shared.ts | 1 + packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-stream.ts | 626 ++++++++++++++---- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 4 + packages/compiler/src/ir/runtime-features.ts | 1 + .../test/native-codegen-integration.test.ts | 4 +- .../7503-pipeline-untyped-sources.mjs.json | 6 + ...04-pipeline-untyped-source-errors.mjs.json | 6 + .../7505-pipeline-untyped-sources.cjs.json | 6 + .../corpus/7506-pipeline-any-source.ts.json | 6 + .../7507-stream-write-error-drain.mjs.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_stream.c | 38 +- packages/runtime/src/scr_stream.h | 4 + .../corpus/7503-pipeline-untyped-sources.mjs | 102 +++ .../7504-pipeline-untyped-source-errors.mjs | 90 +++ .../corpus/7505-pipeline-untyped-sources.cjs | 40 ++ tests/corpus/7506-pipeline-any-source.ts | 48 ++ .../corpus/7507-stream-write-error-drain.mjs | 37 ++ .../7503-pipeline-untyped-sources.mjs.json | 1 + ...04-pipeline-untyped-source-errors.mjs.json | 1 + .../7505-pipeline-untyped-sources.cjs.json | 1 + .../7506-pipeline-any-source.ts.json | 1 + .../7507-stream-write-error-drain.mjs.json | 1 + .../self-hosting-native-toolchain.test.ts | 2 +- 33 files changed, 911 insertions(+), 147 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7503-pipeline-untyped-sources.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7504-pipeline-untyped-source-errors.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7505-pipeline-untyped-sources.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7506-pipeline-any-source.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7507-stream-write-error-drain.mjs.json create mode 100644 tests/corpus/7503-pipeline-untyped-sources.mjs create mode 100644 tests/corpus/7504-pipeline-untyped-source-errors.mjs create mode 100644 tests/corpus/7505-pipeline-untyped-sources.cjs create mode 100644 tests/corpus/7506-pipeline-any-source.ts create mode 100644 tests/corpus/7507-stream-write-error-drain.mjs create mode 100644 tests/harness/corpus-costs/7503-pipeline-untyped-sources.mjs.json create mode 100644 tests/harness/corpus-costs/7504-pipeline-untyped-source-errors.mjs.json create mode 100644 tests/harness/corpus-costs/7505-pipeline-untyped-sources.cjs.json create mode 100644 tests/harness/corpus-costs/7506-pipeline-any-source.ts.json create mode 100644 tests/harness/corpus-costs/7507-stream-write-error-drain.mjs.json diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index f9d69e3db..561840268 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -3,7 +3,8 @@ `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream. When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects. Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol. - A `pipeline` stage and a `for await` source that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. + The first `pipeline` stage may arrive as an untyped value that is a native stream, a `Cipheriv` or `Decipheriv` value, or an iterable: a string, an array, a generator object, or any value with a `Symbol.asyncIterator` or `Symbol.iterator` method, which the pipeline pumps into the next stage as Node does. + Later `pipeline` stages and `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one. `Readable.from` accepts strings, Buffers, arrays, Sets, Maps, Map and Set iterators, generators, async generators, and class iterables and async iterables, and in JavaScript any iterable or async iterable. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. @@ -14,7 +15,7 @@ `node:stream/consumers` and readline interfaces read object-mode streams of strings and Buffers; other values throw a not-supported error. `pipeline` accepts function stages anywhere among its streams: async generator functions (`async function* (source)`, iterating `source` with `for await`), an async generator source function (`async function* ({ signal })`), an async generator function as the destination, and an async function destination whose value resolves the promise form and reaches the callback's second argument. A stage is written inline, or named by a function declaration or a `const` bound to a function expression that nothing reassigns. - The source may also be an array, a string, a synchronous generator object, or a synchronous generator function without parameters; their elements reach the next stage on the same ticks as in Node. + The source may also be an array, a string, a `Set`, a `Map`, a generator or async generator object, or a synchronous generator function without parameters; their elements reach the next stage on the same ticks as in Node. Stages may take the `{ signal }` argument. With a function destination, `pipeline()` returns an object-mode PassThrough that receives an async generator destination's values, or an async function destination's value unless it is `null` or `undefined`. `@types/node` types that result as `NodeJS.WritableStream`, so `for await` over it needs the `PassThrough` type (`pipeline(...) as PassThrough`, or a JSDoc `@type`). diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index 8ac579449..5e862e126 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v37"); + expect(error?.stderr).toContain("scr_runtime_abi_v42"); }); }); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 7a16c37e8..9c38c0983 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -795,6 +795,7 @@ export const LIB_FN_SYMS: Record = { "procStream.write": "scr_proc_stream_write", "stream.stdioWritable": "scr_stream_stdio_writable", "stream.unboxDyn": "scr_stream_unbox_dyn", + "stream.dynIterableSource": "scr_stream_dyn_iterable_source", "stream.handleStream": "scr_stream_handle", "stream.pipeDyn": "scr_stream_pipe_dyn", "stream.unpipeDyn": "scr_stream_unpipe_dyn", diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index 11bde6c2b..0eda18a5d 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 37 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v37" as const; +export const RUNTIME_ABI_VERSION = 42 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v42" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 9846e8d89..6f39c6d08 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index e2b24f8ca..4561fad54 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -128,7 +128,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 04c2fa043..72bea5dd7 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -2837,6 +2837,107 @@ export function streamClassAliasDecl( const PIPELINE_STAGE_EXPECTED = "of type function or an instance of Blob, ReadableStream, WritableStream, Stream, Iterable, AsyncIterable, or Promise or { readable, writable } pair"; +/** A checked-dynamic pipeline stage (an untyped source or destination): + * a boxed native stream unwraps at run time; other values (functions, + * pairs) throw the not-supported Error there. */ +function unboxDynStage(lowerer: Lowerer, v: IrExpr, node: ts.Expression): IrExpr { + noteUntypedStreamFlow(lowerer, null); + return { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + v, + strLit( + lowerer.dynNodeStreams + ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" + : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + locOf(node), + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc: locOf(node), + }; +} + +/** A pipeline whose first stage is checked-dynamic, lowered with that + * stage already evaluated: `expr` is the stream it unboxes to, or, when + * `iterable` is set, the value to pump as an iterable source. */ +interface DynPipelineSource { + expr: IrExpr; + iterable: boolean; +} + +/** pipeline() whose first stage is a stream argument (not a function + * stage, an iterable of a static type, or a cipher value), lowered here. + * When the value is checked-dynamic, Node keeps a stream there and pipes + * it, and keeps an iterable (a string, an array, a generator, any value + * with an iterator method) and pumps it into the next stage. The value is + * evaluated once, and stream.dynIterableSource picks the lowering at run + * time. Null when the first stage is not such an argument. */ +function lowerDynSourcePipeline( + lowerer: Lowerer, + node: ts.Expression, + loc: SrcLoc, + build: (source: DynPipelineSource) => IrExpr, +): IrExpr | null { + if (pipelineStageFunction(lowerer, node) !== null) return null; + if (iterableSourceType(lowerer, node) !== null || isValueStream(lowerer, node)) return null; + let value = lowerer.lowerExpr(node); + // A Set, a Map or a generator object is iterated through its + // checked-dynamic value. + if (BOXED_SOURCE_KINDS.has(value.type.kind) && lowerer.dynConvertible(value.type)) { + value = { kind: "dynFrom", value, type: DYN, loc: value.loc }; + } + if (value.type.kind !== "dyn") { + let stream: IrExpr; + try { + stream = streamArgOf(lowerer, node, value, "pipeline"); + } catch (e) { + if (e instanceof StreamArgTypeThrow) return e.expr; + throw e; + } + return build({ expr: stream, iterable: false }); + } + const local = lowerer.declareHiddenLocal("%pipelineSource", DYN); + const ref = varRef(local.id, DYN, loc); + const stream = build({ expr: unboxDynStage(lowerer, ref, node), iterable: false }); + const iterable = build({ expr: ref, iterable: true }); + const type = stream.type; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: { + kind: "ternary", + cond: { kind: "libCall", fn: "stream.dynIterableSource", args: [ref], type: BOOL, loc }, + then: lowerer.coerceToExpected(iterable, type), + else_: stream, + type, + loc, + }, + type, + loc, + }; +} + +/** The static types of a pipeline source that lowerDynSourcePipeline boxes + * as checked-dynamic values. An array or a string reaches it when the + * checker types the argument as any while its value keeps a static type. */ +const BOXED_SOURCE_KINDS: ReadonlySet = new Set([ + "set", + "map", + "generator", + "array", + "string", +]); + +/** lowerPipelineStages' source arguments for a checked-dynamic first stage. */ +function stagesSource( + src: DynPipelineSource | null, +): [ReadonlyMap | undefined, IrExpr | undefined] { + if (src === null) return [undefined, undefined]; + return src.iterable ? [undefined, src.expr] : [new Map([[0, src.expr]]), undefined]; +} + /** A stream-rooted argument, lowered — or the pointed fence (pipeline's * iterable/generator/function stages have no lowering yet). `what` picks * the validation ladder: finished() rejects EVERY non-stream through @@ -2848,7 +2949,16 @@ function lowerStreamArg( node: ts.Expression, what: "finished" | "pipeline" | "addAbortSignal" | "compose", ): IrExpr { - const lowered = lowerer.lowerExpr(node); + return streamArgOf(lowerer, node, lowerer.lowerExpr(node), what); +} + +/** lowerStreamArg over the argument's lowered value. */ +function streamArgOf( + lowerer: Lowerer, + node: ts.Expression, + lowered: IrExpr, + what: "finished" | "pipeline" | "addAbortSignal" | "compose", +): IrExpr { // A Hash, Hmac, Cipheriv or Decipheriv stage is its Transform; pipeline // and compose stages keep the value alive until the stream settles. const v = isValueStream(lowerer, node) @@ -2857,24 +2967,7 @@ function lowerStreamArg( // A checked-dynamic pipeline stage (an untyped source or destination): // a boxed native stream unwraps at run time; other values (iterables, // functions) throw the not-supported Error there. - if (v.type.kind === "dyn" && what === "pipeline") { - noteUntypedStreamFlow(lowerer, null); - return { - kind: "libCall", - fn: "stream.unboxDyn", - args: [ - v, - strLit( - lowerer.dynNodeStreams - ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" - : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", - locOf(node), - ), - ], - type: { kind: "object", className: "%Duplex" }, - loc: locOf(node), - }; - } + if (v.type.kind === "dyn" && what === "pipeline") return unboxDynStage(lowerer, v, node); const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined; if (!streamSidesOf(lowerer, info)) { // A provably-non-stream value in a JS source (the invalid-input @@ -3210,57 +3303,82 @@ export function lowerStreamModuleCall( ? pipelineOptions(lowerer, lastArg) : null; const stageArgs = opts ? args.slice(0, -1) : args; - if (hasPipelineFunctionStage(lowerer, stageArgs)) { - const staged = lowerPipelineStages(lowerer, stageArgs, loc); - const type = staged.destPromise ?? { kind: "promise", inner: VOID }; - // A promise destination's value settles the result (same type). - if (!opts) { - return staged.wrap({ - kind: "libCall", - fn: "sp.pipelineStages", - args: staged.args, - type, - loc, - }); - } - // the options: end, and the signal (flags bit 6) - const flagsArg = staged.args[3]!; - if (flagsArg.kind !== "numLit") - throw new InternalCompilerError("pipeline flags not a literal"); - const args = [...staged.args, boolLit(opts.end, loc)]; - if (opts.signal) { - args[3] = numLit(flagsArg.value | 64, loc); - args.push(opts.signal); - } - return staged.wrap({ kind: "libCall", fn: "sp.pipelineStagesOpts", args, type, loc }); - } - let streams: IrExpr[]; - try { - streams = stageArgs.map((a) => lowerStreamArg(lowerer, a, "pipeline")); - } catch (e) { - if (e instanceof StreamArgTypeThrow) return e.expr; - throw e; - } - if (opts) { - const head = [numLit(streams.length, loc), ...streams, boolLit(opts.end, loc)]; - return { - kind: "libCall", - fn: opts.signal ? "sp.pipelineOptsSignal" : "sp.pipelineOpts", - args: opts.signal ? [...head, opts.signal] : head, - type: { kind: "promise", inner: VOID }, - loc, - }; - } - return { - kind: "libCall", - fn: "sp.pipeline", - args: [numLit(streams.length, loc), ...streams], - type: { kind: "promise", inner: VOID }, - loc, - }; + const build = (src: DynPipelineSource | null): IrExpr => + lowerPromisePipeline(lowerer, stageArgs, opts, src, loc); + return lowerDynSourcePipeline(lowerer, stageArgs[0]!, loc, build) ?? build(null); } return null; } + return lowerStreamModuleCallbackCall(lowerer, call, bi, loc); +} + +/** stream/promises pipeline(...stages[, options]), with the first stage + * pre-lowered when it is checked-dynamic (lowerDynSourcePipeline). */ +function lowerPromisePipeline( + lowerer: Lowerer, + stageArgs: readonly ts.Expression[], + opts: ReturnType | null, + src: DynPipelineSource | null, + loc: SrcLoc, +): IrExpr { + if (src?.iterable === true || hasPipelineFunctionStage(lowerer, stageArgs)) { + const staged = lowerPipelineStages(lowerer, stageArgs, loc, ...stagesSource(src)); + const type = staged.destPromise ?? { kind: "promise", inner: VOID }; + // A promise destination's value settles the result (same type). + if (!opts) { + return staged.wrap({ + kind: "libCall", + fn: "sp.pipelineStages", + args: staged.args, + type, + loc, + }); + } + // the options: end, and the signal (flags bit 6) + const flagsArg = staged.args[3]!; + if (flagsArg.kind !== "numLit") throw new InternalCompilerError("pipeline flags not a literal"); + const args = [...staged.args, boolLit(opts.end, loc)]; + if (opts.signal) { + args[3] = numLit(flagsArg.value | 64, loc); + args.push(opts.signal); + } + return staged.wrap({ kind: "libCall", fn: "sp.pipelineStagesOpts", args, type, loc }); + } + let streams: IrExpr[]; + try { + streams = stageArgs.map((a, i) => + i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"), + ); + } catch (e) { + if (e instanceof StreamArgTypeThrow) return e.expr; + throw e; + } + if (opts) { + const head = [numLit(streams.length, loc), ...streams, boolLit(opts.end, loc)]; + return { + kind: "libCall", + fn: opts.signal ? "sp.pipelineOptsSignal" : "sp.pipelineOpts", + args: opts.signal ? [...head, opts.signal] : head, + type: { kind: "promise", inner: VOID }, + loc, + }; + } + return { + kind: "libCall", + fn: "sp.pipeline", + args: [numLit(streams.length, loc), ...streams], + type: { kind: "promise", inner: VOID }, + loc, + }; +} + +/** The node:stream and stream/consumers members of lowerStreamModuleCall. */ +function lowerStreamModuleCallbackCall( + lowerer: Lowerer, + call: ts.CallExpression, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr | null { if (bi.module === "stream/consumers") { const args = call.arguments.nodes; // text/json/buffer/arrayBuffer(stream) → a pending promise the @@ -3411,67 +3529,9 @@ export function lowerStreamModuleCall( "the supported form is pipeline(source, ...streams, callback)", ); } - if (hasPipelineFunctionStage(lowerer, args.slice(0, -1))) { - const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc); - const cbNode = args[args.length - 1]!; - const cbParams = - ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) - ? cbNode.parameters.nodes.length - : 0; - if (staged.dest === 2 || (staged.dest === 1 && cbParams > 1)) { - // callback(err, value): the destination's value (undefined for - // an async generator) reaches the callback through the - // checked-dynamic path. - const cb = lowerer.lowerExprExpecting(args[args.length - 1]!, DYN); - // SCR_PIPE_TYPED_CB: a typed callback whose error parameter cannot - // hold undefined takes null for no error; others take undefined. - const stagedArgs = [...staged.args]; - const flagsArg = stagedArgs[3]!; - if (flagsArg.kind !== "numLit") - throw new InternalCompilerError("pipeline flags not a literal"); - if ( - !isJsSourceFile(ts.getSourceFileOfNode(call)) && - !callbackErrorAdmitsUndefined(lowerer, cb) - ) - stagedArgs[3] = numLit(flagsArg.value | 128, loc); - return staged.wrap({ - kind: "libCall", - fn: "stream.pipelineStagesDyn", - args: [...stagedArgs, cb], - type: staged.last, - loc, - }); - } - const { cb, dyn } = lowerEosCallback( - lowerer, - "pipeline", - args[args.length - 1]!, - staged.last, - ); - return staged.wrap({ - kind: "libCall", - fn: dyn ? "stream.pipelineStagesDyn" : "stream.pipelineStages", - args: [...staged.args, cb], - type: staged.last, - loc, - }); - } - let streams: IrExpr[]; - try { - streams = args.slice(0, -1).map((a) => lowerStreamArg(lowerer, a, "pipeline")); - } catch (e) { - if (e instanceof StreamArgTypeThrow) return e.expr; - throw e; - } - const last = streams[streams.length - 1]!; - const { cb, dyn } = lowerEosCallback(lowerer, "pipeline", args[args.length - 1]!, last.type); - return { - kind: "libCall", - fn: dyn ? "stream.pipelineDyn" : "stream.pipeline", - args: [numLit(streams.length, loc), ...streams, cb], - type: last.type, - loc, - }; + const build = (src: DynPipelineSource | null): IrExpr => + lowerCallbackPipeline(lowerer, call, args, src, loc); + return lowerDynSourcePipeline(lowerer, args[0]!, loc, build) ?? build(null); } if (bi.member === "compose") { @@ -3484,6 +3544,75 @@ export function lowerStreamModuleCall( return null; } +/** pipeline(...stages, callback), with the first stage pre-lowered when it + * is checked-dynamic (lowerDynSourcePipeline). */ +function lowerCallbackPipeline( + lowerer: Lowerer, + call: ts.CallExpression, + args: readonly ts.Expression[], + src: DynPipelineSource | null, + loc: SrcLoc, +): IrExpr { + if (src?.iterable === true || hasPipelineFunctionStage(lowerer, args.slice(0, -1))) { + const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc, ...stagesSource(src)); + const cbNode = args[args.length - 1]!; + const cbParams = + ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) + ? cbNode.parameters.nodes.length + : 0; + if (staged.dest === 2 || (staged.dest === 1 && cbParams > 1)) { + // callback(err, value): the destination's value (undefined for + // an async generator) reaches the callback through the + // checked-dynamic path. + const cb = lowerer.lowerExprExpecting(args[args.length - 1]!, DYN); + // SCR_PIPE_TYPED_CB: a typed callback whose error parameter cannot + // hold undefined takes null for no error; others take undefined. + const stagedArgs = [...staged.args]; + const flagsArg = stagedArgs[3]!; + if (flagsArg.kind !== "numLit") + throw new InternalCompilerError("pipeline flags not a literal"); + if ( + !isJsSourceFile(ts.getSourceFileOfNode(call)) && + !callbackErrorAdmitsUndefined(lowerer, cb) + ) + stagedArgs[3] = numLit(flagsArg.value | 128, loc); + return staged.wrap({ + kind: "libCall", + fn: "stream.pipelineStagesDyn", + args: [...stagedArgs, cb], + type: staged.last, + loc, + }); + } + const { cb, dyn } = lowerEosCallback(lowerer, "pipeline", args[args.length - 1]!, staged.last); + return staged.wrap({ + kind: "libCall", + fn: dyn ? "stream.pipelineStagesDyn" : "stream.pipelineStages", + args: [...staged.args, cb], + type: staged.last, + loc, + }); + } + let streams: IrExpr[]; + try { + streams = args + .slice(0, -1) + .map((a, i) => (i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"))); + } catch (e) { + if (e instanceof StreamArgTypeThrow) return e.expr; + throw e; + } + const last = streams[streams.length - 1]!; + const { cb, dyn } = lowerEosCallback(lowerer, "pipeline", args[args.length - 1]!, last.type); + return { + kind: "libCall", + fn: dyn ? "stream.pipelineDyn" : "stream.pipeline", + args: [numLit(streams.length, loc), ...streams, cb], + type: last.type, + loc, + }; +} + /** A pipeline() function stage: an inline function or arrow function, or * an identifier bound to a function declaration or to a `const` * initialized with one, which nothing reassigns. */ @@ -3788,6 +3917,237 @@ function lowerWebStreamSource( }; } +/** A checked-dynamic pipeline source that is an iterable at run time + * (stream.dynIterableSource), as an async generator closure that iterates + * it as Node's pipeline does with `for await`: the value's async iterator, + * or its sync iterator stepped like the typed iterable sources. Each value + * is yielded; leaving early closes the iterator through its return(). */ +function lowerDynIterableSource( + lowerer: Lowerer, + value: IrExpr, + loc: SrcLoc, +): { closure: IrExpr; out: SyncGenType } | null { + const out: SyncGenType = { + kind: "generator", + async: true, + yieldT: DYN, + retT: VOID, + nextT: UNDEFINED_T, + }; + const genResultT = genResultRecord(DYN, VOID, lowerer.shapes, lowerer.unions); + if (!genResultT) return null; + const key = "dynIterableSource"; + let maker = lowerer.valueHelpers.get(key); + if (!maker) { + maker = `%stream.dynIterableSource.${lowerer.valueHelpers.size}`; + const genName = `${maker}.gen`; + const frame = new LiftedFrame(loc); + const srcId = frame.local("src", DYN, false); + const itId = frame.local("it", DYN, false); + const syncId = frame.local("sync", BOOL, false); + const nextId = frame.local("next", DYN, false); + const stepId = frame.local("step", DYN, true); + const activeId = frame.local("active", BOOL, true); + const closeId = frame.local("close", DYN, false); + const it = (): IrExpr => varRef(itId, DYN, loc); + const sync = (): IrExpr => varRef(syncId, BOOL, loc); + const step = (): IrExpr => varRef(stepId, DYN, loc); + const get = (obj: IrExpr, name: string): IrExpr => ({ + kind: "dynKeyGet", + value: obj, + key: strLit(name, loc), + type: DYN, + loc, + }); + const awaitDyn = (v: IrExpr): IrExpr => ({ + kind: "libCall", + fn: "async.awaitDyn", + args: [v], + type: DYN, + loc, + }); + const call = (callee: IrExpr, calleeName: string): IrExpr => ({ + kind: "dynCall", + callee, + receiver: it(), + calleeName, + args: [], + type: DYN, + loc, + }); + const setActive = (on: boolean): IrStmt => ({ + kind: "assign", + localId: activeId, + value: boolLit(on, loc), + loc, + }); + const nextCall = (): IrExpr => call(varRef(nextId, DYN, loc), "iterator method"); + const body: IrStmt[] = [ + { + kind: "varDecl", + localId: itId, + init: { + kind: "libCall", + fn: "dyn.iteratorResult", + args: [ + { + kind: "libCall", + fn: "dyn.asyncIteratorOpen", + args: [varRef(srcId, DYN, loc), strLit("", loc)], + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: syncId, + init: { kind: "libCall", fn: "dyn.asyncIteratorWasSync", args: [], type: BOOL, loc }, + loc, + }, + { kind: "varDecl", localId: nextId, init: get(it(), "next"), loc }, + { kind: "varDecl", localId: stepId, init: dynUndefinedExpr(loc), loc }, + { kind: "varDecl", localId: activeId, init: boolLit(false, loc), loc }, + { + kind: "tryCatch", + tryBody: [ + { + kind: "while", + cond: boolLit(true, loc), + body: [ + setActive(false), + { + kind: "assign", + localId: stepId, + value: { + kind: "libCall", + fn: "dyn.iteratorResult", + args: [ + { + kind: "ternary", + cond: sync(), + then: nextCall(), + else_: awaitDyn(nextCall()), + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "dynTest", + test: "truthy", + value: get(step(), "done"), + type: BOOL, + loc, + }, + then: [{ kind: "break", loc }], + else_: null, + loc, + }, + setActive(true), + { + kind: "exprStmt", + expr: { kind: "yieldExpr", value: get(step(), "value"), type: VOID, loc }, + loc, + }, + ], + loc, + }, + ], + catchBody: null, + catchLocalId: null, + finallyBody: [ + { + kind: "if", + cond: varRef(activeId, BOOL, loc), + then: [ + { kind: "varDecl", localId: closeId, init: get(it(), "return"), loc }, + { + kind: "if", + cond: { + kind: "unary", + op: "!", + operand: { + kind: "dynTest", + test: "nullish", + value: varRef(closeId, DYN, loc), + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + then: [ + { + kind: "exprStmt", + expr: awaitDyn(call(varRef(closeId, DYN, loc), "iterator return")), + loc, + }, + ], + else_: null, + loc, + }, + ], + else_: null, + loc, + }, + ], + loc, + }, + { kind: "return", value: null, loc }, + ]; + const srcLocal = frame.locals.find((l) => l.id === srcId)!; + srcLocal.boxed = true; + lowerer.valueHelpers.set(key, maker); + lowerer.liftedFns.push({ + name: genName, + params: [], + returnType: VOID, + async: true, + captures: [{ localId: srcId, name: "src", type: DYN }], + locals: frame.locals, + body, + generator: { yieldT: DYN, nextT: UNDEFINED_T, resultType: genResultT }, + loc, + }); + const makerSrc: IrLocal = { id: "src.0", name: "src", type: DYN, mutable: false, boxed: true }; + lowerer.liftedFns.push({ + name: maker, + params: [{ localId: makerSrc.id, name: makerSrc.name, type: DYN }], + returnType: funcOf([], out), + locals: [makerSrc], + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: genName, + captures: [makerSrc.id], + type: funcOf([], out), + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + closure: { kind: "call", callee: maker, args: [value], type: funcOf([], out), loc }, + out, + }; +} + function lowerIterableSource( lowerer: Lowerer, node: ts.Expression, @@ -3951,6 +4311,7 @@ function lowerPipelineStages( stageArgs: readonly ts.Expression[], loc: SrcLoc, preLowered?: ReadonlyMap, + dynSource?: IrExpr, ): PipelineStagesLowering { if (stageArgs.length > 52) { lowerer.noLowering( @@ -3975,6 +4336,15 @@ function lowerPipelineStages( const argOrder: { list: IrExpr[]; index: number }[] = []; for (let j = 0; j < stageArgs.length; j++) { const arg = stageArgs[j]!; + if (j === 0 && dynSource !== undefined) { + const source = lowerDynIterableSource(lowerer, dynSource, loc); + if (source === null) lowerer.noLowering("pipeline over this source", arg); + positions += 1; + argOrder.push({ list: stages, index: stages.length }); + stages.push(source!.closure); + srcT = source!.out; + continue; + } // compose's head and tail functions arrive as Duplex.from streams const fn = preLowered?.has(j) ? null : pipelineStageFunction(lowerer, arg); const syncSourceFn = diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..1821b6a6b 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -91,6 +91,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "procStream.write", "child.stdioStream", "stream.unboxDyn", + "stream.dynIterableSource", "stream.handleStream", "stream.pipeDyn", "stream.unpipeDyn", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 216e46a4d..2cc5c708e 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2786,6 +2786,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ argTypes: [DYN, STRING], result: { kind: "object", className: "%Duplex" }, }, + /** Whether pipeline() pumps a checked-dynamic first stage as an + * iterable (a string, or a value with an iterator method that is not a + * stream with pipe() and on()) instead of piping it as a stream. */ + "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, /** The Transform a Cipheriv or Decipheriv reads and writes as a stream * (+1, created on first use and owned by the cipher). */ "stream.handleStream": { diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85b21af7b..f66697567 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -263,6 +263,7 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.prop", "stream.stdioWritable", "stream.unboxDyn", + "stream.dynIterableSource", "stream.handleStream", "stream.unpipeDyn", "stream.workerStdio", diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index cecebb49c..1be1b7a3f 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v37"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v42"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v37"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v42"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7503-pipeline-untyped-sources.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7503-pipeline-untyped-sources.mjs.json new file mode 100644 index 000000000..4d93af132 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7503-pipeline-untyped-sources.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7503-pipeline-untyped-sources.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7504-pipeline-untyped-source-errors.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7504-pipeline-untyped-source-errors.mjs.json new file mode 100644 index 000000000..19402586b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7504-pipeline-untyped-source-errors.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7504-pipeline-untyped-source-errors.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7505-pipeline-untyped-sources.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7505-pipeline-untyped-sources.cjs.json new file mode 100644 index 000000000..29d85351d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7505-pipeline-untyped-sources.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7505-pipeline-untyped-sources.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7506-pipeline-any-source.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7506-pipeline-any-source.ts.json new file mode 100644 index 000000000..942a2b3e8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7506-pipeline-any-source.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7506-pipeline-any-source.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7507-stream-write-error-drain.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7507-stream-write-error-drain.mjs.json new file mode 100644 index 000000000..6fa55d09d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7507-stream-write-error-drain.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7507-stream-write-error-drain.mjs" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index b6b855c05..56f5ca559 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -366,7 +366,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 479d8aad4..c571a25c5 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -469,7 +469,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 086340014..6c15f932c 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v37(void) {} +void scr_runtime_abi_v42(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index 3707b8d50..5415ccdc6 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v37(void); +void scr_runtime_abi_v42(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index ec061e10c..be6d3635f 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6866,10 +6866,12 @@ static bool scr_stream_write_chunk_enc(ScrStream *s, ScrBytes *chunk /*borrowed* scr_stream_do_write(s, scr_stream_wchunk_retain(st, chunk), cb, enc); } /* Node computes the answer AFTER the (possibly synchronous) write: - * a sync completion has already drained its bytes from length. */ - bool ret = st->w.length < st->w.hwm && !st->destroyed && !st->errored; - if (!ret) st->w.need_drain = true; - return ret; + * a sync completion has already drained its bytes from length. Only + * the length sets needDrain; an errored or destroyed stream answers + * false without waiting for 'drain'. */ + bool below = st->w.length < st->w.hwm; + if (!below) st->w.need_drain = true; + return below && !st->destroyed && !st->errored; } static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, ScrClosure *cb /*moves*/) { @@ -8407,3 +8409,31 @@ ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) { scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); return NULL; } + +/* pipeline.js's first stage: a value with pipe() and on() methods is a + * readable node stream, which the stage ladder tests before isIterable; + * a string, or a value with a Symbol.asyncIterator or Symbol.iterator + * method, is an iterable the pipeline pumps into the next stage. */ +bool scr_stream_dyn_iterable_source(const ScrDyn *value) { + ScrStream *stream = scr_stream_of_handle(value); + if (!stream) stream = scr_stream_boxed(value); + if (stream) { + scr_stream_release(stream); + return false; + } + if (value->kind == SCR_DYN_STR) return true; + if (value->kind == SCR_DYN_OBJ) { + ScrDyn *pipe = scr_dyn_obj_read(value, "pipe", 4); + if (!pipe) return false; + bool streamish = pipe->kind == SCR_DYN_FUNC; + scr_dyn_release(pipe); + if (streamish) { + ScrDyn *on = scr_dyn_obj_read(value, "on", 2); + if (!on) return false; + streamish = on->kind == SCR_DYN_FUNC; + scr_dyn_release(on); + if (streamish) return false; + } + } + return scr_dyn_iterable_kind(value) > 0; +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index a4798c629..7acad0eeb 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -145,6 +145,10 @@ ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end); /* A boxed native stream out of a checked-dynamic pipeline stage (+1), or * NULL with `message` thrown as the not-supported Error. */ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); +/* Whether pipeline() keeps a checked-dynamic first stage as an iterable + * to pump rather than as a stream (false with an exception pending when + * reading the value's members throws). */ +bool scr_stream_dyn_iterable_source(const struct ScrDyn *value); /* Handles that are Transforms (ScrDynHandleOps.transform: Cipheriv and * Decipheriv). scr_stream_of_handle: the handle's Transform (+1), built on * first use, or NULL when the value is not such a handle. diff --git a/tests/corpus/7503-pipeline-untyped-sources.mjs b/tests/corpus/7503-pipeline-untyped-sources.mjs new file mode 100644 index 000000000..3dbc90eee --- /dev/null +++ b/tests/corpus/7503-pipeline-untyped-sources.mjs @@ -0,0 +1,102 @@ +// pipeline() over untyped iterable sources: arrays, strings, Sets, generator +// objects, async generator objects, class iterables and object values, +// into Writables, a Transform chain, a function stage and the promise form. +// Each source is pumped as Node pumps an iterable, on the same ticks. +import { pipeline, Writable, Transform } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +const log = []; +function show(chunk) { + return typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString() : JSON.stringify(chunk); +} +function objectSink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + log.push(`${label} write ${show(chunk)}`); + cb(); + }, + final(cb) { log.push(`${label} final`); cb(); }, + }); +} +function sink(label, objectMode = false) { + if (objectMode) return objectSink(label); + return new Writable({ + write(chunk, _enc, cb) { + log.push(`${label} write ${typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString() : JSON.stringify(chunk)}`); + cb(); + }, + final(cb) { log.push(`${label} final`); cb(); }, + }); +} +function* gen() { + log.push("gen start"); + yield "g1"; + log.push("gen mid"); + yield "g2"; + log.push("gen end"); +} +async function* agen() { + yield "a1"; + await null; + yield "a2"; +} +const sources = { + array: () => ["x", "y"], + string: () => "hey", + set: () => new Set(["s1", "s2"]), + gen: () => gen(), + agen: () => agen(), + iterable: () => ({ *[Symbol.iterator]() { yield "i1"; yield "i2"; } }), + objects: () => [{ n: 1 }, { n: 2 }], +}; +const names = Object.keys(sources); +async function run(name) { + const src = sources[name](); + const objectMode = name === "objects"; + await new Promise((resolve) => { + queueMicrotask(() => log.push(`${name} microtask`)); + pipeline(src, sink(name, objectMode), (err) => { + log.push(`${name} done ${err ? err.code ?? err.message : err}`); + resolve(); + }); + log.push(`${name} after call`); + }); +} +for (const n of names) await run(n); + +// a transform chain and a function stage after an untyped source +const src2 = ["p", "q"]; +await new Promise((resolve) => + pipeline( + src2, + new Transform({ transform(c, _e, cb) { cb(null, c.toString().toUpperCase()); } }), + sink("transform"), + (err) => { log.push(`transform done ${err}`); resolve(); }, + ), +); +const src3 = new Set(["f1", "f2"]); +await new Promise((resolve) => + pipeline( + src3, + async function* (source) { for await (const c of source) yield c + "!"; }, + sink("fnstage"), + (err) => { log.push(`fnstage done ${err}`); resolve(); }, + ), +); +// promises form +const src4 = ["m", "n"]; +await pipelineP(src4, sink("promises")); +log.push("promises resolved"); +// an error thrown by the source +function* bad() { yield "ok"; throw new Error("source failed"); } +const src5 = bad(); +await new Promise((resolve) => + pipeline(src5, sink("bad"), (err) => { log.push(`bad done ${err && err.message}`); resolve(); }), +); +// a source of non-string values into a byte-mode writable +const src6 = [1, 2]; +await new Promise((resolve) => + pipeline(src6, sink("numbers"), (err) => { log.push(`numbers done ${err && err.code}`); resolve(); }), +); +console.log(log.join("\n")); diff --git a/tests/corpus/7504-pipeline-untyped-source-errors.mjs b/tests/corpus/7504-pipeline-untyped-source-errors.mjs new file mode 100644 index 000000000..fcbfa9d60 --- /dev/null +++ b/tests/corpus/7504-pipeline-untyped-source-errors.mjs @@ -0,0 +1,90 @@ +// pipeline() over untyped iterable sources: a destination that fails closes +// the source, backpressure waits for drain, an abort signal stops the pump, +// and async generator and Map sources reach object-mode destinations. +import { pipeline, Writable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function* counted(label, n) { + try { + for (let i = 0; i < n; i++) { + console.log(label, "yield", i); + yield `${label}${i}`; + } + } finally { + console.log(label, "finally"); + } +} +function pick(v) { + return v; +} + +// the destination fails on its second write: the source's finally runs +await new Promise((resolve) => { + let n = 0; + const dst = new Writable({ + write(chunk, _e, cb) { + n++; + console.log("fail write", String(chunk)); + cb(n === 2 ? new Error("write failed") : null); + }, + }); + pipeline(pick(counted("f", 5)), dst, (err) => { + console.log("fail done", err && err.message); + resolve(); + }); +}); + +// backpressure: a one-byte highWaterMark and asynchronous writes +await new Promise((resolve) => { + const dst = new Writable({ + highWaterMark: 1, + write(chunk, _e, cb) { + console.log("slow write", String(chunk)); + setTimeout(cb, 1); + }, + }); + pipeline(pick(counted("b", 3)), dst, (err) => { + console.log("slow done", err); + resolve(); + }); +}); + +// an abort signal +const ac = new AbortController(); +try { + await pipelineP( + pick(counted("s", 10)), + new Writable({ + write(chunk, _e, cb) { + console.log("signal write", String(chunk)); + if (String(chunk) === "s1") ac.abort(); + cb(); + }, + }), + { signal: ac.signal }, + ); +} catch (err) { + console.log("signal rejected", err.name); +} + +// an untyped async generator into a PassThrough read with for await +const out = new PassThrough({ objectMode: true }); +const both = pipelineP( + pick((async function* () { + yield "u1"; + yield "u2"; + })()), + out, +); +for await (const c of out) console.log("passthrough", c); +await both; +console.log("passthrough resolved"); + +// a Map source of entries into an object-mode sink +await new Promise((resolve) => { + pipeline( + pick(new Map([["k", 1]])), + new Writable({ objectMode: true, write(c, _e, cb) { console.log("map write", JSON.stringify(c)); cb(); } }), + (err) => { console.log("map done", err); resolve(); }, + ); +}); diff --git a/tests/corpus/7505-pipeline-untyped-sources.cjs b/tests/corpus/7505-pipeline-untyped-sources.cjs new file mode 100644 index 000000000..6dd67b4ff --- /dev/null +++ b/tests/corpus/7505-pipeline-untyped-sources.cjs @@ -0,0 +1,40 @@ +// pipeline() over untyped sources in CommonJS: a source that a function +// answers, which may be an array, a generator, a stream, a string, or a +// Buffer, whose bytes are numbers that a byte-mode Writable refuses. +const { pipeline, Readable, Writable } = require("node:stream"); + +function sink(label) { + const parts = []; + return new Writable({ + write(chunk, _enc, cb) { + parts.push(String(chunk)); + cb(); + }, + final(cb) { + console.log(label, parts.join("|")); + cb(); + }, + }); +} + +function* letters() { + yield "a"; + yield "b"; +} + +const sources = [ + () => ["one", "two"], + () => letters(), + () => Readable.from(["r1", "r2"]), + () => "xyz", + () => Buffer.from("buf"), +]; + +function run(i) { + if (i === sources.length) return; + pipeline(sources[i](), sink(`source ${i}`), (err) => { + console.log(`source ${i} done`, err === undefined ? "ok" : err.code); + run(i + 1); + }); +} +run(0); diff --git a/tests/corpus/7506-pipeline-any-source.ts b/tests/corpus/7506-pipeline-any-source.ts new file mode 100644 index 000000000..84eec0cf4 --- /dev/null +++ b/tests/corpus/7506-pipeline-any-source.ts @@ -0,0 +1,48 @@ +// pipeline() in TypeScript over sources typed any or unknown, and over a +// Set, a Map and an async generator object, which are pumped as iterables. +import { pipeline, Writable } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function sink(label: string): Writable { + const parts: string[] = []; + return new Writable({ + objectMode: true, + write(chunk: unknown, _enc, cb) { + parts.push(JSON.stringify(chunk)); + cb(); + }, + final(cb) { + console.log(label, parts.join(" ")); + cb(); + }, + }); +} + +async function* ticks(): AsyncGenerator { + yield "t1"; + await new Promise((r) => setTimeout(r, 1)); + yield "t2"; +} + +async function main(): Promise { + const anySource: any = ["any1", "any2"]; + await pipelineP(anySource, sink("any")); + const unknownSource: unknown = new Set([1, 2, 3]); + await pipelineP(unknownSource as any, sink("unknown")); + await pipelineP(new Set(["s"]), sink("set")); + await pipelineP( + new Map([ + ["a", 1], + ["b", 2], + ]), + sink("map"), + ); + await pipelineP(ticks(), sink("async generator")); + await new Promise((resolve) => { + pipeline(anySource, sink("callback"), (err?: Error | null) => { + console.log("callback done", err); + resolve(); + }); + }); +} +void main(); diff --git a/tests/corpus/7507-stream-write-error-drain.mjs b/tests/corpus/7507-stream-write-error-drain.mjs new file mode 100644 index 000000000..8298bcb42 --- /dev/null +++ b/tests/corpus/7507-stream-write-error-drain.mjs @@ -0,0 +1,37 @@ +// A write whose callback fails synchronously: write() answers false and +// writableNeedDrain stays false, so no 'drain' follows the error, and a +// pipeline pumping a generator stops at the failed write. +import { pipeline, Writable } from "node:stream"; + +const w = new Writable({ + write(_chunk, _enc, cb) { + cb(new Error("bad write")); + }, +}); +w.on("error", (e) => console.log("error event", e.message)); +w.on("drain", () => console.log("drain")); +const r = w.write("a"); +console.log("returned", r, "needDrain", w.writableNeedDrain, "errored", w.errored.message); + +function* source() { + try { + for (let i = 0; i < 4; i++) { + console.log("yield", i); + yield `c${i}`; + } + } finally { + console.log("source finally"); + } +} +let writes = 0; +pipeline( + source(), + new Writable({ + write(chunk, _enc, cb) { + writes++; + console.log("write", String(chunk)); + cb(writes === 2 ? new Error("second write failed") : null); + }, + }), + (err) => console.log("pipeline done", err.message), +); diff --git a/tests/harness/corpus-costs/7503-pipeline-untyped-sources.mjs.json b/tests/harness/corpus-costs/7503-pipeline-untyped-sources.mjs.json new file mode 100644 index 000000000..d2d431b0f --- /dev/null +++ b/tests/harness/corpus-costs/7503-pipeline-untyped-sources.mjs.json @@ -0,0 +1 @@ +{ "plain": 5499, "san": 7259 } diff --git a/tests/harness/corpus-costs/7504-pipeline-untyped-source-errors.mjs.json b/tests/harness/corpus-costs/7504-pipeline-untyped-source-errors.mjs.json new file mode 100644 index 000000000..4fd20681f --- /dev/null +++ b/tests/harness/corpus-costs/7504-pipeline-untyped-source-errors.mjs.json @@ -0,0 +1 @@ +{ "plain": 3431, "san": 4529 } diff --git a/tests/harness/corpus-costs/7505-pipeline-untyped-sources.cjs.json b/tests/harness/corpus-costs/7505-pipeline-untyped-sources.cjs.json new file mode 100644 index 000000000..3bb5df076 --- /dev/null +++ b/tests/harness/corpus-costs/7505-pipeline-untyped-sources.cjs.json @@ -0,0 +1 @@ +{ "plain": 3197, "san": 4220 } diff --git a/tests/harness/corpus-costs/7506-pipeline-any-source.ts.json b/tests/harness/corpus-costs/7506-pipeline-any-source.ts.json new file mode 100644 index 000000000..454dcb2c0 --- /dev/null +++ b/tests/harness/corpus-costs/7506-pipeline-any-source.ts.json @@ -0,0 +1 @@ +{ "plain": 3250, "san": 4741 } diff --git a/tests/harness/corpus-costs/7507-stream-write-error-drain.mjs.json b/tests/harness/corpus-costs/7507-stream-write-error-drain.mjs.json new file mode 100644 index 000000000..35f298bef --- /dev/null +++ b/tests/harness/corpus-costs/7507-stream-write-error-drain.mjs.json @@ -0,0 +1 @@ +{ "plain": 2735, "san": 4076 } diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c5476328e..f4121bf88 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -109,7 +109,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 42, marker: "scr_runtime_abi_v42" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From 56b9158de21b5d25612c535f37b588ff1d17fcde Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 10:42:05 -0500 Subject: [PATCH 070/395] Keep integer index arithmetic in integers across nested counted loops --- .../src/backend/llvm/counted-loops.ts | 10 +- .../backend/llvm/dense-array-loops.test.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 17 +- .../src/backend/llvm/wrapping-sums.ts | 50 ++++-- .../src/ir/expression-inlining.test.ts | 145 ++++++++++++++++ .../compiler/src/ir/expression-inlining.ts | 157 ++++++++++++++++++ .../compiler/src/ir/integer-loops.test.ts | 68 ++++++++ packages/compiler/src/ir/integer-loops.ts | 105 +++++++++++- .../corpus/8193-counted-loop-products.ts.json | 6 + tests/corpus/8193-counted-loop-products.ts | 59 +++++++ 10 files changed, 588 insertions(+), 30 deletions(-) create mode 100644 packages/compiler/src/ir/expression-inlining.test.ts create mode 100644 packages/compiler/src/ir/expression-inlining.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8193-counted-loop-products.ts.json create mode 100644 tests/corpus/8193-counted-loop-products.ts diff --git a/packages/compiler/src/backend/llvm/counted-loops.ts b/packages/compiler/src/backend/llvm/counted-loops.ts index 1ff4c6580..bb402d5b8 100644 --- a/packages/compiler/src/backend/llvm/counted-loops.ts +++ b/packages/compiler/src/backend/llvm/counted-loops.ts @@ -50,8 +50,8 @@ export function emitCountedLoopLimit( } /** The run-time test of a guarded counted loop: an unproven limit is within - * the integer form's finite range and an unproven start is an exact safe - * integer. Returns `true` when nothing needs testing. */ + * the integer form's finite range and an unproven start is an exact integer + * within the loop's start bound. Returns `true` when nothing needs testing. */ export function emitCountedLoopSafety( host: LlvmEmitterContext, loop: IntegerCountedForLoop, @@ -65,7 +65,11 @@ export function emitCountedLoopSafety( `${safe} = fcmp ${loop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(loop.guardLimit)}`, ); } - if (loop.startGuarded) safe = andSafe(host, safe, emitExactStart(host, loop.start)); + if (loop.startGuarded) { + const start = host.emitExpr(loop.start); + const bound = loop.startBound; + safe = andSafe(host, safe, emitExactIntegerTest(host, start.name, -bound, bound).ok); + } for (const follower of loop.followers) { if (follower.startGuarded) safe = andSafe(host, safe, emitExactStart(host, follower.start)); if (follower.tripGuarded) safe = andSafe(host, safe, emitFollowerReach(host, loop, follower)); diff --git a/packages/compiler/src/backend/llvm/dense-array-loops.test.ts b/packages/compiler/src/backend/llvm/dense-array-loops.test.ts index a88c38575..9ec2d5d39 100644 --- a/packages/compiler/src/backend/llvm/dense-array-loops.test.ts +++ b/packages/compiler/src/backend/llvm/dense-array-loops.test.ts @@ -122,6 +122,7 @@ const counted = ( guarded: false, limitGuarded: false, startGuarded: false, + startBound: Number.MAX_SAFE_INTEGER, guardLimit: 0, followers: [], ...fields, diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6729da197..27389e4ff 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -80,6 +80,7 @@ import { } from "./emit-journal.js"; import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js"; import { versionLoopBoundParameters } from "../../ir/parameter-versions.js"; +import { inlineExpressionFunctions } from "../../ir/expression-inlining.js"; import { allocatedLiteral, fillAllocationLength, @@ -562,13 +563,15 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { scalarizeNumericRecords( specializeCallbackCalls( specializeNumericCalls( - foldConstantGlobals( - copyStableCaptures( - resolveSelfCaptures( - hoistFinalFieldReads( - freezeInitOnlyGlobals( - holdCharLocalsAsUnits( - dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + inlineExpressionFunctions( + foldConstantGlobals( + copyStableCaptures( + resolveSelfCaptures( + hoistFinalFieldReads( + freezeInitOnlyGlobals( + holdCharLocalsAsUnits( + dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + ), ), ), ), diff --git a/packages/compiler/src/backend/llvm/wrapping-sums.ts b/packages/compiler/src/backend/llvm/wrapping-sums.ts index c736fcf72..e93d66909 100644 --- a/packages/compiler/src/backend/llvm/wrapping-sums.ts +++ b/packages/compiler/src/backend/llvm/wrapping-sums.ts @@ -4,13 +4,15 @@ import { integerArithmeticRange, type IntegerRange } from "../../ir/integer-rang import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import type { LlInteger } from "./integer-values.js"; -/** Marks the `+` and `-` operands whose number only reaches ToInt32/ToUint32 - * consumers (`| 0`, the other bitwise operators, `Math.imul`, `Math.clz32`), - * directly or through further sums, in the emitter's `wrappingSums`. Called - * by the consumer before its operands emit. */ +/** Marks the `+`, `-` and `*` operands whose number only reaches + * ToInt32/ToUint32 consumers (`| 0`, the other bitwise operators, + * `Math.imul`, `Math.clz32`), directly or through further sums and + * products, in the emitter's `wrappingSums`. Called by the consumer before + * its operands emit. */ export function markWrappingOperands(host: LlvmEmitterContext, ...operands: IrExpr[]): void { for (const operand of operands) { - if (operand.kind !== "bin" || (operand.op !== "+" && operand.op !== "-")) continue; + if (operand.kind !== "bin" || (operand.op !== "+" && operand.op !== "-" && operand.op !== "*")) + continue; if (operand.type.kind !== "f64" || host.wrappingSums.has(operand)) continue; host.wrappingSums.add(operand); markWrappingOperands(host, operand.left, operand.right); @@ -128,12 +130,15 @@ function wrappingOperand( return { low, valid }; } -/** ToUint32 of `left op right` (`+` or `-`) in 32-bit arithmetic when the - * expression only reaches ToInt32/ToUint32 consumers and both operands are - * exact integers or NaN with an exact result. Both conversions are - * arithmetic mod 2^32, so the wrapping i32 sum of the operands' low halves - * is the converted exact sum; a NaN operand makes the sum NaN, which - * converts to 0. Returns undefined, emitting nothing, for any other sum. */ +/** ToUint32 of `left op right` (`+`, `-` or `*`) in 32-bit arithmetic when + * the expression only reaches ToInt32/ToUint32 consumers and both operands + * are exact integers or NaN with an exact result. Both conversions are + * arithmetic mod 2^32, so the wrapping i32 sum or product of the operands' + * low halves is the converted exact result; a NaN operand makes the result + * NaN, which converts to 0. A product is exact when the operands' magnitude + * bounds multiply to at most 2^53; it may be -0 (`0 * -1`), which converts + * to 0 like the integer 0. Returns undefined, emitting nothing, for any + * other expression. */ export function emitWrappingSum( host: LlvmEmitterContext, e: IrExpr & { kind: "bin" }, @@ -142,15 +147,23 @@ export function emitWrappingSum( li: LlInteger | null, ri: LlInteger | null, ): string | undefined { - if ((e.op !== "+" && e.op !== "-") || !host.wrappingSums.has(e)) return undefined; + if ((e.op !== "+" && e.op !== "-" && e.op !== "*") || !host.wrappingSums.has(e)) return undefined; const leftRange = li ? li.range : integerOrNaNRange(host, e.left); const rightRange = leftRange && (ri ? ri.range : integerOrNaNRange(host, e.right)); - if (!rightRange || !integerArithmeticRange(e.op, leftRange!, rightRange)) return undefined; + if (!rightRange) return undefined; + if ( + e.op === "*" + ? !exactProduct(leftRange!, rightRange) + : !integerArithmeticRange(e.op, leftRange!, rightRange) + ) + return undefined; const B = host.B; const left = wrappingOperand(host, l, li, leftRange!); const right = wrappingOperand(host, r, ri, rightRange); const sum = B.tmp(); - B.line(`${sum} = ${e.op === "+" ? "add" : "sub"} i32 ${left.low}, ${right.low}`); + B.line( + `${sum} = ${e.op === "+" ? "add" : e.op === "-" ? "sub" : "mul"} i32 ${left.low}, ${right.low}`, + ); let valid = left.valid ?? right.valid; if (valid === null) return sum; if (left.valid && right.valid) { @@ -162,6 +175,15 @@ export function emitWrappingSum( return out; } +/** Whether every product of a value in `left` and one in `right` is an + * integer of magnitude at most 2^53, which the double product holds + * exactly. */ +function exactProduct(left: IntegerRange, right: IntegerRange): boolean { + const magnitude = (range: IntegerRange): number => + Math.max(Math.abs(range.min), Math.abs(range.max)); + return magnitude(left) * magnitude(right) <= 2 ** 53; +} + /** The number locals of `fn` that only hold exact integers (never -0) or NaN, with the range of * those integers: locals declared once with an initializer, neither parameters nor captured or * boxed, whose initializer and every later write `integerOrNaNRange` covers. A local joins once diff --git a/packages/compiler/src/ir/expression-inlining.test.ts b/packages/compiler/src/ir/expression-inlining.test.ts new file mode 100644 index 000000000..ae68c44ea --- /dev/null +++ b/packages/compiler/src/ir/expression-inlining.test.ts @@ -0,0 +1,145 @@ +import { expect, test } from "vitest"; +import { inlineExpressionFunctions } from "./expression-inlining.js"; +import { BOOL, F64, VOID, type IrExpr, type IrFunction, type IrModule, type IrStmt } from "./ir.js"; + +const loc = { file: "expression-inlining.ts", start: 0, end: 0 }; +const num = (value: number): IrExpr => ({ loc, kind: "numLit", value, type: F64 }); +const ref = (localId: string): IrExpr => ({ loc, kind: "varRef", localId, type: F64 }); +const bin = (op: "+" | "*" | ">>>", left: IrExpr, right: IrExpr): IrExpr => ({ + loc, + kind: "bin", + op, + left, + right, + type: F64, +}); +const call = (callee: string, args: IrExpr[]): IrExpr => ({ + loc, + kind: "call", + callee, + args, + type: F64, +}); +const local = (id: string, extra: object = {}) => ({ + id, + name: id, + type: F64, + mutable: true, + ...extra, +}); + +// a(i, j) = ((i + j) * (i + j + 1)) >>> 1 +const a: IrFunction = { + loc, + name: "a", + params: [ + { localId: "i", name: "i", type: F64 }, + { localId: "j", name: "j", type: F64 }, + ], + locals: [local("i"), local("j")], + returnType: F64, + body: [ + { + loc, + kind: "return", + value: bin( + ">>>", + bin("*", bin("+", ref("i"), ref("j")), bin("+", bin("+", ref("i"), ref("j")), num(1))), + num(1), + ), + }, + ], +}; + +/** `caller` running `body` in a loop. */ +function caller(body: IrStmt[], extra: object = {}): IrFunction { + return { + loc, + name: "caller", + params: [], + locals: [local("x", extra), local("y"), local("out")], + returnType: VOID, + body: [{ loc, kind: "while", cond: { loc, kind: "boolLit", value: true, type: BOOL }, body }], + }; +} + +const loopBody = (fn: IrFunction): IrStmt[] => (fn.body[0] as IrStmt & { kind: "while" }).body; + +const mod = (fn: IrFunction): IrModule => ({ + irVersion: 17, + sourceFile: "expression-inlining.ts", + entry: "caller", + functions: [a, fn], +}); + +test("calls passing literals and locals take the callee's expression over them", () => { + const result = inlineExpressionFunctions( + mod(caller([{ loc, kind: "assign", localId: "out", value: call("a", [ref("x"), num(3)]) }])), + ); + const assign = loopBody(result.functions[1]!)[0] as IrStmt & { kind: "assign" }; + expect(assign.value).toMatchObject({ + kind: "bin", + op: ">>>", + left: { + kind: "bin", + op: "*", + left: { + kind: "bin", + op: "+", + left: { kind: "varRef", localId: "x" }, + right: { kind: "numLit", value: 3 }, + }, + }, + }); + // Every substituted read is a fresh node. + const left = (assign.value as IrExpr & { kind: "bin" }).left as IrExpr & { kind: "bin" }; + const factors = [left.left, (left.right as IrExpr & { kind: "bin" }).left] as (IrExpr & { + kind: "bin"; + })[]; + expect(factors[0]!.left).not.toBe(factors[1]!.left); + expect(result.functions[0]).toBe(a); +}); + +test("calls with other arguments, boxed locals or callees with other bodies keep their call", () => { + const nested = call("a", [call("a", [ref("x"), ref("y")]), num(1)]); + const kept = inlineExpressionFunctions( + mod(caller([{ loc, kind: "assign", localId: "out", value: nested }])), + ); + const outer = (loopBody(kept.functions[1]!)[0] as IrStmt & { kind: "assign" }).value; + // The inner call inlines; the outer one passes a computed argument. + expect(outer).toMatchObject({ + kind: "call", + callee: "a", + args: [{ kind: "bin", op: ">>>" }, { kind: "numLit" }], + }); + const boxed = caller( + [{ loc, kind: "assign", localId: "out", value: call("a", [ref("x"), ref("y")]) }], + { + boxed: true, + }, + ); + expect(inlineExpressionFunctions(mod(boxed)).functions[1]).toBe(boxed); + const twoStatements: IrFunction = { + ...a, + body: [{ loc, kind: "exprStmt", expr: ref("i") }, ...a.body], + }; + const unchanged: IrModule = { + ...mod( + caller([{ loc, kind: "assign", localId: "out", value: call("a", [ref("x"), ref("y")]) }]), + ), + functions: [ + twoStatements, + caller([{ loc, kind: "assign", localId: "out", value: call("a", [ref("x"), ref("y")]) }]), + ], + }; + expect(inlineExpressionFunctions(unchanged)).toBe(unchanged); +}); + +test("calls outside loops keep their call", () => { + const outside: IrFunction = { + ...caller([]), + body: [{ loc, kind: "assign", localId: "out", value: call("a", [ref("x"), num(3)]) }], + }; + const m = mod(outside); + expect(inlineExpressionFunctions(m)).toBe(m); +}); diff --git a/packages/compiler/src/ir/expression-inlining.ts b/packages/compiler/src/ir/expression-inlining.ts new file mode 100644 index 000000000..377cfaf3f --- /dev/null +++ b/packages/compiler/src/ir/expression-inlining.ts @@ -0,0 +1,157 @@ +import type { IrExpr, IrFunction, IrModule, IrStmt } from "./ir.js"; +import { everyExprChild, everyStmtChild, mapExprChildren, transformStmtList } from "./traverse.js"; + +/** Callee bodies above this many nodes keep their call. */ +const MAX_EXPRESSION_NODES = 32; + +/** Number operators that run no other code and cannot throw. */ +const PURE_BINARY = new Set([ + "+", + "-", + "*", + "/", + "%", + "**", + "&", + "|", + "^", + "<<", + ">>", + ">>>", +]); + +/** The returned expression of a function whose body is `return ` over + * its number parameters: literals, parameter reads, and the number + * operators of PURE_BINARY, unary `-` and `~`. Null for any other function. */ +function inlinableExpression(fn: IrFunction): IrExpr | null { + if (fn.async || fn.generator || fn.captures !== undefined || fn.classCaptures?.length) + return null; + if (fn.tier === "dynamic" || fn.returnType.kind !== "f64") return null; + if (fn.body.length !== 1) return null; + const only = fn.body[0]!; + if (only.kind !== "return" || only.value === null) return null; + const params = new Set(); + for (const param of fn.params) { + if (param.type.kind !== "f64") return null; + params.add(param.localId); + } + const locals = new Map(fn.locals.map((local) => [local.id, local])); + for (const id of params) { + const local = locals.get(id); + if (local === undefined || local.boxed || local.tdz) return null; + } + let nodes = 0; + const pure = (e: IrExpr): boolean => { + if (++nodes > MAX_EXPRESSION_NODES || e.type.kind !== "f64") return false; + switch (e.kind) { + case "numLit": + return true; + case "varRef": + return params.has(e.localId); + case "bin": + return PURE_BINARY.has(e.op) && pure(e.left) && pure(e.right); + case "unary": + return (e.op === "-" || e.op === "~") && pure(e.operand); + default: + return false; + } + }; + return pure(only.value) ? only.value : null; +} + +/** A call argument that reads the same value wherever the inlined body + * reads it: a number literal or a read of one of the caller's own unboxed + * locals outside the temporal dead zone. The body writes nothing, so every + * read sees the value the call would have passed. */ +function duplicableArgument(e: IrExpr, caller: ReadonlyMap) { + if (e.type.kind !== "f64") return false; + // A literal whose source spelling does not round-trip keeps its call. + if (e.kind === "numLit") return e.spelling === undefined; + if (e.kind !== "varRef") return false; + const local = caller.get(e.localId); + return local !== undefined && !local.boxed && !local.tdz; +} + +/** A fresh copy of `e`: facts downstream are keyed by node identity. */ +function copy(e: IrExpr, substitute: (ref: IrExpr & { kind: "varRef" }) => IrExpr): IrExpr { + if (e.kind === "varRef") return substitute(e); + return mapExprChildren( + { ...e }, + (child) => copy(child, substitute), + (stmt) => stmt, + ); +} + +/** The direct calls of `body` that run inside a loop body or condition. */ +function callsInLoops(body: IrStmt[]): Set { + const found = new Set(); + let depth = 0; + const expr = (e: IrExpr): boolean => { + if (depth > 0 && e.kind === "call") found.add(e); + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + const loop = + s.kind === "for" || s.kind === "while" || s.kind === "doWhile" || s.kind === "forOf"; + if (loop) depth++; + everyStmtChild(s, expr, stmt); + if (loop) depth--; + return true; + }; + body.forEach(stmt); + return found; +} + +/** Inlines functions whose body is one number expression over their + * parameters (`function a(i, j) { return (((i + j) * (i + j + 1)) >>> 1) + + * i + 1; }`) at direct call sites that pass literals or reads of the + * caller's locals. + * + * The callee runs no other code, allocates nothing and cannot throw, so + * the call's only effect is its result. Its arguments are reads that + * nothing between them and the callee's reads can change, so substituting + * them gives the same value. The caller's loop and integer analyses then + * see the arithmetic on its own counters (`a(i, j)` inside a counted loop + * over `i` and `j`). Only calls inside loops are inlined: elsewhere the + * integer analyses gain nothing. The function itself stays for its other + * uses. */ +export function inlineExpressionFunctions(mod: IrModule): IrModule { + const members = new Set((mod.classes ?? []).flatMap((cls) => cls.methods ?? [])); + const expressions = new Map(); + for (const fn of mod.functions) { + if (fn.name === mod.entry || members.has(fn.name)) continue; + const value = inlinableExpression(fn); + if (value !== null) expressions.set(fn.name, { fn, value }); + } + if (expressions.size === 0) return mod; + let changed = false; + const functions = mod.functions.map((fn) => { + const locals = new Map(fn.locals.map((local) => [local.id, local])); + const inLoops = callsInLoops(fn.body); + if (inLoops.size === 0) return fn; + let inlined = false; + const body = transformStmtList(fn.body, { + stmt: (s) => s, + expr: (e) => { + if (e.kind !== "call" || e.callee === fn.name || !inLoops.has(e)) return e; + const target = expressions.get(e.callee); + if (target === undefined || target.fn.params.length !== e.args.length) return e; + if (!e.args.every((arg) => duplicableArgument(arg, locals))) return e; + const args = new Map(target.fn.params.map((param, i) => [param.localId, e.args[i]!])); + inlined = true; + return copy(target.value, (ref) => { + const arg = args.get(ref.localId); + if (arg?.kind === "numLit") + return { kind: "numLit", value: arg.value, type: arg.type, loc: ref.loc }; + if (arg?.kind === "varRef") + return { kind: "varRef", localId: arg.localId, type: arg.type, loc: ref.loc }; + return ref; + }); + }, + }); + if (!inlined) return fn; + changed = true; + return { ...fn, body }; + }); + return changed ? { ...mod, functions } : mod; +} diff --git a/packages/compiler/src/ir/integer-loops.test.ts b/packages/compiler/src/ir/integer-loops.test.ts index 2cca43a00..48be67fbf 100644 --- a/packages/compiler/src/ir/integer-loops.test.ts +++ b/packages/compiler/src/ir/integer-loops.test.ts @@ -552,3 +552,71 @@ test("counters stepped beside the tested one run as integers", () => { { startGuarded: true, tripGuarded: true }, ]); }); + +/** `counted(limit)` with the body `x = ((i + k) * (i + k + 1)) >>> 1` + * inside a nested `for (let j = 0; j < 4; j++)` loop. */ +function productLoop(limit: IrExpr, start: IrExpr = number(0)): IrStmt & { kind: "for" } { + const s = counted(limit); + if (s.init?.kind === "varDecl") s.init.init = start; + const sum = (extra: number): IrExpr => { + const base: IrExpr = { kind: "bin", op: "+", left: ref("i"), right: ref("k"), type: F64, loc }; + return extra === 0 + ? base + : { kind: "bin", op: "+", left: base, right: number(extra), type: F64, loc }; + }; + const product: IrExpr = { kind: "bin", op: "*", left: sum(0), right: sum(1), type: F64, loc }; + const inner = counted(number(4)); + if (inner.init?.kind === "varDecl") inner.init.localId = "j"; + if (inner.cond?.kind === "bin") inner.cond.left = ref("j"); + if (inner.update?.kind === "assign") { + inner.update.localId = "j"; + if (inner.update.value.kind === "bin") inner.update.value.left = ref("j"); + } + inner.body = [ + { + kind: "assign", + localId: "x", + value: { kind: "bin", op: ">>>", left: product, right: number(1), type: F64, loc }, + loc, + }, + ]; + s.body = [inner]; + return s; +} + +test("guarded counted loops whose products the counter bound proves exact may hold nested loops", () => { + const scope = new Map(locals); + for (const id of ["limit", "start", "k", "x", "j"]) + scope.set(id, { id, name: id, type: F64, mutable: true }); + const tight = matchIntegerCountedForLoop(productLoop(ref("limit"), ref("start")), scope); + expect(tight).toMatchObject({ + guarded: true, + limitGuarded: true, + startGuarded: true, + startBound: 2 ** 25, + guardLimit: 2 ** 25, + range: { min: -(2 ** 25), max: 2 ** 25 - 1 }, + }); + // A literal trip count past the bound keeps the safe-integer bound, and + // a guarded loop under that bound holds no nested loop. + expect(matchIntegerCountedForLoop(productLoop(number(1e9), ref("start")), scope)).toBeNull(); + const plain = counted(ref("limit")); + plain.body = [productLoop(number(4)).body[0]!]; + const inner = plain.body[0] as IrStmt & { kind: "for" }; + inner.body = [ + { + kind: "assign", + localId: "x", + value: { kind: "bin", op: "|", left: ref("i"), right: number(0), type: F64, loc }, + loc, + }, + ]; + expect(matchIntegerCountedForLoop(plain, scope)).toBeNull(); + // Without a nested loop the product bound still applies to the limit. + const flat = productLoop(ref("limit"), ref("start")); + flat.body = (flat.body[0] as IrStmt & { kind: "for" }).body; + expect(matchIntegerCountedForLoop(flat, scope)).toMatchObject({ + startBound: 2 ** 25, + guardLimit: 2 ** 25, + }); +}); diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index 2f7d2bea6..7cc49b7a0 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -24,8 +24,12 @@ export interface IntegerCountedForLoop { /** The limit needs the run-time `guardLimit` test (part of `guarded`). */ limitGuarded: boolean; /** The start has no proven integer range: the versioned loop also tests - * that it is an exact integer (not -0) of at most 2^53 in magnitude. */ + * that it is an exact integer (not -0) of at most `startBound` in + * magnitude. */ startGuarded: boolean; + /** The magnitude the guarded start is tested against: 2^53 - 1, or + * PRODUCT_COUNTER_BOUND when the body multiplies sums of the counter. */ + startBound: number; guardLimit: number; range: IntegerRange; /** Counters declared and stepped beside this one that the condition does @@ -50,6 +54,64 @@ export interface IntegerLoopFollower { range: IntegerRange; } +/** The counter magnitude below which a product of two sums of counters and + * small literals stays exact in a double: `(i + j) * (i + j + 1)` with both + * counters within 2^25 is below 2^52. A guarded loop whose body multiplies + * such sums for a bitwise operator tests its counter against this bound, so + * the product's integer form is proven exact. */ +export const PRODUCT_COUNTER_BOUND = 2 ** 25; + +/** Whether `e` is a sum or difference of locals and safe integer literals + * of magnitude at most 2^20, and whether it reads `counter`. */ +function localSum(e: IrExpr, counter: string): { sum: boolean; reads: boolean } { + let reads = false; + const sum = (x: IrExpr): boolean => { + if (x.kind === "varRef") { + if (x.localId === counter) reads = true; + return true; + } + if (x.kind === "numLit") return Number.isSafeInteger(x.value) && Math.abs(x.value) <= 2 ** 20; + return x.kind === "bin" && (x.op === "+" || x.op === "-") && sum(x.left) && sum(x.right); + }; + return { sum: sum(e), reads }; +} + +/** Whether a bitwise operand `e` multiplies two sums of locals and + * literals, one of which reads `counter` (`((i + j) * (i + j + 1)) >>> 1`). */ +function multipliesCounterSums(e: IrExpr, counter: string): boolean { + if (e.kind !== "bin") return false; + if (e.op === "*") { + const left = localSum(e.left, counter), + right = localSum(e.right, counter); + return left.sum && right.sum && (left.reads || right.reads); + } + return ( + (e.op === "+" || e.op === "-") && + (multipliesCounterSums(e.left, counter) || multipliesCounterSums(e.right, counter)) + ); +} + +const BITWISE = new Set(["&", "|", "^", "<<", ">>", ">>>"]); + +/** Whether `body`, nested loops included, takes a bitwise operator of a + * product that multipliesCounterSums matches. */ +function bodyMultipliesCounter(body: IrStmt[], counter: string): boolean { + let found = false; + everyStmtList(body, { + stmt: () => !found, + expr: (e) => { + if ( + e.kind === "bin" && + BITWISE.has(e.op) && + (multipliesCounterSums(e.left, counter) || multipliesCounterSums(e.right, counter)) + ) + found = true; + return !found; + }, + }); + return found; +} + /** The bound of the run-time follower test. */ export const FOLLOWER_GUARD_LIMIT = 2 ** 52; /** The magnitude a follower that passed the test can reach. The test's four @@ -171,6 +233,23 @@ export function matchIntegerCountedForLoop( // The condition tests the driving counter; every other declaration // follows it. const driver = declared(cond.left) ?? declared(cond.right); + // A body that multiplies sums of the driver tests it against the bound + // under which those products are exact. A limit proven past that bound + // (a literal trip count) keeps the safe-integer bound instead. + const limitSide = driver !== null && declared(cond.left) === driver ? cond.right : cond.left; + const knownLimit = + limitSide.kind === "numLit" + ? { min: limitSide.value, max: limitSide.value } + : ranges.get(limitSide); + const driverBound = + driver !== null && + !( + knownLimit && + Math.max(Math.abs(knownLimit.min), Math.abs(knownLimit.max)) > PRODUCT_COUNTER_BOUND + ) && + bodyMultipliesCounter(stmt.body, driver) + ? PRODUCT_COUNTER_BOUND + : Number.MAX_SAFE_INTEGER; const counterIds = decls.map((d) => (d.kind === "varDecl" ? d.localId : "")); const stepOf = (id: string): number | null => { const steps = updates.map((u) => constantStep(u, id)).filter((v) => v !== null); @@ -191,7 +270,8 @@ export function matchIntegerCountedForLoop( ? { min: start.value, max: start.value } : ranges.get(start); const guarded = !known && start.kind !== "numLit" && start.type.kind === "f64"; - const range = guarded ? { min: -Number.MAX_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER } : known; + const bound = id === driver ? driverBound : Number.MAX_SAFE_INTEGER; + const range = guarded ? { min: -bound, max: bound } : known; const step = stepOf(id); if ( !range || @@ -248,7 +328,9 @@ export function matchIntegerCountedForLoop( ); if (!stableLoopValue(limit, locals, stmt.body, true, local.id, stableLength)) return null; const inclusive = op === "<=" || op === ">="; - const guardLimit = step > 0 ? 2 ** 53 - (inclusive ? 1 : 0) : -(2 ** 53) + (inclusive ? 1 : 0); + const limitBound = driverBound === Number.MAX_SAFE_INTEGER ? 2 ** 53 : driverBound; + const guardLimit = + step > 0 ? limitBound - (inclusive ? 1 : 0) : -limitBound + (inclusive ? 1 : 0); const boundRange = limit.kind === "numLit" ? { min: limit.value, max: limit.value } : ranges.get(limit); const limitGuarded = @@ -264,7 +346,9 @@ export function matchIntegerCountedForLoop( Number.MAX_SAFE_INTEGER, boundRange ? Math.ceil(boundRange.max) - (inclusive ? 0 : 1) - : Number.MAX_SAFE_INTEGER, + : limitGuarded + ? Math.ceil(guardLimit) - (inclusive ? 0 : 1) + : Number.MAX_SAFE_INTEGER, ), ), } @@ -275,7 +359,9 @@ export function matchIntegerCountedForLoop( -Number.MAX_SAFE_INTEGER, boundRange ? Math.floor(boundRange.min) + (inclusive ? 0 : 1) - : -Number.MAX_SAFE_INTEGER, + : limitGuarded + ? Math.floor(guardLimit) + (inclusive ? 0 : 1) + : -Number.MAX_SAFE_INTEGER, ), ), max: startRange.max, @@ -366,9 +452,15 @@ export function matchIntegerCountedForLoop( const bodyStmt = (s: IrStmt): boolean => { if (s.kind === "bytesSet" && counter(s.index)) useful = true; if (!guarded && s.kind === "arraySet" && counter(s.index)) useful = true; + // A guarded loop's copy duplicates nested loops too, so only a body + // whose counter products the bound proves exact may hold a nested + // `for` loop; the node budget counts its body. if ( guarded && - (s.kind === "for" || s.kind === "forOf" || s.kind === "while" || s.kind === "doWhile") + (s.kind === "forOf" || + s.kind === "while" || + s.kind === "doWhile" || + (s.kind === "for" && driverBound === Number.MAX_SAFE_INTEGER)) ) return false; return (!guarded || ++cost <= 80) && everyStmtChild(s, expr, bodyStmt); @@ -383,6 +475,7 @@ export function matchIntegerCountedForLoop( guarded, limitGuarded, startGuarded, + startBound: driverBound, guardLimit, range, followers, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8193-counted-loop-products.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8193-counted-loop-products.ts.json new file mode 100644 index 000000000..7a3b97b6c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8193-counted-loop-products.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8193-counted-loop-products.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8193-counted-loop-products.ts b/tests/corpus/8193-counted-loop-products.ts new file mode 100644 index 000000000..d347aee76 --- /dev/null +++ b/tests/corpus/8193-counted-loop-products.ts @@ -0,0 +1,59 @@ +// Nested counted loops whose bounds arrive at run time, with the counters' +// products reaching bitwise operators. Starts and limits take the integer +// copy or the double copy: negative, fractional, NaN, -0, and starts or +// limits past 2^25, past which the guarded copy no longer proves products +// exact. +function a(i: number, j: number): number { + return (((i + j) * (i + j + 1)) >>> 1) + i + 1; +} + +function rows(start: number, end: number, n: number): number[] { + const out: number[] = []; + for (let i = start; i < end; i++) { + let t = 0; + for (let j = 0; j < n; j++) t += 1 / a(i, j); + out.push(t); + } + return out; +} + +function mixed(start: number, end: number, n: number): number { + let h = 0; + for (let i = start; i < end; i++) { + for (let j = start; j < n; j++) h = (h + ((i - j) * (j - i + 3) | 0)) | 0; + } + return h; +} + +function wide(start: number, count: number): number[] { + const out: number[] = []; + for (let i = start; i < start + count; i++) out.push(((i * (i + 1)) >>> 0) ^ (i * i) | 0); + return out; +} + +const args = process.argv.slice(2).map(Number); +const big = 2 ** 25; +for (const [start, end, n] of [ + [0, 4, 6], + [-3, 2, 5], + [1.5, 4, 3], + [-0, 2, 2], + [NaN, 2, 2], + [big - 2, big + 2, 3], + [0, 3, args.length > 0 ? big + 1 : 4], +]) { + console.log(start, end, n, rows(start!, end!, Math.min(n!, 8)).map((x) => x.toFixed(12)).join(" ")); + console.log("mixed", mixed(start!, end!, Math.min(n!, 8))); +} +for (const start of [0, -5, 2 ** 26, 2 ** 31 - 2, 2 ** 52, -(2 ** 40), 0.5]) console.log(start, wide(start, 4).join(" ")); +console.log(Object.is(((-0 * 5) >>> 0), 0), a(-1, 0), a(0, -1), a(2 ** 30, 2 ** 30)); + +// Single-expression functions inlined at calls that pass literals and +// locals, also read as values and called with other arguments. +function mix(x: number, y: number): number { + return ((x * 31 + y) ^ (y << 3)) % 1000; +} +const table = [mix, (x: number, y: number) => x - y]; +let acc = 0; +for (let k = 0; k < 5; k++) acc += mix(k, 7) + mix(3, k) + table[k % 2]!(k, 2) + mix(k * 2, args.length); +console.log(acc, mix(2 ** 40, -0), mix(NaN, 1), mix(Infinity, 2), [1, 2, 3].map((v) => mix(v, v)).join()); From 2967a1b8c064094958b6b1b8fc23af76f5141bf6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 11:03:34 -0500 Subject: [PATCH 071/395] Propagate exceptions by unwinding from directly called functions on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emitted functions report an exception by returning with the thread's pending flag set, and every caller tests the flag after each call that may throw. On Linux executables (glibc and musl), functions reached only by direct calls now propagate exceptions by unwinding the native stack instead: - unwindAbiFunctions selects the may-throw functions that are not async or generators, constructors, virtually called or overridden methods, closure targets, the entry, library exports, module initializers or callees of coroutine bodies. - A native unwind costs about 2 µs a raise and 0.1 µs a frame, so functions that exceptions are likely to cross keep the flag: those with a `throw` of their own, those up to three calls above one, and recursive functions, counting calls through methods, closures and callbacks. - Their propagating exits call scr_unwind_raise (scr_unwind.c), a forced unwind through _Unwind_ForcedUnwind with the exception still in the active cell. - Direct calls to them are `invoke`s without a pending check. The landing pad runs the frame's usual unwind path: it releases the frame's values and then enters the enclosing try handler or resumes the unwind. - Remaining plain calls to them (wrappers, adapters, runtime-facing entry points) become invokes whose landing pad continues on the pending flag, or resumes when the wrapper is itself invoked. Every other call is marked `nounwind`, so inlining a raising function at an invoke keeps its calls plain. - When emitted text breaks these rules (a raising function's address is taken, or an invoked function returns with the flag pending), the module is emitted again with those functions on the pending flag. Emitted code does not declare scr_unwind_raise `noreturn`, so sanitized builds do not run the sanitizer's no-return hook, which needs stack bounds that fiber stacks do not report. Instead, scr_unwind_raise clears the shadow of the frames it unwinds. Executables link a static unwinder: libgcc_eh with the host toolchain and the glibc runtime packs, and Zig's libunwind with Zig and the musl packs. macOS, Windows, Android, WASI, libraries and debug builds keep the pending flag everywhere and emit the same code as before. The throw-chains runtime workload measures exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and a callback, and a parser that backtracks by throwing. --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/throw-chains.json | 6 + benchmarks/runtime/workloads/throw-chains.ts | 196 +++++++ packages/compiler/src/backend/llvm/blocks.ts | 14 +- .../src/backend/llvm/emit-function.ts | 10 +- packages/compiler/src/backend/llvm/emitter.ts | 135 ++++- .../compiler/src/backend/llvm/expr-calls.ts | 31 +- .../src/backend/llvm/unwind-abi.test.ts | 285 ++++++++++ .../compiler/src/backend/llvm/unwind-abi.ts | 491 ++++++++++++++++++ .../compiler/src/backend/native/executable.ts | 12 + .../src/backend/native/runtime-inputs.ts | 1 + packages/compiler/src/index.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../runtime-linux-arm64-gnu/scripts/build.mjs | 6 +- .../scripts/build.mjs | 6 +- .../runtime-linux-x64-gnu/scripts/build.mjs | 6 +- .../runtime-linux-x64-musl/scripts/build.mjs | 6 +- .../runtime-pack-matrix.mjs | 1 + packages/runtime/src/scr_exception.h | 5 + packages/runtime/src/scr_unwind.c | 76 +++ scripts/zero-cost-compare.mjs | 5 +- 21 files changed, 1269 insertions(+), 26 deletions(-) create mode 100644 benchmarks/runtime/workloads/throw-chains.json create mode 100644 benchmarks/runtime/workloads/throw-chains.ts create mode 100644 packages/compiler/src/backend/llvm/unwind-abi.test.ts create mode 100644 packages/compiler/src/backend/llvm/unwind-abi.ts create mode 100644 packages/runtime/src/scr_unwind.c diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 03238871d..726a9951b 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -30,6 +30,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `regex-logs` | cpu | regex `exec` with groups, `test`, global `replace` | | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | | `template-render` | cpu | template literals, escaping, `map`/`join` | +| `throw-chains` | cpu | exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and through a callback, and a backtracking parser that throws on a failed alternative | | `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread | | `typed-array-kernels` | cpu | element-wise loops that run to one typed array's length and index others at the same position: `Float64Array` axpy, `Float32Array` blend, `Uint8Array` mask, wrapping `Int32Array` add, `Uint8ClampedArray` brighten, RGBA to gray, a row-major `Float32Array` matrix-vector product, and `reduce`/`some`/`map` callbacks | | `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage | diff --git a/benchmarks/runtime/workloads/throw-chains.json b/benchmarks/runtime/workloads/throw-chains.json new file mode 100644 index 000000000..79cf5ea87 --- /dev/null +++ b/benchmarks/runtime/workloads/throw-chains.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/throw-chains.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/throw-chains.ts b/benchmarks/runtime/workloads/throw-chains.ts new file mode 100644 index 000000000..c96816aed --- /dev/null +++ b/benchmarks/runtime/workloads/throw-chains.ts @@ -0,0 +1,196 @@ +// Exceptions that cross many frames: throws from helpers below a recursion, +// caught 2, 5 and 20 recursive calls up (direct calls, then calls through a +// callback), with each frame holding an array across its call, and a +// backtracking parser that uses exceptions for control flow. +class ParseError extends Error { + readonly at: number; + constructor(at: number, message: string) { + super(message); + this.at = at; + } +} + +/** A thrown value that is not an Error (no stack to capture). */ +class Stop { + readonly code: number; + constructor(code: number) { + this.code = code; + } +} + +const held: number[][] = []; +function fail(n: number, plain: boolean): number { + if (plain) throw new Stop(n); + throw new RangeError("chain " + n); +} +function check(n: number, plain: boolean): number { + return (n & 3) === 0 ? fail(n, plain) : n; +} +function validate(n: number, plain: boolean): number { + return check(n, plain) + (n & 1); +} +function bottom(n: number, plain: boolean): number { + const box = [n, -1]; + const r = validate(n, plain); + held[0] = box; + return r + box.length; +} +function walk(n: number, depth: number, plain: boolean): number { + const box = [n, depth]; + const r = depth === 0 ? bottom(n, plain) : walk(n, depth - 1, plain); + held[depth] = box; + return r + box.length; +} + +function chains(rounds: number): string { + const out: string[] = []; + for (const depth of [2, 5, 20]) + for (const plain of [false, true]) { + let caught = 0; + let sum = 0; + for (let i = 0; i < rounds; i++) { + try { + sum += walk(i, depth - 2, plain); + } catch (error) { + caught++; + sum += error instanceof Stop ? error.code & 7 : 1; + } + } + out.push(`depth ${depth} ${plain ? "object" : "error"}: caught ${caught} sum ${sum}`); + } + return out.join("\n"); +} + +// The same chains through a callback: each level visits the next with a +// closure, as tree visitors do. +function visit(n: number, depth: number, each: (n: number, depth: number) => number): number { + const box = [n, depth]; + const r = depth === 0 ? bottom(n, false) : each(n, depth - 1); + held[depth] = box; + return r + box.length; +} +function visits(rounds: number): string { + const out: string[] = []; + const each = (n: number, depth: number): number => visit(n, depth, each); + for (const depth of [2, 5, 20]) { + let caught = 0; + let sum = 0; + for (let i = 0; i < rounds; i++) { + try { + sum += visit(i, depth - 2, each); + } catch (error) { + caught++; + sum += error instanceof RangeError ? 1 : 0; + } + } + out.push(`visit depth ${depth}: caught ${caught} sum ${sum}`); + } + return out.join("\n"); +} + +// expr := term (("+" | "-") term)*; term := atom ("*" atom)*; +// atom := number | "(" expr ")" | name "(" expr ("," expr)* ")" | name. +// A call and a name start alike: the parser tries the call first and +// backtracks when it fails. +let text = ""; +let pos = 0; +function peek(): string { + return pos < text.length ? text[pos]! : ""; +} +function expect(c: string): void { + if (peek() !== c) throw new ParseError(pos, `expected ${c}`); + pos++; +} +function name(): string { + const start = pos; + while (pos < text.length && text.charCodeAt(pos) >= 97 && text.charCodeAt(pos) <= 122) pos++; + if (pos === start) throw new ParseError(pos, "expected a name"); + return text.slice(start, pos); +} +function call(): number { + const callee = name(); + expect("("); + let value = expr() + callee.length; + while (peek() === ",") { + pos++; + value += expr(); + } + expect(")"); + return value; +} +function atom(): number { + const c = peek(); + if (c >= "0" && c <= "9") { + let value = 0; + while (peek() >= "0" && peek() <= "9") value = value * 10 + (text.charCodeAt(pos++) - 48); + return value; + } + if (c === "(") { + pos++; + const value = expr(); + expect(")"); + return value; + } + const start = pos; + try { + return call(); + } catch (error) { + if (!(error instanceof ParseError)) throw error; + pos = start; + return name().length; + } +} +function term(): number { + let value = atom(); + while (peek() === "*") { + pos++; + value = (value * atom()) % 1000003; + } + return value; +} +function expr(): number { + let value = term(); + while (peek() === "+" || peek() === "-") { + const op = text[pos++]; + value = op === "+" ? value + term() : value - term(); + } + return value; +} + +let seed = 4242; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +function source(depth: number): string { + const r = random(); + if (depth > 6 || r < 0.25) return String(Math.floor(random() * 100)); + if (r < 0.45) return "abcdefgh".slice(0, 1 + Math.floor(random() * 8)); + if (r < 0.6) return `(${source(depth + 1)})`; + if (r < 0.8) return `${source(depth + 1)}${random() < 0.5 ? "+" : "*"}${source(depth + 1)}`; + return `f${"xyz".slice(0, Math.floor(random() * 3))}(${source(depth + 1)},${source(depth + 1)})`; +} + +function parse(count: number): string { + let total = 0; + let failed = 0; + for (let i = 0; i < count; i++) { + // Every tenth input lacks its last character, which fails deep inside it. + const full = source(0); + text = i % 10 === 0 ? full.slice(0, -1) : full; + pos = 0; + try { + total = (total + expr()) % 1000003; + if (pos !== text.length) throw new ParseError(pos, "trailing input"); + } catch (error) { + if (!(error instanceof ParseError)) throw error; + failed++; + } + } + return `parsed ${count}: total ${total} failed ${failed}`; +} + +const scale = Number(process.argv[2] ?? "1"); +console.log(chains(Math.floor(60000 * scale))); +console.log(visits(Math.floor(60000 * scale))); +console.log(parse(Math.floor(40000 * scale))); diff --git a/packages/compiler/src/backend/llvm/blocks.ts b/packages/compiler/src/backend/llvm/blocks.ts index 354e631c6..eee4d00b9 100644 --- a/packages/compiler/src/backend/llvm/blocks.ts +++ b/packages/compiler/src/backend/llvm/blocks.ts @@ -9,6 +9,7 @@ * code after it); here the lines are DROPPED instead, and emitStmts skips * whole statements once the current block is terminated, so no dropped * definition can leak into a later block. */ +import { UNWIND_RESUME, UNWIND_SLOT } from "./unwind-abi.js"; export class BlockBuilder { private readonly blocks: { label: string; lines: string[]; term: string | null }[] = []; @@ -86,7 +87,18 @@ export class BlockBuilder { terminate(s: string): void { if (this.cur.term === null) { - if (this.returnEpilogue && s.startsWith("ret ")) this.line(this.returnEpilogue); + if ( + this.returnEpilogue && + (s.startsWith("ret ") || + s.startsWith("call void @scr_unwind_raise()") || + s === UNWIND_RESUME) + ) + this.line(this.returnEpilogue); + if (s === UNWIND_RESUME) { + const exception = this.tmp(); + this.line(`${exception} = load { ptr, i32 }, ptr ${UNWIND_SLOT}`); + s = `resume { ptr, i32 } ${exception}`; + } this.cur.term = ` ${this.withDebugLocation(s)}`; } } diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index e015c2d72..749c91858 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -56,11 +56,14 @@ import { emitIntegerClone, emitIntegerDispatcher } from "./integer-clones.js"; import { beginFunctionLoops, LOOPS } from "./loop-state.js"; import { runningExtremumUpdates } from "./running-extrema.js"; import { findIntegerOrNaNLocals } from "./wrapping-sums.js"; +import { UNWIND_PERSONALITY, UNWIND_SLOT } from "./unwind-abi.js"; export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { const B = new BlockBuilder(); emitter.B = B; emitter.currentFunctionName = fn.name; + emitter.currentUnwindAbi = emitter.unwindAbi.has(fn.name); + emitter.usesLandingPads = false; emitter.findInt32Allocations(fn); emitter.currentTier = fn.tier; // Fiber switches restore the active exception cell before control @@ -596,6 +599,8 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { B.terminate(cleanup.terminator); } + if (emitter.usesLandingPads && emitter.currentUnwindAbi) + B.entryAllocas.push(`${UNWIND_SLOT} = alloca { ptr, i32 }`); if (emitter.logArgSlots > 0) { B.entryAllocas.push(`%logargs = alloca [${emitter.logArgSlots} x %ScrLogArg]`); } @@ -636,7 +641,10 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { const debug = emitter.debugScope === null ? "" : ` !dbg ${emitter.debugScope}`; const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name); emitter.privateGlobalSlots.clear(); - let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`; + // Landing pads (emitDirectCall) need the personality. + const personality = emitter.usesLandingPads ? ` ${UNWIND_PERSONALITY}` : ""; + if (emitter.usesLandingPads) emitter.declare(`declare i32 @__gcc_personality_v0(...)`); + let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${personality}${debug} { ; ${fn.name}\n${B.render()}\n}`; if (int32Abi) body += "\n" + emitter.emitInt32AbiWrapper(fn, symbol, int32Abi); if (emitter.integerClones.members.has(fn.name)) { body += "\n" + emitIntegerClone(fn, emitter, emitter.stackChecks?.has(fn.name) === true); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6729da197..814c1a23e 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -128,6 +128,16 @@ import { type FfiCallbackAdapter, } from "../ffi-callbacks.js"; import { computeMayThrow } from "../may-throw.js"; +import { + UNWIND_PERSONALITY, + UNWIND_RAISE, + UNWIND_RAISE_DECLARATION, + UNWIND_RESUME, + UNWIND_SLOT, + UnwindAbiViolation, + bridgePlainUnwindCalls, + unwindAbiFunctions, +} from "./unwind-abi.js"; import { mangleAsyncInlineAwaitCall, mangleAsyncSpawn, @@ -439,6 +449,12 @@ export interface ForkPrimitives { export interface LlvmTargetOptions { /** C ABI target; omitted direct emission uses the host ABI. */ targetTriple?: string; + /** Linux (glibc and musl) executables: functions reached only by direct + * calls propagate exceptions by unwinding the native stack (unwind-abi.ts). */ + unwindAbi?: boolean; + /** Functions kept on the pending-flag ABI under `unwindAbi`: the ones an + * earlier emission found breaking its rules (UnwindAbiViolation). */ + unwindExcluded?: ReadonlySet; /** Emit the function bodies in this many contiguous ranges, each by its * own emitter, and merge them in function order (emit-journal.ts). The * output is the same as one emitter's. Debug builds (`debugSources`) @@ -590,16 +606,36 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { return new LlEmitter(emitted, options, null, prepared?.mod === emitted ? prepared : null); } +/** Emits the module's parts, emitting it again with the functions an + * UnwindAbiViolation names on the pending-flag ABI. */ +function emitModuleText(input: IrModule, options: LlvmTargetOptions): string[] { + for (let excluded = options.unwindExcluded ?? new Set(); ;) { + const emitter = moduleEmitter(input, { ...options, unwindExcluded: excluded }); + try { + return emitter.emitParts(); + } catch (error) { + if (!(error instanceof UnwindAbiViolation)) throw error; + const names = new Set(excluded); + for (const name of emitter.unwindAbi) + for (const symbol of [mangleFunction(name), mangleBorrowedFunction(name)]) + if (error.symbols.has(symbol) || error.symbols.has(symbol + INT32_ABI_SUFFIX)) + names.add(name); + if (names.size === excluded.size) throw error; + excluded = names; + } + } +} + export function emitLlvmModule(input: IrModule, options: LlvmTargetOptions = {}): string { - return moduleEmitter(input, options).emit(); + return emitModuleText(input, options).join("\n"); } /** emitLlvmModule's text as parts that concatenate to it, without the copy * that joining them makes: a large program's text is 150 MB. */ export function emitLlvmModuleParts(input: IrModule, options: LlvmTargetOptions = {}): string[] { - return moduleEmitter(input, options) - .emitParts() - .flatMap((part, index) => (index === 0 ? [part] : ["\n", part])); + return emitModuleText(input, options).flatMap((part, index) => + index === 0 ? [part] : ["\n", part], + ); } /** Keep large translation units below the host's single-string limit. */ @@ -609,7 +645,7 @@ export function emitLlvmModuleSource( ): string | readonly string[] { // IR validation, contracts and the frontend's disposal run after lowering without checks. relieveHeapPressure(true); - const parts = moduleEmitter(input, options).emitParts(); + const parts = emitModuleText(input, options); const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1)); if (length <= 256 * 1024 * 1024) return parts.join("\n"); return parts.flatMap((part, index) => (index === 0 ? [part] : ["\n", part])); @@ -1253,6 +1289,10 @@ export class LlEmitter { /** May-throw analysis (computeMayThrow): pending * checks are emitted only after calls that can actually raise. */ readonly mayThrow: Set; + /** The functions that propagate exceptions by unwinding (unwind-abi.ts). */ + readonly unwindAbi: Set; + /** Whether the function being emitted is one of unwindAbi. */ + currentUnwindAbi = false; readonly indirectMayThrow: boolean; /** Worker programs: the functions that poll for termination on entry * (workerTerminationPolls); null outside worker programs. */ @@ -1711,6 +1751,16 @@ export class LlEmitter { nodes: this.nodeIndex, }); this.mayThrow = mt.fns; + this.unwindAbi = + options.unwindAbi === true && + !this.wasi && + mod.lib === undefined && + options.debugSources === undefined && + (options.targetTriple + ? /-linux-(?:gnu|musl)/.test(options.targetTriple) + : process.platform === "linux") + ? unwindAbiFunctions(mod, this.mayThrow, options.unwindExcluded) + : new Set(); this.workerEntryPolls = mt.workerEntryPolls ?? null; this.stackChecks = mt.stackChecks ?? null; // A stackless call's eager prefix borrows its caller's stack; the @@ -1846,6 +1896,9 @@ export class LlEmitter { : undefined, ) : NO_INTEGER_CLONES; + // Integer clones and their dispatchers call each other through their + // own lowering, which keeps plain calls and flag tests. + for (const name of this.integerClones.members) this.unwindAbi.delete(name); this.stackCallbacks = new StackCallbacks(this.fnByName); this.receiverReaders = new AmbientReceiverReaders(this.fnByName, this.nodeIndex); this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks); @@ -2125,7 +2178,8 @@ export class LlEmitter { return this.emitParts().join("\n"); } emitParts(): string[] { - return emitParts(this); + const parts = emitParts(this); + return this.unwindAbi.size > 0 ? bridgePlainUnwindCalls(parts) : parts; } /** The dispose-observer gates the emitted frees and record writers test @@ -3235,6 +3289,13 @@ export class LlEmitter { terminator = `br label %${target.label}`; } else if (this.currentCoro !== null) { terminator = `br label %${this.currentCoro.finalLabel}`; + } else if (this.currentUnwindAbi && this.inLandingPad) { + // The unwind that entered the landing pad goes on to the caller's. + terminator = UNWIND_RESUME; + } else if (this.currentUnwindAbi) { + // The caller's invoke takes the exception at its landing pad. + this.declare(UNWIND_RAISE_DECLARATION); + terminator = UNWIND_RAISE; } else { const t = this.llType(this.currentReturnType); terminator = @@ -3265,6 +3326,7 @@ export class LlEmitter { passThrough !== undefined && !target && this.currentCoro === null && + !this.currentUnwindAbi && !this.currentInt32Result ) { const t = this.llType(this.currentReturnType); @@ -3451,6 +3513,46 @@ export class LlEmitter { B.startBlock(B.newLabel("exc.dead")); } + /** Emits `line`, a `call` of `callee`, and reports whether the call + * needs no pending check after it: a call of an unwinding-ABI function + * becomes an `invoke` whose landing pad runs this frame's unwind (the + * same releases and handler as emitPendingCheck's unwind path), and the + * normal path continues in a fresh block. Coroutine bodies keep plain + * calls (unwindAbiFunctions excludes their callees). */ + emitDirectCall(line: string, callee: string): boolean { + const B = this.B; + if (!this.unwindAbi.has(callee) || this.currentCoro !== null || B.isTerminated()) { + B.line(line); + return false; + } + const ok = B.newLabel("inv.ok"); + const pad = B.newLabel("inv.lp"); + this.usesLandingPads = true; + // A trailing comment goes after the invoke's labels. + const comment = line.indexOf(" ; "); + const text = comment < 0 ? line : line.slice(0, comment); + B.terminate( + `${text.replace(/^((?:%[\w.]+ = )?)call /, "$1invoke ")} to label %${ok} unwind label %${pad}`, + ); + B.startBlock(pad); + const exception = B.tmp(); + B.line(`${exception} = landingpad { ptr, i32 } cleanup`); + if (this.currentUnwindAbi) B.line(`store { ptr, i32 } ${exception}, ptr ${UNWIND_SLOT}`); + this.inLandingPad = true; + this.emitUnwind(); + this.inLandingPad = false; + B.startBlock(ok); + return true; + } + + /** Whether emitUnwind runs for a landing pad, whose unwind goes on with + * `resume` instead of raising again. */ + private inLandingPad = false; + + /** Whether the function being emitted has a landing pad (its definition + * needs the personality). */ + usesLandingPads = false; + /** A recursive function's entry check: below the running stack's guard, * throw Node's RangeError and unwind like any other pending exception. */ emitStackCheck(): void { @@ -5214,7 +5316,22 @@ export class LlEmitter { if (fn.captures !== undefined) params.unshift("ptr %sc_env"); const ret = this.llType(fn.returnType); const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`; - B.line(ret === "void" ? call : `%result = ${call}`); + // The owned entry of an unwinding-ABI body releases the borrowed + // parameters on the unwind too, then resumes it to its caller. + const unwinding = this.unwindAbi.has(fn.name) && kept.size === 0; + if (unwinding) { + const ok = B.newLabel("adapt.ok"); + const pad = B.newLabel("adapt.lp"); + B.terminate( + `${ret === "void" ? "" : "%result = "}invoke ${call.slice(5)} to label %${ok} unwind label %${pad}`, + ); + B.startBlock(pad); + const exception = B.tmp(); + B.line(`${exception} = landingpad { ptr, i32 } cleanup`); + for (const index of borrowed) this.releaseFrame(`%p${index}`, fn.params[index]!.type); + B.terminate(`resume { ptr, i32 } ${exception}`); + B.startBlock(ok); + } else B.line(ret === "void" ? call : `%result = ${call}`); // A borrowed result is reachable from the parameters: own it first. A // throwing body returns a null dummy, which needs no owner (and string // or array retains do not accept NULL). @@ -5240,7 +5357,9 @@ export class LlEmitter { B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`); const symbol = kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name); - return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`; + if (unwinding) this.declare(`declare i32 @__gcc_personality_v0(...)`); + const personality = unwinding ? ` ${UNWIND_PERSONALITY}` : ""; + return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS}${personality} {\n${B.render()}\n}`; } canBorrowCallArgument(value: IrExpr): boolean { return canBorrowCallArgument(this, value); diff --git a/packages/compiler/src/backend/llvm/expr-calls.ts b/packages/compiler/src/backend/llvm/expr-calls.ts index 6ff52619e..d411d9172 100644 --- a/packages/compiler/src/backend/llvm/expr-calls.ts +++ b/packages/compiler/src/backend/llvm/expr-calls.ts @@ -244,38 +244,46 @@ export function emitCallExpr( ? INTEGER_DISPATCH_SUFFIX : ""; const target = `@${borrowed ? mangleBorrowedFunction(e.callee) : host.callTarget(e.callee)}${abi ? INT32_ABI_SUFFIX : ""}${dispatch}`; + // A call of an unwinding-ABI callee is an invoke (emitDirectCall): + // its landing pad unwinds, so no pending check follows it. if (e.type.kind === "void") { - B.line(`call void ${target}(${argList})`); - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(); + const invoked = host.emitDirectCall(`call void ${target}(${argList})`, e.callee); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(); if (borrowed) host.releaseTemps(host.frames.pop()!); return { name: "", type: e.type }; } const t = B.tmp(); if (abi?.union) { - B.line(`${t} = call ${SCALAR_UNION_RESULT} ${target}(${argList})`); - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(); + const invoked = host.emitDirectCall( + `${t} = call ${SCALAR_UNION_RESULT} ${target}(${argList})`, + e.callee, + ); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(); if (borrowed) host.releaseTemps(host.frames.pop()!); if (stackResult) return host.stackScalarUnion(t, e.type); return host.own({ name: host.boxScalarUnion(t, e.type), type: e.type }); } if (abi?.result) { - B.line(`${t} = call i32 ${target}(${argList})`); + const invoked = host.emitDirectCall(`${t} = call i32 ${target}(${argList})`, e.callee); const out = host.int32Result(t, e.type); - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(out); if (borrowed) host.releaseTemps(host.frames.pop()!); return out; } - B.line(`${t} = call ${host.llType(e.type)} ${target}(${argList})`); + const invoked = host.emitDirectCall( + `${t} = call ${host.llType(e.type)} ${target}(${argList})`, + e.callee, + ); let out: LlValue; if (borrowed && host.borrowedReturns.has(e.callee)) { // A borrowed-return body hands back +0 (a null dummy when it threw): // test the exception first, then own the result before the // argument snapshots that keep it reachable are released. - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(); out = host.own({ name: host.retainFrame(t, e.type), type: e.type }); } else { out = host.own({ name: t, type: e.type }); - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(out); } if (borrowed) { host.toFrame(out); @@ -1366,10 +1374,11 @@ export function emitBorrowedResultCall(host: LlvmEmitterContext, e: ExprOf<"call ) .join(", "); const t = host.B.tmp(); - host.B.line( + const invoked = host.emitDirectCall( `${t} = call ${host.llType(e.type)} @${mangleBorrowedFunction(e.callee)}${abi ? INT32_ABI_SUFFIX : ""}(${argList}) ; borrowed result`, + e.callee, ); - if (host.mayThrow.has(e.callee)) host.emitPendingCheck(); + if (!invoked && host.mayThrow.has(e.callee)) host.emitPendingCheck(); return { name: t, type: e.type }; } diff --git a/packages/compiler/src/backend/llvm/unwind-abi.test.ts b/packages/compiler/src/backend/llvm/unwind-abi.test.ts new file mode 100644 index 000000000..5f7f5895b --- /dev/null +++ b/packages/compiler/src/backend/llvm/unwind-abi.test.ts @@ -0,0 +1,285 @@ +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"; +import { UnwindAbiViolation, bridgePlainUnwindCalls } from "./unwind-abi.js"; + +async function moduleFor(source: string) { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unwind-abi-")); + 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 module; + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +/** The definition of the source function `name` (its i32 or borrowing entry when it has one). */ +function body(ir: string, name: string): string { + return new RegExp( + `^define internal [^\\n]*@sc_b?f_[^\\n(]*${name}(?:\\.i32)?\\([^]*?^}`, + "m", + ).exec(ir)![0]; +} + +const SOURCE = ` +function leaf(n: number): number { + if (n === 3) throw new RangeError("three"); + return n * 2; +} +function first(n: number): number { + return leaf(n) + 1; +} +function second(n: number): number { + return first(n) * 3; +} +function third(n: number): number { + return second(n) - 2; +} +function mid(n: number): number { + const parts: string[] = ["a" + n]; + const r = third(n); + parts.push("b"); + return r + parts.length; +} +class Counter { + total = 0; + add(n: number): void { this.total += mid(n); } +} +function outer(n: number): string { + try { return "ok " + mid(n); } catch (e) { return "caught " + (e as Error).message; } +} +const counter = new Counter(); +for (let i = 0; i < 5; i++) { console.log(outer(i)); try { counter.add(i); } catch { console.log("add", i); } } +const f = (x: number) => mid(x); +console.log(f(1), counter.total); +`; + +const LINUX = { targetTriple: "x86_64-unknown-linux-gnu", unwindAbi: true } as const; + +test("directly called functions raise, and their callers invoke them without a pending check", async () => { + const ir = emitLlvmModule(await moduleFor(SOURCE), LINUX); + // leaf throws: the exception starts on the pending flag, and the three + // calls above it test the flag too; mid raises it on. + for (const name of ["leaf", "first", "second", "third"]) + expect(body(ir, name)).not.toContain("@scr_unwind_raise"); + const mid = body(ir, "mid"); + expect(mid).toMatch(/= call double @sc_b?f_[^(]*third\(/); + expect(mid).toContain("call void @scr_unwind_raise()"); + // outer invokes mid; the landing pad takes the exception to its handler. + const outer = body(ir, "outer"); + expect(outer).toMatch( + /= invoke double @sc_b?f_[^(]*mid[^\n]* to label %inv\.ok\d+ unwind label %inv\.lp\d+/, + ); + expect(outer).toMatch(/inv\.lp\d+:\n\s+%t\d+ = landingpad \{ ptr, i32 \} cleanup/); + expect(outer).toContain("personality ptr @__gcc_personality_v0"); + expect(ir).toContain("declare i32 @__gcc_personality_v0(...)"); + expect(ir).toContain("declare void @scr_unwind_raise() cold"); +}); + +test("a method of a class outside any hierarchy is on the unwinding ABI; closures keep the flag", async () => { + const ir = emitLlvmModule(await moduleFor(SOURCE), LINUX); + // Counter.add invokes mid and resumes the unwind from its landing pad; the + // closure over mid is a pointer target and returns with the flag. + const add = body(ir, "Counter_add"); + expect(add).toMatch(/invoke double @sc_b?f_[^(]*mid/); + expect(add).toMatch(/resume \{ ptr, i32 \} %[\w.]+/); + expect(add).not.toContain("@scr_unwind_raise"); + const closures = ir + .split(/\n(?=define )/) + .filter((def) => /^define internal [^\n]*@sc_b?f_[^\n(]*fn_/.test(def)); + expect(closures.length).toBeGreaterThan(0); + for (const def of closures) expect(def).not.toContain("call void @scr_unwind_raise()"); +}); + +test("recursive functions keep the pending flag, also when they recurse through a callback", async () => { + const source = ` +function leaf(n: number): number { + if (n < 0) throw new RangeError("negative"); + return n; +} +function one(n: number): number { return leaf(n) + 1; } +function two(n: number): number { return one(n) + 1; } +function three(n: number): number { return two(n) + 1; } +function four(n: number): number { + const parts: string[] = ["a" + n]; + const r = three(n); + return r + parts.length; +} +function walk(n: number, depth: number): number { + const parts: string[] = ["w" + n]; + const r = depth === 0 ? four(n) : walk(n, depth - 1); + return r + parts.length; +} +function visit(n: number, depth: number, each: (n: number, depth: number) => number): number { + const parts: string[] = ["v" + n]; + const r = depth === 0 ? four(n) : each(n, depth - 1); + return r + parts.length; +} +const each = (n: number, depth: number): number => visit(n, depth, each); +for (let i = -1; i < 2; i++) { + try { console.log(walk(i, 3), visit(i, 3, each)); } catch (e) { console.log((e as Error).message); } +} +`; + const ir = emitLlvmModule(await moduleFor(source), LINUX); + expect(body(ir, "four")).toContain("call void @scr_unwind_raise()"); + for (const name of ["walk", "visit"]) { + expect(body(ir, name)).not.toContain("@scr_unwind_raise"); + expect(body(ir, name)).toMatch(/invoke double @sc_b?f_[^(]*four/); + } +}); + +test("without the option, and off Linux, emission keeps the pending-flag ABI", async () => { + const mod = await moduleFor(SOURCE); + for (const options of [{}, { targetTriple: "arm64-apple-macosx14.0.0", unwindAbi: true }]) { + const ir = emitLlvmModule(mod, options); + expect(ir).not.toContain("invoke "); + expect(ir).not.toContain("@scr_unwind_raise"); + expect(ir).not.toContain("personality"); + } +}); + +test("bridgePlainUnwindCalls keeps the pending-flag contract at plain calls of raising functions", () => { + const raising = [ + "define internal ptr @sc_bf_raises(ptr %p) #0 { ; raises", + "entry:", + " call void @scr_unwind_raise()", + " unreachable", + "}", + ].join("\n"); + const caller = [ + "define internal ptr @sc_f_adapter(ptr %p0) #0 {", + "entry:", + " %result = call ptr @sc_bf_raises(ptr %p0) ; borrowed result", + " br label %next", + "next:", + " %v = phi ptr [ %result, %entry ]", + " ret ptr %v", + "}", + ].join("\n"); + const [out] = bridgePlainUnwindCalls([`${raising}\n${caller}`]); + expect(out).toContain( + "%result.ub = invoke ptr @sc_bf_raises(ptr %p0) to label %ub.n.0 unwind label %ub.lp.0", + ); + expect(out).toContain("%result = phi ptr [ %result.ub, %ub.n.0 ], [ null, %ub.lp.0 ]"); + // The split block's successor now comes from its last part. + expect(out).toContain("%v = phi ptr [ %result, %ub.ok.0 ]"); + expect(out).toMatch( + /define internal ptr @sc_f_adapter\(ptr %p0\) #0 personality ptr @__gcc_personality_v0 \{/, + ); + expect(out).toContain("declare i32 @__gcc_personality_v0(...)"); +}); + +test("an invoked wrapper of a raising function resumes the unwind; its address must not escape", () => { + const raising = [ + "define internal double @sc_bf_body.i32(i32 %a) #0 {", + "entry:", + " call void @scr_unwind_raise()", + " unreachable", + "}", + ].join("\n"); + const wrapper = [ + "define internal double @sc_bf_body(double %a) #0 {", + "entry:", + " %i = fptosi double %a to i32", + " %r = call double @sc_bf_body.i32(i32 %i)", + " ret double %r", + "}", + ].join("\n"); + const caller = [ + "define internal double @sc_bf_caller(double %a) #0 personality ptr @__gcc_personality_v0 {", + "entry:", + " %r = invoke double @sc_bf_body(double %a) to label %ok unwind label %lp", + "lp:", + " %x = landingpad { ptr, i32 } cleanup", + " call void @scr_unwind_raise()", + " unreachable", + "ok:", + " ret double %r", + "}", + ].join("\n"); + const [out] = bridgePlainUnwindCalls([[raising, wrapper, caller].join("\n")]); + expect(out).toContain( + "%r = invoke double @sc_bf_body.i32(i32 %i) to label %ub.ok.0 unwind label %ub.lp.0", + ); + expect(out).toContain("resume { ptr, i32 } %ub.v.0"); + expect(() => + bridgePlainUnwindCalls([ + `${raising}\n@table = internal constant [1 x ptr] [ptr @sc_bf_body.i32]`, + ]), + ).toThrow(UnwindAbiViolation); + try { + bridgePlainUnwindCalls([ + `${raising}\n@table = internal constant [1 x ptr] [ptr @sc_bf_body.i32]`, + ]); + } catch (error) { + expect([...(error as UnwindAbiViolation).symbols]).toEqual(["sc_bf_body.i32"]); + } +}); + +test("an invoked function that never tests the pending flag needs no raise", () => { + const raising = [ + "define internal void @sc_bf_raises() #0 {", + "entry:", + " call void @scr_unwind_raise()", + " unreachable", + "}", + ].join("\n"); + const leaf = [ + "define internal double @sc_bf_leaf() #0 {", + "entry:", + " ret double 1.0", + "}", + ].join("\n"); + const caller = (callee: string) => + [ + "define internal double @sc_bf_caller() #0 personality ptr @__gcc_personality_v0 {", + "entry:", + ` %r = invoke double @${callee}() to label %ok unwind label %lp`, + "lp:", + " %x = landingpad { ptr, i32 } cleanup", + " call void @scr_unwind_raise()", + " unreachable", + "ok:", + " ret double %r", + "}", + ].join("\n"); + expect(() => + bridgePlainUnwindCalls([[raising, leaf, caller("sc_bf_leaf")].join("\n")]), + ).not.toThrow(); + const testing = leaf.replace( + " ret double 1.0", + " %f = load i32, ptr @scr_exc_flag\n ret double 1.0", + ); + expect(() => + bridgePlainUnwindCalls([[raising, testing, caller("sc_bf_leaf")].join("\n")]), + ).toThrow(UnwindAbiViolation); +}); + +test("calls that cannot unwind are marked nounwind", () => { + const raising = [ + "define internal void @sc_bf_raises(ptr %b) #0 {", + "entry:", + " %n = call i64 @scr_bytes_length(ptr %b) ; length", + " call void @scr_bytes_release(ptr %b), !dbg !4", + " %r = call ptr %b(ptr %b)", + " call void @scr_unwind_raise()", + " unreachable", + "}", + ].join("\n"); + const [out] = bridgePlainUnwindCalls([raising]); + expect(out).toContain("%n = call i64 @scr_bytes_length(ptr %b) nounwind ; length"); + expect(out).toContain("call void @scr_bytes_release(ptr %b) nounwind, !dbg !4"); + expect(out).toContain("%r = call ptr %b(ptr %b) nounwind"); + expect(out).toContain(" call void @scr_unwind_raise()\n"); +}); diff --git a/packages/compiler/src/backend/llvm/unwind-abi.ts b/packages/compiler/src/backend/llvm/unwind-abi.ts new file mode 100644 index 000000000..4dc611913 --- /dev/null +++ b/packages/compiler/src/backend/llvm/unwind-abi.ts @@ -0,0 +1,491 @@ +/* The functions that propagate exceptions by unwinding instead of the + * pending flag (see unwindAbiFunctions). */ +import type { IrExpr, IrModule } from "../../ir/ir.js"; +import { everyStmtList } from "../../ir/traverse.js"; + +/** How many calls above a `throw` functions keep the pending flag. */ +const THROW_LEVELS = 3; +/** The call graph's node for calls through values. */ +const ANY_FUNCTION = " any function"; + +/** The nodes of `edges` on a cycle (a recursion), by Tarjan's algorithm + * without recursion of its own: a large module's call chains are deeper + * than the host stack. */ +function recursiveFunctions(edges: ReadonlyMap): Set { + const index = new Map(); + const low = new Map(); + const stack: string[] = []; + const onStack = new Set(); + const result = new Set(); + let counter = 0; + const enter = (name: string): { name: string; next: number } => { + index.set(name, counter); + low.set(name, counter++); + stack.push(name); + onStack.add(name); + return { name, next: 0 }; + }; + for (const root of edges.keys()) { + if (index.has(root)) continue; + const work = [enter(root)]; + while (work.length > 0) { + const top = work[work.length - 1]!; + const out = edges.get(top.name) ?? []; + if (top.next < out.length) { + const callee = out[top.next++]!; + if (!index.has(callee)) work.push(enter(callee)); + else if (onStack.has(callee)) + low.set(top.name, Math.min(low.get(top.name)!, index.get(callee)!)); + continue; + } + work.pop(); + const parent = work[work.length - 1]; + if (parent !== undefined) + low.set(parent.name, Math.min(low.get(parent.name)!, low.get(top.name)!)); + if (low.get(top.name) !== index.get(top.name)) continue; + const members: string[] = []; + let member: string; + do { + member = stack.pop()!; + onStack.delete(member); + members.push(member); + } while (member !== top.name); + if (members.length > 1 || out.includes(top.name)) for (const m of members) result.add(m); + } + } + return result; +} + +/** The may-throw functions whose exceptions reach their callers by + * unwinding the native stack (scr_unwind_raise) instead of returning with + * the pending flag set. Their callers call them with `invoke`, so the call + * needs no flag test; the landing pad runs the frame's unwind. + * + * A function qualifies only when every way it can be reached is a direct + * `call` from emitted code that can lower it as an `invoke`: it is not + * async or a generator, not a constructor, not a method a virtual call can + * reach or one a vtable holds (an overridden method), not a closure + * target, not the entry or a library export, not named by any node other + * than a `call`'s callee, and not called from an async or generator body + * (coroutine bodies keep plain calls). + * + * A native unwind costs about 2 µs a raise and 0.1 µs a frame, against a + * few nanoseconds for a flag test, so the functions an exception is likely to + * cross many of keep the flag: those with a `throw` or rethrow of their own + * and those up to THROW_LEVELS calls above one (a handler near the throw + * takes it without a native unwind), and the recursive ones (deep stacks + * are recursions). */ +export function unwindAbiFunctions( + mod: IrModule, + mayThrow: ReadonlySet, + excluded: ReadonlySet = new Set(), +): Set { + const names = new Set(mod.functions.map((fn) => fn.name)); + const blocked = new Set(excluded); + blocked.add(mod.entry); + // The runtime calls the error property readers through the pointers the + // module installs (emit-module.ts). + blocked.add("%error.name.read"); + blocked.add("%error.message.read"); + for (const entry of mod.lib?.exports ?? []) blocked.add(entry.fnName); + // Methods a virtual call can reach, and the overridden methods of class + // hierarchies (whose vtables list them), are called through pointers; the + // others are called directly, like functions. A virtual call reaches its listed + // targets, or else the method in its receiver class's hierarchy; a + // receiver that is no class of the module (an interface) may reach any + // class's method of that name. + const classes = new Map((mod.classes ?? []).map((cls) => [cls.name, cls])); + const anyClass = new Set(); + for (const fn of mod.functions) + everyStmtList(fn.body, { + expr: (e: IrExpr) => { + if (e.kind !== "virtualCall") return true; + if (e.targets !== undefined) for (const target of e.targets) blocked.add(target); + else if (classes.has(e.className)) blocked.add(`%${e.className}.${e.method}`); + else anyClass.add(e.method); + return true; + }, + stmt: () => true, + }); + // A hierarchy's vtables hold the methods declared by more than one of its + // classes (an override and what it overrides). + const rootOf = (name: string): string => { + let cls = classes.get(name); + while (cls?.base !== undefined && classes.has(cls.base)) cls = classes.get(cls.base); + return cls?.name ?? name; + }; + const declarers = new Map(); + for (const cls of mod.classes ?? []) + for (const m of cls.methods ?? []) { + const key = `${rootOf(cls.name)}\0${m}`; + declarers.set(key, (declarers.get(key) ?? 0) + 1); + } + for (const cls of mod.classes ?? []) { + blocked.add(`%${cls.name}.constructor`); + blocked.add(`%${cls.name}.construct`); + const root = rootOf(cls.name); + for (const m of cls.methods ?? []) + if (anyClass.has(m) || declarers.get(`${root}\0${m}`)! > 1) blocked.add(`%${cls.name}.${m}`); + } + // Any function a node names other than as a call's callee may be reached + // through a pointer. + const referenced = new Set(); + const noteNames = (node: object, skip: string | undefined): void => { + for (const key in node) { + if (key === skip) continue; + const value = (node as Record)[key]; + if (typeof value === "string" && names.has(value)) referenced.add(value); + } + }; + // The call graph, for the functions near a throw and the recursive ones. + // Calls through values (closures, class values, dynamic calls, callbacks + // passed to the runtime) reach ANY_FUNCTION, which reaches every function + // a value can hold. + const throws = new Set(); + const callers = new Map(); + const callees = new Map(); + const edge = (from: string, to: string): void => { + let list = callers.get(to); + if (list === undefined) callers.set(to, (list = [])); + list.push(from); + let out = callees.get(from); + if (out === undefined) callees.set(from, (out = [])); + out.push(to); + }; + const methodsNamed = new Map(); + for (const cls of mod.classes ?? []) + for (const m of cls.methods ?? []) { + let list = methodsNamed.get(m); + if (list === undefined) methodsNamed.set(m, (list = [])); + list.push(`%${cls.name}.${m}`); + } + for (const fn of mod.functions) { + const coroutine = fn.async === true || fn.generator !== undefined; + everyStmtList(fn.body, { + expr: (e: IrExpr) => { + const kind = e.kind as string; + if (kind === "throw" || kind === "rethrow") throws.add(fn.name); + if (e.kind === "call") { + edge(fn.name, e.callee); + // The entry's callees (the module initializers) run once: on the + // flag they keep landing pads out of the top-level code inlined + // into the entry. + if (coroutine || fn.name === mod.entry) blocked.add(e.callee); + noteNames(e, "callee"); + return true; + } + noteNames(e, undefined); + if (e.kind === "virtualCall") + for (const target of e.targets ?? methodsNamed.get(e.method) ?? []) edge(fn.name, target); + else if (e.kind === "new") { + edge(fn.name, `%${e.className}.constructor`); + edge(fn.name, `%${e.className}.construct`); + } else if (kind === "callValue" || kind === "dynCall" || kind === "newValue") + edge(fn.name, ANY_FUNCTION); + else if ("args" in e) { + const args: unknown = e.args; + if (Array.isArray(args)) + for (const arg of args as IrExpr[]) + if (arg.type.kind === "func") edge(fn.name, ANY_FUNCTION); + } + return true; + }, + stmt: (s) => { + const kind = s.kind as string; + if (kind === "throw" || kind === "rethrow") throws.add(fn.name); + noteNames(s, undefined); + return true; + }, + }); + } + for (const name of referenced) { + blocked.add(name); + edge(ANY_FUNCTION, name); + } + // A throw starts on the pending flag, and so do the functions up to + // THROW_LEVELS calls above it. The union narrowing helpers' throws (a + // failed narrowing) don't count for their callers. + for (const name of throws) blocked.add(name); + let level = [...throws].filter((name) => !name.startsWith("%union.")); + const near = new Set(level); + for (let i = 0; i < THROW_LEVELS; i++) { + const next: string[] = []; + for (const name of level) + for (const caller of callers.get(name) ?? []) + if (!near.has(caller)) { + near.add(caller); + next.push(caller); + } + level = next; + } + for (const name of near) blocked.add(name); + for (const name of recursiveFunctions(callees)) blocked.add(name); + const result = new Set(); + for (const fn of mod.functions) { + if (!mayThrow.has(fn.name) || blocked.has(fn.name)) continue; + if (fn.async === true || fn.generator !== undefined) continue; + result.add(fn.name); + } + return result; +} + +/** What an unwinding-ABI function's unwind ends with when no handler of + * its own takes the exception. */ +export const UNWIND_RAISE = "call void @scr_unwind_raise()\n unreachable"; +/** What the unwind of a landing pad ends with when no handler of the + * function takes the exception: the unwind goes on (`resume`) with the + * exception the landing pad stored in UNWIND_SLOT. The block builder + * expands it (BlockBuilder.terminate). */ +export const UNWIND_RESUME = "resume { ptr, i32 } "; +/** The function's slot for the exception its landing pads take. */ +export const UNWIND_SLOT = "%unw.exc"; +/** scr_unwind_raise's declaration. It is not `noreturn`: sanitized builds + * would call the sanitizer's no-return hook before it, which fails on fiber + * stacks; scr_unwind_raise clears the unwound frames' shadow itself. */ +export const UNWIND_RAISE_DECLARATION = "declare void @scr_unwind_raise() cold"; + +/** The personality of functions with landing pads: the landing pads are + * cleanups that scr_unwind_raise's forced unwind runs. */ +export const UNWIND_PERSONALITY = "personality ptr @__gcc_personality_v0"; + +/** An unwind leaving the function: a raise, or a landing pad's `resume`. */ +const UNWINDS = /call void @scr_unwind_raise\(\)|\n\s+resume /; +/** A pending-flag test: the thread's exception word, the active cell, or the runtime query. */ +const PENDING_TEST = /@scr_exc_flag\b|%exc\.cell\b|@scr_exc_active\b|@scr_exc_pending\(/; + +/** Functions emission found breaking the unwinding ABI's rules + * (bridgePlainUnwindCalls), by symbol: the caller emits the module again + * with them on the pending-flag ABI. */ +export class UnwindAbiViolation extends Error { + readonly symbols: ReadonlySet; + constructor(symbols: ReadonlySet) { + super(`unwinding ABI: ${[...symbols].slice(0, 3).join(", ")} must keep the pending flag`); + this.symbols = symbols; + } +} +const DEFINE_NAME = /^define [^@\n]*@([\w.$%-]+)\(/; +const PLAIN_CALL = /^(\s*)(%[\w.$-]+) = call (.*?)@([\w.$%-]+)\(|^(\s*)call (.*?)@([\w.$%-]+)\(/; +const RETURN_ATTRIBUTES = + /\b(?:zeroext|signext|noalias|nonnull|noundef|inreg|dereferenceable(?:_or_null)?\(\d+\)|align \d+)\s+/g; + +/** A call instruction's line. */ +const CALL_LINE = /^\s*(?:%[\w.$-]+ = )?(?:(?:tail|musttail|notail) )?call /; +/** A call's callee (a function or a register) and the parenthesis that + * opens its arguments. */ +const CALLEE = /[@%][\w.$%-]+\(/; + +/** The index of the parenthesis that closes the argument list opened at `open`. */ +function argumentsEnd(line: string, open: number): number { + let depth = 0; + let close = open; + for (; close < line.length; close++) { + const c = line.charAt(close); + if (c === "(") depth++; + else if (c === ")" && --depth === 0) break; + } + return close; +} + +/** A call that cannot unwind, marked so: only scr_unwind_raise and the + * raising functions propagate an unwind (runtime code and every function + * reached through a pointer return normally), so every other call is + * `nounwind`, and the inliner keeps it a call where it inlines a raising + * function at an invoke. */ +function markNounwind(line: string): string { + const call = line.indexOf("call "); + const callee = CALLEE.exec(line.slice(call)); + if (callee === null || callee[0] === "@scr_unwind_raise(") return line; + const close = argumentsEnd(line, call + callee.index + callee[0].length - 1); + return close >= line.length + ? line + : `${line.slice(0, close + 1)} nounwind${line.slice(close + 1)}`; +} + +/** 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 { + if (type === "ptr") return "null"; + if (type === "double" || type === "float") return "0.0"; + if (/^i\d+$/.test(type)) return "0"; + return "zeroinitializer"; +} + +/** Rewrites every remaining plain call of an unwinding-ABI function (one + * that raises, scr_unwind_raise, or resumes an unwind) in `parts`, the module's text, into an + * invoke whose landing pad continues after the call with the exception + * pending and the call's dummy result: the pending-flag contract those + * call sites were emitted for, whichever lowering emitted them. Calls of + * 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 )/)); + for (const chunks of split) + for (const chunk of chunks) { + const m = DEFINE_NAME.exec(chunk); + if (m !== null && UNWINDS.test(chunk)) raisers.add(m[1]!); + } + if (raisers.size === 0) return parts; + // An invoke's callee must raise or never test the pending flag: one that + // returned with the flag pending would pass the exception by its invoking + // caller unseen. A wrapper an invoke reaches (an int32 or borrowed-ABI + // entry forwarding to the body) propagates its callee's unwind instead + // (`resume`), so it raises too. A raising function must be reached only by + // calls: a pointer to it (a vtable or table entry, a runtime callback) + // could be called without a landing pad. Functions that break either rule + // go back to the pending-flag ABI (UnwindAbiViolation). + const violations = new Set(); + const invoked = new Set(); + const testsFlag = new Set(); + for (const chunks of split) + for (const chunk of chunks) { + const m = DEFINE_NAME.exec(chunk); + if (m !== null && PENDING_TEST.test(chunk)) testsFlag.add(m[1]!); + for (const c of chunk.matchAll(/ invoke [^@\n]*@([\w.$%-]+)\(/g)) invoked.add(c[1]!); + } + const resumers = new Set(); + for (let grew = true; grew;) { + grew = false; + for (const chunks of split) + for (const chunk of chunks) { + const m = DEFINE_NAME.exec(chunk); + const name = m?.[1]; + if (name === undefined || raisers.has(name) || !invoked.has(name)) continue; + if (![...chunk.matchAll(/ call [^@\n]*@([\w.$%-]+)\(/g)].some((c) => raisers.has(c[1]!))) + continue; + if (testsFlag.has(name)) continue; + raisers.add(name); + resumers.add(name); + grew = true; + } + } + for (const name of invoked) if (!raisers.has(name) && testsFlag.has(name)) violations.add(name); + 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]!); + } + } + if (violations.size > 0) throw new UnwindAbiViolation(violations); + let personality = false; + let counter = 0; + const out = split.map((chunks) => + chunks + .map((chunk) => { + if (!DEFINE_NAME.test(chunk) || !chunk.includes("call ")) return chunk; + const lines = chunk.split("\n"); + const resume = resumers.has(DEFINE_NAME.exec(chunk)![1]!); + const result: string[] = []; + // The label a block's terminator now follows, for phi operands. + const moved = new Map(); + let block = ""; + let changed = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]!; + const label = /^([\w.$-]+):/.exec(line); + if (label !== null) block = label[1]!; + const m = PLAIN_CALL.exec(line); + const callee = m === null ? undefined : (m[4] ?? m[7]); + if (m === null || callee === undefined || !raisers.has(callee)) { + const marked = CALL_LINE.test(line) ? markNounwind(line) : line; + changed ||= marked !== line; + result.push(marked); + continue; + } + // The call's text from its callee to the end of the argument list. + const close = argumentsEnd(line, line.indexOf(`@${callee}(`) + callee.length + 1); + const head = line.slice(line.indexOf("call ") + 5, close + 1); + const tail = line.slice(close + 1); + const meta = /,\s*!/.exec(tail); + const attrs = ( + meta === null ? tail.replace(/;.*$/, "") : tail.slice(0, meta.index) + ).trimEnd(); + const dbg = meta === null ? "" : tail.slice(meta.index).replace(/\s;.*$/, ""); + const n = counter++; + if (block === "") { + result.splice(1, 0, "entry.ub:"); + block = "entry.ub"; + } + const indent = m[1] ?? m[5] ?? " "; + const ok = `ub.ok.${n}`; + const pad = `ub.lp.${n}`; + if (resume) { + // The wrapper holds nothing to release: the unwind goes on to + // its caller's landing pad. + const assign = m[2] !== undefined ? `${m[2]} = ` : ""; + result.push( + `${indent}${assign}invoke ${head}${attrs} to label %${ok} unwind label %${pad}${dbg}`, + ); + result.push( + `${pad}:`, + `${indent}%ub.v.${n} = landingpad { ptr, i32 } cleanup`, + `${indent}resume { ptr, i32 } %ub.v.${n}`, + ); + result.push(`${ok}:`); + } else if (m[2] !== undefined) { + const type = (m[3] ?? "").replace(RETURN_ATTRIBUTES, "").trim(); + const normal = `ub.n.${n}`; + result.push( + `${indent}${m[2]}.ub = invoke ${head}${attrs} to label %${normal} unwind label %${pad}${dbg}`, + ); + result.push(`${normal}:`, `${indent}br label %${ok}`); + result.push( + `${pad}:`, + `${indent}%ub.v.${n} = landingpad { ptr, i32 } cleanup`, + `${indent}br label %${ok}`, + ); + result.push( + `${ok}:`, + `${indent}${m[2]} = phi ${type} [ ${m[2]}.ub, %${normal} ], [ ${dummyValue(type)}, %${pad} ]`, + ); + } else { + result.push( + `${indent}invoke ${head}${attrs} to label %${ok} unwind label %${pad}${dbg}`, + ); + result.push( + `${pad}:`, + `${indent}%ub.v.${n} = landingpad { ptr, i32 } cleanup`, + `${indent}br label %${ok}`, + ); + result.push(`${ok}:`); + } + moved.set(block, ok); + changed = true; + } + if (!changed) return chunk; + if (moved.size === 0) return result.join("\n"); + personality = true; + const header = result[0]!; + const brace = header.lastIndexOf(" {"); + if (!header.includes("personality") && brace >= 0) { + // Before the function's metadata attachments (`!dbg`), if any. + const dbg = header.slice(0, brace).search(/\s!dbg !\d+$/); + const at = dbg >= 0 ? dbg : brace; + result[0] = `${header.slice(0, at)} ${UNWIND_PERSONALITY}${header.slice(at)}`; + } + // A split block's successors now come from its last part. + if (moved.size > 0) + for (let i = 0; i < result.length; i++) { + const line = result[i]!; + if (!line.includes(" phi ")) continue; + result[i] = line.replace(/, %([\w.$-]+) \]/g, (all, name: string) => { + const to = moved.get(name); + return to === undefined ? all : `, %${to} ]`; + }); + } + return result.join("\n"); + }) + .join("\n"), + ); + if (personality && !out.some((part) => part.includes("@__gcc_personality_v0(...)"))) + out[0] = `declare i32 @__gcc_personality_v0(...)\n${out[0]}`; + return out; +} diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index ceb162bcc..1364bca4d 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -43,6 +43,7 @@ import { cacheTargetIdentity, isZigDriver, isMuslTarget, + type CcDriver, } from "./driver.js"; import { resolveProgramShardMergeIdentity, @@ -821,6 +822,7 @@ export async function compileCInternal( // generated/native input that references inflate symbols. Cross // and targetless Zig builds use vendored zlib objects in the input section above. ...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []), + ...unwinderLinkArgs(driver), // glibc keeps libm separate from libc. This must trail the generated // program and every native FFI input because GNU ld resolves archives // from left to right. @@ -1068,6 +1070,7 @@ export async function compileCInternal( ...(mbedtls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []), ...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []), ...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []), + ...unwinderLinkArgs(driver), ...driver.linkArgs, ...executableLinkFlags, ]; @@ -1080,6 +1083,7 @@ export async function compileCInternal( ...(mbedtls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []), ...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []), ...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []), + ...unwinderLinkArgs(driver), ...driver.linkArgs, ...executableLinkFlags, ]; @@ -1689,3 +1693,11 @@ export async function compileC(opts: CcOptions): Promise { await rm(`${opts.outPath}.dSYM`, { recursive: true, force: true }); } } + +/** Linux runtimes raise through the native unwinder (scr_unwind_raise): + * Zig's libunwind, or the toolchain's libgcc_eh. Both are static archives, + * so programs load no shared library for them. */ +function unwinderLinkArgs(driver: CcDriver): string[] { + if (targetPlatform(driver) !== "linux") return []; + return isZigDriver(driver) ? ["-lunwind"] : ["-lgcc_eh"]; +} diff --git a/packages/compiler/src/backend/native/runtime-inputs.ts b/packages/compiler/src/backend/native/runtime-inputs.ts index c7c5bc32b..d32867331 100644 --- a/packages/compiler/src/backend/native/runtime-inputs.ts +++ b/packages/compiler/src/backend/native/runtime-inputs.ts @@ -53,6 +53,7 @@ export const EXECUTABLE_RUNTIME_SOURCES = [ "scr_object.c", "scr_union.c", "scr_exception.c", + "scr_unwind.c", "scr_error.c", "scr_console.c", "scr_lib.c", diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d1c97cc..4fb64968b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -875,6 +875,7 @@ async function prepareExecutableInput( pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: useRuntimePack, + unwindAbi: buildPlatform === "linux", objectAudit: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 67a4b5a9e..e90c1f043 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -402,6 +402,7 @@ export function prepareNativeExecutable( pointerBits: toolchain.target.pointerBits, wasi: toolchain.target.platform === "wasi", runtimeAbiMarker: outputKind === "obj" || outputKind === "exe", + unwindAbi: outputKind === "exe" && toolchain.target.platform === "linux", objectAudit: options.sanitize === true, lowRegion: lowRegionTarget(toolchain.target.platform, options.sanitize === true), functionWorkers: emitWorkers(), diff --git a/packages/runtime-linux-arm64-gnu/scripts/build.mjs b/packages/runtime-linux-arm64-gnu/scripts/build.mjs index 729c3fbb6..d16fb1528 100644 --- a/packages/runtime-linux-arm64-gnu/scripts/build.mjs +++ b/packages/runtime-linux-arm64-gnu/scripts/build.mjs @@ -24,6 +24,10 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ }, ...toolchain, compileFlags: ["-ffunction-sections", "-fdata-sections"], - systemLibraries: [{ name: "m", predicate: true }], + // scr_unwind_raise: the static libgcc_eh, which loads no shared library. + systemLibraries: [ + { name: "m", predicate: true }, + { name: "gcc_eh", predicate: true }, + ], }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-linux-arm64-musl/scripts/build.mjs b/packages/runtime-linux-arm64-musl/scripts/build.mjs index 10635d857..6c9b38244 100644 --- a/packages/runtime-linux-arm64-musl/scripts/build.mjs +++ b/packages/runtime-linux-arm64-musl/scripts/build.mjs @@ -26,6 +26,10 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ }, targetArgs: ["-target", "aarch64-linux-musl"], compileFlags: ["-ffunction-sections", "-fdata-sections"], - systemLibraries: [{ name: "m", predicate: true }], + // scr_unwind_raise: Zig's libunwind. + systemLibraries: [ + { name: "m", predicate: true }, + { name: "unwind", predicate: true }, + ], }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-linux-x64-gnu/scripts/build.mjs b/packages/runtime-linux-x64-gnu/scripts/build.mjs index 811bb537b..53df5f7e7 100644 --- a/packages/runtime-linux-x64-gnu/scripts/build.mjs +++ b/packages/runtime-linux-x64-gnu/scripts/build.mjs @@ -24,6 +24,10 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ }, ...toolchain, compileFlags: ["-ffunction-sections", "-fdata-sections"], - systemLibraries: [{ name: "m", predicate: true }], + // scr_unwind_raise: the static libgcc_eh, which loads no shared library. + systemLibraries: [ + { name: "m", predicate: true }, + { name: "gcc_eh", predicate: true }, + ], }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-linux-x64-musl/scripts/build.mjs b/packages/runtime-linux-x64-musl/scripts/build.mjs index 33fc4c3d1..88538de76 100644 --- a/packages/runtime-linux-x64-musl/scripts/build.mjs +++ b/packages/runtime-linux-x64-musl/scripts/build.mjs @@ -26,6 +26,10 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ }, targetArgs: ["-target", "x86_64-linux-musl"], compileFlags: ["-ffunction-sections", "-fdata-sections"], - systemLibraries: [{ name: "m", predicate: true }], + // scr_unwind_raise: Zig's libunwind. + systemLibraries: [ + { name: "m", predicate: true }, + { name: "unwind", predicate: true }, + ], }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 600637ba6..4df534739 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -30,6 +30,7 @@ export const BASE_RUNTIME_SOURCES = [ "scr_object.c", "scr_union.c", "scr_exception.c", + "scr_unwind.c", "scr_error.c", "scr_console.c", "scr_lib.c", diff --git a/packages/runtime/src/scr_exception.h b/packages/runtime/src/scr_exception.h index e99f84243..63dd4a3ca 100644 --- a/packages/runtime/src/scr_exception.h +++ b/packages/runtime/src/scr_exception.h @@ -123,6 +123,11 @@ static inline ScrExcCell *scr_exc_swap_cell_inline(ScrExcCell *cell) { _Static_assert(offsetof(ScrExcCell, kind) == 0 && sizeof(ScrExcKind) == 4 && SCR_EXC_NONE == 0, "the LLVM emitter's inline pending check loads an i32 kind at offset 0"); +/* Unwinds the native stack to the nearest landing pad of emitted code with + * the exception pending in the active cell (the unwinding ABI of emitted + * functions, unwind-abi.ts). Linux (glibc and musl) only; it never + * returns. */ +SCR_COLD _Noreturn void scr_unwind_raise(void); SCR_COLD void scr_throw_f64(double v); SCR_COLD void scr_throw_bool(bool v); SCR_COLD void scr_throw_str(ScrStr *v); /* takes ownership */ diff --git a/packages/runtime/src/scr_unwind.c b/packages/runtime/src/scr_unwind.c new file mode 100644 index 000000000..9bdf2dbd2 --- /dev/null +++ b/packages/runtime/src/scr_unwind.c @@ -0,0 +1,76 @@ +/* scr_unwind_raise: the native unwind of the unwinding exception ABI of + * emitted functions (the compiler's unwind-abi.ts). Linux (glibc and musl) + * only; elsewhere this unit is empty. */ +#include "scr_runtime.h" + +#if defined(__linux__) && !defined(__ANDROID__) && !defined(__wasi__) +#include +#include +#include +#include +#include + +#if defined(__SANITIZE_ADDRESS__) +#define SCR_UNWIND_ASAN 1 +#elif defined(__has_feature) +#if __has_feature(address_sanitizer) +#define SCR_UNWIND_ASAN 1 +#endif +#endif +#ifdef SCR_UNWIND_ASAN +#include +#endif + +#ifdef SCR_UNWIND_ASAN +/* The lowest stack address the unwind has not cleared yet. The unwind + * outlives the frame that started it: landing pads resume it. */ +static SCR_TL uintptr_t scr_unwind_low; +#endif + +/* Emitted landing pads are cleanups: they release the frame's references + * and then either take the exception (a handler of theirs, or a frame on + * the pending-flag ABI that returns with it pending) or resume the unwind. + * So the unwind is forced, a single pass through every landing pad; the + * exception itself stays in the active cell. */ +static _Unwind_Reason_Code scr_unwind_stop(int version, _Unwind_Action actions, + _Unwind_Exception_Class cls, + struct _Unwind_Exception *exc, + struct _Unwind_Context *context, void *arg) { + (void)version; + (void)cls; + (void)exc; +#ifdef SCR_UNWIND_ASAN + /* The frames this unwind passes never return, so it clears their + * redzones' shadow itself. Emitted code calls scr_unwind_raise as an + * ordinary function: the sanitizer's hook before no-return calls needs + * stack bounds, which fiber stacks do not report. */ + uintptr_t cfa = _Unwind_GetCFA(context); + if (cfa > scr_unwind_low) { + __asan_unpoison_memory_region((void *)scr_unwind_low, cfa - scr_unwind_low); + scr_unwind_low = cfa; + } +#else + (void)context; +#endif + (void)arg; + if (actions & _UA_END_OF_STACK) { + fputs("scriptc: an exception unwound past the program's outermost frame\n", stderr); + abort(); + } + return _URC_NO_REASON; +} + +static SCR_TL struct _Unwind_Exception scr_unwind_exception; + +void scr_unwind_raise(void) { + struct _Unwind_Exception *exc = &scr_unwind_exception; + memset(exc, 0, sizeof *exc); + exc->exception_class = 0x5343524950540000ull; /* "SCRIPT\0\0" */ +#ifdef SCR_UNWIND_ASAN + scr_unwind_low = (uintptr_t)__builtin_frame_address(0); +#endif + _Unwind_ForcedUnwind(exc, scr_unwind_stop, NULL); + fputs("scriptc: the native unwinder could not raise an exception\n", stderr); + abort(); +} +#endif diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index a1ab3ca60..b23f61a0a 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -395,7 +395,9 @@ export function dynamicTierSymbols(names) { * bench/channel-messages and bench/worker-messages, added later, link them * through worker messages: * node:worker_threads values are dyn objects. bench/json-unions, added - * later too, decodes JSON.parse trees. bench/dynamic-methods and + * later too, decodes JSON.parse trees. bench/throw-chains, also added + * later, tests caught values with `instanceof`, as bench/validate-errors + * does. bench/dynamic-methods and * bench/dynamic-properties are untyped JavaScript. Every other bench workload is fully typed and must link none. The gate and * tests/harness/bench-dynamic-tier.test.ts enforce that, and the test also * fails when a listed workload stops linking them, so remove a workload here @@ -417,6 +419,7 @@ export const DYNAMIC_WORKLOADS = new Set([ "bench/log-summary", "bench/inventory-report", "bench/json-unions", + "bench/throw-chains", ]); /** Names of the functions (text and weak symbols) in `nm` output. Mach-O From 396c6944ecad5bda1d8ab9dd0179181aa0cf97a2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 11:20:47 -0500 Subject: [PATCH 072/395] Report how well the native compiler's profile matches its functions The native compiler is built with a committed optimization profile. A function whose control flow changed since the profile was collected is optimized as without one, so the profile loses its gain as compiler code changes, with nothing to show it. With --profile-use, the helper now counts what the profile annotation reports while it applies the profile: functions that took the profile's counts, functions whose control flow no longer matches (hash mismatch), and functions the profile has no record of. It sums the counts over partitions, keeps each partition's counts beside its cached summary, and prints them as one line of JSON after a successful emit. The compiler reads that line (an older helper prints none) and returns it as the executable artifact's profileMatch. build-native-cli prints the share of profiled functions that match, and warns below 90% that the profile should be refreshed. On main aa1c26845, a profile refreshed on that commit matches 92.7% of the compiler's profiled functions, and the one committed about 200 commits earlier 87.2%; that one kept about half of the fresh profile's gain on a cold tsc-ts build. --- native/llvm-codegen/src/emit.cpp | 157 +++++++++++++++++- .../compiler/src/backend/native-codegen.ts | 10 +- .../src/backend/native-profile.test.ts | 13 ++ .../compiler/src/backend/native-profile.ts | 39 +++++ packages/compiler/src/compile-types.ts | 3 + packages/compiler/src/index.ts | 5 + scripts/build-native-cli.mts | 25 +++ 7 files changed, 242 insertions(+), 10 deletions(-) diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 9cae164d3..35d7ce4f0 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -19,6 +19,7 @@ #include "llvm/CodeGen/MachineFunction.h" #include "llvm/CodeGen/MachineFunctionPass.h" #include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/IR/DiagnosticHandler.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IR/LegacyPassManager.h" @@ -41,11 +42,13 @@ #include "llvm/Support/SourceMgr.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/Threading.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/Utils/SplitModule.h" +#include #include #include #include @@ -170,6 +173,108 @@ void limitProfileImports() { (void)Limited; } +// How the --profile-use profile matched the functions it annotated, summed +// over partitions: functions that received its counts, functions whose +// control flow changed since it was collected (hash mismatch), and functions +// it has no record of (new, or never run in training: the merged profile is +// sparse). A partition reused from the cache without its counts is counted +// in Unknown. +struct ProfileMatch { + std::atomic Annotated{0}; + std::atomic Mismatched{0}; + std::atomic Missing{0}; + std::atomic Unknown{0}; + + void add(const ProfileMatch &Other) { + Annotated += Other.Annotated; + Mismatched += Other.Mismatched; + Missing += Other.Missing; + } + + std::string serialize() const { + return formatv("{0} {1} {2}", Annotated.load(), Mismatched.load(), + Missing.load()) + .str(); + } + + bool deserialize(StringRef Text) { + SmallVector Parts; + Text.split(Parts, ' '); + uint64_t Values[3]; + if (Parts.size() != 3) + return false; + for (size_t I = 0; I < 3; ++I) + if (Parts[I].getAsInteger(10, Values[I])) + return false; + Annotated += Values[0]; + Mismatched += Values[1]; + Missing += Values[2]; + return true; + } +}; + +// Counts the profile annotation's hash-mismatch and missing-function +// warnings instead of printing them. Every other diagnostic takes the +// context's default handling. +class ProfileMatchHandler : public DiagnosticHandler { +public: + explicit ProfileMatchHandler(ProfileMatch &Match) : Match(Match) {} + + bool handleDiagnostics(const DiagnosticInfo &Info) override { + if (Info.getKind() != DK_PGOProfile) + return false; + std::string Message; + raw_string_ostream Stream(Message); + DiagnosticPrinterRawOStream Printer(Stream); + Info.print(Printer); + if (StringRef(Message).contains("hash mismatch")) + ++Match.Mismatched; + else if (StringRef(Message).contains_insensitive("no profile data")) + ++Match.Missing; + else + return false; + return true; + } + +private: + ProfileMatch &Match; +}; + +// The annotation warns about functions the profile does not describe only +// when asked; the match counts need those warnings. Process-wide, set once. +void reportMissingProfiles() { + static const bool Reported = [] { + if (cl::Option *Option = + cl::getRegisteredOptions().lookup("pgo-warn-missing-function")) + Option->addOccurrence(0, "pgo-warn-missing-function", "true"); + return true; + }(); + (void)Reported; +} + +// Functions of `M` that the profile annotated with counts. +uint64_t annotatedFunctions(const Module &M) { + uint64_t Count = 0; + for (const Function &F : M) { + if (F.isDeclaration()) + continue; + std::optional Entry = F.getEntryCount(); + if (Entry && !Entry->isSynthetic()) + ++Count; + } + return Count; +} + +// Prints the profile match as the successful emit's one line of JSON. +void reportProfileMatch(const ProfileMatch &Match) { + json::Object Profile{{"annotated", Match.Annotated.load()}, + {"mismatched", Match.Mismatched.load()}, + {"missing", Match.Missing.load()}, + {"unknown_partitions", Match.Unknown.load()}}; + json::Object Response{{"ok", true}, {"profile", std::move(Profile)}}; + outs() << formatv("{0}\n", json::Value(std::move(Response))); +} + // Reads the profile's header once, before any partition needs it: it must // hold IR-level counts. std::optional inspectProfile(const EmitOptions &Options) { @@ -387,9 +492,16 @@ std::optional publish(StringRef TemporaryPath, std::optional emitModule(Module &M, TargetMachine &Machine, OptimizationLevel Level, std::optional Profile, CodeGenFileType Type, - StringRef OutputPath, std::vector> &Temporaries) { + StringRef OutputPath, std::vector> &Temporaries, + ProfileMatch &Match) { + bool Annotating = Profile && Profile->Action == PGOOptions::IRUse; + if (Annotating) + M.getContext().setDiagnosticHandler( + std::make_unique(Match)); Pipeline P(Machine, Level, std::move(Profile)); P.PB.buildPerModuleDefaultPipeline(Level).run(M, P.MAM); + if (Annotating) + Match.Annotated += annotatedFunctions(M); if (std::optional Failure = verify(M, "post_optimization_verification_failed")) return Failure; @@ -555,20 +667,32 @@ std::optional compileOptimizedPartitions(const EmitOptions &Options, const PartitionPlan &Plan, OptimizationLevel Level, StringRef Salt, const ArtifactCache &Cache, - std::vector> &Objects) { + std::vector> &Objects, + ProfileMatch &Match) { size_t Count = Plan.Modules.size(); + bool Annotating = !Options.ProfileUse.empty(); std::vector> Summaries(Count); SharedFailure Failures; forEachPartition(Count, [&](size_t I) { std::string Key = hashParts({Salt, "summary", Plan.Modules[I]}); - if ((Summaries[I] = Cache.lookup(Key, "bc"))) + if ((Summaries[I] = Cache.lookup(Key, "bc"))) { + if (Annotating) { + std::unique_ptr Counts = Cache.lookup(Key, "match"); + if (!Counts || !Match.deserialize(Counts->getBuffer())) + ++Match.Unknown; + } return; + } std::string LookupError; std::unique_ptr Machine = createTargetMachine(Options.Target, Options.OptLevel, LookupError); if (!Machine) return Failures.set({"target_machine_failed", LookupError}); LLVMContext Context; + ProfileMatch PartitionMatch; + if (Annotating) + Context.setDiagnosticHandler( + std::make_unique(PartitionMatch)); Expected> Loaded = loadPartition(Options, Plan.Modules[I], I, Context, *Machine); if (!Loaded) @@ -598,6 +722,12 @@ compileOptimizedPartitions(const EmitOptions &Options, /*EmitModuleHash=*/true)); MPM.run(Mod, P.MAM); } + if (Annotating) { + PartitionMatch.Annotated += annotatedFunctions(Mod); + Match.add(PartitionMatch); + // Stored before the summary, so a reused summary finds its counts. + Cache.store(Key, "match", PartitionMatch.serialize()); + } StringRef Contents(Bitcode.data(), Bitcode.size()); Cache.store(Key, "bc", Contents); Summaries[I] = MemoryBuffer::getMemBufferCopy(Contents); @@ -744,7 +874,8 @@ compileOptimizedPartitions(const EmitOptions &Options, std::optional compilePartitions(const EmitOptions &Options, const PartitionPlan &Plan, - std::vector> &Objects) { + std::vector> &Objects, + ProfileMatch &Match) { // Everything besides a partition's own bytes that determines its // artifacts. The caller's cache directory already identifies the exact // helper build; source content and partition count arrive per module. @@ -778,7 +909,7 @@ compilePartitions(const EmitOptions &Options, const PartitionPlan &Plan, return compileUnoptimizedPartitions(Options, Plan, Salt, Cache, Objects); return compileOptimizedPartitions(Options, Plan, optimizationLevel(Options.OptLevel), Salt, - Cache, Objects); + Cache, Objects, Match); } // Publishes in-memory partition objects through private sibling files. @@ -819,6 +950,8 @@ int emitPartitions(const EmitOptions &Options) { std::vector> Objects; std::optional Failure; bool Compiled = false; + // A failed attempt's counts are discarded with its objects. + auto Match = std::make_unique(); { ErrorOr> Input = MemoryBuffer::getFile(Options.Input, /*IsText=*/false, @@ -830,15 +963,16 @@ int emitPartitions(const EmitOptions &Options) { Input->reset(); PartitionPlan Plan{std::move(Text->Sources), std::move(Text->PublicSymbols)}; - Compiled = !compilePartitions(Options, Plan, Objects); + Compiled = !compilePartitions(Options, Plan, Objects, *Match); } } } if (!Compiled) { + Match = std::make_unique(); PartitionPlan Plan; Failure = splitParsedModule(Options, Plan); if (!Failure) - Failure = compilePartitions(Options, Plan, Objects); + Failure = compilePartitions(Options, Plan, Objects, *Match); } std::vector> Temporaries; if (!Failure) @@ -851,6 +985,8 @@ int emitPartitions(const EmitOptions &Options) { return reportError(Failure->Code, Failure->Message, Options.DiagnosticFormat); } + if (!Options.ProfileUse.empty()) + reportProfileMatch(*Match); return 0; } @@ -895,6 +1031,8 @@ int emit(const EmitOptions &Options) { return reportError("invalid_import", "runtime bitcode import requires an optimized build", Options.DiagnosticFormat); + if (!Options.ProfileUse.empty()) + reportMissingProfiles(); if (std::optional Failure = inspectProfile(Options)) return reportError(Failure->Code, Failure->Message, Options.DiagnosticFormat); @@ -957,9 +1095,10 @@ int emit(const EmitOptions &Options) { ? CodeGenFileType::ObjectFile : CodeGenFileType::AssemblyFile; std::vector> Temporaries; + ProfileMatch Match; std::optional Failure = emitModule(*Mod, *Machine, Level, profileOptions(Options), Type, - Options.Outputs[0], Temporaries); + Options.Outputs[0], Temporaries, Match); if (!Failure) Failure = publish(Temporaries[0], Options.Outputs[0]); if (Failure) { @@ -968,6 +1107,8 @@ int emit(const EmitOptions &Options) { return reportError(Failure->Code, Failure->Message, Options.DiagnosticFormat); } + if (!Options.ProfileUse.empty()) + reportProfileMatch(Match); return 0; } diff --git a/packages/compiler/src/backend/native-codegen.ts b/packages/compiler/src/backend/native-codegen.ts index 5b91c60e7..413fb7e02 100644 --- a/packages/compiler/src/backend/native-codegen.ts +++ b/packages/compiler/src/backend/native-codegen.ts @@ -30,6 +30,8 @@ import { compilerReleaseVersion } from "../library/sidecar.js"; import { nativeProfileHelperArgs, nativeProfileKey, + parseNativeProfileMatch, + type NativeProfileMatch, type NativeProfileOptions, } from "./native-profile.js"; import { writeTextParts } from "../text-parts.js"; @@ -69,6 +71,8 @@ export interface NativeCodegenArtifact { dependencies: NativeArtifactDependency[]; /** Every emitted file, starting with the requested output path. */ outputPaths: string[]; + /** How the build's profile matched, when it used one and the helper ran. */ + profileMatch?: NativeProfileMatch; } const resolvedHelperCache = new Map>(); @@ -458,7 +462,7 @@ export async function emitNativeArtifact( try { if (isLlvmFile(options.llvm)) await copyFile(options.llvm.file, input); else await writeLlvmSource(input, options.llvm, 0o600); - await invoke(helper.binaryPath, [ + const { stdout } = await invoke(helper.binaryPath, [ "emit", "--input", input, @@ -502,7 +506,9 @@ export async function emitNativeArtifact( for (let index = 0; index < stages.length; index++) await rename(stages[index]!, outputs[index]!); await pruneBuildCache(root); - return artifact; + 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)), diff --git a/packages/compiler/src/backend/native-profile.test.ts b/packages/compiler/src/backend/native-profile.test.ts index 590c39eef..b2cb0c75d 100644 --- a/packages/compiler/src/backend/native-profile.test.ts +++ b/packages/compiler/src/backend/native-profile.test.ts @@ -1,6 +1,7 @@ import { expect, test } from "vitest"; import { executableLinkInputs } from "./link-plan-core.js"; import { + parseNativeProfileMatch, nativeProfileHelperArgs, nativeProfileKey, nativeProfileRefusal, @@ -71,3 +72,15 @@ test("only the instrumented link adds the profile runtime", () => { "-fprofile-generate", ); }); + +test("the helper's profile match is read from its output", () => { + expect( + parseNativeProfileMatch( + '{"ok":true,"profile":{"annotated":120,"mismatched":7,"missing":40,"unknown_partitions":1}}\n', + ), + ).toEqual({ annotated: 120, mismatched: 7, missing: 40, unknownPartitions: 1 }); + // A helper that predates the report prints nothing. + expect(parseNativeProfileMatch("")).toBeNull(); + expect(parseNativeProfileMatch('{"ok":true}\n')).toBeNull(); + expect(parseNativeProfileMatch('{"ok":true,"profile":{"annotated":-1}}\n')).toBeNull(); +}); diff --git a/packages/compiler/src/backend/native-profile.ts b/packages/compiler/src/backend/native-profile.ts index 4cd766319..904994a7d 100644 --- a/packages/compiler/src/backend/native-profile.ts +++ b/packages/compiler/src/backend/native-profile.ts @@ -44,6 +44,45 @@ export function nativeProfileHelperArgs(profile: NativeProfileOptions | undefine return []; } +/** How an optimized build's profile matched the program's functions, as the + * helper reports it: functions that took the profile's counts, functions + * whose control flow changed since the profile was collected, functions the + * profile has no record of (new, or never run while it was collected), and + * partitions reused from the cache without their counts. */ +export interface NativeProfileMatch { + annotated: number; + mismatched: number; + missing: number; + unknownPartitions: number; +} + +/** The profile match in a successful helper emit's output, or null when the + * helper reports none (a helper that predates the report). */ +export function parseNativeProfileMatch(stdout: string): NativeProfileMatch | null { + for (const line of stdout.split("\n")) { + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch { + continue; + } + const profile = (parsed as { profile?: Record } | null)?.profile; + if (typeof profile !== "object" || profile === null) continue; + const count = (key: string): number | null => { + const value = profile[key]; + return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null; + }; + const annotated = count("annotated"); + const mismatched = count("mismatched"); + const missing = count("missing"); + const unknownPartitions = count("unknown_partitions"); + if (annotated === null || mismatched === null || missing === null || unknownPartitions === null) + continue; + return { annotated, mismatched, missing, unknownPartitions }; + } + return null; +} + /** 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/compile-types.ts b/packages/compiler/src/compile-types.ts index c50931c6a..2a5cea5ea 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -2,6 +2,7 @@ import type { TargetCpu } from "./backend/target-cpu.js"; import type { WindowsSubsystem } from "./backend/targets.js"; import type { NativeLinkInfo } from "./backend/native-link-info.js"; import type { NativeOptimization } from "./backend/optimization.js"; +import type { NativeProfileMatch } from "./backend/native-profile.js"; import type { ScrDiagnostic } from "./diagnostics/diagnostic.js"; import type { CoverageInput } from "./coverage/report.js"; import type { TierMode } from "./frontend/tier-analysis.js"; @@ -100,6 +101,8 @@ export type CompileArtifact = path: string; translationUnitPath: string; backend: "llvm"; + /** How a `profileUse` build's profile matched the program's functions. */ + profileMatch?: NativeProfileMatch; }; export type CompileFailure = { diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d1c97cc..607298427 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -94,6 +94,7 @@ import { createNativeLinkPlan } from "./backend/link-plan.js"; import { nativeProfileKey, nativeProfileRefusal, + type NativeProfileMatch, type NativeProfileOptions, } from "./backend/native-profile.js"; import { @@ -659,6 +660,7 @@ interface NativeProgramObject { partitionPaths: string[]; artifactPath: string; dependencies: NativeArtifactDependency[]; + profileMatch?: NativeProfileMatch; } async function emitNativeProgramObject( @@ -694,6 +696,7 @@ async function emitNativeProgramObject( partitionPaths: artifact.outputPaths.slice(1), artifactPath, dependencies: artifact.dependencies, + ...(artifact.profileMatch === undefined ? {} : { profileMatch: artifact.profileMatch }), }; } catch (error) { await removeNativeProgramObject(linkPath, nativePartitionPaths(linkPath, partitions)); @@ -1851,6 +1854,7 @@ async function compileTracked( } await pruneBuildCache(cacheRoot); timing("complete"); + const profileMatch = nativeProgramObject?.profileMatch; return { ok: true, artifact: { @@ -1858,6 +1862,7 @@ async function compileTracked( path: opts.outPath, translationUnitPath: llvmPath, backend, + ...(profileMatch === undefined ? {} : { profileMatch }), }, binaryPath: opts.outPath, llvmPath, diff --git a/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index e539cea1d..30e0ae39b 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -20,6 +20,12 @@ const sanitize = process.env["SCRIPTC_SAN"] === "1"; // SCRIPTC_NATIVE_PROFILE_USE names; an empty SCRIPTC_NATIVE_PROFILE_USE // builds without one. const profileGenerate = process.env["SCRIPTC_NATIVE_PROFILE_GENERATE"] || undefined; +// Below this share of matching functions, the build warns that the profile +// has lost most of its gain. A profile refreshed on the same commit matched +// 92.7% of the native compiler's profiled functions, one refreshed 1.5 days +// and about 200 commits earlier 87.2%, and that one kept about half of the +// fresh profile's gain on a cold tsc-ts build. +const PROFILE_MATCH_WARNING = 0.9; const committedProfile = join(root, "packages/compiler/native/cli.profdata.gz"); const requestedProfile = process.env["SCRIPTC_NATIVE_PROFILE_USE"]; const useProfile = @@ -127,6 +133,25 @@ if (!result.ok) ) .join("\n"), ); +// How well the profile still describes the compiler: a function whose +// control flow changed since the profile was collected is optimized as +// without one. The share falls as compiler code changes, and with it the +// profile's gain (refresh it with scripts/refresh-cli-profile.mjs). +const match = useProfile && "artifact" in result ? result.artifact.profileMatch : undefined; +if (match !== undefined) { + const profiled = match.annotated + match.mismatched; + const share = profiled === 0 ? 0 : match.annotated / profiled; + console.log( + `native compiler profile: ${match.annotated} of ${profiled} profiled functions match (${(100 * share).toFixed(1)}%); ${match.missing} functions have no profile` + + (match.unknownPartitions > 0 + ? `; ${match.unknownPartitions} partitions reused without counts` + : ""), + ); + if (match.unknownPartitions === 0 && share < PROFILE_MATCH_WARNING) + console.warn( + `warning: the native compiler profile matches ${(100 * share).toFixed(1)}% of the functions it describes; run scripts/refresh-cli-profile.mjs`, + ); +} const compilerVersion = ( JSON.parse(readFileSync(join(root, "packages/compiler/package.json"), "utf8")) as { version: string; From 4f0975cbbe0d43e5ce2a3c2be936472227ff253c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 12:01:26 -0500 Subject: [PATCH 073/395] Compile more npm and CommonJS JavaScript dependencies statically --- .../builtin-apis/intl-segmenter.mdx | 5 +- .../src/backend/executable-features.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 3 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 3 + .../compiler/src/backend/native/executable.ts | 2 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/executable/executable-cache.ts | 1 + .../src/frontend/lowering/binding-analysis.ts | 17 +- .../frontend/lowering/builtins/reflection.ts | 34 +++- .../lowering/class-dynamic-dispatch.ts | 1 + .../lowering/expressions/identifiers.ts | 32 +++ .../src/frontend/lowering/function-returns.ts | 17 ++ .../src/frontend/lowering/lower-calls.ts | 12 +- .../src/frontend/lowering/lower-classes.ts | 183 ++++++++++++------ .../src/frontend/lowering/lower-namespaces.ts | 50 ++++- .../src/frontend/lowering/lower-stmts.ts | 138 ++++++++++++- .../compiler/src/frontend/lowering/lowerer.ts | 35 +++- .../lowering/module-namespace-elements.ts | 124 +++++++++++- .../compiler/src/frontend/package-shims.ts | 47 +++-- packages/compiler/src/frontend/program.ts | 121 +++++++++--- packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 6 + packages/compiler/src/ir/runtime-features.ts | 4 + .../compiler/src/native/sanitizer.test.ts | 1 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../8218-cjs-namespace-imports/main.mjs.json | 9 + .../8219-cjs-stable-var-exports/main.js.json | 9 + .../main.js.json | 9 + .../main.ts.json | 6 + .../8222-js-class-static-reflection.js.json | 6 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_blob.h | 12 +- packages/runtime/src/scr_dyn.h | 3 + packages/runtime/src/scr_grapheme.c | 55 ++++++ packages/runtime/src/scr_json.c | 39 ++++ packages/runtime/src/scr_module_namespace.c | 121 ++++++++++++ .../8214-unshimmed-package-version/main.ts | 5 + .../node_modules/chalk/package.json | 1 + .../node_modules/chalk/source/index.d.ts | 3 + .../node_modules/chalk/source/index.js | 16 ++ .../node_modules/chalk/source/styles.js | 1 + .../8216-module-namespace-live-values/main.ts | 11 ++ .../node_modules/ns-registry/counter.js | 7 + .../node_modules/ns-registry/index.d.ts | 5 + .../node_modules/ns-registry/index.js | 40 ++++ .../node_modules/ns-registry/package.json | 1 + .../main.ts | 8 + .../node_modules/text-width/index.d.ts | 2 + .../node_modules/text-width/index.js | 11 ++ .../node_modules/text-width/package.json | 1 + .../client/index.mjs | 12 ++ .../client/package.json | 1 + .../client/transport.js | 26 +++ .../8218-cjs-namespace-imports/main.mjs | 13 ++ .../8218-cjs-namespace-imports/table.cjs | 10 + .../8219-cjs-stable-var-exports/lib/core.js | 31 +++ .../8219-cjs-stable-var-exports/lib/index.js | 8 + .../lib/options.js | 12 ++ .../8219-cjs-stable-var-exports/main.js | 21 ++ .../8219-cjs-stable-var-exports/package.json | 3 + .../classes/comparator.js | 22 +++ .../formdata.js | 26 +++ .../main.js | 39 ++++ .../package.json | 3 + .../ranges/outside.js | 13 ++ .../main.ts | 10 + .../node_modules/token-parser/dist/index.d.ts | 4 + .../node_modules/token-parser/dist/index.js | 131 +++++++++++++ .../node_modules/token-parser/package.json | 1 + .../corpus/8222-js-class-static-reflection.js | 94 +++++++++ .../cjs-mutable-exports/main.js | 5 + .../cjs-mutable-exports/package.json | 1 + .../cjs-mutable-exports/reassigned.js | 4 + .../cjs-mutable-exports/redeclared.js | 4 + .../cjs-mutable-exports/stable.js | 3 + .../cjs-mutable-exports/written.js | 4 + tests/diagnostics/intl-segmenter.ts | 4 +- .../node_modules/word-segments/index.d.ts | 1 + .../node_modules/word-segments/index.js | 9 + .../node_modules/word-segments/package.json | 1 + .../fixtures/npm-static/word-segments-cli.ts | 3 + .../__snapshots__/intl-segmenter.ts.txt | 10 +- .../main.ts.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../8218-cjs-namespace-imports/main.mjs.json | 1 + .../8219-cjs-stable-var-exports/main.js.json | 1 + .../main.js.json | 1 + .../main.ts.json | 1 + .../8222-js-class-static-reflection.js.json | 1 + tests/harness/coverage.test.ts | 10 + tests/harness/npm-static.test.ts | 22 +++ tests/harness/package-shims.test.ts | 30 ++- 97 files changed, 1725 insertions(+), 119 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8214-unshimmed-package-version/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8216-module-namespace-live-values/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8217-intl-segmenter-word-construct/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8218-cjs-namespace-imports/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8219-cjs-stable-var-exports/main.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8220-js-class-static-destructuring/main.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8221-js-recursive-generator-returns/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8222-js-class-static-reflection.js.json create mode 100644 packages/runtime/src/scr_module_namespace.c create mode 100644 tests/corpus/8214-unshimmed-package-version/main.ts create mode 100644 tests/corpus/8214-unshimmed-package-version/node_modules/chalk/package.json create mode 100644 tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.d.ts create mode 100644 tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.js create mode 100644 tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/styles.js create mode 100644 tests/corpus/8216-module-namespace-live-values/main.ts create mode 100644 tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/counter.js create mode 100644 tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.d.ts create mode 100644 tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.js create mode 100644 tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/package.json create mode 100644 tests/corpus/8217-intl-segmenter-word-construct/main.ts create mode 100644 tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts create mode 100644 tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js create mode 100644 tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/package.json create mode 100644 tests/corpus/8218-cjs-namespace-imports/client/index.mjs create mode 100644 tests/corpus/8218-cjs-namespace-imports/client/package.json create mode 100644 tests/corpus/8218-cjs-namespace-imports/client/transport.js create mode 100644 tests/corpus/8218-cjs-namespace-imports/main.mjs create mode 100644 tests/corpus/8218-cjs-namespace-imports/table.cjs create mode 100644 tests/corpus/8219-cjs-stable-var-exports/lib/core.js create mode 100644 tests/corpus/8219-cjs-stable-var-exports/lib/index.js create mode 100644 tests/corpus/8219-cjs-stable-var-exports/lib/options.js create mode 100644 tests/corpus/8219-cjs-stable-var-exports/main.js create mode 100644 tests/corpus/8219-cjs-stable-var-exports/package.json create mode 100644 tests/corpus/8220-js-class-static-destructuring/classes/comparator.js create mode 100644 tests/corpus/8220-js-class-static-destructuring/formdata.js create mode 100644 tests/corpus/8220-js-class-static-destructuring/main.js create mode 100644 tests/corpus/8220-js-class-static-destructuring/package.json create mode 100644 tests/corpus/8220-js-class-static-destructuring/ranges/outside.js create mode 100644 tests/corpus/8221-js-recursive-generator-returns/main.ts create mode 100644 tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.d.ts create mode 100644 tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.js create mode 100644 tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/package.json create mode 100644 tests/corpus/8222-js-class-static-reflection.js create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/main.js create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/package.json create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/reassigned.js create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/redeclared.js create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/stable.js create mode 100644 tests/coverage-fixtures/cjs-mutable-exports/written.js create mode 100644 tests/fixtures/npm-static/node_modules/word-segments/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/word-segments/index.js create mode 100644 tests/fixtures/npm-static/node_modules/word-segments/package.json create mode 100644 tests/fixtures/npm-static/word-segments-cli.ts create mode 100644 tests/harness/corpus-costs/8214-unshimmed-package-version/main.ts.json create mode 100644 tests/harness/corpus-costs/8216-module-namespace-live-values/main.ts.json create mode 100644 tests/harness/corpus-costs/8217-intl-segmenter-word-construct/main.ts.json create mode 100644 tests/harness/corpus-costs/8218-cjs-namespace-imports/main.mjs.json create mode 100644 tests/harness/corpus-costs/8219-cjs-stable-var-exports/main.js.json create mode 100644 tests/harness/corpus-costs/8220-js-class-static-destructuring/main.js.json create mode 100644 tests/harness/corpus-costs/8221-js-recursive-generator-returns/main.ts.json create mode 100644 tests/harness/corpus-costs/8222-js-class-static-reflection.js.json diff --git a/docs/content/limitations/builtin-apis/intl-segmenter.mdx b/docs/content/limitations/builtin-apis/intl-segmenter.mdx index 41a61711c..2682acaf0 100644 --- a/docs/content/limitations/builtin-apis/intl-segmenter.mdx +++ b/docs/content/limitations/builtin-apis/intl-segmenter.mdx @@ -1,2 +1,3 @@ -- **Intl.Segmenter:** Default Unicode grapheme segmentation, iteration, and `containing()` compile. - Locale negotiation, word and sentence segmentation, `resolvedOptions()`, and detached methods are unsupported. +- **Intl.Segmenter:** Unicode grapheme segmentation, iteration, and `containing()` compile, with the default locale or one language tag of the form language[-script][-region] written as a string literal (grapheme clusters do not depend on the locale). + In JavaScript dependencies, word and sentence segmenters construct, and their `segment()` throws an SC2020 error. + Locale negotiation over several locales, word and sentence segmentation, `resolvedOptions()`, and detached methods are unsupported. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 2afb887ea..b51f957a3 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -19,6 +19,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.wasm ? { wasm: true } : {}), ...(features.warningReplace ? { warningReplace: true } : {}), ...(features.stdioExtended ? { stdioExtended: true } : {}), + ...(features.moduleNamespace ? { moduleNamespace: true } : {}), zlib: features.zlib, ...(features.brotli ? { brotli: true } : {}), assert: features.assert, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 7a16c37e8..de2fbaa2e 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -40,6 +40,7 @@ export const LIB_FN_SYMS: Record = { "insp.jsonDyn": "scr_util_format_j", "http.reqH2StreamOrThrow": "scr_http_req_h2_stream_or_throw", "intl.segmenterNew": "scr_intl_segmenter_new", + "intl.segmenterRefusing": "scr_intl_segmenter_refusing", "weakMap.is": "scr_weak_map_is", "weakSet.is": "scr_weak_set_is", "weakMap.new": "scr_weak_map_new", @@ -254,6 +255,7 @@ export const LIB_FN_SYMS: Record = { "global.native": "scr_global_native", "process.builtinModule": "scr_process_builtin_module", "process.builtinUnsupported": "scr_process_builtin_unsupported", + "module.namespaceValue": "scr_module_namespace_value", "fs.callbackValue": "scr_fs_callback_value", "fs.callbackCall": "scr_fs_callback_call", "process.hrtimeValue": "scr_process_hrtime_value", @@ -987,6 +989,7 @@ export const LIB_FN_SYMS: Record = { "dyn.keySetComputed": "scr_dyn_key_set_computed", "dyn.keyDelete": "scr_dyn_key_delete", "dyn.keyDeleteComputed": "scr_dyn_key_delete_computed", + "dyn.reflectDeleteProperty": "scr_dyn_reflect_delete_property", "dyn.hasKeyComputed": "scr_dyn_has_key_computed", "dyn.hasOwnComputed": "scr_dyn_has_own_computed", "dyn.propertyIsEnumerableComputed": "scr_dyn_property_is_enumerable_computed", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 1e9994422..6af58fb0e 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -34,6 +34,9 @@ export interface NativeLinkFeatures { /** The program uses process.stdin.setEncoding, string 'data' listeners * or stdio 'resize' events: links the SCR_STDIO_EXTENDED events unit. */ stdioExtended?: boolean; + /** Module namespace objects as checked-dynamic values link + * scr_module_namespace.c; absent (false) otherwise. */ + moduleNamespace?: boolean; zlib: boolean; /** node:zlib's Brotli forms: scr_brotli.c and the vendored brotli archive. */ brotli?: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 40bb2352b..ce89cb829 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -147,6 +147,9 @@ export interface CcOptions { warningReplace?: boolean; /** SCR_STDIO_EXTENDED: stdin encoding, string data listeners, resize. */ stdioExtended?: boolean; + /** Module namespace objects as checked-dynamic values: compiles + * scr_module_namespace.c. */ + moduleNamespace?: boolean; /** The program uses the process-events surface (signal/exit listeners, * stdin events, for-await over stdin — moduleUsesProcessEvents on the * IR): compiles scr_events.c into the binary. Event-free binaries keep diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index ceb162bcc..1dd88117e 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -194,6 +194,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.dynTier, "scr_js_value.c", "scr_js_object.c"), unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"), unit((u) => u.opts.wasm, "scr_wasm.c"), + // Module namespace objects as checked-dynamic values. + unit((u) => u.opts.moduleNamespace === true, "scr_module_namespace.c"), unit( (u) => u.opts.dynAsync || diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 49165d8df..22bba85d2 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -94,6 +94,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { warningReplace: boolean; stdioExtended: boolean; brotli: boolean; + moduleNamespace: boolean; nativeFetch: boolean; netEffective: boolean; httpEffective: boolean; @@ -293,6 +294,7 @@ export function effectiveRuntimeFeatures( warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, brotli: features.brotli === true, + moduleNamespace: features.moduleNamespace === true, nativeFetch, netEffective, httpEffective, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 754c20648..e641b8db0 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -62,6 +62,7 @@ export interface EarlyExecutableNativeFeatures { wasm?: boolean; warningReplace?: boolean; stdioExtended?: boolean; + moduleNamespace?: boolean; zlib: boolean; brotli?: boolean; assert: boolean; diff --git a/packages/compiler/src/frontend/lowering/binding-analysis.ts b/packages/compiler/src/frontend/lowering/binding-analysis.ts index 1987c86ba..c88c06c90 100644 --- a/packages/compiler/src/frontend/lowering/binding-analysis.ts +++ b/packages/compiler/src/frontend/lowering/binding-analysis.ts @@ -19,6 +19,18 @@ export function bindingNeverReassigned( ): boolean { const cached = lowerer.neverReassignedCache.get(sym); if (cached !== undefined) return cached; + const written = fileWritesBinding(sym, ts.getSourceFileOfNode(decl), resolve); + lowerer.neverReassignedCache.set(sym, !written); + return !written; +} + +/** True when something in `file` writes `sym` (the writes + * bindingNeverReassigned counts), resolving identifiers with `resolve`. */ +export function fileWritesBinding( + sym: ts.Symbol, + file: ts.SourceFile, + resolve: (ident: ts.Identifier) => ts.Symbol | null | undefined, +): boolean { let written = false; // Text pre-check keeps the file walk cheap: only same-named // identifiers pay a symbol resolution. @@ -62,9 +74,8 @@ export function bindingNeverReassigned( } ts.forEachChild(n, visit); }; - ts.forEachChild(ts.getSourceFileOfNode(decl), visit); - lowerer.neverReassignedCache.set(sym, !written); - return !written; + ts.forEachChild(file, visit); + return written; } /** Strips the value-preserving wrappers off an expression: parens, diff --git a/packages/compiler/src/frontend/lowering/builtins/reflection.ts b/packages/compiler/src/frontend/lowering/builtins/reflection.ts index 8e1e6fd94..2ce07ea2f 100644 --- a/packages/compiler/src/frontend/lowering/builtins/reflection.ts +++ b/packages/compiler/src/frontend/lowering/builtins/reflection.ts @@ -3,7 +3,8 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; import { builtinModuleFnOf } from "../surfaces.js"; -import { BOOL, DYN, type IrExpr, STRING, arrayOf } from "../../../ir/ir.js"; +import { hasRuntimeStatics } from "../class-runtime-statics.js"; +import { BOOL, DYN, type IrExpr, STRING, arrayOf, isUnitType } from "../../../ir/ir.js"; /** Reflect.ownKeys uses native computed-key reflection. Reflect.apply(target, * thisArg, argsList) where the TARGET is a builtin @@ -63,6 +64,37 @@ export function lowerReflectCall( loc, }; } + // Reflect.deleteProperty over a value with a live property table: a + // checked-dynamic value, or a JavaScript class whose statics are runtime + // properties of its constructor + // (`Reflect.deleteProperty(Response, "getResponseState")`). + if ( + reflectMember === "deleteProperty" && + call.arguments.nodes.length === 2 && + !call.arguments.nodes.some(ts.isSpreadElement) + ) { + const loc = locOf(call); + let target = lowerer.lowerExpr(call.arguments.nodes[0]!); + const info = + target.type.kind === "classval" ? lowerer.classes.get(target.type.className) : undefined; + // A primitive target throws the TypeError, so it boxes as is. + const primitive = + isUnitType(target.type) || + ["bool", "f64", "string", "bigint", "symbol"].includes(target.type.kind); + if ( + primitive || + (info !== undefined && hasRuntimeStatics(info) && lowerer.dynConvertible(target.type)) + ) + target = lowerer.coerceToExpected(target, DYN); + if (target.type.kind !== "dyn") return null; + return { + kind: "libCall", + fn: "dyn.reflectDeleteProperty", + args: [target, lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN)], + type: BOOL, + loc, + }; + } if (reflectMember === "get" || reflectMember === "set") { const required = reflectMember === "get" ? 2 : 3; if ( diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index fc6d0a922..a943b9156 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -262,6 +262,7 @@ const KEY_CALLS = new Map([ ["dyn.propertyIsEnumerableComputed", "any"], ["dyn.hasOwnComputed", "value"], ["dyn.keyDeleteComputed", "value"], + ["dyn.reflectDeleteProperty", "object"], ["dyn.keySetComputed", "value"], ]); diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index f7ba7a115..db1c1938a 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -57,6 +57,7 @@ import { contextualUndefReadType, fenceEarlyAliasUse, isTemporalGlobalRoot, + cjsNamespaceImportDep, lowerNsIdentifierValue, nsUndefRead, } from "../lower-namespaces.js"; @@ -958,6 +959,37 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // representation (ambient namespaces compile to Node's // ReferenceError instead — the object never exists at runtime). { + // `ns.name` through a namespace import of a CommonJS module, where Node's + // namespace has no `name` property (its lexer does not detect it), or + // `ns.default` (module.exports itself). + const cjsNamespace = cjsNamespaceImportDep(lowerer, expr); + if ( + cjsNamespace !== null && + ts.isPropertyAccessExpression(expr.parent) && + expr.parent.expression === expr && + ts.isIdentifier(expr.parent.name) + ) { + const name = expr.parent.name.text; + const { names, assignedLater } = lowerer.cjsNamespaceOf(cjsNamespace); + if (name === "default") + lowerer.unsupported( + "SC1090", + expr.parent, + `reads of 'default' through namespace imports of CommonJS modules (module.exports itself: read it through a default import)`, + ); + if (!names.has(name)) + lowerer.unsupported( + "SC1090", + expr.parent, + `reads of '${name}' through namespace imports of CommonJS modules (Node's lexer does not detect '${name}' among the module's exports, so Node reads undefined)`, + ); + if (assignedLater === "all" || assignedLater.has(name)) + lowerer.unsupported( + "SC1090", + expr.parent, + `reads of '${name}' through namespace imports of CommonJS modules whose functions assign exports (Node's namespace keeps the values from the end of the module's evaluation)`, + ); + } const moduleNs = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (moduleNs?.kind === "moduleNs") { return { kind: "moduleNsRef", moduleId: moduleNs.moduleId, type: moduleNs, loc }; diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index 6d0989e2b..c4563a234 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -175,6 +175,23 @@ export function declaredReturnType( // `() => void`), and throw-only callbacks are ordinary code // (`.action(() => { throw ... })`). `never` VALUES stay unmapped. if (retTsType.flags & ts.TypeFlags.Never) return VOID; + // A JS generator whose result the checker cannot infer (mutually + // recursive methods returning `yield*` of each other, as in yaml's + // Parser) has the return type `any`: a generator over untyped values. + if ( + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + decl.asteriskToken && + retTsType.flags & ts.TypeFlags.Any + ) + return { + kind: "generator", + ...(decl.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) + ? { async: true as const } + : {}), + yieldT: DYN, + retT: DYN, + nextT: DYN, + }; let mappedReturn = lowerer.mapTypeOf(retTsType); // A generator declared to return Iterable returns a generator over T. if (mappedReturn && decl.asteriskToken) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 14565aebc..4b833ee50 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -8464,7 +8464,17 @@ function lowerObjectStaticCall( ...(fnName !== null ? { fnName } : {}), }; } - if (target && isUnitType(target.type)) { + // A class value whose statics are runtime properties of its constructor + // boxes to that constructor function, as for Object.defineProperty above. + const runtimeStaticClass = + target?.type.kind === "classval" ? lowerer.classes.get(target.type.className) : undefined; + if ( + target && + (isUnitType(target.type) || + (runtimeStaticClass !== undefined && + hasRuntimeStatics(runtimeStaticClass) && + lowerer.dynConvertible(target.type))) + ) { target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments.nodes[0]!) }; } if (target?.type.kind === "dyn") { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b829b6ddc..e50466114 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -11,7 +11,7 @@ import { lowerDateStaticCall } from "./builtins/dates.js"; import { keyedHelperName } from "./stable-names.js"; import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; import { adaptPromiseExecutor } from "./promise-executor-adapter.js"; -import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; +import { dynUndefinedExpr, nodeThrowExpr, strLit, varRef } from "../../ir/build.js"; import { constructedFromValue, constructedValue, @@ -124,6 +124,7 @@ import { lowerWorkerNew, } from "./builtins/workers.js"; import { + noLoweringDiag, unsupportedPackageValueDiag, unsupportedDiag, type ScrDiagnostic, @@ -5687,15 +5688,22 @@ export function lowerStaticFieldRead( ): IrExpr | null { if (expr.name.text === "prototype") return null; if (expr.questionDotToken) return null; + const info = staticReceiverClass(lowerer, expr.expression); + if (!info) return null; + return lowerClassStaticRead(lowerer, info, expr.name.text, expr.expression, expr); +} + +/** The class a static member read through `receiver` reads exactly: the + * class a name binds (directly, through a require binding, or through a + * const alias), or null. */ +export function staticReceiverClass(lowerer: Lowerer, receiver: ts.Expression): ClassInfo | null { if ( - !ts.isIdentifier(expr.expression) && - expr.expression.kind !== ts.SyntaxKind.ThisKeyword && - exactClassOfReceiver(lowerer, expr.expression) === null + !ts.isIdentifier(receiver) && + receiver.kind !== ts.SyntaxKind.ThisKeyword && + exactClassOfReceiver(lowerer, receiver) === null ) return null; - const symbol = ts.isIdentifier(expr.expression) - ? lowerer.resolveValueSymbol(expr.expression) - : null; + const symbol = ts.isIdentifier(receiver) ? lowerer.resolveValueSymbol(receiver) : null; const info = (symbol ? lowerer.classBySymbol.get(symbol) : undefined) ?? // A require binding over `module.exports = class {…}` (the alias @@ -5703,7 +5711,7 @@ export function lowerStaticFieldRead( // or `C.name` below would fall through to paths that answer for // stdlib globals instead of this class. propertyAssignedClassInfoOf(lowerer, symbol) ?? - exactClassOfReceiver(lowerer, expr.expression) ?? + exactClassOfReceiver(lowerer, receiver) ?? undefined; if (!info) return null; // A decorated name that can REBIND (a replacing decorator): the @@ -5712,22 +5720,36 @@ export function lowerStaticFieldRead( // whose devirtualization and .name rules answer for every legal // runtime value. if (info.classDecorators?.valueGlobalId !== undefined) return null; - const loc = locOf(expr); - const found = findStaticOn(lowerer, info, expr.name.text); + return info; +} + +/** The static member `name` of `info` read through `receiver` (an exact + * reference to the class, see staticReceiverClass): `C.name` for the + * property access `site`, or a destructuring element over `C` whose + * initializer is `site`. Null when the class has no such member. */ +export function lowerClassStaticRead( + lowerer: Lowerer, + info: ClassInfo, + name: string, + receiver: ts.Expression, + site: ts.Expression, +): IrExpr | null { + const loc = locOf(site); + const found = findStaticOn(lowerer, info, name); // A Date static read as a value through a class declared `extends Date`: // the statics lower only as calls. if ( !found && dateRootOf(info) && - ["now", "parse", "UTC"].includes(expr.name.text) && - !(ts.isCallExpression(expr.parent) && expr.parent.expression === expr) + ["now", "parse", "UTC"].includes(name) && + !(ts.isCallExpression(site.parent) && site.parent.expression === site) ) - lowerer.noLowering(`Date.${expr.name.text} read through a class that extends Date`, expr); + lowerer.noLowering(`Date.${name} read through a class that extends Date`, site); if (hasRuntimeStatics(info) && !found?.field) return { kind: "dynKeyGet", - value: lowerer.coerceToExpected(classValueRef(lowerer, info, expr.expression), DYN), - key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + value: lowerer.coerceToExpected(classValueRef(lowerer, info, receiver), DYN), + key: { kind: "strLit", value: name, type: STRING, loc }, type: DYN, loc, }; @@ -5735,11 +5757,11 @@ export function lowerStaticFieldRead( // name: in JS the brand lives on that one constructor object, so // `D.#s` (a subclass receiver) throws Node's TypeError instead of // reaching up the chain — reported as SC1090 instead of resolved. - if (found && expr.name.text.startsWith("#") && found.declarer !== info) { + if (found && name.startsWith("#") && found.declarer !== info) { lowerer.unsupported( "SC1090", - expr, - `reading the private static '${expr.name.text}' through the subclass '${info.def.name.replace(/^%|^%m\d+\./, "")}' (JS brands the declaring class object alone — Node throws a TypeError here; spell the declaring class's name)`, + site, + `reading the private static '${name}' through the subclass '${info.def.name.replace(/^%|^%m\d+\./, "")}' (JS brands the declaring class object alone — Node throws a TypeError here; spell the declaring class's name)`, ); } if (found?.field !== undefined) { @@ -5749,29 +5771,31 @@ export function lowerStaticFieldRead( type: found.field.type, loc, }; - return found.field.runtimeOptional ? value : lowerer.maybeNarrow(value, expr); + return found.field.runtimeOptional || !ts.isPropertyAccessExpression(site) + ? value + : lowerer.maybeNarrow(value, site); } if (found) { - return staticMethodValue(lowerer, found.declarer, expr.name.text, found.method, expr, loc); + return staticMethodValue(lowerer, found.declarer, name, found.method, site, loc); } // A GENERIC static method as a VALUE: the pinned-value rule verbatim // (lowerGenericFnValue) — a slot spelling one concrete signature names // an instance, an unpinned reference fences by name. { - const gfound = findGenericStaticOn(lowerer, info, expr.name.text); - if (gfound) return lowerer.lowerGenericFnValue(expr, gfound.info); + const gfound = findGenericStaticOn(lowerer, info, name); + if (gfound) return lowerer.lowerGenericFnValue(site, gfound.info); } // `C.name` — the JS-observable class name, a compile-time constant on // the direct spelling (tsc rejects user statics named `name`, so the // chain above can never shadow it in TypeScript sources). A class of an // import cycle reads it from its class object instead, whose reference // checks the class's initialization (checkCycleBindingAccesses). - if (expr.name.text === "name" && info.def.jsName !== undefined) { + if (name === "name" && info.def.jsName !== undefined) { if (lowerer.classInitFlags.get(info.def.name)?.cycle === true) return { kind: "libCall", fn: "class.name", - args: [classValueRef(lowerer, info, expr.expression)], + args: [classValueRef(lowerer, info, receiver)], type: STRING, loc, }; @@ -5780,26 +5804,26 @@ export function lowerStaticFieldRead( if (info.callableBase) return { kind: "dynKeyGet", - value: lowerer.coerceToExpected(classValueRef(lowerer, info, expr.expression), DYN), - key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + value: lowerer.coerceToExpected(classValueRef(lowerer, info, receiver), DYN), + key: { kind: "strLit", value: name, type: STRING, loc }, type: DYN, loc, }; - const data = classStaticDataFor(lowerer, info, expr.name.text, loc); + const data = classStaticDataFor(lowerer, info, name, loc); if (data) { const value: IrExpr = { kind: "dynKeyGet", value: data, - key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + key: { kind: "strLit", value: name, type: STRING, loc }, type: DYN, loc, }; // JavaScript often assigns public statics after the class declaration. // Their storage stays mutable and checked, while reads retain the // checker's concrete type instead of losing the method receiver. - const type = isJsSourceFile(ts.getSourceFileOfNode(expr)) + const type = isJsSourceFile(ts.getSourceFileOfNode(site)) ? null - : lowerer.mapTypeOf(lowerer.typeOf(expr)); + : lowerer.mapTypeOf(lowerer.typeOf(site)); return type ? lowerer.coerceToExpected(value, type) : value; } return null; @@ -6625,7 +6649,7 @@ function staticCallOn( kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: access.name.text, - calleeName: access.name.text, + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((argument) => lowerer.lowerExprExpecting(argument, DYN)), type: DYN, loc, @@ -9902,34 +9926,47 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const args = expr.arguments?.nodes ?? []; const undefinedArg = (arg: ts.Expression): boolean => (lowerer.typeOf(arg).flags & ts.TypeFlags.Undefined) !== 0; - if (args.length > 2 || (args[0] && !undefinedArg(args[0]))) { + // A literal language tag: Unicode grapheme clusters have no locale + // tailoring. Only tags of the form language[-script][-region] are + // accepted; each is well-formed, so Node constructs the segmenter + // without the RangeError a malformed tag throws. + const literalLocale = (arg: ts.Expression): boolean => + ts.isStringLiteralLike(arg) && + /^[A-Za-z]{2,3}(-[A-Za-z]{4})?(-([A-Za-z]{2}|[0-9]{3}))?$/.test(arg.text); + if (args.length > 2 || (args[0] && !undefinedArg(args[0]) && !literalLocale(args[0]))) { lowerer.noLowering( "Intl.Segmenter locale negotiation", expr, - "omit the locale or pass undefined for default Unicode grapheme segmentation", + "omit the locale, pass undefined, or pass one language tag as a string literal", ); } + // JavaScript dependencies may construct word and sentence segmenters + // that they use only on some paths (pi-tui's shared word segmenter): + // the construction compiles and their segment() throws SC2020. + // TypeScript sources keep the build-time refusal. + const granularities = isJsSourceFile(ts.getSourceFileOfNode(expr)) + ? ["grapheme", "word", "sentence"] + : ["grapheme"]; const options = args[1]; - if ( - options && - !undefinedArg(options) && - !( - ts.isObjectLiteralExpression(options) && - options.properties.nodes.every( - (property) => - ts.isPropertyAssignment(property) && - (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) && - property.name.text === "granularity" && - ts.isStringLiteral(property.initializer) && - property.initializer.text === "grapheme", - ) - ) - ) { - lowerer.noLowering( - "Intl.Segmenter options", - options, - 'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported', - ); + let granularity = "grapheme"; + if (options && !undefinedArg(options)) { + const properties = ts.isObjectLiteralExpression(options) ? options.properties.nodes : null; + const property = properties?.length === 1 ? properties[0] : undefined; + if ( + property === undefined || + !ts.isPropertyAssignment(property) || + !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || + property.name.text !== "granularity" || + !ts.isStringLiteral(property.initializer) || + !granularities.includes(property.initializer.text) + ) { + lowerer.noLowering( + "Intl.Segmenter options", + options, + 'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported', + ); + } + granularity = property.initializer.text; } // Explicit undefined expressions can carry effects; retain argument order. const stmts: IrStmt[] = args.map((arg) => { @@ -9940,7 +9977,28 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc: locOf(arg), }; }); - const result: IrExpr = { kind: "libCall", fn: "intl.segmenterNew", args: [], type: DYN, loc }; + let result: IrExpr = { kind: "libCall", fn: "intl.segmenterNew", args: [], type: DYN, loc }; + // A word or sentence segmenter carries its segment() refusal, which + // coverage lists as deferred to runtime. + if (granularity !== "grapheme") { + const surface = `Intl.Segmenter segmentation with granularity '${granularity}'`; + lowerer.runtimeFences.push( + noLoweringDiag( + surface, + loc, + "only grapheme segmenters segment; word and sentence segmenters construct", + ), + ); + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(expr), loc.start).line; + const refusal = `${surface} is not supported yet [SC2020 at ${loc.file}:${line + 1}]`; + result = { + kind: "libCall", + fn: "intl.segmenterRefusing", + args: [strLit(refusal, loc)], + type: DYN, + loc, + }; + } return stmts.length ? { kind: "seqExpr", stmts, result, type: DYN, loc } : result; } // A package declaration without a compiled constructor cannot provide @@ -11266,13 +11324,18 @@ function genericFieldFnNodeOf( } /** Whether a JavaScript class's own binding is the target of - * `Object.defineProperty`/`defineProperties` in its file - * (`Object.defineProperty(Definition, "name", { value })`): its constructor + * `Object.defineProperty`/`defineProperties` or `Reflect.deleteProperty` in + * its file (`Object.defineProperty(Definition, "name", { value })`, + * `Reflect.deleteProperty(Response, "getResponseState")`): its constructor * then needs a live own-property table. */ function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): boolean { const name = decl.name; const sf = ts.getSourceFileOfNode(decl); - if (name === undefined || !sf.text.includes("definePropert")) return false; + if ( + name === undefined || + (!sf.text.includes("definePropert") && !sf.text.includes("deleteProperty")) + ) + return false; const symbol = lowerer.checker.getSymbolAtLocation(name); if (symbol === undefined) return false; let found = false; @@ -11281,9 +11344,11 @@ function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaratio if ( ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && - (node.expression.name.text === "defineProperty" || + (((node.expression.name.text === "defineProperty" || node.expression.name.text === "defineProperties") && - lowerer.stdlibGlobalMember(node.expression, "Object") !== null + lowerer.stdlibGlobalMember(node.expression, "Object") !== null) || + (node.expression.name.text === "deleteProperty" && + lowerer.stdlibGlobalMember(node.expression, "Reflect") !== null)) ) { const target = node.arguments.nodes[0]; if ( diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index ea844ab1e..13d1c29fc 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -42,7 +42,7 @@ import { isPackageShimFile, isShimIntrinsicName, packageShimFor } from "../packa import type { Lowerer } from "./lowerer.js"; import { boundIdentifiersOf } from "./lowerer.js"; import type { FileParts } from "./lower-modules.js"; -import { locOf, resolveImport } from "../program.js"; +import { isCjsJsFile, isJsSourceFile, locOf, resolveImport } from "../program.js"; import { F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js"; import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; @@ -311,6 +311,30 @@ function moduleNsSourceFileOf(lowerer: Lowerer, e: ts.Expression): ts.SourceFile return null; } +/** The CommonJS module a JavaScript namespace import binding names + * (`import * as ns from "./m.cjs"`, `ns`), or null. */ +export function cjsNamespaceImportDep(lowerer: Lowerer, e: ts.Expression): ts.SourceFile | null { + if (!ts.isIdentifier(e)) return null; + const decl = lowerer.checker.getSymbolAtLocation(e)?.declarations.find(ts.isNamespaceImport); + const stmt = decl?.parent?.parent; + if ( + decl === undefined || + stmt === undefined || + !ts.isImportDeclaration(stmt) || + !ts.isStringLiteral(stmt.moduleSpecifier) + ) + return null; + const from = ts.getSourceFileOfNode(decl); + if (!isJsSourceFile(from)) return null; + const dep = resolveImport(lowerer.program, from, stmt.moduleSpecifier.text); + return dep !== null && + !dep.isDeclarationFile && + lowerer.fileTag.has(dep) && + isCjsJsFile(dep, lowerer.program) + ? dep + : null; +} + /** The member identifier of a qualified namespace reference, when `access` * reads a member declared in a lowered (or type-only) namespace block — * or an EXPORT of a program module read through its namespace-import @@ -330,6 +354,21 @@ export function nsMemberIdentOf( if (!ts.isIdentifier(access.name)) return null; const memberSym = lowerer.checker.getSymbolAtLocation(access.name); if (!memberSym) return null; + // A CommonJS module's namespace has the names Node's lexer detects, with + // the values they had when the module finished evaluating; the member + // then reads like the named import `import { name }`. `default` is + // module.exports itself. + const cjs = cjsNamespaceImportDep(lowerer, access.expression); + if (cjs !== null) { + const { names, assignedLater } = lowerer.cjsNamespaceOf(cjs); + const name = access.name.text; + return name !== "default" && + names.has(name) && + assignedLater !== "all" && + !assignedLater.has(name) + ? access.name + : null; + } if (nsBlockKindOfSymbol(lowerer, memberSym) !== null) return access.name; if (moduleNsSourceFileOf(lowerer, access.expression) !== null) return access.name; return null; @@ -857,6 +896,15 @@ export function nsWritableTarget( const nsMember = nsMemberIdentOf(lowerer, access); if (!nsMember) return null; const memberSym = lowerer.checker.getSymbolAtLocation(nsMember); + // An export read through a module namespace object (`import * as ns`): + // the object is read-only, and Node's assignment throws a TypeError. + if (memberSym && nsBlockKindOfSymbol(lowerer, memberSym) === null) { + lowerer.unsupported( + "SC1090", + access, + `assignment to the module namespace member '${nsMember.text}' (module namespace objects are read-only: Node throws a TypeError)`, + ); + } if (memberSym) fenceEarlyNsMemberRef(lowerer, access, memberSym); const resolved = lowerer.resolveValueSymbol(nsMember); const g = resolved ? lowerer.globalsBySymbol.get(resolved) : undefined; diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index c50676863..95e4f7e80 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -168,6 +168,7 @@ import { import { bindingNeverReassigned, deadUnmappableBinding, + fileWritesBinding, nullishExprUnitOf, nullishGenericBindingUnitOf, } from "./binding-analysis.js"; @@ -181,7 +182,12 @@ import { classStaticDataFor } from "./class-static-data.js"; import { objectFactorySignature } from "./object-factory-new.js"; import { isClassCallback, lowerClassCallbackAssign } from "./class-callbacks.js"; import { classForInHasKey, classPropertiesHelper } from "./class-dynamic-dispatch.js"; -import { genericIfaceBindingKeepsClass, superConditionLead } from "./lower-classes.js"; +import { + genericIfaceBindingKeepsClass, + lowerClassStaticRead, + staticReceiverClass, + superConditionLead, +} from "./lower-classes.js"; import { lowerStreamUnderscoreAssign, streamClassAliasDecl, @@ -3053,6 +3059,18 @@ export function lowerBindingPattern( ); return; } + // A class in a JavaScript file, destructured through its own name + // (`const { ANY, parse } = Comparator`): each element is the static + // read `Comparator.ANY` — the static getter, field or method as Node + // reads it, with the element's default applied when the read answers + // undefined. Other class-valued sources keep the refusal below. + if ( + srcType.kind === "classval" && + ts.isObjectBindingPattern(pattern) && + isJsSourceFile(ts.getSourceFileOfNode(pattern)) && + lowerClassValuePattern(lowerer, pattern, isLet, out) + ) + return; if (srcType.kind === "dyn") { // A runtime computed first key evaluates after RequireObjectCoercible: // a nullish source throws before the key's side effects run. Later @@ -4364,6 +4382,60 @@ function stdlibGlobalTokenDestructure( return out; } +/** `const { ANY, parse } = Comparator` in a JavaScript file, where the + * declaration's initializer names the class exactly (staticReceiverClass): + * one static read per element, left to right. False, with nothing + * emitted, for other sources (a parameter or nested pattern over a class + * value), which keep the class-value refusal. */ +function lowerClassValuePattern( + lowerer: Lowerer, + pattern: ts.ObjectBindingPattern, + isLet: boolean, + out: IrStmt[], +): boolean { + const decl = pattern.parent; + if (!ts.isVariableDeclaration(decl) || decl.initializer === undefined) return false; + let receiver: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(receiver)) receiver = receiver.expression; + const info = staticReceiverClass(lowerer, receiver); + if (info === null) return false; + for (const el of pattern.elements.nodes) { + if (el.name === undefined) continue; + if (el.dotDotDotToken) + lowerer.unsupported( + "SC1031", + el, + "rest bindings over class values (the rest object copies the class's own enumerable statics)", + ); + const prop = el.propertyName ?? el.name; + const name = + ts.isIdentifier(prop) || + ts.isComputedPropertyName(prop) || + ts.isStringLiteralLike(prop) || + ts.isNumericLiteral(prop) + ? patternKeyNameOf(lowerer, prop as ts.PropertyName) + : null; + if (name === null) + lowerer.unsupported( + "SC1031", + el, + "destructuring with computed keys that do not fold to one property name", + ); + if (name === "prototype" || name.startsWith("#")) + lowerer.unsupported("SC1031", el, `destructuring '${name}' from a class value`); + let value = lowerClassStaticRead(lowerer, info, name, receiver, receiver); + if (value === null) + lowerer.unsupported( + "SC1031", + el, + `destructuring the property '${name}' the class '${info.def.jsName ?? info.def.name}' does not declare as a static`, + ); + if (el.initializer) value = applyBindingDefault(lowerer, el, value); + lowerer.bindPatternTarget(el.name, value, isLet, out); + } + return true; +} + /** `x = dflt` on a pattern position whose source value always EXISTS * (tuple positions, record fields): JS evaluates the default exactly * when the value is undefined — the undefined-arm ternary, lazy, so @@ -7262,6 +7334,66 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt ); } +/** True when the `var`/`let` binding `nameNode` names cannot change after + * the top-level CommonJS export statement `exportNode` that exports it by + * reference, so importers read the value Node copies at the export: its + * module's top level declares it once, with an initializer that runs + * before the export, and nothing assigns it. The initializer runs earlier + * in the same module, or in a module the exporting module required before + * the export (`var hljs = require('./core'); module.exports = hljs;` over + * highlight.js's `var highlight = HLJS({}); module.exports = highlight;`). */ +function unchangedAfterExport( + lowerer: Lowerer, + nameNode: ts.Identifier, + exportNode: ts.Node, +): boolean { + const topLevelDeclaration = (symbol: ts.Symbol): ts.VariableDeclaration | null => { + const decl = symbol.declarations.length === 1 ? symbol.declarations[0]! : undefined; + return decl !== undefined && + ts.isVariableDeclaration(decl) && + ts.isIdentifier(decl.name) && + decl.initializer !== undefined && + ts.isVariableDeclarationList(decl.parent) && + ts.isVariableStatement(decl.parent.parent) && + ts.isSourceFile(decl.parent.parent.parent) + ? decl + : null; + }; + const exportFile = ts.getSourceFileOfNode(exportNode); + let exportStatement: ts.Node = exportNode; + for (let parent = exportNode.parent; parent !== undefined && !ts.isSourceFile(parent);) { + if (ts.isFunctionLike(parent) || ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) + return false; + exportStatement = parent; + parent = parent.parent; + } + const runsBeforeExport = (decl: ts.VariableDeclaration): boolean => { + const statement = decl.parent?.parent; + return statement?.parent === exportFile && statement.end <= exportStatement.pos; + }; + const sym = lowerer.resolveValueSymbol(nameNode); + const decl = sym ? topLevelDeclaration(sym) : null; + if (!sym || !decl || !bindingNeverReassigned(lowerer, sym, decl)) return false; + if (ts.getSourceFileOfNode(decl) === exportFile) return runsBeforeExport(decl); + // A binding of another module: the exporting module's own binding + // `nameNode` names holds that module's require, evaluated before the + // export, and this file does not assign the binding either. + const local = lowerer.checker.getSymbolAtLocation(nameNode); + const localDecl = local ? topLevelDeclaration(local) : null; + if (!localDecl || !runsBeforeExport(localDecl)) return false; + let init: ts.Expression = localDecl.initializer!; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if ( + !ts.isCallExpression(init) || + !ts.isIdentifier(init.expression) || + init.expression.text !== "require" || + init.arguments.nodes.length !== 1 || + !ts.isStringLiteralLike(init.arguments.nodes[0]!) + ) + return false; + return !fileWritesBinding(sym, exportFile, (ident) => lowerer.resolveValueSymbol(ident)); +} + /** A top-level CommonJS export statement (see cjsExportAssignmentOf): * `exports.f = ` and each expression-valued property of * `module.exports = { ... }` assign their pre-registered export globals @@ -7445,7 +7577,7 @@ function lowerCjsExportStatement( const fenceMutable = (nameNode: ts.Identifier): void => { const sym = lowerer.resolveValueSymbol(nameNode); const g = sym ? lowerer.globalsBySymbol.get(sym) : undefined; - if (g?.mutable) { + if (g?.mutable && !unchangedAfterExport(lowerer, nameNode, cjs.expr)) { lowerer.unsupported( "SC1090", nameNode, @@ -7605,7 +7737,7 @@ function lowerCjsSingleValueExport( } const g = lowerer.globalsBySymbol.get(sym); if (g) { - if (g.mutable) { + if (g.mutable && !unchangedAfterExport(lowerer, rhs, expr)) { lowerer.unsupported( "SC1090", rhs, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 78980d322..59dda3d08 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,6 +1,6 @@ import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; -import { moduleNamespaceObject } from "./module-namespace-elements.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"; @@ -269,6 +269,8 @@ import { type StartupCrash, cjsExportAssignmentOf, cjsExportDiscardReason, + cjsExportsAssignedInFunctions, + cjsNamespaceExportNames, fallbackDtsPath, isCheckedJsSourceFile, isCjsExportTableLiteral, @@ -3998,6 +4000,31 @@ export class Lowerer { return null; } + /** For each CommonJS module read through a JavaScript namespace import: + * the property names of Node's namespace object + * (cjsNamespaceExportNames), and the exports the module assigns inside + * functions, which the namespace does not see + * (cjsExportsAssignedInFunctions). */ + private readonly cjsNamespaces = new Map< + ts.SourceFile, + { names: ReadonlySet; assignedLater: ReadonlySet | "all" } + >(); + + cjsNamespaceOf(dep: ts.SourceFile): { + names: ReadonlySet; + assignedLater: ReadonlySet | "all"; + } { + let namespace = this.cjsNamespaces.get(dep); + if (namespace === undefined) { + namespace = { + names: cjsNamespaceExportNames(this.program, dep), + assignedLater: cjsExportsAssignedInFunctions(dep), + }; + this.cjsNamespaces.set(dep, namespace); + } + return namespace; + } + sourceFileOfModuleNamespace(type: IrType): ts.SourceFile | null { if (type.kind !== "moduleNs" || !type.moduleId.startsWith("file:")) return null; const path = type.moduleId.slice("file:".length); @@ -9792,10 +9819,8 @@ export class Lowerer { // so its module binding is too (`ReleaseEmitter = EventEmitter` over // `const EventEmitter = require("events")`). if (expr.type.kind === "moduleNs" && isJsSourceFileName(expr.loc.file)) { - const object = moduleNamespaceObject( - this, - expr as IrExpr & { type: IrType & { kind: "moduleNs" } }, - ); + const ns = expr as IrExpr & { type: IrType & { kind: "moduleNs" } }; + const object = moduleNamespaceObject(this, ns) ?? moduleNamespaceValue(this, ns); if (object) return object; } if ( diff --git a/packages/compiler/src/frontend/lowering/module-namespace-elements.ts b/packages/compiler/src/frontend/lowering/module-namespace-elements.ts index cf828c6f0..2c8e6a475 100644 --- a/packages/compiler/src/frontend/lowering/module-namespace-elements.ts +++ b/packages/compiler/src/frontend/lowering/module-namespace-elements.ts @@ -1,4 +1,5 @@ import { dynUndefinedExpr, strLit, varRef } from "../../ir/build.js"; +import { arrayOf, funcOf } from "../../ir/ir.js"; import * as ts from "../ts7/adapter.js"; import { DYN, @@ -9,7 +10,7 @@ import { type IrType, } from "../../ir/ir.js"; import { locOf } from "../program.js"; -import { newFnCtx, type Lowerer } from "./lowerer.js"; +import { newFnCtx, PoisonError, type Lowerer } from "./lowerer.js"; /** Computed namespace reads select live exports instead of copying a * namespace into a registry snapshot. Constructor registries may contain @@ -171,3 +172,124 @@ export function moduleNamespaceObject( loc, }; } + +/** A module namespace taken as an untyped value whose exports a copy + * cannot represent: a binding that can change after initialization (`let`, + * `var`), or an export the copy cannot name. The value is the runtime's + * namespace object (scr_module_namespace_value): one per module, whose + * property reads call a compiled getter for the export's current value and + * whose writes throw Node's TypeError. An export whose value has no + * checked-dynamic form throws its fence when it is read, and listing the + * object's keys or inspecting it throws the "not supported yet" error. */ +export function moduleNamespaceValue( + lowerer: Lowerer, + ns: IrExpr & { type: IrType & { kind: "moduleNs" } }, +): IrExpr | null { + const source = lowerer.sourceFileOfModuleNamespace(ns.type); + if (!source) return null; + const symbol = lowerer.checker.getSymbolAtLocation(source); + if (!symbol) return null; + const exports: { key: string; declaration: ts.Node | undefined }[] = []; + for (const exported of lowerer.checker.getExportsOfModule(symbol)) { + const target = + exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported; + if (!(target.flags & ts.SymbolFlags.Value)) continue; + exports.push({ key: ts.symbolName(exported), declaration: target.valueDeclaration }); + } + exports.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)); + const name = `%module.namespace:${ns.type.moduleId}`; + const loc = ns.loc; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const context = newFnCtx(false, null, null, DYN); + const previousClass = lowerer.currentClass; + lowerer.currentClass = null; + lowerer.fnStack.push(context); + try { + const body: IrStmt[] = []; + for (const { key, declaration } of exports) { + const node = declaration ?? source; + const diagsBefore = lowerer.diags.length; + let read: IrStmt[]; + try { + const identifier = declaration?.name as ts.Node | undefined; + if (!identifier || !ts.isIdentifier(identifier)) { + lowerer.unsupported( + "SC1090", + node, + `reading the unnamed export '${key}' through a module namespace object`, + ); + } + const value = lowerer.lowerExprExpecting(identifier, DYN); + if (value.type.kind !== "dyn") { + lowerer.unsupported( + "SC1101", + identifier, + `reading the export '${key}' ('${lowerer.fmt(value.type)}') through a module namespace object`, + ); + } + read = [{ kind: "return", value, loc }]; + } catch (err) { + if (!(err instanceof PoisonError)) throw err; + const fence = lowerer.deferToRuntimeFence(diagsBefore, node, { kind: "statement" }); + if (!fence) throw err; + read = [fence]; + } + body.push({ + kind: "if", + cond: { + kind: "strEq", + negated: false, + left: varRef("key", STRING, loc), + right: strLit(key, loc), + type: { kind: "bool" }, + loc, + }, + then: read, + else_: null, + loc, + }); + } + body.push({ kind: "return", value: dynUndefinedExpr(loc), loc }); + lowerer.liftedFns.push({ + name, + params: [{ localId: "key", name: "key", type: STRING }], + returnType: DYN, + locals: [{ id: "key", name: "key", type: STRING, mutable: false }, ...context.locals], + body, + loc, + }); + } finally { + lowerer.fnStack.pop(); + lowerer.currentClass = previousClass; + } + } + const value: IrExpr = { + kind: "libCall", + fn: "module.namespaceValue", + args: [ + strLit(ns.type.moduleId, loc), + { + kind: "arrayLit", + elems: exports.map(({ key }) => strLit(key, loc)), + type: arrayOf(STRING), + loc, + }, + { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([STRING], DYN), loc }, + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }; + if (ns.kind === "moduleNsRef") return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: ns, loc }], + result: value, + type: DYN, + loc, + }; +} diff --git a/packages/compiler/src/frontend/package-shims.ts b/packages/compiler/src/frontend/package-shims.ts index 10beeaae8..06a6c4eca 100644 --- a/packages/compiler/src/frontend/package-shims.ts +++ b/packages/compiler/src/frontend/package-shims.ts @@ -55,6 +55,10 @@ interface ImplementationShim { /** Supported major versions and their entry modules, relative to the * shim root. */ versions: ReadonlyMap; + /** Major versions without an entry module that `--npm-static` compiles + * from the package's own published JavaScript. An installed version that + * is in neither list stays out of the program. */ + sourceVersions?: ReadonlySet; /** The entry has a default export (a CommonJS package's whole export * object, which a default import reads). */ defaultExport?: boolean; @@ -98,6 +102,7 @@ const IMPLEMENTATION_SHIMS: ReadonlyMap = new Map< mainFile: "source/index.js", typesFile: "source/index.d.ts", versions: new Map([[5, "chalk/v5.ts"]]), + sourceVersions: new Set([6]), }, ], [ @@ -233,10 +238,17 @@ export function configurePackageShims(entryPath: string): void { unloadedPackages = packages; } -/** True for a package name the registry implements or the project marks - * never loaded: such a package never compiles its published JavaScript. */ -export function isPackageShimName(name: string): boolean { - return IMPLEMENTATION_SHIMS.has(name) || unloadedPackages.has(name); +/** True when `--npm-static` leaves the package `name` out of its package + * list: the project marks it never loaded, or the registry implements it + * and the version installed for `entryPath` has an entry module or is not + * one of the entry's `sourceVersions`. */ +export function npmStaticOmitsPackage(entryPath: string, name: string): boolean { + if (unloadedPackages.has(name)) return true; + const shim = IMPLEMENTATION_SHIMS.get(name); + if (shim === undefined) return false; + const dir = installedPackageDir(entryPath, name); + const major = dir === null ? null : installedMajorVersion(dir, name); + return major === null || shim.versions.has(major) || !shim.sourceVersions?.has(major); } /** True when `specifier` names a package the project marks never loaded. */ @@ -272,9 +284,9 @@ function installedPackageDir(fromFile: string, name: string): string | null { return found; } -/** The shim entry for an installed package directory, or null when the - * directory is not a registered package at a supported major version. */ -function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): string | null { +/** The installed package's name and major version from its package.json, + * or null when either is missing. */ +function installedPackageVersion(pkgDir: string): { name: string; major: number } | null { const text = trackedReadFile(join(pkgDir, "package.json")); if (text === null) return null; let pkg: { name?: unknown; version?: unknown }; @@ -284,11 +296,24 @@ function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): st return null; } if (typeof pkg.name !== "string" || typeof pkg.version !== "string") return null; - if (expectedName !== null && pkg.name !== expectedName) return null; - const shim = IMPLEMENTATION_SHIMS.get(pkg.name); + return { name: pkg.name, major: Number(/^(\d+)\./.exec(pkg.version)?.[1] ?? NaN) }; +} + +/** The major version of the package `name` installed at `pkgDir`, or null. */ +function installedMajorVersion(pkgDir: string, name: string): number | null { + const installed = installedPackageVersion(pkgDir); + return installed === null || installed.name !== name ? null : installed.major; +} + +/** The shim entry for an installed package directory, or null when the + * directory is not a registered package at a supported major version. */ +function shimEntryForPackageDir(pkgDir: string, expectedName: string | null): string | null { + const installed = installedPackageVersion(pkgDir); + if (installed === null) return null; + if (expectedName !== null && installed.name !== expectedName) return null; + const shim = IMPLEMENTATION_SHIMS.get(installed.name); if (shim === undefined) return null; - const major = Number(/^(\d+)\./.exec(pkg.version)?.[1] ?? NaN); - const entry = shim.versions.get(major); + const entry = shim.versions.get(installed.major); if (entry === undefined) return null; return join(packageShimRoot(), shim.variant?.(pkgDir) ?? entry); } diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index bf75127c4..ce5f3c2bc 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -96,8 +96,8 @@ import type { import { configurePackageShims, isPackageShimFile, - isPackageShimName, isShimmedImport, + npmStaticOmitsPackage, } from "./package-shims.js"; import { provenanceEntryFor, provenancePaths } from "./provenance-registry.js"; import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js"; @@ -1072,12 +1072,13 @@ export function loadProgram( } externalTypes.set(specifier, declarationPath); } - // Packages scriptc implements itself or the project marks never loaded - // (package-shims.ts) never compile their published JavaScript, even when - // named. + // Packages scriptc implements itself, and packages the project marks + // never loaded (package-shims.ts), never compile their published + // JavaScript, even when named. A registry entry's `sourceVersions` + // (chalk 6) compile from their own source. configurePackageShims(entryPath); const npmStaticPackages = [...new Set(opts?.npmStatic ?? [])].filter( - (name) => !isPackageShimName(name), + (name) => !npmStaticOmitsPackage(entryPath, name), ); // Resolve authored declaration entries before enabling the JS-only // package resolver. Safe overload metadata binds through the runtime @@ -5941,9 +5942,15 @@ function preflight7(load: LoadResult): { // to Node's no-op, the module still evaluates through its // ordinary init edge, and no member is ever read through the // lexer facade (self-imports of a CJS module included — the - // partially-built exports are never observed). Any OTHER use - // keeps the fence. - if (!nsBindingUsesAreBareStatements7(program, sf, clause.namedBindings.name)) { + // partially-built exports are never observed). In JavaScript + // sources every use passes here: member reads lower through the + // names Node's lexer detects, and the other reads refuse at + // their site (lower-namespaces.ts, cjsNamespaceImportDep). Any + // OTHER use in TypeScript keeps the fence. + if ( + !isJsSourceFileName(sf.fileName) && + !nsBindingUsesAreBareStatements7(program, sf, clause.namedBindings.name) + ) { diags.push( unsupportedDiag( "SC1013", @@ -6372,6 +6379,89 @@ function preflight7(load: LoadResult): { * Node refuses the program). This walk asks Node's question at compile * time, in Node's exact ORDER, and answers with Node's exact message. */ +/** The CommonJS program file a re-export (`module.exports = require(spec)`) + * of `from` resolves to, or null: reexport targets union in only when they + * resolve to CommonJS program files (Node's cjsPreparseModuleExports rule). */ +function cjsReexportDep7(program: ts.Program) { + return (from: ts.SourceFile, spec: string): ts.SourceFile | null => { + const dep = + resolveImport7(program, from, spec, "require") ?? + npmStaticDepSf7(program, from, spec, "require"); + return dep !== null && isCjsJsFile7(dep, program) ? dep : null; + }; +} + +/** Node's lexer analysis of a CommonJS file. A bundler rewrite spells some + * lexer-visible names as accessors. */ +function lexWithAccessorNames(sf: ts.SourceFile): ReturnType { + const lexed = cjsLexedExportsOfFile(sf); + for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name); + return lexed; +} + +/** The property names of Node's namespace object for the CommonJS module + * `dep`: the names its lexer detects, CommonJS re-exports included (the + * rule of the named-import link check), plus `default` and + * `module.exports`. */ +export function cjsNamespaceExportNames(program: ts.Program, dep: ts.SourceFile): Set { + const names = cjsVisibleNames(dep, lexWithAccessorNames, cjsReexportDep7(program), new Map()); + return new Set([...names, "default", "module.exports"]); +} + +/** The export names a CommonJS module assigns inside functions + * (`exports.current = value` in a function body), or "all" when such code + * writes a computed name, replaces module.exports or redefines exports + * through Object.defineProperty. Node's namespace object of the module + * holds the values the exports had when the module finished evaluating, + * so it does not see those writes. */ +export function cjsExportsAssignedInFunctions(sf: ts.SourceFile): ReadonlySet | "all" { + const names = new Set(); + let all = false; + const exportsObject = (e: ts.Expression): boolean => + (ts.isIdentifier(e) && e.text === "exports") || isModuleExportsAccess(e); + const target = (e: ts.Expression): void => { + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (isModuleExportsAccess(e)) all = true; + else if (ts.isPropertyAccessExpression(e) && exportsObject(e.expression)) + names.add(e.name.text); + else if (ts.isElementAccessExpression(e) && exportsObject(e.expression)) { + if (ts.isStringLiteralLike(e.argumentExpression)) names.add(e.argumentExpression.text); + else all = true; + } + }; + const visit = (node: ts.Node, inFunction: boolean): void => { + if (all) return; + const nested = inFunction || ts.isFunctionLike(node); + if (nested) { + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + node.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + target(node.left); + else if ( + (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && + (node.operator === ts.SyntaxKind.PlusPlusToken || + node.operator === ts.SyntaxKind.MinusMinusToken) + ) + target(node.operand); + else if (ts.isDeleteExpression(node)) target(node.expression); + else if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + ts.isIdentifier(node.expression.expression) && + node.expression.expression.text === "Object" && + node.arguments.nodes[0] !== undefined && + exportsObject(node.arguments.nodes[0]) + ) + all = true; + } + ts.forEachChild(node, (child) => visit(child, nested)); + }; + visit(sf, false); + return all ? "all" : names; +} + interface BadCjsImport { /** The export name requested from the CJS module. */ exportName: string; @@ -6412,20 +6502,7 @@ function cjsNamedImportLinkCheck( const npmStatic = npmStaticDepSf7(program, from, spec); return npmStatic; }; - // Reexport targets union in only when they resolve to CommonJS program - // files (Node's cjsPreparseModuleExports rule). - const resolveCjsDep = (from: ts.SourceFile, spec: string): ts.SourceFile | null => { - const dep = - resolveImport7(program, from, spec, "require") ?? - npmStaticDepSf7(program, from, spec, "require"); - return dep !== null && isCjsJsFile7(dep, program) ? dep : null; - }; - // A bundler rewrite spells some lexer-visible names as accessors. - const lexWithAccessorNames = (sf: ts.SourceFile): ReturnType => { - const lexed = cjsLexedExportsOfFile(sf); - for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name); - return lexed; - }; + const resolveCjsDep = cjsReexportDep7(program); const visible = (dep: ts.SourceFile, name: string): boolean => // `default` is the module.exports binding itself — always provided. // The program already owns native source ASTs; reuse them without diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d1c97cc..033d98d3d 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -595,6 +595,7 @@ async function compileExecutableNative( ...(features.wasm ? { wasm: true } : {}), ...(features.warningReplace ? { warningReplace: true } : {}), ...(features.stdioExtended ? { stdioExtended: true } : {}), + ...(features.moduleNamespace ? { moduleNamespace: true } : {}), zlib: features.zlib, ...(features.brotli ? { brotli: true } : {}), assert: features.assert, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..69b2d8dd0 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -683,6 +683,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "fs.callbackCall", "dyn.keyDelete", "dyn.keyDeleteComputed", + "dyn.reflectDeleteProperty", "dyn.hasKeyComputed", "dyn.hasOwnComputed", "dyn.propertyIsEnumerableComputed", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 216e46a4d..072c006e9 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -196,6 +196,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * arguments; other receiver representations retain a runtime refusal. */ "dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID }, "dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID }, + "dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL }, "dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL }, "dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL }, "dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL }, @@ -3425,6 +3426,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING }, "process.builtinModule": { argTypes: [STRING, DYN], result: DYN }, "process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN }, + /** A module namespace object as a checked-dynamic value + * (scr_module_namespace_value): the module id, its sorted export names + * and the compiled getter. One object per module. Never throws. */ + "module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN }, "fs.callbackValue": { argTypes: [STRING], result: DYN }, "fs.callbackCall": { argTypes: [STRING, DYN], result: DYN }, "process.hrtimeValue": { argTypes: [], result: DYN }, @@ -3710,6 +3715,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "ffi.argument": { argTypes: [DYN, STRING], result: DYN }, "ffi.memoryModule": { argTypes: [DYN], result: DYN }, "intl.segmenterNew": { argTypes: [], result: DYN }, + "intl.segmenterRefusing": { argTypes: [STRING], result: DYN }, "arrayBuffer.is": { argTypes: [DYN], result: BOOL }, "arrayBuffer.isView": { argTypes: [DYN], result: BOOL }, "arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85b21af7b..bddb3f78b 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -1124,6 +1124,9 @@ const RUNTIME_FEATURE_RULES = { utilPromisify: rule({ fns: ["util.promisifyDyn"] }), /** process.emitWarning is assigned (process.setEmitWarning). */ warningReplace: rule({ fns: ["process.setEmitWarning"] }), + /** Module namespace objects as checked-dynamic values + * (scr_module_namespace.c). */ + moduleNamespace: rule({ fns: ["module.namespaceValue"] }), /** The WebAssembly API (scr_wasm.c). */ wasm: rule({ prefixes: ["wasm."] }), /** crypto.subtle importKey/sign/verify/digest and CryptoKey: @@ -1188,6 +1191,7 @@ function noRuntimeFeatures(): RuntimeFeatures { searchParams: false, urlIdna: false, stdioExtended: false, + moduleNamespace: false, stream: false, streamDyn: false, handleProps: false, diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 1415f2971..689d67367 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -87,6 +87,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( warningReplace: false, stdioExtended: false, brotli: false, + moduleNamespace: false, regex: true, copying: false, textDecoderLegacy: false, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8214-unshimmed-package-version/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8214-unshimmed-package-version/main.ts.json new file mode 100644 index 000000000..6a6a885dc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8214-unshimmed-package-version/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8214-unshimmed-package-version/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8216-module-namespace-live-values/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8216-module-namespace-live-values/main.ts.json new file mode 100644 index 000000000..d856e307e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8216-module-namespace-live-values/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8216-module-namespace-live-values/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8217-intl-segmenter-word-construct/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8217-intl-segmenter-word-construct/main.ts.json new file mode 100644 index 000000000..d2ad44948 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8217-intl-segmenter-word-construct/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8217-intl-segmenter-word-construct/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8218-cjs-namespace-imports/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8218-cjs-namespace-imports/main.mjs.json new file mode 100644 index 000000000..d627f9153 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8218-cjs-namespace-imports/main.mjs.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/8218-cjs-namespace-imports/table.cjs", + "/tests/corpus/8218-cjs-namespace-imports/client/transport.js", + "/tests/corpus/8218-cjs-namespace-imports/client/index.mjs", + "/tests/corpus/8218-cjs-namespace-imports/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8219-cjs-stable-var-exports/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8219-cjs-stable-var-exports/main.js.json new file mode 100644 index 000000000..71e59739d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8219-cjs-stable-var-exports/main.js.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/8219-cjs-stable-var-exports/lib/core.js", + "/tests/corpus/8219-cjs-stable-var-exports/lib/index.js", + "/tests/corpus/8219-cjs-stable-var-exports/lib/options.js", + "/tests/corpus/8219-cjs-stable-var-exports/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8220-js-class-static-destructuring/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8220-js-class-static-destructuring/main.js.json new file mode 100644 index 000000000..7a2ede696 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8220-js-class-static-destructuring/main.js.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/8220-js-class-static-destructuring/classes/comparator.js", + "/tests/corpus/8220-js-class-static-destructuring/ranges/outside.js", + "/tests/corpus/8220-js-class-static-destructuring/formdata.js", + "/tests/corpus/8220-js-class-static-destructuring/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8221-js-recursive-generator-returns/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8221-js-recursive-generator-returns/main.ts.json new file mode 100644 index 000000000..003137231 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8221-js-recursive-generator-returns/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8221-js-recursive-generator-returns/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8222-js-class-static-reflection.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8222-js-class-static-reflection.js.json new file mode 100644 index 000000000..2e961ad08 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8222-js-class-static-reflection.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8222-js-class-static-reflection.js" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 600637ba6..88348bdc2 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -80,6 +80,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_perf.c", "dynAsync"], // perf_hooks event-loop metrics, only for programs that read them. ["scr_perf_loop.c", "perfLoop"], + // Module namespace objects as checked-dynamic values. + ["scr_module_namespace.c", "moduleNamespace"], // Blob and File box their reads as dyn promises through scr_async_dyn.c. ["scr_blob.c", "dynAsync"], ["scr_zlib.c", "zlib"], diff --git a/packages/runtime/src/scr_blob.h b/packages/runtime/src/scr_blob.h index 15f4a10d4..0882e41b6 100644 --- a/packages/runtime/src/scr_blob.h +++ b/packages/runtime/src/scr_blob.h @@ -1,5 +1,6 @@ -/* scriptc runtime — Blob and File (scr_blob.c). Included by scr_runtime.h, - * which says where new declarations go. */ +/* scriptc runtime — Blob and File (scr_blob.c), and module namespace + * objects as checked-dynamic values (scr_module_namespace.c). Included by + * scr_runtime.h, which says where new declarations go. */ #ifndef SCR_BLOB_H #define SCR_BLOB_H @@ -23,9 +24,16 @@ ScrPromise *scr_blob_array_buffer(ScrDyn *self); bool scr_blob_is(const ScrDyn *value, double kind); ScrDyn *scr_intl_segmenter_new(void); +/* A word or sentence Intl.Segmenter whose segment() throws `refusal` + * (borrowed), the compiler's SC2020 message. */ +ScrDyn *scr_intl_segmenter_refusing(ScrStr *refusal); ScrStr *scr_process_builtin_id(ScrDyn *id, ScrArr *known); ScrDyn *scr_process_builtin_module(ScrStr *id, ScrDyn *getter); ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member); +/* A module namespace object as a checked-dynamic value (scr_module_namespace.c): + * one handle per module `id`, whose reads call `getter` with the export + * name when `keys` lists it. Borrows all three; +1 handle. */ +ScrDyn *scr_module_namespace_value(ScrStr *id, ScrArr *keys, ScrDyn *getter); ScrDyn *scr_global_native(ScrArr *known); ScrDyn *scr_global_native_init(ScrArr *known, ScrDyn *(*console_get)(void)); ScrDyn *scr_console_native(void); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 82fe1a6d9..cc2506b90 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -186,6 +186,7 @@ typedef enum { X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \ X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ + X(MODULE_NAMESPACE, 78) /* module namespace object read through its exports (scr_module_namespace.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ @@ -195,6 +196,7 @@ typedef enum { X(REGEXP, 26) /* native RegExp, shared compiled pattern */ \ X(SEARCH_PARAMS, 33) /* live WHATWG URLSearchParams */ \ X(SEGMENTER, 19) /* default Unicode grapheme segmentation */ \ + X(SEGMENTER_REFUSING, 80) /* word or sentence Segmenter whose segment() throws (scr_grapheme.c) */ \ X(SEGMENTS, 20) /* iterable immutable segmentation result */ \ X(SET, 25) /* native Set, shared backing map */ \ X(SHARED_ARRAY_BUFFER, 37) \ @@ -875,6 +877,7 @@ bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key); bool scr_dyn_has_own_computed(const ScrDyn *value, const ScrDyn *key); bool scr_dyn_property_is_enumerable_computed(const ScrDyn *value, const ScrDyn *key); void scr_dyn_key_delete_computed(ScrDyn *value, const ScrDyn *key, bool strict); +bool scr_dyn_reflect_delete_property(ScrDyn *target, const ScrDyn *key); ScrStr *scr_dyn_string_constructor(const ScrDyn *value); ScrDyn *scr_dyn_property_key_value(const ScrDyn *value); ScrDyn *scr_dyn_to_numeric(const ScrDyn *value); diff --git a/packages/runtime/src/scr_grapheme.c b/packages/runtime/src/scr_grapheme.c index 2c93bc395..f9e5ca28d 100644 --- a/packages/runtime/src/scr_grapheme.c +++ b/packages/runtime/src/scr_grapheme.c @@ -201,3 +201,58 @@ ScrDyn *scr_intl_segmenter_new(void) { scr_dyn_handle_install(SCR_DYNH_SEGMENTS, &segments); return scr_graphemes_box(NULL, SCR_DYNH_SEGMENTER); } + +/* A word or sentence Segmenter, which JavaScript dependencies may build at + * module load and use on some paths only: it constructs, and its segment() + * throws the compiler's SC2020 message. */ +typedef struct { + size_t rc; + ScrStr *refusal; /* retained */ +} ScrRefusingSegmenter; + +static void *scr_refusing_segmenter_retain(void *ptr) { + ScrRefusingSegmenter *value = ptr; + value->rc++; + return value; +} + +static void scr_refusing_segmenter_release(void *ptr) { + ScrRefusingSegmenter *value = ptr; + if (--value->rc) return; + scr_str_release(value->refusal); + free(value); +} + +static ScrDyn *scr_refusing_segmenter_invoke(void *ptr, ScrDyn *self, const char *method, + ScrDyn *const *args, size_t argc, const char *what) { + ScrRefusingSegmenter *value = ptr; + if (strcmp(method, "segment") == 0) { + ScrStr *input = scr_dyn_string_coerce_js(argc ? args[0] : scr_dyn_undefined()); + if (!input) return NULL; + scr_str_release(input); + scr_throw_error_msg_code(SCR_ERR_ERROR, value->refusal->data, value->refusal->len, "SC2020"); + return NULL; + } + return scr_graphemes_invoke(&(ScrGraphemes){ .rc = 1 }, self, method, args, argc, what); +} + +static ScrDyn *scr_refusing_segmenter_get(void *ptr, const char *key, size_t len) { + (void)ptr; + return scr_graphemes_get(&(ScrGraphemes){ .rc = 1 }, key, len); +} + +ScrDyn *scr_intl_segmenter_refusing(ScrStr *refusal) { + static const ScrDynHandleOps segmenter = { + .cls = "Segmenter", .retain = scr_refusing_segmenter_retain, + .release = scr_refusing_segmenter_release, .invoke = scr_refusing_segmenter_invoke, + .get = scr_refusing_segmenter_get, .set = scr_graphemes_set, + }; + scr_dyn_handle_install(SCR_DYNH_SEGMENTER_REFUSING, &segmenter); + ScrRefusingSegmenter *value = calloc(1, sizeof *value); + if (!value) scr_trap("scriptc: out of memory\n"); + value->rc = 1; + value->refusal = scr_str_retain(refusal); + ScrDyn *boxed = scr_dyn_new_handle(value, SCR_DYNH_SEGMENTER_REFUSING); + scr_refusing_segmenter_release(value); + return boxed; +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 49af072ed..4eb53bbec 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -8017,6 +8017,45 @@ void scr_dyn_key_delete_computed(ScrDyn *value, const ScrDyn *raw_key, bool stri scr_dyn_release(key); } +/* Reflect.deleteProperty(target, key): a TypeError for a primitive target, + * then the key's ToPropertyKey and a sloppy-mode delete. Answers whether + * the property is absent afterwards (false when it is non-configurable). + * A Proxy would observe the own-property check through its traps, and a + * compiled object's native fields keep their values after a delete through + * its checked-dynamic view, so both refuse. */ +bool scr_dyn_reflect_delete_property(ScrDyn *target, const ScrDyn *raw_key) { + switch (target->kind) { + case SCR_DYN_NULL: + case SCR_DYN_UNDEF: + case SCR_DYN_BOOL: + case SCR_DYN_NUM: + case SCR_DYN_STR: + case SCR_DYN_BIGINT: + case SCR_DYN_SYMBOL: { + static const char message[] = "Reflect.deleteProperty called on non-object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return false; + } + default: + break; + } + if (target->kind == SCR_DYN_PROXY) { + scr_dyn_proxy_unsupported("Reflect.deleteProperty"); + return false; + } + if (target->kind == SCR_DYN_TYPED_REF) { + static const char message[] = "Reflect.deleteProperty on a compiled object is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return false; + } + ScrDyn *key = scr_dyn_property_key_value(raw_key); + if (!key) return false; + scr_dyn_key_delete_computed(target, key, false); + bool deleted = !scr_exc_pending() && !scr_dyn_has_own_computed(target, key); + scr_dyn_release(key); + return deleted; +} + /* Native call/apply/bind lowering still owns these prototype operations. * Refuse overrides until every typed call site observes the property table. */ static bool scr_dyn_fn_property_fence(const ScrStr *key) { diff --git a/packages/runtime/src/scr_module_namespace.c b/packages/runtime/src/scr_module_namespace.c new file mode 100644 index 000000000..b0d7e80c4 --- /dev/null +++ b/packages/runtime/src/scr_module_namespace.c @@ -0,0 +1,121 @@ +/* Module namespace objects as checked-dynamic values + * (module-namespace-elements.ts lowers `import * as ns` values here). */ +#include "scr_runtime.h" + +#include +#include + +/* A module namespace object taken as a checked-dynamic value + * (module-namespace-elements.ts). The compiled getter answers an export's + * current value by name, so reads see live bindings; `keys` lists the + * namespace's export names. One object per module keeps its identity. + * Writes throw Node's strict-mode TypeError for a frozen namespace. */ +typedef struct ScrModuleNamespace { + size_t rc; + ScrStr *id; + ScrDyn *getter; + ScrArr *keys; + struct ScrModuleNamespace *next; +} ScrModuleNamespace; + +static SCR_TL ScrModuleNamespace *scr_module_namespaces; +static SCR_TL bool scr_module_namespace_cleanup_registered; + +static void *scr_module_namespace_retain(void *handle) { + ScrModuleNamespace *ns = handle; + ns->rc++; + return ns; +} + +static void scr_module_namespace_release(void *handle) { + ScrModuleNamespace *ns = handle; + if (--ns->rc) return; + scr_str_release(ns->id); + scr_dyn_release(ns->getter); + scr_arr_release(ns->keys); + free(ns); +} + +static void scr_module_namespace_cleanup(void) { + while (scr_module_namespaces) { + ScrModuleNamespace *ns = scr_module_namespaces; + scr_module_namespaces = ns->next; + scr_module_namespace_release(ns); + } +} + +static bool scr_module_namespace_has(const ScrModuleNamespace *ns, const char *key, size_t length) { + for (size_t i = 0; i < ns->keys->len; i++) { + ScrStr *entry = (ScrStr *)scr_arr_get_ref(ns->keys, (double)i); + bool same = entry->len == length && memcmp(entry->data, key, length) == 0; + scr_str_release(entry); + if (same) return true; + } + return false; +} + +static ScrDyn *scr_module_namespace_get(void *handle, const char *key, size_t length) { + ScrModuleNamespace *ns = handle; + if (!scr_module_namespace_has(ns, key, length)) return NULL; + ScrStr *name = scr_str_new(key, length); + ScrDyn *arg = scr_dyn_new_str(name); + scr_str_release(name); + ScrDyn *result = scr_dyn_call(ns->getter, &arg, 1, "module namespace export"); + scr_dyn_release(arg); + return result; +} + +static bool scr_module_namespace_set(void *handle, const char *key, size_t length, const ScrDyn *value) { + (void)value; + ScrModuleNamespace *ns = handle; + ScrJsonBuf text; + scr_jb_init(&text); + if (scr_module_namespace_has(ns, key, length)) { + scr_jb_puts(&text, "Cannot assign to read only property '"); + scr_jb_write(&text, key, length); + scr_jb_puts(&text, "' of object '[object Module]'"); + } else { + scr_jb_puts(&text, "Cannot add property "); + scr_jb_write(&text, key, length); + scr_jb_puts(&text, ", object is not extensible"); + } + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg(SCR_ERR_TYPE, message->data, message->len); + scr_str_release(message); + return true; +} + +static ScrDyn *scr_module_namespace_invoke(void *handle, ScrDyn *self, const char *key, + ScrDyn *const *args, size_t argc, const char *what) { + ScrDyn *fn = scr_module_namespace_get(handle, key, strlen(key)); + if (fn == NULL) { + if (scr_exc_pending()) return NULL; + fn = scr_dyn_retain(scr_dyn_undefined()); + } + scr_dyn_this_push_dyn(self); + ScrDyn *result = scr_dyn_call(fn, args, argc, what); + scr_dyn_this_pop(); + scr_dyn_release(fn); + return result; +} + +ScrDyn *scr_module_namespace_value(ScrStr *id, ScrArr *keys, ScrDyn *getter) { + static const ScrDynHandleOps ops = { + .cls = "Module", .retain = scr_module_namespace_retain, .release = scr_module_namespace_release, + .get = scr_module_namespace_get, .set = scr_module_namespace_set, .invoke = scr_module_namespace_invoke, + }; + scr_dyn_handle_install(SCR_DYNH_MODULE_NAMESPACE, &ops); + for (ScrModuleNamespace *ns = scr_module_namespaces; ns; ns = ns->next) { + if (ns->id->len == id->len && memcmp(ns->id->data, id->data, id->len) == 0) + return scr_dyn_new_handle(ns, SCR_DYNH_MODULE_NAMESPACE); + } + if (!scr_module_namespace_cleanup_registered) { + scr_atexit(scr_module_namespace_cleanup); + scr_module_namespace_cleanup_registered = true; + } + ScrModuleNamespace *ns = malloc(sizeof(*ns)); + if (!ns) scr_trap("scriptc: out of memory\n"); + *ns = (ScrModuleNamespace){ 1, scr_str_retain(id), scr_dyn_retain(getter), scr_arr_retain(keys), scr_module_namespaces }; + scr_module_namespaces = ns; + return scr_dyn_new_handle(ns, SCR_DYNH_MODULE_NAMESPACE); +} diff --git a/tests/corpus/8214-unshimmed-package-version/main.ts b/tests/corpus/8214-unshimmed-package-version/main.ts new file mode 100644 index 000000000..d582a339b --- /dev/null +++ b/tests/corpus/8214-unshimmed-package-version/main.ts @@ -0,0 +1,5 @@ +// A package that scriptc implements at some major versions (chalk 5) +// compiles from its own source at a version without an implementation. +import chalk, { level } from "chalk"; + +console.log(chalk.version, level, chalk.red("red"), chalk.bold("bold")); diff --git a/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/package.json b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/package.json new file mode 100644 index 000000000..7ab814eb0 --- /dev/null +++ b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/package.json @@ -0,0 +1 @@ +{ "name": "chalk", "version": "6.0.0", "type": "module", "exports": { "types": "./source/index.d.ts", "default": "./source/index.js" }, "imports": { "#styles": "./source/styles.js" } } diff --git a/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.d.ts b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.d.ts new file mode 100644 index 000000000..84dd75758 --- /dev/null +++ b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.d.ts @@ -0,0 +1,3 @@ +export declare const level: number; +declare const chalk: { version: number; red(text: string): string; bold(text: string): string }; +export default chalk; diff --git a/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.js b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.js new file mode 100644 index 000000000..2d85a4ae3 --- /dev/null +++ b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/index.js @@ -0,0 +1,16 @@ +import { styles } from "#styles"; + +export const level = 0; + +function paint(name, text) { + const [open, close] = styles[name]; + return level > 0 ? `\u001B[${open}m${text}\u001B[${close}m` : `${text}`; +} + +const chalk = { + version: 6, + red: (text) => paint("red", text), + bold: (text) => paint("bold", text), +}; + +export default chalk; diff --git a/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/styles.js b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/styles.js new file mode 100644 index 000000000..03e60b5e0 --- /dev/null +++ b/tests/corpus/8214-unshimmed-package-version/node_modules/chalk/source/styles.js @@ -0,0 +1 @@ +export const styles = { red: [31, 39], bold: [1, 22] }; diff --git a/tests/corpus/8216-module-namespace-live-values/main.ts b/tests/corpus/8216-module-namespace-live-values/main.ts new file mode 100644 index 000000000..419ce772e --- /dev/null +++ b/tests/corpus/8216-module-namespace-live-values/main.ts @@ -0,0 +1,11 @@ +// A module namespace object stored as an untyped value, here in a registry +// keyed by module name (pi-coding-agent's VIRTUAL_MODULES): reads see the +// exports' current values, both registry entries are the same object, and +// writes throw Node's TypeError. +import { addThrough, bumpThrough, describe, same, writeThrough } from "ns-registry"; + +console.log(describe()); +bumpThrough(); +bumpThrough(); +console.log(describe()); +console.log(same(), writeThrough(), addThrough()); diff --git a/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/counter.js b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/counter.js new file mode 100644 index 000000000..b971201d8 --- /dev/null +++ b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/counter.js @@ -0,0 +1,7 @@ +export let count = 0; +export var legacy = "legacy"; +export const label = "counter"; +export function bump() { + count++; + legacy = `legacy ${count}`; +} diff --git a/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.d.ts b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.d.ts new file mode 100644 index 000000000..451d646f7 --- /dev/null +++ b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.d.ts @@ -0,0 +1,5 @@ +export declare function describe(): string; +export declare function bumpThrough(): void; +export declare function same(): boolean; +export declare function writeThrough(): string; +export declare function addThrough(): string; diff --git a/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.js b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.js new file mode 100644 index 000000000..51558bcba --- /dev/null +++ b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/index.js @@ -0,0 +1,40 @@ +import * as counter from "./counter.js"; + +export const registry = { + counter: counter, + "alias/counter": counter, +}; + +export function describe() { + const ns = registry["counter"]; + return `${ns.count} ${ns.legacy} ${ns.label} ${typeof ns.bump} ${typeof ns}`; +} + +export function bumpThrough() { + registry.counter.bump(); +} + +export function same() { + return registry.counter === registry["alias/counter"]; +} + +function assign(target, key, value) { + try { + target[key] = value; + } catch (error) { + return `${error.name}: ${error.message}`; + } + return "no error"; +} + +function lookup(name) { + return registry[name]; +} + +export function writeThrough() { + return assign(lookup("counter"), "count", 5); +} + +export function addThrough() { + return assign(lookup("alias/counter"), "extra", 5); +} diff --git a/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/package.json b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/package.json new file mode 100644 index 000000000..30fae6921 --- /dev/null +++ b/tests/corpus/8216-module-namespace-live-values/node_modules/ns-registry/package.json @@ -0,0 +1 @@ +{ "name": "ns-registry", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/8217-intl-segmenter-word-construct/main.ts b/tests/corpus/8217-intl-segmenter-word-construct/main.ts new file mode 100644 index 000000000..5a2a42e08 --- /dev/null +++ b/tests/corpus/8217-intl-segmenter-word-construct/main.ts @@ -0,0 +1,8 @@ +// A package builds its segmenters at module load (pi-tui, grok-mermaid): +// a grapheme segmenter with a locale tag, and a word segmenter that only +// some paths use. Grapheme segmentation matches Node; the word segmenter +// constructs, and this program never segments with it. +import { columns, describe } from "text-width"; + +console.log(describe()); +console.log(columns("éa👍🏽🇫🇷"), columns("abc")); diff --git a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts new file mode 100644 index 000000000..471b4fd0a --- /dev/null +++ b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts @@ -0,0 +1,2 @@ +export declare function columns(text: string): string; +export declare function describe(): string; diff --git a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js new file mode 100644 index 000000000..d58b4ccbb --- /dev/null +++ b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js @@ -0,0 +1,11 @@ +const graphemes = new Intl.Segmenter("en", { granularity: "grapheme" }); +const words = new Intl.Segmenter(undefined, { granularity: "word" }); +const sentences = new Intl.Segmenter(undefined, { granularity: "sentence" }); + +export function columns(text) { + return [...graphemes.segment(text)].map((part) => part.segment).join("|"); +} + +export function describe() { + return `${typeof words} ${typeof sentences}`; +} diff --git a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/package.json b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/package.json new file mode 100644 index 000000000..4f3af53f4 --- /dev/null +++ b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/package.json @@ -0,0 +1 @@ +{ "name": "text-width", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/8218-cjs-namespace-imports/client/index.mjs b/tests/corpus/8218-cjs-namespace-imports/client/index.mjs new file mode 100644 index 000000000..f0d49451f --- /dev/null +++ b/tests/corpus/8218-cjs-namespace-imports/client/index.mjs @@ -0,0 +1,12 @@ +import * as transport from './transport.js' + +export function connect (origin) { + const pool = new transport.Pool(origin, transport.defaults) + console.log(transport.setGlobalDispatcher(pool), pool.connections, transport.defaults.keepAlive) + return pool +} + +export function describe (pool) { + const format = transport.format + return format(pool.request('/status')) +} diff --git a/tests/corpus/8218-cjs-namespace-imports/client/package.json b/tests/corpus/8218-cjs-namespace-imports/client/package.json new file mode 100644 index 000000000..c866a30a1 --- /dev/null +++ b/tests/corpus/8218-cjs-namespace-imports/client/package.json @@ -0,0 +1 @@ +{ "name": "ns-client", "version": "1.0.0", "main": "index.mjs" } diff --git a/tests/corpus/8218-cjs-namespace-imports/client/transport.js b/tests/corpus/8218-cjs-namespace-imports/client/transport.js new file mode 100644 index 000000000..8d9efbe36 --- /dev/null +++ b/tests/corpus/8218-cjs-namespace-imports/client/transport.js @@ -0,0 +1,26 @@ +'use strict' +// A CommonJS module of the package: member exports, a class, and a table +// whose getter stops Node's lexer before the names after it. +class Pool { + constructor (origin, options) { + this.origin = origin + this.connections = options.connections + this.requests = 0 + } + + request (path) { + this.requests += 1 + return `${this.origin}${path} #${this.requests}` + } +} + +function setGlobalDispatcher (dispatcher) { + exports.current = dispatcher + return dispatcher.origin +} + +exports.Pool = Pool +exports.setGlobalDispatcher = setGlobalDispatcher +exports.current = null +exports.defaults = { connections: 4, keepAlive: true } +exports.format = function format (value) { return `[${value}]` } diff --git a/tests/corpus/8218-cjs-namespace-imports/main.mjs b/tests/corpus/8218-cjs-namespace-imports/main.mjs new file mode 100644 index 000000000..7d2d0e862 --- /dev/null +++ b/tests/corpus/8218-cjs-namespace-imports/main.mjs @@ -0,0 +1,13 @@ +// Namespace imports of CommonJS modules in JavaScript: member reads, calls, +// constructions and a method read as a value, through a package's ES +// module and directly. Each name is one Node's lexer detects. +import * as table from './table.cjs' +import { connect, describe } from './client/index.mjs' + +console.log(table.add(20, 22), table.scale) +const add = table.add +console.log(add(1, 2)) + +const pool = connect('http://localhost') +console.log(describe(pool)) +console.log(describe(pool)) diff --git a/tests/corpus/8218-cjs-namespace-imports/table.cjs b/tests/corpus/8218-cjs-namespace-imports/table.cjs new file mode 100644 index 000000000..38696c6f9 --- /dev/null +++ b/tests/corpus/8218-cjs-namespace-imports/table.cjs @@ -0,0 +1,10 @@ +'use strict' +const add = (a, b) => a + b +const scale = 10 +let calls = 0 +module.exports = { + add, + scale, + get calls () { return calls }, + bump () { calls += 1 } +} diff --git a/tests/corpus/8219-cjs-stable-var-exports/lib/core.js b/tests/corpus/8219-cjs-stable-var-exports/lib/core.js new file mode 100644 index 000000000..1210659ac --- /dev/null +++ b/tests/corpus/8219-cjs-stable-var-exports/lib/core.js @@ -0,0 +1,31 @@ +// highlight.js's lib/core.js shape: the module builds one instance and +// exports the var that holds it. +'use strict'; + +const HLJS = function (options) { + const languages = new Map(); + const prefix = options.classPrefix || 'hljs-'; + + function registerLanguage(name, keywords) { + languages.set(name, keywords); + } + + function listLanguages() { + return Array.from(languages.keys()); + } + + function highlight(code, name) { + const keywords = languages.get(name); + if (keywords === undefined) throw new Error(`Unknown language: "${name}"`); + return code + .split(' ') + .map((word) => (keywords.includes(word) ? `${word}` : word)) + .join(' '); + } + + return { registerLanguage, listLanguages, highlight, versionString: '10.7.3' }; +}; + +var highlight = HLJS({}); + +module.exports = highlight; diff --git a/tests/corpus/8219-cjs-stable-var-exports/lib/index.js b/tests/corpus/8219-cjs-stable-var-exports/lib/index.js new file mode 100644 index 000000000..5f7726ea2 --- /dev/null +++ b/tests/corpus/8219-cjs-stable-var-exports/lib/index.js @@ -0,0 +1,8 @@ +// highlight.js's lib/index.js shape: require the core instance, register +// languages on it and export the same var. +var hljs = require('./core'); + +hljs.registerLanguage('javascript', ['const', 'let', 'function', 'return']); +hljs.registerLanguage('python', ['def', 'return', 'lambda']); + +module.exports = hljs; diff --git a/tests/corpus/8219-cjs-stable-var-exports/lib/options.js b/tests/corpus/8219-cjs-stable-var-exports/lib/options.js new file mode 100644 index 000000000..17512396f --- /dev/null +++ b/tests/corpus/8219-cjs-stable-var-exports/lib/options.js @@ -0,0 +1,12 @@ +// A let that is never reassigned, exported in a table. +'use strict'; + +let tabReplace = ' '; +let calls = []; + +function render(text) { + calls.push(text); + return text.split('\t').join(tabReplace); +} + +module.exports = { tabReplace, calls, render }; diff --git a/tests/corpus/8219-cjs-stable-var-exports/main.js b/tests/corpus/8219-cjs-stable-var-exports/main.js new file mode 100644 index 000000000..8607165a6 --- /dev/null +++ b/tests/corpus/8219-cjs-stable-var-exports/main.js @@ -0,0 +1,21 @@ +// A var and a let exported by reference, where neither changes after the +// export: importers see the same instance and value as Node. +'use strict'; + +const hljs = require('./lib'); +const core = require('./lib/core'); +const options = require('./lib/options'); + +console.log(hljs === core, hljs.versionString); +console.log(hljs.listLanguages()); +console.log(hljs.highlight('const x = function', 'javascript')); +hljs.registerLanguage('ruby', ['def', 'end']); +console.log(core.listLanguages().length, core.highlight('def run end', 'ruby')); +try { + hljs.highlight('x', 'cobol'); +} catch (error) { + console.log(error.message); +} + +console.log(JSON.stringify(options.tabReplace), options.render('a\tb'), options.render('\tc')); +console.log(options.calls); diff --git a/tests/corpus/8219-cjs-stable-var-exports/package.json b/tests/corpus/8219-cjs-stable-var-exports/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/corpus/8219-cjs-stable-var-exports/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/corpus/8220-js-class-static-destructuring/classes/comparator.js b/tests/corpus/8220-js-class-static-destructuring/classes/comparator.js new file mode 100644 index 000000000..3b128847c --- /dev/null +++ b/tests/corpus/8220-js-class-static-destructuring/classes/comparator.js @@ -0,0 +1,22 @@ +'use strict' +// semver's Comparator: a static getter returning a module symbol. +const ANY = Symbol('SemVer ANY') + +class Comparator { + static get ANY () { + return ANY + } + + static loose = false + + static parse (text) { + return new Comparator(text) + } + + constructor (text) { + this.semver = text === '' ? ANY : text + this.value = text === '' ? '' : `>=${text}` + } +} + +module.exports = Comparator diff --git a/tests/corpus/8220-js-class-static-destructuring/formdata.js b/tests/corpus/8220-js-class-static-destructuring/formdata.js new file mode 100644 index 000000000..851d3ba28 --- /dev/null +++ b/tests/corpus/8220-js-class-static-destructuring/formdata.js @@ -0,0 +1,26 @@ +'use strict' +// undici's FormData: statics that read private instance state, taken off +// the class before the class is exposed. +class FormData { + #state = [] + + append (name, value) { + this.#state.push({ name, value }) + } + + static getFormDataState (formData) { + return formData.#state + } + + static setFormDataState (formData, state) { + formData.#state = state + } + + static get boundary () { + return undefined + } +} + +const { getFormDataState, setFormDataState, boundary = '----formdata-default' } = FormData + +module.exports = { FormData, getFormDataState, setFormDataState, boundary } diff --git a/tests/corpus/8220-js-class-static-destructuring/main.js b/tests/corpus/8220-js-class-static-destructuring/main.js new file mode 100644 index 000000000..6cc17b94e --- /dev/null +++ b/tests/corpus/8220-js-class-static-destructuring/main.js @@ -0,0 +1,39 @@ +'use strict' +// Object patterns over a class value read its static getters, fields and +// methods as Node does, defaults included. +const Comparator = require('./classes/comparator') +const { outside, ANY, any, isLoose, name } = require('./ranges/outside') +const { FormData, getFormDataState, setFormDataState, boundary } = require('./formdata') + +console.log(typeof ANY, ANY === Comparator.ANY, any === ANY, isLoose, name) +console.log(outside(''), outside('1.2.3')) +console.log(Comparator.parse('').semver === ANY, Comparator.parse('2.0.0').semver === ANY) + +const form = new FormData() +form.append('a', 1) +form.append('b', 'two') +console.log(getFormDataState(form)) +setFormDataState(form, [{ name: 'c', value: true }]) +console.log(getFormDataState(form), boundary) + +class Defaults { + static count = 0 + static missing = undefined + static nested = { inner: 'value' } +} +const { count = 5, missing = 'fallback', nested: { inner, other = 'other' } } = Defaults +console.log(count, missing, inner, other) + +// Inherited statics, and a static getter reading the class it is read +// through. +class Base { + static tag = 'base' + static get label () { return `label:${this.tag}` } + static make () { return this.tag } +} +class Sub extends Base { + static tag = 'sub' +} +const { tag, label, make, name: subName } = Sub +const { label: baseLabel } = Base +console.log(tag, label, typeof make, subName, baseLabel) diff --git a/tests/corpus/8220-js-class-static-destructuring/package.json b/tests/corpus/8220-js-class-static-destructuring/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/corpus/8220-js-class-static-destructuring/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/corpus/8220-js-class-static-destructuring/ranges/outside.js b/tests/corpus/8220-js-class-static-destructuring/ranges/outside.js new file mode 100644 index 000000000..a8ffe06f2 --- /dev/null +++ b/tests/corpus/8220-js-class-static-destructuring/ranges/outside.js @@ -0,0 +1,13 @@ +'use strict' +// semver's ranges/outside.js and ranges/subset.js destructure the class's +// statics at module load. +const Comparator = require('../classes/comparator') +const { ANY } = Comparator +const { ANY: any, loose: isLoose = true, parse, name } = Comparator + +const outside = (text) => { + const comparator = parse(text) + return comparator.semver === ANY ? 'any' : comparator.value +} + +module.exports = { outside, ANY, any, isLoose, name } diff --git a/tests/corpus/8221-js-recursive-generator-returns/main.ts b/tests/corpus/8221-js-recursive-generator-returns/main.ts new file mode 100644 index 000000000..72b9a7b10 --- /dev/null +++ b/tests/corpus/8221-js-recursive-generator-returns/main.ts @@ -0,0 +1,10 @@ +// A JavaScript package's parser of mutually recursive generator methods that +// return `yield*` of each other (yaml 2.9.0's parse/parser.js): the checker +// cannot infer their results. Tokens, values passed to next() and return +// values reach each generator in the same order as in Node. +import { parseTokens, drive, spreadTypes, walkAll } from "token-parser"; + +for (const token of parseTokens("a { b [ c d ] e } f")) console.log(token); +console.log(drive("{ x } y ! z")); +console.log(spreadTypes("[ 1 2 3 ]")); +console.log(await walkAll("a ( b c ) d ( e )")); diff --git a/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.d.ts b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.d.ts new file mode 100644 index 000000000..763a34ae4 --- /dev/null +++ b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function parseTokens(source: string): string[]; +export declare function drive(source: string): string; +export declare function spreadTypes(source: string): string; +export declare function walkAll(source: string): Promise; diff --git a/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.js b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.js new file mode 100644 index 000000000..aab64a1ac --- /dev/null +++ b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/dist/index.js @@ -0,0 +1,131 @@ +'use strict' + +class Parser { + constructor (source) { + this.tokens = source.split(' ') + this.index = 0 + this.stack = [] + } + + * parse () { + while (this.index < this.tokens.length) { + const done = yield * this.step() + if (done) return 'stopped' + } + return yield * this.end() + } + + * step () { + const token = this.tokens[this.index++] + if (token === '{') return yield * this.blockMap(token) + if (token === '[') return yield * this.flowSeq(token) + if (token === '!') return true + return yield * this.scalar(token) + } + + * blockMap (open) { + this.stack.push(open) + yield { type: 'map-start', depth: this.stack.length } + while (this.index < this.tokens.length && this.tokens[this.index] !== '}') { + const stop = yield * this.step() + if (stop) return stop + } + this.index++ + this.stack.pop() + const reply = yield { type: 'map-end', depth: this.stack.length + 1 } + if (reply !== undefined) yield { type: 'reply', value: reply } + return false + } + + * flowSeq (open) { + this.stack.push(open) + let count = 0 + while (this.index < this.tokens.length && this.tokens[this.index] !== ']') { + yield * this.step() + count++ + } + this.index++ + this.stack.pop() + yield { type: 'seq', count } + return false + } + + * scalar (token) { + const value = yield { type: 'scalar', source: token } + if (value !== undefined) return yield * this.scalar(String(value)) + return false + } + + * end () { + yield { type: 'end', depth: this.stack.length } + return this.index + } +} + +function parseTokens (source) { + const tokens = [] + for (const token of new Parser(source).parse()) tokens.push(JSON.stringify(token)) + return tokens +} + +// next(value) reaches the innermost delegated generator, and return values +// pass back through each yield*. +function drive (source) { + const parser = new Parser(source).parse() + const log = [] + let result = parser.next() + while (!result.done) { + const value = result.value + log.push(JSON.stringify(value)) + result = parser.next(value.type === 'map-end' ? 'ack' : value.source === 'x' ? 'x2' : undefined) + } + log.push('return ' + result.value) + return log.join('\n') +} + +function spreadTypes (source) { + const tokens = [...new Parser(source).parse()] + return tokens.length + ' ' + tokens.map((token) => token.type).join(',') +} + +// Async generator methods with the same mutual recursion. +class AsyncWalker { + constructor (source) { + this.tokens = source.split(' ') + this.index = 0 + } + + async * walk () { + while (this.index < this.tokens.length) yield * this.next() + return this.index + } + + async * next () { + const token = this.tokens[this.index++] + await null + if (token === '(') return yield * this.group() + yield token + return token + } + + async * group () { + const inner = [] + while (this.tokens[this.index] !== ')') inner.push(yield * this.next()) + this.index++ + yield 'group:' + inner.join('+') + return inner.length + } +} + +async function walkAll (source) { + const seen = [] + const walker = new AsyncWalker(source).walk() + let result = await walker.next() + while (!result.done) { + seen.push(result.value) + result = await walker.next() + } + return seen.join(' ') + ' -> ' + result.value +} + +module.exports = { parseTokens, drive, spreadTypes, walkAll } diff --git a/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/package.json b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/package.json new file mode 100644 index 000000000..1fa77c2eb --- /dev/null +++ b/tests/corpus/8221-js-recursive-generator-returns/node_modules/token-parser/package.json @@ -0,0 +1 @@ +{ "name": "token-parser", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts" } diff --git a/tests/corpus/8222-js-class-static-reflection.js b/tests/corpus/8222-js-class-static-reflection.js new file mode 100644 index 000000000..74027cc64 --- /dev/null +++ b/tests/corpus/8222-js-class-static-reflection.js @@ -0,0 +1,94 @@ +'use strict' +// A class's statics extracted, then removed with Reflect.deleteProperty, and +// redefined with Object.defineProperties (undici's FormData and Response). + +const kEnumerableProperty = { __proto__: null, enumerable: true } + +class FormData { + #state = [] + append (name, value) { this.#state.push({ name, value }) } + static getFormDataState (formData) { return formData.#state } + static setFormDataState (formData, state) { formData.#state = state } +} + +const getFormDataState = FormData.getFormDataState +const setFormDataState = FormData.setFormDataState +console.log(Reflect.deleteProperty(FormData, 'getFormDataState')) +console.log(Reflect.deleteProperty(FormData, 'setFormDataState')) +console.log(Reflect.deleteProperty(FormData, 'missing')) +console.log(typeof FormData.getFormDataState, typeof FormData.setFormDataState) + +const form = new FormData() +form.append('a', 1) +form.append('b', 'two') +console.log(getFormDataState(form)) +setFormDataState(form, [{ name: 'c', value: true }]) +console.log(getFormDataState(form).length, getFormDataState(form)[0].name) +try { + FormData.getFormDataState(form) +} catch (error) { + console.log(error.constructor.name, error.message) +} + +class Response { + #status + constructor (status) { this.#status = status } + get status () { return this.#status } + static json () { return new Response(200) } + static error () { return new Response(0) } + static getResponseState (response) { return response.#status } +} + +const getResponseState = Response.getResponseState +Reflect.deleteProperty(Response, 'getResponseState') +Object.defineProperties(Response, { + json: kEnumerableProperty, + error: kEnumerableProperty +}) +const response = Response.json() +console.log(response.status, Response.error().status, getResponseState(response)) +for (const key of ['json', 'error', 'getResponseState']) { + const descriptor = Object.getOwnPropertyDescriptor(Response, key) + console.log(key, descriptor === undefined ? 'absent' : [descriptor.enumerable, descriptor.writable, descriptor.configurable, typeof descriptor.value]) +} + +// Statics a subclass inherits, a static field and a static accessor. +class Base { + static helper () { return 'helper' } + static count = 3 + static get label () { return 'label' } +} +class Child extends Base {} +console.log(Child.helper(), Child.count, Child.label) +console.log(Reflect.deleteProperty(Base, 'helper'), Reflect.deleteProperty(Base, 'count')) +console.log(typeof Child.helper, Child.count, typeof Base.helper, Base.count) +console.log(Reflect.deleteProperty(Base, 'label'), Base.label, Child.label) +Base.helper = () => 'replaced' +console.log(Base.helper(), Child.helper()) +Base.count = 4 +const countKey = { toString () { return 'count' } } +console.log(Reflect.deleteProperty(Base, countKey), Base.count) +class Key { + constructor (name) { this.name = name } + toString () { return this.name } +} +Base.count = 5 +console.log(Reflect.deleteProperty(Base, new Key('count')), Base.count) + +// Checked-dynamic objects and primitive targets. +const parsed = JSON.parse('{"a":1,"b":2}') +console.log(Reflect.deleteProperty(parsed, 'a'), parsed) +const frozen = Object.freeze(JSON.parse('{"a":1}')) +console.log(Reflect.deleteProperty(frozen, 'a'), frozen) +const attempt = (run) => { + try { + run() + } catch (error) { + console.log(error.constructor.name, error.message) + } +} +attempt(() => Reflect.deleteProperty(1, 'a')) +attempt(() => Reflect.deleteProperty('text', 'a')) +attempt(() => Reflect.deleteProperty(null, 'a')) +attempt(() => Reflect.deleteProperty(undefined, 'a')) +attempt(() => Reflect.deleteProperty(true, 'a')) diff --git a/tests/coverage-fixtures/cjs-mutable-exports/main.js b/tests/coverage-fixtures/cjs-mutable-exports/main.js new file mode 100644 index 000000000..ac16cc4ec --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/main.js @@ -0,0 +1,5 @@ +const reassigned = require('./reassigned'); +const written = require('./written'); +const redeclared = require('./redeclared'); +const stable = require('./stable'); +console.log(reassigned.n, written.tab, redeclared.twice, stable.n); diff --git a/tests/coverage-fixtures/cjs-mutable-exports/package.json b/tests/coverage-fixtures/cjs-mutable-exports/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/coverage-fixtures/cjs-mutable-exports/reassigned.js b/tests/coverage-fixtures/cjs-mutable-exports/reassigned.js new file mode 100644 index 000000000..6c77f3b43 --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/reassigned.js @@ -0,0 +1,4 @@ +// Reassigned after the export: Node's importers keep the first object. +var counter = { n: 1 }; +module.exports = counter; +counter = { n: 2 }; diff --git a/tests/coverage-fixtures/cjs-mutable-exports/redeclared.js b/tests/coverage-fixtures/cjs-mutable-exports/redeclared.js new file mode 100644 index 000000000..3150903f6 --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/redeclared.js @@ -0,0 +1,4 @@ +// Declared twice: the second declaration assigns the binding again. +var twice = 1; +module.exports = { twice }; +var twice = 2; diff --git a/tests/coverage-fixtures/cjs-mutable-exports/stable.js b/tests/coverage-fixtures/cjs-mutable-exports/stable.js new file mode 100644 index 000000000..c2846bbc9 --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/stable.js @@ -0,0 +1,3 @@ +// Initialized before the export and never assigned. +var stable = { n: 3 }; +module.exports = stable; diff --git a/tests/coverage-fixtures/cjs-mutable-exports/written.js b/tests/coverage-fixtures/cjs-mutable-exports/written.js new file mode 100644 index 000000000..8e0c29cd2 --- /dev/null +++ b/tests/coverage-fixtures/cjs-mutable-exports/written.js @@ -0,0 +1,4 @@ +// Written by a function: the table holds the value at the export. +let tab = ' '; +function setTab(next) { tab = next; } +module.exports = { tab, setTab }; diff --git a/tests/diagnostics/intl-segmenter.ts b/tests/diagnostics/intl-segmenter.ts index 42bdae846..0cf30eabb 100644 --- a/tests/diagnostics/intl-segmenter.ts +++ b/tests/diagnostics/intl-segmenter.ts @@ -1,5 +1,5 @@ -// The native implementation covers default Unicode grapheme segmentation. -new Intl.Segmenter("en-US"); +// The native implementation covers Unicode grapheme segmentation. +new Intl.Segmenter(["en-US", "de"]); new Intl.Segmenter(undefined, { granularity: "word" }); new Intl.Segmenter(undefined, { granularity: "sentence" }); // End of the diagnostic fixture. diff --git a/tests/fixtures/npm-static/node_modules/word-segments/index.d.ts b/tests/fixtures/npm-static/node_modules/word-segments/index.d.ts new file mode 100644 index 000000000..2fd45310e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/word-segments/index.d.ts @@ -0,0 +1 @@ +export declare function splitWords(text: string): string; diff --git a/tests/fixtures/npm-static/node_modules/word-segments/index.js b/tests/fixtures/npm-static/node_modules/word-segments/index.js new file mode 100644 index 000000000..a67aa5381 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/word-segments/index.js @@ -0,0 +1,9 @@ +const words = new Intl.Segmenter(undefined, { granularity: "word" }); + +export function splitWords(text) { + try { + return [...words.segment(text)].map((part) => part.segment).join("|"); + } catch (error) { + return `${error.code}: ${error.message}`; + } +} diff --git a/tests/fixtures/npm-static/node_modules/word-segments/package.json b/tests/fixtures/npm-static/node_modules/word-segments/package.json new file mode 100644 index 000000000..37b0d6f73 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/word-segments/package.json @@ -0,0 +1 @@ +{ "name": "word-segments", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm-static/word-segments-cli.ts b/tests/fixtures/npm-static/word-segments-cli.ts new file mode 100644 index 000000000..28e3bd5e1 --- /dev/null +++ b/tests/fixtures/npm-static/word-segments-cli.ts @@ -0,0 +1,3 @@ +import { splitWords } from "word-segments"; + +console.log(splitWords("two words")); diff --git a/tests/harness/__snapshots__/intl-segmenter.ts.txt b/tests/harness/__snapshots__/intl-segmenter.ts.txt index e50205eec..19a13ef4f 100644 --- a/tests/harness/__snapshots__/intl-segmenter.ts.txt +++ b/tests/harness/__snapshots__/intl-segmenter.ts.txt @@ -1,15 +1,15 @@ intl-segmenter.ts:2:1 - error SC2020: 'Intl.Segmenter locale negotiation' is part of the standard library types but has no scriptc lowering yet - 1 | // The native implementation covers default Unicode grapheme segmentation. - 2 | new Intl.Segmenter("en-US"); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 | // The native implementation covers Unicode grapheme segmentation. + 2 | new Intl.Segmenter(["en-US", "de"]); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | new Intl.Segmenter(undefined, { granularity: "word" }); - hint: omit the locale or pass undefined for default Unicode grapheme segmentation + hint: omit the locale, pass undefined, or pass one language tag as a string literal intl-segmenter.ts:3:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet - 2 | new Intl.Segmenter("en-US"); + 2 | new Intl.Segmenter(["en-US", "de"]); 3 | new Intl.Segmenter(undefined, { granularity: "word" }); | ^~~~~~~~~~~~~~~~~~~~~~~ 4 | new Intl.Segmenter(undefined, { granularity: "sentence" }); diff --git a/tests/harness/corpus-costs/8214-unshimmed-package-version/main.ts.json b/tests/harness/corpus-costs/8214-unshimmed-package-version/main.ts.json new file mode 100644 index 000000000..d5d1c5f65 --- /dev/null +++ b/tests/harness/corpus-costs/8214-unshimmed-package-version/main.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3500 } diff --git a/tests/harness/corpus-costs/8216-module-namespace-live-values/main.ts.json b/tests/harness/corpus-costs/8216-module-namespace-live-values/main.ts.json new file mode 100644 index 000000000..d5d1c5f65 --- /dev/null +++ b/tests/harness/corpus-costs/8216-module-namespace-live-values/main.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3500 } diff --git a/tests/harness/corpus-costs/8217-intl-segmenter-word-construct/main.ts.json b/tests/harness/corpus-costs/8217-intl-segmenter-word-construct/main.ts.json new file mode 100644 index 000000000..d5d1c5f65 --- /dev/null +++ b/tests/harness/corpus-costs/8217-intl-segmenter-word-construct/main.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3500 } diff --git a/tests/harness/corpus-costs/8218-cjs-namespace-imports/main.mjs.json b/tests/harness/corpus-costs/8218-cjs-namespace-imports/main.mjs.json new file mode 100644 index 000000000..ffabf028f --- /dev/null +++ b/tests/harness/corpus-costs/8218-cjs-namespace-imports/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 7000, "san": 7200 } diff --git a/tests/harness/corpus-costs/8219-cjs-stable-var-exports/main.js.json b/tests/harness/corpus-costs/8219-cjs-stable-var-exports/main.js.json new file mode 100644 index 000000000..f8d95630e --- /dev/null +++ b/tests/harness/corpus-costs/8219-cjs-stable-var-exports/main.js.json @@ -0,0 +1 @@ +{ "plain": 8300, "san": 7400 } diff --git a/tests/harness/corpus-costs/8220-js-class-static-destructuring/main.js.json b/tests/harness/corpus-costs/8220-js-class-static-destructuring/main.js.json new file mode 100644 index 000000000..8b730689b --- /dev/null +++ b/tests/harness/corpus-costs/8220-js-class-static-destructuring/main.js.json @@ -0,0 +1 @@ +{ "plain": 10100, "san": 9400 } diff --git a/tests/harness/corpus-costs/8221-js-recursive-generator-returns/main.ts.json b/tests/harness/corpus-costs/8221-js-recursive-generator-returns/main.ts.json new file mode 100644 index 000000000..e6c42bf47 --- /dev/null +++ b/tests/harness/corpus-costs/8221-js-recursive-generator-returns/main.ts.json @@ -0,0 +1 @@ +{ "plain": 6600, "san": 6000 } diff --git a/tests/harness/corpus-costs/8222-js-class-static-reflection.js.json b/tests/harness/corpus-costs/8222-js-class-static-reflection.js.json new file mode 100644 index 000000000..7f7902ca1 --- /dev/null +++ b/tests/harness/corpus-costs/8222-js-class-static-reflection.js.json @@ -0,0 +1 @@ +{ "plain": 4500, "san": 4100 } diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index e1831c609..5b541cc54 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -543,6 +543,16 @@ test("settled generic rest-order fences count each source statement once", () => expect(coverage.stats.statementsFailed).toBe(2); }); +test("CommonJS var and let exports that can change after the export keep their refusal", () => { + // JavaScript refusals defer to run time: they are the runtime fences. + const { coverage } = analyze(fixture("cjs-mutable-exports/main.js")); + const refused = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])] + .map((d) => /exporting the mutable 'let' binding '(\w+)'/.exec(d.message)?.[1]) + .filter((name) => name !== undefined) + .sort(); + expect(refused).toEqual(["counter", "tab", "twice"]); +}); + test("inferred JavaScript compiles natively", async () => { // The js-gap fixture turns noImplicitAny off. Its inferred parameter // uses checked native values alongside the JSDoc-typed neighbor. diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 388da2138..838302af4 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -523,6 +523,28 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { } }); + test("a word segmenter in a package constructs, and its segment() throws SC2020", async () => { + const entry = join(pilotRoot, "word-segments-cli.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toEqual([{ package: "word-segments", status: "static" }]); + expect(coverage.diagnostics).toHaveLength(0); + const outDir = join(cacheDir, "word-segments", sanitize ? "san" : "plain"); + const result = await compile(entry, { + backend: "llvm", + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const nativeRes = await runBinary(result.binaryPath, []); + expect(nativeRes.stdout.toString()).toMatch( + /^SC2020: Intl\.Segmenter segmentation with granularity 'word' is not supported yet \[SC2020 at .*index\.js:1\]\n$/, + ); + expect(nativeRes.exitCode).toBe(0); + }); + test.each([ 'import { Console as Logger } from "node:console"; new Logger({ stdout: process.stdout }).dir(1);', 'import * as output from "console"; new output.Console(process.stdout).table([1, 2]);', diff --git a/tests/harness/package-shims.test.ts b/tests/harness/package-shims.test.ts index ca3d1589b..a21c4e768 100644 --- a/tests/harness/package-shims.test.ts +++ b/tests/harness/package-shims.test.ts @@ -8,7 +8,8 @@ * stays unloaded: its require throws MODULE_NOT_FOUND; * - a shimmed package that is not installed, or whose major version has no * shim, resolves exactly as before (here: refused like any missing or - * uncompiled package); + * uncompiled package), and `--npm-static` does not compile a version the + * registry entry does not list in `sourceVersions`; * - the undici shims keep memory bounded against a peer that sends endless * chunk-size lines and large or compressed bodies, and the 5.x profile * rejects a Host value with CR or LF as undici 5.29 does (the vendored @@ -253,6 +254,33 @@ describe("package shims", () => { } }, 180_000); + test("--npm-static leaves out an implemented package at a version it does not list", async () => { + const dir = join(scratch, "undici-v6-npm-static"); + const pkg = join(dir, "node_modules/undici"); + mkdirSync(pkg, { recursive: true }); + writeFileSync( + join(pkg, "package.json"), + '{ "name": "undici", "version": "6.21.0", "main": "index.js" }\n', + ); + writeFileSync(join(pkg, "index.js"), "exports.version = 6;\n"); + writeFileSync( + join(dir, "main.mjs"), + 'import { version } from "undici";\nconsole.log(version);\n', + ); + const result = await compile(join(dir, "main.mjs"), { + outPath: join(dir, ".scriptc/program"), + outDir: join(dir, ".scriptc"), + npmStatic: ["undici"], + sanitize, + }); + // The package's JavaScript is not part of the program, so the import + // has no declarations. + expect(result.ok).toBe(false); + expect(result.diagnostics.map((d) => d.message).join("\n")).toContain( + "Could not find a declaration file for module 'undici'", + ); + }, 180_000); + for (const name of ["undici7", "undici5"]) { test(`${name}: endless chunk-size lines and large bodies stay in bounded memory`, async () => { const { dir, binary } = await buildInCase(name, "undici-bounded.ts"); From 41664c903b18e943bb26e94c0cd743692b364036 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 12:03:38 -0500 Subject: [PATCH 074/395] Walk files iteratively in the untyped collection inference The declaration and reference sweeps recursed through ts.forEachChild, two JS frames per AST level, so a 7000-level expression overflowed the stack before the expression lowering's 200-level SC1090 fence. Both sweeps use ts.walkPreorder now, and primitiveOf answers null past the same 200 levels. --- .../frontend/lowering/js-collection-types.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/js-collection-types.ts b/packages/compiler/src/frontend/lowering/js-collection-types.ts index 2be3c067f..8820ade4a 100644 --- a/packages/compiler/src/frontend/lowering/js-collection-types.ts +++ b/packages/compiler/src/frontend/lowering/js-collection-types.ts @@ -55,6 +55,8 @@ const MAP_LOOKUPS = new Set(["get", "has", "delete"]); const SET_LOOKUPS = new Set(["has", "delete"]); const ARRAY_READS = new Set(["pop", "shift"]); const ARRAY_SEARCHES = new Set(["includes", "indexOf"]); +/** The expression lowering refuses deeper nesting (SC1090). */ +const MAX_DEPTH = 200; /** The inferred type of the binding a `new Map()` or `new Set()` initializes, * which the construction adopts. */ @@ -153,9 +155,8 @@ function inferFile(lowerer: Lowerer, file: ts.SourceFile): Candidate[] { value: undefined, }); } - ts.forEachChild(node, declarations); }; - declarations(file); + ts.walkPreorder(file, declarations); if (directEval || candidates.size === 0) return []; const names = new Set( @@ -171,9 +172,8 @@ function inferFile(lowerer: Lowerer, file: ts.SourceFile): Candidate[] { else refused.add(symbol); } } - ts.forEachChild(node, references); }; - references(file); + ts.walkPreorder(file, references); for (const symbol of refused) candidates.delete(symbol); const proven = [...candidates.values()].filter( (c) => c.valueWrites.length > 0 || c.keyWrites.length > 0, @@ -381,16 +381,20 @@ function checkerPrimitive(type: ts.Type): Primitive | null { /** The primitive type `e` always evaluates to, or null. The checker answers * where it knows; where it types `e` as `any`, the value's structure does: * a read of a decided candidate, arithmetic and comparisons over such - * values, and constants defined from them. */ + * values, and constants defined from them. An expression nested deeper than + * the lowering's nesting limit answers null. */ function primitiveOf( lowerer: Lowerer, bySymbol: Map, e: ts.Expression, visiting: Set, + depth = 0, ): Primitive | null { + if (depth > MAX_DEPTH) return null; const type = lowerer.checker.getTypeAtLocation(e); if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0) return checkerPrimitive(type); - const of = (x: ts.Expression): Primitive | null => primitiveOf(lowerer, bySymbol, x, visiting); + const of = (x: ts.Expression): Primitive | null => + primitiveOf(lowerer, bySymbol, x, visiting, depth + 1); if (ts.isParenthesizedExpression(e)) return of(e.expression); if (ts.isPrefixUnaryExpression(e)) { if (e.operator === ts.SyntaxKind.ExclamationToken) return "boolean"; @@ -461,7 +465,7 @@ function primitiveOf( (declaration.parent.flags & ts.NodeFlags.Const) !== 0 ) { visiting.add(e); - const p = primitiveOf(lowerer, bySymbol, declaration.initializer, visiting); + const p = primitiveOf(lowerer, bySymbol, declaration.initializer, visiting, depth + 1); visiting.delete(e); return p; } From 684a0abd59e0645f609566a400a1052f782c76b7 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 09:22:05 -0700 Subject: [PATCH 075/395] Store AbortController and AbortSignal handles in class fields A class field typed AbortController or AbortSignal (plain, optional, nullable, or in an array) maps to checked-dynamic storage, and the class-field rule refused it (SC1090) although the same handles already live in locals, Map values, and closures. They now take the checked-dynamic slot, like the fetch Headers, Request, and Response handles. controller?.abort() on a nullable controller was refused as a method call (also for locals): the static abort lowering ran only without an optional chain. It now runs again once the chain has narrowed the receiver. Found by pi's TUI Editor (autocompleteAbort: AbortController | undefined). Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stdlib.ts | 12 +++-- .../compiler/src/frontend/lowering/lowerer.ts | 3 +- packages/compiler/src/frontend/type-mapper.ts | 18 ++++--- .../class-abort-controller-fields.ts.json | 6 +++ tests/corpus/class-abort-controller-fields.ts | 52 +++++++++++++++++++ .../class-abort-controller-fields.ts.json | 1 + 6 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/class-abort-controller-fields.ts.json create mode 100644 tests/corpus/class-abort-controller-fields.ts create mode 100644 tests/harness/corpus-costs/class-abort-controller-fields.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index dc9d3def8..3b5be4d23 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -1630,16 +1630,22 @@ export function lowerStaticAbortControllerCall( call: ts.CallExpression, ): IrExpr | null { const access = call.expression; + // An optional chain (`controller?.abort()`) lowers the call again once it + // has narrowed the receiver (Lowerer.chainHandled). if ( - call.questionDotToken || + (call.questionDotToken && !lowerer.chainHandled.has(call)) || call.arguments.nodes.some((argument) => ts.isSpreadElement(argument)) || (!ts.isPropertyAccessExpression(access) && !ts.isElementAccessExpression(access)) || - access.questionDotToken || + (access.questionDotToken && !lowerer.chainHandled.has(access)) || staticResponseMemberName(lowerer, access) !== "abort" ) { return null; } - const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); + const receiverTs = lowerer.checker.getBaseTypeOfLiteralType( + access.questionDotToken + ? lowerer.checker.getNonNullableType(lowerer.typeOf(access.expression)) + : lowerer.typeOf(access.expression), + ); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( symbol === undefined || diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 78980d322..988b4b804 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3910,7 +3910,8 @@ export class Lowerer { /** Declared class fields may store a DYN value only when its static type * is `unknown` or `object` (also with `null` or `undefined`, or an array * of those), names checked protocol identity, is an owned runtime - * handle (a Worker or MessagePort, a fetch Headers/Request/Response, or + * handle (a Worker or MessagePort, a fetch Headers/Request/Response or + * AbortController/AbortSignal, or * a node:crypto ECDH, DiffieHellman or DiffieHellmanGroup), or is a * WeakMap/WeakSet (native weak collections live in checked-dynamic * storage, as locals of those types do); `any` fields stay refused. */ diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 161f73d27..e52361dc8 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1747,9 +1747,17 @@ export function mapAsyncGeneratorAnnotation(type: ts.Type, ctx: TypeMapperCtx): return { kind: "generator", async: true, ...channels }; } -/** The native fetch handles (Headers, Request, Response), optionally in - * arrays or with null/undefined: checked-dynamic handles wherever they are - * stored, like the worker handles above. */ +const FETCH_HANDLE_NAMES: ReadonlySet = new Set([ + "Headers", + "Request", + "Response", + "AbortController", + "AbortSignal", +]); + +/** The native fetch handles (Headers, Request, Response, AbortController, + * AbortSignal), optionally in arrays or with null/undefined: checked-dynamic + * handles wherever they are stored, like the worker handles above. */ export function isFetchHandleType( type: ts.Type, ctx: Pick, @@ -1765,9 +1773,7 @@ export function isFetchHandleType( const symbol = member.getSymbol(); if ( !symbol || - (ts.symbolName(symbol) !== "Headers" && - ts.symbolName(symbol) !== "Request" && - ts.symbolName(symbol) !== "Response") || + !FETCH_HANDLE_NAMES.has(ts.symbolName(symbol)) || !symbol.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/class-abort-controller-fields.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/class-abort-controller-fields.ts.json new file mode 100644 index 000000000..80466c474 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/class-abort-controller-fields.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/class-abort-controller-fields.ts" + ], + "diags": [] +} diff --git a/tests/corpus/class-abort-controller-fields.ts b/tests/corpus/class-abort-controller-fields.ts new file mode 100644 index 000000000..b82f11a0a --- /dev/null +++ b/tests/corpus/class-abort-controller-fields.ts @@ -0,0 +1,52 @@ +// Class fields typed AbortController and AbortSignal (plain, optional, +// `| undefined`, `| null`, in arrays) hold the live handles: aborting +// through the field is seen through every other reference, and optional +// calls on a nullable field skip an empty slot. +class Editor { + private abort: AbortController | undefined; + private signal: AbortSignal | null = null; + private readonly lifetime = new AbortController(); + history: AbortController[] = []; + last?: AbortSignal; + start(): AbortSignal { + this.abort?.abort(); + this.abort = new AbortController(); + this.history.push(this.abort); + this.signal = this.abort.signal; + this.last = this.signal; + return this.signal; + } + cancel(reason?: string): boolean { + this.abort?.abort(reason); + return this.signal?.aborted ?? false; + } + reason(): unknown { + return this.signal?.reason; + } + dispose(): boolean { + this.lifetime.abort(); + return this.lifetime.signal.aborted; + } +} + +const idle = new Editor(); +console.log(idle.cancel(), idle.reason()); + +const e = new Editor(); +const first = e.start(); +let events = 0; +first.addEventListener("abort", () => { + events++; +}); +const second = e.start(); +console.log(first.aborted, second.aborted, events, e.history.length); +console.log(e.cancel("stop"), e.reason(), second.aborted, second.reason); +console.log(e.history[0]!.signal === first, e.last === second, e.dispose()); + +// A nullable local takes the same optional call. +let maybe: AbortController | undefined = e.history.length > 5 ? new AbortController() : undefined; +maybe?.abort(); +console.log(maybe?.signal.aborted); +maybe = new AbortController(); +maybe?.abort("late"); +console.log(maybe?.signal.aborted, maybe?.signal.reason); diff --git a/tests/harness/corpus-costs/class-abort-controller-fields.ts.json b/tests/harness/corpus-costs/class-abort-controller-fields.ts.json new file mode 100644 index 000000000..28efce0e8 --- /dev/null +++ b/tests/harness/corpus-costs/class-abort-controller-fields.ts.json @@ -0,0 +1 @@ +{ "plain": 4302 } From 864feec4cf66894f88598507829b0f2a57b5d4c5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 09:53:28 -0700 Subject: [PATCH 076/395] Map resolved empty mapped types as the empty object type A mapped type with no members was refused (SC2001) to keep `Partial` inside a generic body, where keyof T is still unknown, from interning a wrong empty record. That also refused mapped types the checker resolved from concrete inputs whose key filter keeps no key: `Record`, `Partial<{}>`, and a catalog type such as `{ [K in KeyOf as Id]: Model }` for data without image models. When the mapped type comes through an alias whose arguments mention no type parameter (or a module-level alias without arguments), it is the empty object type and lowers like `{}`. pi-ai declares its per-provider image and classifier model catalogs this way; most providers have none, which refused 105 declaration groups. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 45 ++++++++++++++++++- .../corpus/empty-mapped-alias-types.ts.json | 6 +++ tests/corpus/empty-mapped-alias-types.ts | 33 ++++++++++++++ .../empty-mapped-alias-types.ts.json | 1 + 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/empty-mapped-alias-types.ts.json create mode 100644 tests/corpus/empty-mapped-alias-types.ts create mode 100644 tests/harness/corpus-costs/empty-mapped-alias-types.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 161f73d27..9d450716f 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -7015,6 +7015,43 @@ function immutableSourceType(type: ts.Type, ctx: TypeMapperCtx): ts.Type | null return null; } +/** A mapped type reached through a type alias whose arguments mention no + * type parameter (`Record`, `Catalog`, + * a module-level alias without arguments): + * the checker computed its members from concrete inputs, so an empty + * result really is the empty object type, unlike `Partial` inside a + * generic body. */ +function resolvedMappedAlias(t: ts.Type, ctx: TypeMapperCtx): boolean { + if (!isMappedShape(t) || t.getAliasSymbol() === undefined) return false; + const args = t.getAliasTypeArguments(); + const seen = new Set(); + const concrete = (part: ts.Type): boolean => { + if (part.flags & (ts.TypeFlags.TypeParameter | ts.TypeFlags.Index)) return false; + if (part.flags & (ts.TypeFlags.IndexedAccess | ts.TypeFlags.Conditional)) return false; + if (seen.has(part)) return true; + seen.add(part); + if (part.isUnionType() || part.isIntersectionType()) + return ts.constituentTypes(part).every(concrete); + if (!part.getAliasTypeArguments().every(concrete)) return false; + return ( + (part.flags & ts.TypeFlags.Object) === 0 || + ((part as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) === 0 || + ctx.checker.getTypeArguments(part as ts.TypeReference).every(concrete) + ); + }; + // An alias without type arguments is concrete when it is declared at + // module level (a function body's alias may mention that function's type + // parameters). + if (args.length === 0) + return t + .getAliasSymbol()! + .declarations.every( + (d) => + ts.isTypeAliasDeclaration(d) && (ts.isSourceFile(d.parent) || ts.isModuleBlock(d.parent)), + ); + return args.every(concrete); +} + /** True for MAPPED-type results — `Partial`, `Record<"a", n>`, * whatever `Pick`/`Omit` reduce to. Their shape is computed by the checker, * not declared anywhere: the only declaration behind them is the utility @@ -7969,8 +8006,12 @@ function mapRecordTypeInner( // still unknown there) — interning `{}` would give a wrong type. The // degenerate empties (`Partial<{}>`, `Omit`) go with it. // An INDEX-SIGNATURE shape is exempt: `Record` legitimately - // has zero declared members — the signature is the shape. - if (computed && props.length === 0 && !indexValue) return null; + // has zero declared members — the signature is the shape. So is a mapped + // alias instantiated over concrete arguments (`Record`, a + // catalog type whose key filter keeps no key): the checker resolved it, + // and it is the empty object type below. + if (computed && props.length === 0 && !indexValue && !resolvedMappedAlias(widened, ctx)) + return null; // An empty object type — `{}` (spelled, the checker's shared // intrinsic, or inferred from an empty object literal), `interface // Empty {}` — is tsc's TOP type over non-nullish values: every number, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/empty-mapped-alias-types.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/empty-mapped-alias-types.ts.json new file mode 100644 index 000000000..08ec4c0f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/empty-mapped-alias-types.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/empty-mapped-alias-types.ts" + ], + "diags": [] +} diff --git a/tests/corpus/empty-mapped-alias-types.ts b/tests/corpus/empty-mapped-alias-types.ts new file mode 100644 index 000000000..43f3ed831 --- /dev/null +++ b/tests/corpus/empty-mapped-alias-types.ts @@ -0,0 +1,33 @@ +// Mapped types instantiated over concrete arguments whose key filter keeps +// no key (a per-kind model catalog with no models of that kind, +// `Record`, `Partial<{}>`) are the empty object type: values flow +// through them like any `{}`-typed value. +type Groups = Record>; +type Key = { [A in keyof G]: keyof G[A] }[keyof G] & string; +type KeyOf = Extract, `${T}:${string}`>; +type Id = K extends `${string}:${infer M}` ? M : never; +type Catalog = { + [K in KeyOf as Id]: { id: Id; name: string }; +}; + +const data = { api: { "chat:a": { id: "a", name: "A" }, "chat:b": { id: "b", name: "B" } } }; + +const chat: Catalog = { a: { id: "a", name: "A" }, b: { id: "b", name: data.api["chat:b"].name } }; +const image: Catalog = {}; +const video: Catalog = image; +console.log(chat.a.name, chat.b.id, JSON.stringify(image), video === image); + +const none: Record = {}; +const partial: Partial<{}> = { extra: 1 } as {}; +console.log(JSON.stringify(none), JSON.stringify(partial)); + +function describe(value: Catalog): string { + return JSON.stringify(value); +} +const catalogs: Catalog[] = [image, {}, none]; +console.log(describe(image), describe({}), catalogs.map(describe).join(" ")); + +// A module-level alias without type arguments whose filter keeps no key. +type Flags = { [K in "a" | "b" as K extends "c" ? K : never]: boolean }; +const flags: Flags = {}; +console.log(JSON.stringify(flags), JSON.stringify([flags, none])); diff --git a/tests/harness/corpus-costs/empty-mapped-alias-types.ts.json b/tests/harness/corpus-costs/empty-mapped-alias-types.ts.json new file mode 100644 index 000000000..855a18b97 --- /dev/null +++ b/tests/harness/corpus-costs/empty-mapped-alias-types.ts.json @@ -0,0 +1 @@ +{ "plain": 2395 } From 50ccfbcbe290b0751723e3af04012217d41f273b Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 23 Oct 2026 13:41:29 -0700 Subject: [PATCH 077/395] Report inferred shapes for untyped npm JavaScript (shape inference, stage 1) SCRIPTC_DEBUG_SHAPES=1 runs a whole-program allocation-site points-to analysis over opted-in npm packages' JavaScript and the user files that call into them, and prints which object and array literals a native record would represent exactly, the inferred class field types, the census of writes and reflection on untracked receivers, and the hot functions' parameter and return types. Report only: nothing reaches the lowering, and the analysis runs only when the variable is set. Design: results/design-shape-inference/DESIGN.md (scriptc-perf). Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/program.ts | 5 + .../src/frontend/shape-inference/analyze.ts | 2864 +++++++++++++++++ .../src/frontend/shape-inference/domain.ts | 153 + .../src/frontend/shape-inference/index.ts | 21 + .../src/frontend/shape-inference/report.ts | 374 +++ .../compiler/test/shape-inference.test.ts | 166 + 6 files changed, 3583 insertions(+) create mode 100644 packages/compiler/src/frontend/shape-inference/analyze.ts create mode 100644 packages/compiler/src/frontend/shape-inference/domain.ts create mode 100644 packages/compiler/src/frontend/shape-inference/index.ts create mode 100644 packages/compiler/src/frontend/shape-inference/report.ts create mode 100644 packages/compiler/test/shape-inference.test.ts diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index bf75127c4..8e582af89 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -140,6 +140,7 @@ import { } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; +import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { containsJsx, @@ -1260,6 +1261,10 @@ export function loadProgram( npmStaticPackages.length > 0, projector, ); + // Shape inference, stage S1: a report only (SCRIPTC_DEBUG_SHAPES). + const shapes = process.env["SCRIPTC_DEBUG_SHAPES"]; + if (shapes !== undefined && shapes !== "" && shapes !== "0" && npmStaticPackages.length > 0) + reportShapes(load.program, shapes); return { ...load, services, diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts new file mode 100644 index 000000000..55e0e0107 --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -0,0 +1,2864 @@ +/* Shape inference (results/design-shape-inference/DESIGN.md), stage S1: + * a whole-program allocation-site points-to analysis over the JavaScript of + * opted-in npm packages and the user files that call into them. + * + * Stage S1 is report-only: the analysis runs when SCRIPTC_DEBUG_SHAPES is + * set and prints its plan (report.ts). Nothing it computes reaches the + * lowering yet, so every program compiles exactly as before. + * + * The analysis (DESIGN.md §4): + * - Andersen-style inclusion constraints, solved with a worklist. Locals + * and parameters are one value node per binding (flow-insensitive in S1); + * heap fields are one node per (atom, name); arrays one element node. + * - Calls resolve by the callee's atoms: functions, class constructors, + * methods by the receiver's class chain (inherited members, getters and + * setters, `this.constructor`), closures stored in fields, `bind`, and + * builtin summaries for the common library surface. + * - Escape: an atom escapes when it reaches code the analysis cannot see + * (a ⊤ callee, a builtin without a summary, a store into a ⊤ object). + * Escaped functions run with ⊤ arguments; everything reachable from an + * escaped atom escapes. + * - Census: writes and reflection whose receiver may be ⊤ apply to every + * escaped atom (§5.4); the report uses them to decide which allocation + * sites could become native records (§5.5). */ +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFile } from "../program.js"; +import { npmStaticPackageOfPath } from "../npm-static.js"; +import { AtomTable, MODELED_GLOBALS, STRING_METHOD_RESULTS, type Atom } from "./domain.js"; + +type Node = ts.Node; + +export interface ValueNode { + readonly id: number; + readonly atoms: Set; + readonly succ: Set; + /** Constraints to run for each atom that arrives. */ + readonly onAtom: ((atom: Atom) => void)[]; + /** Every atom that arrives escapes. */ + escapeSink: string | null; +} + +export interface FunctionInfo { + readonly node: Node; + /** The analysis context: "" or a method's receiver (`C`). */ + readonly ctx: string; + /** The function whose clone the context is (bindings inside it are per context). */ + readonly root: Node | null; + /** Bodies are walked on first call: code that never runs adds no facts. */ + walked: boolean; + readonly label: string; + readonly params: number[]; + /** Index of a rest parameter, or -1. */ + readonly rest: number; + readonly thisNode: number; + readonly ret: number; + readonly isArrow: boolean; + readonly isAsync: boolean; + readonly isGenerator: boolean; + /** Reads `arguments` other than through `...arguments` forwarding. */ + usesArguments: boolean; + /** `f(...arguments)` forwarding: one node per argument position. */ + argPos: number[] | null; + /** The class whose member this function is (method, accessor, constructor). */ + readonly ownerClass: Node | null; + readonly inPackage: boolean; +} + +export interface LiteralSite { + readonly atom: Atom; + readonly node: Node; + readonly kind: "object" | "array"; + /** Static keys in creation order; null when a spread or computed key makes them unknown. */ + readonly keys: string[] | null; + readonly hasAccessor: boolean; + readonly hasProto: boolean; + readonly hasComputedKey: boolean; + readonly spreads: number[]; + readonly inPackage: boolean; +} + +export interface Op { + readonly kind: + | "read" + | "write" + | "computedRead" + | "computedWrite" + | "delete" + | "freeze" + | "seal" + | "defineProperty" + | "setPrototypeOf" + | "assign" + | "proxy" + | "lengthWrite"; + readonly recv: number; + readonly name: string | null; + readonly node: Node; +} + +export interface CallEdge { + readonly caller: FunctionInfo | null; + readonly callee: FunctionInfo; + readonly inLoop: boolean; +} + +export interface ShapeAnalysis { + readonly atoms: AtomTable; + readonly nodes: ValueNode[]; + readonly functions: Map; + readonly sites: LiteralSite[]; + readonly ops: Op[]; + readonly calls: CallEdge[]; + readonly escaped: Map; + readonly topWriteNames: Map; + readonly topComputedWrites: Node[]; + /** Fields per atom: name → node id. */ + readonly fields: Map>; + readonly elements: Map; + readonly top: Atom; + readonly files: readonly ts.SourceFile[]; + readonly packageFiles: number; + readonly checker: ts.TypeChecker; + readonly budgetExceeded: boolean; + readonly steps: number; + readonly topCause: ReadonlyMap; + readonly nodeLabel: ReadonlyMap; +} + +/** Array.prototype members: other names on an array are own properties. */ +const ARRAY_METHODS: ReadonlySet = new Set([ + "at", + "concat", + "copyWithin", + "entries", + "every", + "fill", + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flat", + "flatMap", + "forEach", + "includes", + "indexOf", + "join", + "keys", + "lastIndexOf", + "map", + "pop", + "push", + "reduce", + "reduceRight", + "reverse", + "shift", + "slice", + "some", + "sort", + "splice", + "toLocaleString", + "toReversed", + "toSorted", + "toSpliced", + "toString", + "unshift", + "values", + "with", +]); +const MAX_STEPS = 20_000_000; + +function list(arr: { readonly nodes: readonly T[] } | undefined): readonly T[] { + return arr?.nodes ?? []; +} + +function isStatic(node: Node): boolean { + const mods = (node as { modifiers?: { nodes: readonly Node[] } }).modifiers; + return list(mods).some((m) => m.kind === ts.SyntaxKind.StaticKeyword); +} + +function unparen(node: ts.Expression): ts.Expression { + let n = node; + for (;;) { + if (ts.isParenthesizedExpression(n)) n = n.expression; + else if (ts.isNonNullExpression(n)) n = n.expression; + else if (ts.isAsExpression(n) || ts.isSatisfiesExpression(n)) n = n.expression; + else return n; + } +} + +function propertyNameText(name: Node): string | null { + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) return name.text; + if (ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) return name.text; + if (ts.isNumericLiteral(name)) return String(Number(name.text)); + if (ts.isComputedPropertyName(name)) { + const e = unparen(name.expression); + if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) return e.text; + if (ts.isNumericLiteral(e)) return String(Number(e.text)); + } + return null; +} + +function shortFile(fileName: string): string { + const norm = fileName.split("\\").join("/"); + const nm = norm.lastIndexOf("/node_modules/"); + if (nm >= 0) { + const rest = norm.slice(nm + "/node_modules/".length).split("/"); + const pkgLen = rest[0]?.startsWith("@") ? 2 : 1; + const pkg = rest.slice(0, pkgLen).join("/"); + return `${pkg}/${rest[rest.length - 1]}`; + } + return norm.slice(norm.lastIndexOf("/") + 1); +} + +export function locLabel(node: Node): string { + const sf = node.getSourceFile(); + const { line } = ts.getLineAndCharacterOfPosition(sf, node.getStart(sf)); + return `${shortFile(sf.fileName)}:${line + 1}`; +} + +/** Whether a source file belongs to an opted-in package's JavaScript. */ +export function isPackageJsFile(sf: ts.SourceFile): boolean { + return isJsSourceFile(sf) && npmStaticPackageOfPath(sf.fileName) !== null; +} + +function isLoop(node: Node): boolean { + return ( + ts.isForStatement(node) || + ts.isForOfStatement(node) || + ts.isForInStatement(node) || + ts.isWhileStatement(node) || + ts.isDoStatement(node) + ); +} + +const LOOP_CALLBACK_METHODS: ReadonlySet = new Set([ + "map", + "forEach", + "filter", + "some", + "every", + "find", + "findIndex", + "findLast", + "findLastIndex", + "reduce", + "reduceRight", + "flatMap", + "sort", +]); + +export function analyzeShapes(program: ts.Program, checker: ts.TypeChecker): ShapeAnalysis { + return new Analyzer(program, checker).run(); +} + +class Analyzer { + readonly atoms = new AtomTable(); + readonly nodes: ValueNode[] = []; + readonly functions = new Map(); + readonly sites: LiteralSite[] = []; + private readonly siteAtoms = new Set(); + readonly ops: Op[] = []; + readonly calls: CallEdge[] = []; + readonly escaped = new Map(); + readonly topWriteNames = new Map(); + readonly topComputedWrites: Node[] = []; + readonly fields = new Map>(); + readonly elements = new Map(); + readonly top: Atom; + private readonly bindings = new Map(); + private readonly exprMemo = new Map(); + /** Current analysis context and the function whose clone it is. */ + private ctx = ""; + private ctxRoot: Node | null = null; + private readonly classInfo = new Map(); + private readonly boundCalls = new Set(); + private readonly escapedFunctionCalls = new Set(); + private readonly worklist: [number, Atom][] = []; + private steps = 0; + private budgetExceeded = false; + private currentFn: FunctionInfo | null = null; + private loopDepth = 0; + private files: ts.SourceFile[] = []; + private packageFiles = 0; + private readonly prims = new Map(); + /** Per call site: the calling function and whether the site is in a loop + * (constraints fire during solving, after the walk has moved on). */ + private readonly siteCtx = new Map(); + private forceLoop = false; + + noteSite(site: Node): void { + if (!this.siteCtx.has(site)) + this.siteCtx.set(site, { caller: this.currentFn, inLoop: this.loopDepth > 0 }); + } + + edgeFor(site: Node, callee: FunctionInfo): void { + const ctx = this.siteCtx.get(site); + this.calls.push({ + caller: ctx?.caller ?? null, + callee, + inLoop: (ctx?.inLoop ?? false) || this.forceLoop, + }); + } + + constructor( + private readonly program: ts.Program, + private readonly checker: ts.TypeChecker, + ) { + this.top = this.atoms.intern("top", () => ({ kind: "top", label: "⊤" })); + } + + run(): ShapeAnalysis { + for (const sf of this.program.getSourceFiles()) { + if (sf.isDeclarationFile) continue; + const name = sf.fileName.split("\\").join("/"); + const inPackage = isPackageJsFile(sf); + if (!inPackage && name.includes("/node_modules/")) continue; + this.files.push(sf); + if (inPackage) this.packageFiles++; + } + for (const sf of this.files) this.checker.prefetchSourceFile(sf); + for (const sf of this.files) this.moduleBody(sf); + this.finish(); + return { + atoms: this.atoms, + nodes: this.nodes, + functions: this.functions, + sites: this.sites, + ops: this.ops, + calls: this.calls, + escaped: this.escaped, + topWriteNames: this.topWriteNames, + topComputedWrites: this.topComputedWrites, + fields: this.fields, + elements: this.elements, + top: this.top, + files: this.files, + packageFiles: this.packageFiles, + checker: this.checker, + budgetExceeded: this.budgetExceeded, + steps: this.steps, + topCause: this.topCause, + nodeLabel: this.nodeLabel, + }; + } + + // ---- value nodes ------------------------------------------------------- + + newNode(): number { + const id = this.nodes.length; + this.nodes.push({ id, atoms: new Set(), succ: new Set(), onAtom: [], escapeSink: null }); + return id; + } + + /** Debugging (SCRIPTC_DEBUG_SHAPES_WHY): where each node's ⊤ came from. */ + readonly topCause = new Map(); + readonly nodeLabel = new Map(); + private cause: number | null = null; + private readonly traceTop = process.env["SCRIPTC_DEBUG_SHAPES_WHY"] !== undefined; + + add(node: number, atom: Atom): void { + const n = this.nodes[node]!; + if (n.atoms.has(atom)) return; + n.atoms.add(atom); + if (this.traceTop && atom === this.top && !this.topCause.has(node)) + this.topCause.set( + node, + this.cause ?? + (new Error().stack ?? "") + .split("\n") + .slice(2, 4) + .map((l) => l.trim()) + .join(" < "), + ); + this.worklist.push([node, atom]); + } + + edge(from: number, to: number): void { + if (from === to) return; + const n = this.nodes[from]!; + if (n.succ.has(to)) return; + n.succ.add(to); + const saved = this.cause; + this.cause = from; + for (const a of n.atoms) this.add(to, a); + this.cause = saved; + } + + on(node: number, fn: (atom: Atom) => void): void { + const n = this.nodes[node]!; + n.onAtom.push(fn); + for (const a of [...n.atoms]) fn(a); + } + + sink(node: number, reason: string): void { + const n = this.nodes[node]!; + if (n.escapeSink !== null) return; + n.escapeSink = reason; + for (const a of [...n.atoms]) this.escape(a, reason); + } + + constNode(atom: Atom): number { + const n = this.newNode(); + this.add(n, atom); + return n; + } + + prim(name: string): Atom { + let a = this.prims.get(name); + if (a === undefined) { + a = this.atoms.intern(`prim:${name}`, () => ({ kind: "prim", label: name })); + this.prims.set(name, a); + } + return a; + } + + builtin(path: string, receiver?: Atom): Atom { + const key = receiver === undefined ? `builtin:${path}` : `builtin:${path}@${receiver.id}`; + return this.atoms.intern(key, () => ({ kind: "builtin", label: path, path, receiver })); + } + + solve(): void { + while (this.worklist.length > 0) { + if (++this.steps > MAX_STEPS) { + this.budgetExceeded = true; + return; + } + const [id, atom] = this.worklist.pop()!; + const n = this.nodes[id]!; + this.cause = id; + for (const s of n.succ) this.add(s, atom); + this.cause = null; + for (const f of n.onAtom) f(atom); + if (n.escapeSink !== null) this.escape(atom, n.escapeSink); + } + } + + // ---- heap ---------------------------------------------------------------- + + field(atom: Atom, name: string): number { + let m = this.fields.get(atom); + if (m === undefined) { + m = new Map(); + this.fields.set(atom, m); + } + let id = m.get(name); + if (id === undefined) { + id = this.newNode(); + m.set(name, id); + if (this.traceTop) this.nodeLabel.set(id, `${atom.label}.${name}`); + const reason = this.escaped.get(atom); + if (reason !== undefined) { + this.sink(id, reason); + if (this.topComputedWrites.length > 0 || this.topWriteNames.has(name)) + this.add(id, this.top); + } + } + return id; + } + + element(atom: Atom): number { + let id = this.elements.get(atom); + if (id === undefined) { + id = this.newNode(); + this.elements.set(atom, id); + const reason = this.escaped.get(atom); + if (reason !== undefined) { + this.sink(id, reason); + if (this.topComputedWrites.length > 0) this.add(id, this.top); + } + } + return id; + } + + escape(atom: Atom, reason: string): void { + if (atom.kind === "prim" || atom.kind === "top") return; + if (atom.kind === "builtin" && atom.receiver === undefined) return; + if (this.escaped.has(atom)) return; + this.escaped.set(atom, reason); + const via = `via ${atom.label}`; + const fm = this.fields.get(atom); + if (fm !== undefined) { + for (const [name, id] of fm) { + this.sink(id, via); + if (this.topComputedWrites.length > 0 || this.topWriteNames.has(name)) + this.add(id, this.top); + } + } + const el = this.elements.get(atom); + if (el !== undefined) { + this.sink(el, via); + if (this.topComputedWrites.length > 0) this.add(el, this.top); + } + if (atom.receiver !== undefined) this.escape(atom.receiver, via); + if (atom.kind === "function") { + const info = this.infoOfAtom(atom); + if (info !== undefined) this.escapeFunction(info, via); + } + if ((atom.kind === "instance" || atom.kind === "class") && atom.node !== undefined) { + // Unknown code may call any member with any arguments. + for (const member of this.classMembers(atom.node, atom.kind === "class")) { + const info = this.functionInfo(member, this.ownerOf(member), this.receiverCtx(atom)); + this.add(info.thisNode, atom); + this.escapeFunction(info, via); + } + } + if (atom.kind === "promise") this.sink(this.field(atom, "value"), via); + } + + /** A function unknown code may call: ⊤ arguments and receiver; its + * results escape. */ + escapeFunction(info: FunctionInfo, reason: string): void { + if (this.escapedFunctionCalls.has(info)) return; + this.escapedFunctionCalls.add(info); + this.activate(info); + for (const p of info.params) this.add(p, this.top); + if (!info.isArrow && info.ownerClass === null) this.add(info.thisNode, this.top); + this.sink(info.ret, reason); + } + + /** Property names that untyped code writes on receivers that may be ⊤ (census). */ + noteTopWrite(name: string | null, node: Node): void { + if (name === null) { + const first = this.topComputedWrites.length === 0; + this.topComputedWrites.push(node); + if (first) { + for (const atom of [...this.escaped.keys()]) { + for (const id of (this.fields.get(atom) ?? new Map()).values()) + this.add(id, this.top); + const el = this.elements.get(atom); + if (el !== undefined) this.add(el, this.top); + } + } + return; + } + if (this.topWriteNames.has(name)) return; + this.topWriteNames.set(name, node); + for (const atom of [...this.escaped.keys()]) { + const id = this.fields.get(atom)?.get(name); + if (id !== undefined) this.add(id, this.top); + } + } + + // ---- classes --------------------------------------------------------------- + + /** The base class declaration of `decl`, via the checker; null at a + * builtin base or none; undefined when unresolved. */ + baseClass(decl: Node): Node | null | undefined { + const heritage = list((decl as ts.ClassLikeDeclaration).heritageClauses).find( + (c) => c.token === ts.SyntaxKind.ExtendsKeyword, + )?.types?.nodes[0]; + if (heritage === undefined) return null; + const e = unparen(heritage.expression); + if (!ts.isIdentifier(e) && !ts.isPropertyAccessExpression(e)) return undefined; + let d = this.resolve(ts.isIdentifier(e) ? e : e.name); + // `var C = class C { … }` (bundler output) + if ( + d !== undefined && + ts.isVariableDeclaration(d) && + d.initializer !== undefined && + ts.isClassExpression(unparen(d.initializer)) + ) + d = unparen(d.initializer); + if (d !== undefined && ts.isClassLikeDeclaration(d)) return d; + if (d !== undefined && d.getSourceFile().isDeclarationFile) return null; + return undefined; + } + + classChain(decl: Node): Node[] { + const out: Node[] = []; + let cur: Node | null | undefined = decl; + while (cur && out.length < 64 && !out.includes(cur)) { + out.push(cur); + cur = this.baseClass(cur); + } + return out; + } + + /** Method, accessor and constructor declarations of a class chain. */ + classMembers(decl: Node, statics: boolean): Node[] { + const out: Node[] = []; + for (const c of this.classChain(decl)) { + for (const m of list((c as ts.ClassLikeDeclaration).members)) { + if (isStatic(m) !== statics) continue; + if ( + ts.isMethodDeclaration(m) || + ts.isGetAccessorDeclaration(m) || + ts.isSetAccessorDeclaration(m) + ) + out.push(m); + else if (ts.isPropertyDeclaration(m) && m.initializer !== undefined) { + const init = unparen(m.initializer); + if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) out.push(init); + } + } + } + return out; + } + + lookupMember( + decl: Node, + name: string, + statics: boolean, + ): { + kind: "method" | "getter" | "setter" | "both"; + get?: Node; + set?: Node; + method?: Node; + } | null { + for (const c of this.classChain(decl)) { + let get: Node | undefined; + let set: Node | undefined; + for (const m of list((c as ts.ClassLikeDeclaration).members)) { + if (isStatic(m) !== statics) continue; + const mn = (m as { name?: Node }).name; + if (mn === undefined || propertyNameText(mn) !== name) continue; + if (ts.isMethodDeclaration(m)) return { kind: "method", method: m }; + if (ts.isGetAccessorDeclaration(m)) get = m; + if (ts.isSetAccessorDeclaration(m)) set = m; + if (ts.isPropertyDeclaration(m)) return null; // an own field: data + } + if (get || set) + return { + kind: get && set ? "both" : get ? "getter" : "setter", + ...(get ? { get } : {}), + ...(set ? { set } : {}), + }; + } + return null; + } + + // ---- symbols ----------------------------------------------------------------- + + resolve(id: Node): Node | undefined { + let sym = this.checker.getSymbolAtLocation(id); + if (sym === undefined) return undefined; + if (sym.flags & ts.SymbolFlags.Alias) { + try { + sym = this.checker.getAliasedSymbol(sym); + } catch { + return undefined; + } + } + return sym.valueDeclaration ?? sym.declarations[0]; + } + + binding(decl: Node): number { + const key = this.ctxKey(decl); + let id = this.bindings.get(key); + if (id === undefined) { + id = this.newNode(); + this.bindings.set(key, id); + if (this.traceTop) + this.nodeLabel.set( + id, + `binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${locLabel(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`, + ); + } + return id; + } + + /** Bindings and expressions inside the function being cloned belong to + * the clone's context; everything else to the default context. */ + ctxKey(node: Node): string { + const root = this.ctxRoot; + const inside = + root !== null && + node.getSourceFile() === root.getSourceFile() && + node.pos >= root.pos && + node.end <= root.end; + return `${inside ? this.ctx : ""}|${this.nodeKey(node)}`; + } + + receiverCtx(atom: Atom): string { + return atom.kind === "instance" || atom.kind === "class" ? `C${atom.id}` : ""; + } + + infoOfAtom(atom: Atom): FunctionInfo | undefined { + if (atom.node === undefined) return undefined; + return this.functions.get(`${atom.ctx ?? ""}|${this.nodeKey(atom.node)}`); + } + + // ---- functions --------------------------------------------------------------- + + functionInfo(node: Node, ownerClass: Node | null, ctxArg = ""): FunctionInfo { + // A nested closure walked inside a clone shares the clone's context. + const inherit = + ctxArg === "" && + this.ctxRoot !== null && + node.getSourceFile() === this.ctxRoot.getSourceFile() && + node.pos >= this.ctxRoot.pos && + node.end <= this.ctxRoot.end; + const ctx = ctxArg !== "" ? ctxArg : inherit ? this.ctx : ""; + const root = ctxArg !== "" ? node : inherit ? this.ctxRoot : null; + const fkey = `${ctx}|${this.nodeKey(node)}`; + const existing = this.functions.get(fkey); + if (existing !== undefined) return existing; + const savedCtx = this.ctx; + const savedRoot = this.ctxRoot; + this.ctx = ctx; + this.ctxRoot = root; + const fn = node as ts.FunctionLikeDeclaration; + const isArrow = ts.isArrowFunction(node); + const params: number[] = []; + let rest = -1; + for (const p of list(fn.parameters)) { + const id = this.binding(p); + if (p.dotDotDotToken !== undefined) rest = params.length; + params.push(id); + } + const mods = list((fn as { modifiers?: { nodes: readonly Node[] } }).modifiers); + const sf = node.getSourceFile(); + const nameNode = (fn as { name?: Node }).name; + const name = nameNode !== undefined ? (propertyNameText(nameNode) ?? "") : null; + const owner = ownerClass !== null ? this.className(ownerClass) : null; + const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "")}@${locLabel(node)}`; + const info: FunctionInfo = { + node, + ctx, + root, + walked: false, + label: ctx === "" ? label : `${label} [${this.ctxLabel(ctx)}]`, + params, + rest, + thisNode: isArrow && this.currentFn !== null ? this.currentFn.thisNode : this.newNode(), + ret: this.newNode(), + isArrow, + isAsync: mods.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword), + isGenerator: (fn as { asteriskToken?: Node }).asteriskToken !== undefined, + usesArguments: false, + argPos: null, + ownerClass, + inPackage: isPackageJsFile(sf), + }; + this.functions.set(fkey, info); + this.ctx = savedCtx; + this.ctxRoot = savedRoot; + return info; + } + + /** Walks a function's body the first time it can run. */ + activate(info: FunctionInfo): void { + if (info.walked) return; + info.walked = true; + if (ts.isSourceFile(info.node) || ts.isClassLikeDeclaration(info.node)) return; + const fn = info.node as ts.FunctionLikeDeclaration; + const savedCtx = this.ctx; + const savedRoot = this.ctxRoot; + const saved = this.currentFn; + const savedLoop = this.loopDepth; + this.ctx = info.ctx; + this.ctxRoot = info.root; + this.currentFn = info; + this.loopDepth = 0; + for (const p of list(fn.parameters)) { + if (p.initializer !== undefined) this.edge(this.expr(p.initializer), this.binding(p)); + if (p.dotDotDotToken !== undefined) { + const arr = this.atoms.intern(`rest:${this.nodes.length}`, () => ({ + kind: "array", + label: `rest@${locLabel(p)}`, + node: p, + })); + this.add(this.binding(p), arr); + } + this.bindPattern(p.name, this.binding(p)); + } + const body = fn.body; + if (body !== undefined) { + if (ts.isBlock(body)) this.stmts(list(body.statements)); + else this.edge(this.expr(body as ts.Expression), info.ret); + } + this.currentFn = saved; + this.loopDepth = savedLoop; + this.ctx = savedCtx; + this.ctxRoot = savedRoot; + if (info.usesArguments) { + for (const p of info.params) this.add(p, this.top); + } + } + + /** The atom of a function node (memoized by its info). */ + fnAtom(node: Node, ownerClass: Node | null, ctx = ""): Atom { + const info = this.functionInfo(node, ownerClass, ctx); + return this.atoms.intern(`fn:${info.ret}`, () => ({ + kind: "function", + label: info.label, + node, + ctx: info.ctx, + })); + } + + ctxLabel(ctx: string): string { + if (ctx.startsWith("C")) return `this=${this.atoms.atoms[Number(ctx.slice(1))]?.label ?? ctx}`; + return ctx; + } + + className(decl: Node): string { + const own = (decl as ts.ClassLikeDeclaration).name?.text; + if (own !== undefined) return own; + const p = decl.parent; + if (p !== undefined && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) + return p.name.text; + return ``; + } + + classAtoms(decl: Node): { cls: Atom; inst: Atom } { + const name = this.className(decl); + const cls = this.atoms.intern(`class:${this.nodeKey(decl)}`, () => ({ + kind: "class", + label: `class ${name}`, + node: decl, + })); + const inst = this.atoms.intern(`inst:${this.nodeKey(decl)}`, () => ({ + kind: "instance", + label: name, + node: decl, + })); + this.ensureClass(decl); + return { cls, inst }; + } + + private readonly nodeKeys = new Map(); + nodeKey(node: Node): number { + let k = this.nodeKeys.get(node); + if (k === undefined) { + k = this.nodeKeys.size; + this.nodeKeys.set(node, k); + } + return k; + } + + /** Registers a class: member functions, field initializers (as a pseudo + * function run at construction) and static initializers. */ + ensureClass(decl: Node): { initFn: FunctionInfo; staticInit: FunctionInfo } { + const existing = this.classInfo.get(decl); + if (existing !== undefined) return existing; + const c = decl as ts.ClassLikeDeclaration; + const label = this.className(decl); + const mk = (suffix: string): FunctionInfo => ({ + node: decl, + ctx: "", + root: null, + walked: true, + label: `${label}.${suffix}@${locLabel(decl)}`, + params: [], + rest: -1, + thisNode: this.newNode(), + ret: this.newNode(), + isArrow: false, + isAsync: false, + isGenerator: false, + usesArguments: false, + argPos: null, + ownerClass: decl, + inPackage: isPackageJsFile(decl.getSourceFile()), + }); + const entry = { initFn: mk(""), staticInit: mk("") }; + this.classInfo.set(decl, entry); + const cls = this.atoms.intern(`class:${this.nodeKey(decl)}`, () => ({ + kind: "class", + label: `class ${label}`, + node: decl, + })); + this.add(entry.staticInit.thisNode, cls); + const saved = this.currentFn; + for (const m of list(c.members)) { + const statics = isStatic(m); + if ( + ts.isMethodDeclaration(m) || + ts.isGetAccessorDeclaration(m) || + ts.isSetAccessorDeclaration(m) || + ts.isConstructorDeclaration(m) + ) { + continue; + } + if (ts.isPropertyDeclaration(m)) { + const name = propertyNameText(m.name); + const init = statics ? entry.staticInit : entry.initFn; + this.currentFn = init; + const value = + m.initializer !== undefined + ? this.exprIn(m.initializer, decl) + : this.constNode(this.prim("undefined")); + if (name !== null) this.storeConstraint(init.thisNode, name, value, m); + continue; + } + if (ts.isClassStaticBlockDeclaration(m)) { + this.currentFn = entry.staticInit; + this.stmts(list(m.body.statements)); + } + } + this.currentFn = saved; + return entry; + } + + /** An initializer expression: arrow functions in field initializers take + * the instance as `this` and belong to the class. */ + exprIn(e: ts.Expression, ownerClass: Node): number { + const u = unparen(e); + if (ts.isArrowFunction(u) || ts.isFunctionExpression(u)) { + const n = this.newNode(); + this.add(n, this.fnAtom(u, ts.isArrowFunction(u) ? null : ownerClass)); + return n; + } + return this.expr(e); + } + + /** `new C(args)` on a class atom: the instance, the field initializers of + * every class in the chain, and the constructor (or the implicit one). */ + construct( + decl: Node, + args: number[], + spread: boolean, + dst: number, + site: Node, + inst?: Atom, + ): void { + const atoms = this.classAtoms(decl); + const instance = inst ?? atoms.inst; + this.add(dst, instance); + this.callConstructorChain(decl, instance, args, spread, site); + } + + callConstructorChain( + decl: Node, + instance: Atom, + args: number[], + spread: boolean, + site: Node, + ): void { + const key = `ctor:${this.nodeKey(decl)}:${instance.id}:${this.nodeKey(site)}`; + if (this.boundCalls.has(key)) return; + this.boundCalls.add(key); + const entry = this.ensureClass(decl); + this.add(entry.initFn.thisNode, instance); + const ctor = list((decl as ts.ClassLikeDeclaration).members).find((m) => + ts.isConstructorDeclaration(m), + ); + if (ctor !== undefined) { + const info = this.functionInfo(ctor, decl, this.receiverCtx(instance)); + this.activate(info); + this.add(info.thisNode, instance); + this.bindArgs(info, args, spread); + this.edgeFor(site, info); + return; + } + const base = this.baseClass(decl); + if (base) this.callConstructorChain(base, instance, args, spread, site); + } + + bindArgs(info: FunctionInfo, args: number[], spread: boolean): void { + if (info.argPos !== null) { + for (let i = 0; i < info.argPos.length; i++) { + if (spread || i >= info.argPos.length - 1) { + for (const a of args.slice(i)) this.edge(a, info.argPos[i]!); + if (spread) this.add(info.argPos[i]!, this.top); + } else if (i < args.length) this.edge(args[i]!, info.argPos[i]!); + else this.add(info.argPos[i]!, this.prim("undefined")); + } + } + if (spread) { + for (const p of info.params) this.add(p, this.top); + for (const a of args) this.sink(a, `spread into ${info.label}`); + return; + } + for (let i = 0; i < info.params.length; i++) { + if (i === info.rest) { + const restAtoms = [...this.nodes[info.params[i]!]!.atoms]; + for (const ra of restAtoms) + for (let j = i; j < args.length; j++) this.edge(args[j]!, this.element(ra)); + break; + } + if (i < args.length) this.edge(args[i]!, info.params[i]!); + else this.add(info.params[i]!, this.prim("undefined")); + } + } + + /** Call of a function atom with a receiver node. */ + callFunction( + info: FunctionInfo, + thisNode: number | null, + args: number[], + spread: boolean, + dst: number, + site: Node, + ): void { + // `() => Object.create(null)` helpers: each call site gets its own + // dictionary, as if the helper were inlined. + if (this.isFactory(info.node)) { + const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ + kind: "dictionary", + label: `Object.create@${locLabel(site)}`, + node: site, + })); + this.add(dst, d); + return; + } + this.activate(info); + const fnNode = info.node; + const key = `call:${info.ctx}|${this.nodeKey(fnNode)}:${this.nodeKey(site)}:${thisNode ?? -1}:${dst}`; + if (this.boundCalls.has(key)) return; + this.boundCalls.add(key); + if (thisNode !== null && !info.isArrow) this.edge(thisNode, info.thisNode); + this.bindArgs(info, args, spread); + if (info.isAsync) { + const p = this.promiseAtom(fnNode, `async ${info.label}`); + this.awaitInto(info.ret, this.field(p, "value")); + this.add(dst, p); + } else if (info.isGenerator) { + this.add(dst, this.top); + } else this.edge(info.ret, dst); + this.edgeFor(site, info); + } + + private readonly factories = new Map(); + isFactory(node: Node): boolean { + let r = this.factories.get(node); + if (r === undefined) { + r = false; + const fn = node as ts.FunctionLikeDeclaration; + let e: Node | undefined = fn.body; + if (e !== undefined && ts.isBlock(e)) { + const st = e.statements.nodes; + e = st.length === 1 && ts.isReturnStatement(st[0]!) ? st[0]!.expression : undefined; + } + if (e !== undefined && !ts.isSourceFile(node)) { + const u = unparen(e as ts.Expression); + r = + ts.isCallExpression(u) && + u.expression.getText() === "Object.create" && + u.arguments.nodes.length === 1 && + u.arguments.nodes[0]!.kind === ts.SyntaxKind.NullKeyword; + } + this.factories.set(node, r); + } + return r; + } + + promiseAtom(site: Node, label: string): Atom { + return this.atoms.intern(`promise:${this.nodeKey(site)}`, () => ({ + kind: "promise", + label: `Promise@${label}`, + node: site, + })); + } + + /** dst ⊇ awaited(src). */ + awaitInto(src: number, dst: number): void { + this.on(src, (a) => { + if (a.kind === "promise") this.edge(this.field(a, "value"), dst); + else this.add(dst, a); + }); + } + + syntheticArray(site: Node, tag: string): Atom { + return this.atoms.intern(`arr:${tag}:${this.nodeKey(site)}`, () => ({ + kind: "array", + label: `${tag}[]@${locLabel(site)}`, + node: site, + })); + } + + // ---- statements ---------------------------------------------------------------- + + moduleBody(sf: ts.SourceFile): void { + const info: FunctionInfo = { + node: sf, + ctx: "", + root: null, + walked: true, + label: ``, + params: [], + rest: -1, + thisNode: this.constNode(this.prim("undefined")), + ret: this.newNode(), + isArrow: false, + isAsync: false, + isGenerator: false, + usesArguments: false, + argPos: null, + ownerClass: null, + inPackage: isPackageJsFile(sf), + }; + this.functions.set(`|${this.nodeKey(sf)}`, info); + this.currentFn = info; + this.stmts(list(sf.statements)); + this.currentFn = null; + } + + stmts(stmts: readonly Node[]): void { + for (const s of stmts) this.stmt(s); + } + + stmt(s: Node): void { + if (ts.isVariableStatement(s)) { + for (const d of list(s.declarationList.declarations)) this.varDecl(d); + return; + } + if (ts.isExpressionStatement(s)) { + this.expr(s.expression); + return; + } + if (ts.isReturnStatement(s)) { + if (s.expression !== undefined && this.currentFn !== null) + this.edge(this.expr(s.expression), this.currentFn.ret); + return; + } + if (ts.isFunctionDeclaration(s)) { + const b = this.binding(s); + const saved = this.currentFn; + this.currentFn = null; + this.add(b, this.fnAtom(s, null)); + this.currentFn = saved; + return; + } + if (ts.isClassDeclaration(s)) { + const b = this.binding(s); + this.add(b, this.classAtoms(s).cls); + return; + } + if (isLoop(s)) { + if (ts.isForOfStatement(s) || ts.isForInStatement(s)) { + const src = this.expr(s.expression); + const target = this.newNode(); + if (ts.isForInStatement(s)) this.add(target, this.prim("string")); + else this.iterateInto(src, target, s); + const init = s.initializer; + if (ts.isVariableDeclarationList(init)) { + for (const d of list(init.declarations)) { + this.edge(target, this.binding(d)); + this.bindPattern(d.name, this.binding(d)); + } + } else this.assignTo(init as ts.Expression, target); + } + this.loopDepth++; + if (ts.isForStatement(s)) { + if (s.initializer !== undefined) { + if (ts.isVariableDeclarationList(s.initializer)) + for (const d of list(s.initializer.declarations)) this.varDecl(d); + else this.expr(s.initializer as ts.Expression); + } + if (s.condition !== undefined) this.expr(s.condition); + if (s.incrementor !== undefined) this.expr(s.incrementor); + } + if (ts.isWhileStatement(s) || ts.isDoStatement(s)) this.expr(s.expression); + const body = (s as { statement?: Node }).statement; + if (body !== undefined) this.stmt(body); + this.loopDepth--; + return; + } + if (ts.isTryStatement(s)) { + this.stmts(list(s.tryBlock.statements)); + if (s.catchClause !== undefined) { + const v = s.catchClause.variableDeclaration; + if (v !== undefined) { + this.add(this.binding(v), this.top); + this.bindPattern(v.name, this.binding(v)); + } + this.stmts(list(s.catchClause.block.statements)); + } + if (s.finallyBlock !== undefined) this.stmts(list(s.finallyBlock.statements)); + return; + } + if (ts.isThrowStatement(s)) { + this.sink(this.expr(s.expression), `thrown at ${locLabel(s)}`); + return; + } + if ( + ts.isImportDeclaration(s) || + ts.isExportDeclaration(s) || + ts.isInterfaceDeclaration(s) || + ts.isTypeAliasDeclaration(s) + ) + return; + if (ts.isExportAssignment(s)) { + this.expr(s.expression); + return; + } + // Blocks, ifs, switches, labels: walk children, evaluating expressions. + ts.forEachChild(s, (child) => { + if (ts.isExpression(child)) this.expr(child); + else this.stmt(child); + return undefined; + }); + } + + varDecl(d: ts.VariableDeclaration): void { + const b = this.binding(d); + if (d.initializer !== undefined) this.edge(this.expr(d.initializer), b); + this.bindPattern(d.name, b); + } + + /** Destructuring: binding elements read from the source node. */ + bindPattern(name: Node, src: number): void { + if (ts.isIdentifier(name)) return; + if (ts.isObjectBindingPattern(name)) { + for (const el of list(name.elements)) { + const b = this.binding(el); + if (el.dotDotDotToken !== undefined) { + this.add(b, this.top); + continue; + } + const keyNode = el.propertyName ?? el.name; + const key = keyNode !== undefined ? propertyNameText(keyNode) : null; + if (key !== null) this.loadConstraint(src, key, b, el); + else this.add(b, this.top); + if (el.initializer !== undefined) this.edge(this.expr(el.initializer), b); + if (el.name !== undefined) this.bindPattern(el.name, b); + } + return; + } + if (ts.isArrayBindingPattern(name)) { + let i = 0; + for (const el of list(name.elements)) { + if (ts.isBindingElement(el)) { + const b = this.binding(el); + if (el.dotDotDotToken !== undefined) this.add(b, this.top); + else this.iterateInto(src, b, el, i); + if (el.initializer !== undefined) this.edge(this.expr(el.initializer), b); + if (el.name !== undefined) this.bindPattern(el.name, b); + } + i++; + } + } + } + + /** Element values of an iterable. */ + iterateInto(src: number, dst: number, site: Node, _index?: number): void { + this.on(src, (a) => { + if (a.kind === "array" || a.kind === "collection") this.edge(this.element(a), dst); + else if (a.kind === "prim" && a.label === "string") this.add(dst, this.prim("string")); + else if (a.kind === "prim" || a.kind === "object" || a.kind === "dictionary") { + /* not iterable (a TypeError): no value. Literals with a + Symbol.iterator member have a computed key; they are G sites. */ + } else { + this.add(dst, this.top); + if (a.kind !== "top") this.escape(a, `iterated at ${locLabel(site)}`); + } + }); + } + + // ---- expressions --------------------------------------------------------------- + + expr(e: ts.Expression): number { + const key = this.ctxKey(e); + const memo = this.exprMemo.get(key); + if (memo !== undefined) return memo; + const id = this.exprUncached(e); + this.exprMemo.set(key, id); + if (this.traceTop && !this.nodeLabel.has(id)) + this.nodeLabel.set( + id, + `expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${locLabel(e)}`, + ); + return id; + } + + private exprUncached(e: ts.Expression): number { + const u = unparen(e); + if (u !== e) return this.expr(u); + switch (e.kind) { + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + case ts.SyntaxKind.TemplateExpression: + if (ts.isTemplateExpression(e)) + for (const span of list(e.templateSpans)) this.expr(span.expression); + return this.constNode(this.prim("string")); + case ts.SyntaxKind.NumericLiteral: + return this.constNode(this.prim("number")); + case ts.SyntaxKind.BigIntLiteral: + return this.constNode(this.prim("bigint")); + case ts.SyntaxKind.TrueKeyword: + case ts.SyntaxKind.FalseKeyword: + return this.constNode(this.prim("boolean")); + case ts.SyntaxKind.NullKeyword: + return this.constNode(this.prim("null")); + case ts.SyntaxKind.RegularExpressionLiteral: + return this.constNode(this.builtin("RegExp.instance")); + case ts.SyntaxKind.ThisKeyword: + return this.currentFn !== null + ? this.currentFn.thisNode + : this.constNode(this.prim("undefined")); + case ts.SyntaxKind.SuperKeyword: + return this.constNode(this.top); + } + if (ts.isIdentifier(e)) return this.identifier(e); + if (ts.isObjectLiteralExpression(e)) return this.objectLiteral(e); + if (ts.isArrayLiteralExpression(e)) return this.arrayLiteral(e); + if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) { + const n = this.newNode(); + this.add(n, this.fnAtom(e, null)); + if (ts.isFunctionExpression(e) && e.name !== undefined) + this.add(this.binding(e), this.fnAtom(e, null)); + return n; + } + if (ts.isClassExpression(e)) { + const n = this.newNode(); + this.add(n, this.classAtoms(e).cls); + return n; + } + if (ts.isPropertyAccessExpression(e)) { + this.noteSite(e); + const recv = this.expr(e.expression); + const name = e.name.text; + const dst = this.newNode(); + this.loadConstraint(recv, name, dst, e); + this.ops.push({ kind: "read", recv, name, node: e }); + return dst; + } + if (ts.isElementAccessExpression(e)) { + const recv = this.expr(e.expression); + const key = unparen(e.argumentExpression); + const dst = this.newNode(); + const name = + ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key) ? key.text : null; + if (name !== null) { + this.loadConstraint(recv, name, dst, e); + this.ops.push({ kind: "read", recv, name, node: e }); + } else { + this.expr(e.argumentExpression); + this.computedLoad(recv, dst, e); + this.ops.push({ kind: "computedRead", recv, name: null, node: e }); + } + return dst; + } + if (ts.isCallExpression(e)) return this.call(e); + if (ts.isNewExpression(e)) return this.newExpr(e); + if (ts.isBinaryExpression(e)) return this.binary(e); + if (ts.isConditionalExpression(e)) { + this.expr(e.condition); + const n = this.newNode(); + this.edge(this.expr(e.whenTrue), n); + this.edge(this.expr(e.whenFalse), n); + return n; + } + if (ts.isAwaitExpression(e)) { + const n = this.newNode(); + this.awaitInto(this.expr(e.expression), n); + return n; + } + if (ts.isTypeOfExpression(e)) { + this.expr(e.expression); + return this.constNode(this.prim("string")); + } + if (ts.isVoidExpression(e)) { + this.expr(e.expression); + return this.constNode(this.prim("undefined")); + } + if (ts.isDeleteExpression(e)) { + const target = unparen(e.expression); + if (ts.isPropertyAccessExpression(target) || ts.isElementAccessExpression(target)) { + const recv = this.expr(target.expression); + const name = ts.isPropertyAccessExpression(target) ? target.name.text : null; + this.ops.push({ kind: "delete", recv, name, node: e }); + } + return this.constNode(this.prim("boolean")); + } + if (ts.isPrefixUnaryExpression(e) || ts.isPostfixUnaryExpression(e)) { + const operand = e.operand; + this.expr(operand); + if ( + e.operator === ts.SyntaxKind.PlusPlusToken || + e.operator === ts.SyntaxKind.MinusMinusToken + ) + this.assignTo(operand, this.constNode(this.prim("number"))); + if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken) + return this.constNode(this.prim("boolean")); + return this.constNode(this.prim("number")); + } + if (ts.isSpreadElement(e)) return this.expr(e.expression); + if (ts.isTaggedTemplateExpression(e)) { + this.expr(e.tag); + return this.constNode(this.top); + } + if (ts.isYieldExpression(e)) { + if (e.expression !== undefined) + this.sink(this.expr(e.expression), `yielded at ${locLabel(e)}`); + return this.constNode(this.top); + } + if (ts.isMetaProperty(e)) return this.constNode(this.top); + // Anything else: evaluate children, answer ⊤. + ts.forEachChild(e, (child) => { + if (ts.isExpression(child)) this.expr(child); + return undefined; + }); + return this.constNode(this.top); + } + + identifier(id: ts.Identifier): number { + if (id.text === "undefined") return this.constNode(this.prim("undefined")); + if (id.text === "arguments") { + const fn = this.enclosingNonArrow(); + if (fn !== null) { + const parent = id.parent; + const forwarding = parent !== undefined && ts.isSpreadElement(parent); + if (!forwarding) fn.usesArguments = true; + const arr = this.syntheticArray(id, "arguments"); + for (const p of fn.params) this.edge(p, this.element(arr)); + return this.constNode(arr); + } + } + const decl = this.resolve(id); + if (decl === undefined) return this.globalNode(id.text); + if (decl.getSourceFile().isDeclarationFile) return this.globalNode(id.text); + if (!this.files.includes(decl.getSourceFile())) return this.constNode(this.top); + if (ts.isFunctionDeclaration(decl)) { + const b = this.binding(decl); + const saved = this.currentFn; + this.currentFn = null; + this.add(b, this.fnAtom(decl, null)); + this.currentFn = saved; + return b; + } + if (ts.isClassDeclaration(decl) || ts.isClassExpression(decl)) { + const b = this.binding(decl); + this.add(b, this.classAtoms(decl).cls); + return b; + } + if (ts.isFunctionExpression(decl)) return this.binding(decl); + if ( + ts.isImportSpecifier(decl) || + ts.isImportClause(decl) || + ts.isNamespaceImport(decl) || + ts.isImportEqualsDeclaration(decl) + ) + return this.constNode(this.top); + if (ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) return this.namespaceNode(decl); + return this.narrowed(id, decl, this.binding(decl)); + } + + /** Flow narrowing at guarded references (S1 handles the guards hot code + * uses: `x instanceof C` and calls of predicate functions whose body is + * `… && x instanceof C`, in the true arm of an `if`, `?:` or `&&`). The + * narrowed value keeps the guard's class arms and ⊤. */ + narrowed(id: ts.Identifier, decl: Node, value: number): number { + let child: Node = id; + let p = id.parent; + while (p !== undefined && !ts.isFunctionLike(p) && !ts.isSourceFile(p)) { + let cond: ts.Expression | undefined; + if (ts.isIfStatement(p) && p.thenStatement === child) cond = p.expression; + else if (ts.isConditionalExpression(p) && p.whenTrue === child) cond = p.condition; + else if ( + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && + p.right === child + ) + cond = p.left; + if (cond !== undefined) { + const cls = this.guardClass(cond, decl); + if (cls !== null) { + const out = this.newNode(); + this.on(value, (a) => { + if (a.kind === "top") this.add(out, a); + else if ( + cls === "Promise" + ? a.kind === "promise" + : a.kind === "instance" && + a.node !== undefined && + this.classChain(a.node).includes(cls) + ) + this.add(out, a); + }); + return out; + } + } + child = p; + p = p.parent; + } + return value; + } + + /** The class a condition proves `decl`'s value an instance of, if any. */ + guardClass(cond: ts.Expression, decl: Node): Node | "Promise" | null { + const e = unparen(cond); + if (ts.isBinaryExpression(e)) { + if (e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) + return this.guardClass(e.left, decl) ?? this.guardClass(e.right, decl); + if (e.operatorToken.kind !== ts.SyntaxKind.InstanceOfKeyword) return null; + const l = unparen(e.left); + if (!ts.isIdentifier(l) || this.resolve(l) !== decl) return null; + return this.instanceofTarget(e.right); + } + if ( + ts.isCallExpression(e) && + ts.isIdentifier(unparen(e.expression)) && + e.arguments.nodes.length === 1 + ) { + const arg = unparen(e.arguments.nodes[0]!); + if (!ts.isIdentifier(arg) || this.resolve(arg) !== decl) return null; + const fnDecl = this.resolve(unparen(e.expression)); + let fn: Node | undefined = fnDecl; + if (fn !== undefined && ts.isVariableDeclaration(fn) && fn.initializer !== undefined) + fn = unparen(fn.initializer); + if ( + fn === undefined || + !(ts.isArrowFunction(fn) || ts.isFunctionDeclaration(fn) || ts.isFunctionExpression(fn)) + ) + return null; + const param = fn.parameters.nodes[0]; + if (param === undefined || !ts.isIdentifier(param.name)) return null; + let body: Node | undefined = fn.body; + if (body !== undefined && ts.isBlock(body)) { + const stmts = body.statements.nodes; + body = + stmts.length === 1 && ts.isReturnStatement(stmts[0]!) ? stmts[0]!.expression : undefined; + } + if (body === undefined) return null; + return this.predicateClass(body as ts.Expression, param); + } + return null; + } + + /** `… && p instanceof C` in a predicate body over parameter `p`. */ + predicateClass(e: ts.Expression, param: Node): Node | "Promise" | null { + const u = unparen(e); + if (!ts.isBinaryExpression(u)) return null; + if (u.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) + return this.predicateClass(u.right, param) ?? this.predicateClass(u.left, param); + if (u.operatorToken.kind !== ts.SyntaxKind.InstanceOfKeyword) return null; + const l = unparen(u.left); + if (!ts.isIdentifier(l) || this.resolve(l) !== param) return null; + return this.instanceofTarget(u.right); + } + + instanceofTarget(right: ts.Expression): Node | "Promise" | null { + const r = unparen(right); + if (!ts.isIdentifier(r)) return null; + const d = this.resolve(r); + if (d === undefined || d.getSourceFile().isDeclarationFile) + return r.text === "Promise" ? "Promise" : null; + return ts.isClassLikeDeclaration(d) ? d : null; + } + + private readonly namespaces = new Map(); + /** `import * as ns`: an object whose fields are the module's exports. */ + namespaceNode(mod: Node): number { + const existing = this.namespaces.get(mod); + if (existing !== undefined) return existing; + const n = this.newNode(); + this.namespaces.set(mod, n); + const atom = this.atoms.intern(`ns:${this.nodeKey(mod)}`, () => ({ + kind: "object", + label: `namespace ${shortFile((mod as ts.SourceFile).fileName ?? "")}`, + node: mod, + })); + this.add(n, atom); + const sym = this.checker.getSymbolAtLocation(mod); + if (sym !== undefined) { + for (const ex of this.checker.getExportsOfModule(sym)) { + let target = ex; + if (target.flags & ts.SymbolFlags.Alias) { + try { + target = this.checker.getAliasedSymbol(target); + } catch { + continue; + } + } + const d = target.valueDeclaration ?? target.declarations[0]; + if (d === undefined) continue; + const value = this.declValue(d); + if (value !== null) this.edge(value, this.field(atom, String(ex.escapedName))); + } + } + return n; + } + + declValue(d: Node): number | null { + if (d.getSourceFile().isDeclarationFile || !this.files.includes(d.getSourceFile())) return null; + if (ts.isFunctionDeclaration(d)) { + const b = this.binding(d); + const saved = this.currentFn; + this.currentFn = null; + this.add(b, this.fnAtom(d, null)); + this.currentFn = saved; + return b; + } + if (ts.isClassDeclaration(d)) { + const b = this.binding(d); + this.add(b, this.classAtoms(d).cls); + return b; + } + if (ts.isVariableDeclaration(d) || ts.isBindingElement(d) || ts.isParameter(d)) + return this.binding(d); + if (ts.isSourceFile(d)) return this.namespaceNode(d); + return null; + } + + globalNode(name: string): number { + if (MODELED_GLOBALS.has(name)) return this.constNode(this.builtin(name)); + if (name === "NaN" || name === "Infinity") return this.constNode(this.prim("number")); + return this.constNode(this.top); + } + + enclosingNonArrow(): FunctionInfo | null { + const fn = this.currentFn; + if (fn === null) return null; + if (!fn.isArrow) return fn; + // An arrow's `arguments` is its enclosing function's. + let p: Node | undefined = fn.node.parent; + while (p !== undefined) { + if (ts.isFunctionLike(p) && !ts.isArrowFunction(p)) + return ( + this.functions.get(this.ctxKey(p)) ?? this.functions.get(`|${this.nodeKey(p)}`) ?? null + ); + p = p.parent; + } + return null; + } + + objectLiteral(e: ts.ObjectLiteralExpression): number { + const keys: string[] = []; + let keysKnown = true; + let hasAccessor = false; + let hasProto = false; + let hasComputedKey = false; + const spreads: number[] = []; + const inPackage = isPackageJsFile(e.getSourceFile()); + const label = (): string => + `{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${locLabel(e)}`; + // keys first, so the label is complete + for (const p of list(e.properties)) { + if (ts.isSpreadAssignment(p)) { + keysKnown = false; + continue; + } + const name = (p as { name?: Node }).name; + const key = name !== undefined ? propertyNameText(name) : null; + if (key === null) { + hasComputedKey = true; + keysKnown = false; + continue; + } + if (key === "__proto__" && ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) + hasProto = true; + if (!keys.includes(key)) keys.push(key); + } + const atom = this.atoms.intern(`obj:${this.nodeKey(e)}`, () => ({ + kind: "object", + label: label(), + node: e, + })); + const n = this.newNode(); + this.add(n, atom); + for (const p of list(e.properties)) { + if (ts.isSpreadAssignment(p)) { + const src = this.expr(p.expression); + spreads.push(src); + this.copyFields(src, atom, p); + continue; + } + if (ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p)) { + hasAccessor = true; + const info = this.functionInfo(p, null); + this.activate(info); + this.add(info.thisNode, atom); + const key = propertyNameText(p.name); + if (key !== null && ts.isGetAccessorDeclaration(p)) + this.edge(info.ret, this.field(atom, key)); + continue; + } + const name = (p as { name?: Node }).name; + const key = name !== undefined ? propertyNameText(name) : null; + if (name !== undefined && ts.isComputedPropertyName(name)) this.expr(name.expression); + let value: number; + if (ts.isPropertyAssignment(p)) value = this.expr(p.initializer); + else if (ts.isShorthandPropertyAssignment(p)) { + const sym = this.checker.getShorthandAssignmentValueSymbol(p); + const d = sym?.valueDeclaration ?? sym?.declarations[0]; + const id = p.name as ts.Identifier; + value = d !== undefined ? (this.declValue(d) ?? this.identifier(id)) : this.identifier(id); + } else if (ts.isMethodDeclaration(p)) { + const fa = this.fnAtom(p, null); + this.add(this.infoOfAtom(fa)!.thisNode, atom); + value = this.constNode(fa); + } else continue; + if (key !== null) this.edge(value, this.field(atom, key)); + else this.edge(value, this.anyField(atom)); + } + if (!this.siteAtoms.has(atom) && this.siteAtoms.add(atom)) + this.sites.push({ + atom, + node: e, + kind: "object", + keys: keysKnown ? keys : null, + hasAccessor, + hasProto, + hasComputedKey, + spreads, + inPackage, + }); + return n; + } + + /** Values written under unknown keys. */ + anyField(atom: Atom): number { + return this.field(atom, "*"); + } + + copyFields(src: number, dstAtom: Atom, site: Node): void { + this.on(src, (a) => { + if (a.kind === "top") { + this.edge(this.constNode(this.top), this.anyField(dstAtom)); + return; + } + if (a.kind === "prim" || a.kind === "builtin") return; + if (a.kind === "array") { + this.edge(this.element(a), this.anyField(dstAtom)); + return; + } + // Copy every own field the source has, now and later. + const fm = this.fields.get(a); + const copied = new Set(); + const copy = (): void => { + for (const [name, id] of this.fields.get(a) ?? new Map()) { + if (copied.has(name)) continue; + copied.add(name); + this.edge(id, this.field(dstAtom, name)); + } + }; + copy(); + if (fm === undefined) this.fields.set(a, new Map()); + this.lateCopies.push(copy); + if (a.kind === "instance" || a.kind === "dictionary" || a.kind === "collection") + this.edge(this.constNode(this.top), this.anyField(dstAtom)); + void site; + }); + } + + /** Field copies re-run after solving rounds (fields created later). */ + private readonly lateCopies: (() => void)[] = []; + + arrayLiteral(e: ts.ArrayLiteralExpression): number { + const atom = this.atoms.intern(`arr:${this.nodeKey(e)}`, () => ({ + kind: "array", + label: `[]@${locLabel(e)}`, + node: e, + })); + const n = this.newNode(); + this.add(n, atom); + const el = this.element(atom); + let hasHole = false; + for (const item of list(e.elements)) { + if (ts.isOmittedExpression(item)) { + hasHole = true; + continue; + } + if (ts.isSpreadElement(item)) this.iterateInto(this.expr(item.expression), el, item); + else this.edge(this.expr(item), el); + } + if (!this.siteAtoms.has(atom) && this.siteAtoms.add(atom)) + this.sites.push({ + atom, + node: e, + kind: "array", + keys: null, + hasAccessor: false, + hasProto: hasHole, + hasComputedKey: false, + spreads: [], + inPackage: isPackageJsFile(e.getSourceFile()), + }); + return n; + } + + binary(e: ts.BinaryExpression): number { + const op = e.operatorToken.kind; + if (op === ts.SyntaxKind.EqualsToken) { + const value = this.expr(e.right); + this.assignTo(e.left, value); + return value; + } + if ( + op === ts.SyntaxKind.BarBarEqualsToken || + op === ts.SyntaxKind.QuestionQuestionEqualsToken || + op === ts.SyntaxKind.AmpersandAmpersandEqualsToken + ) { + const n = this.newNode(); + this.edge(this.expr(e.left), n); + const value = this.expr(e.right); + this.edge(value, n); + this.assignTo(e.left, value); + return n; + } + if (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment) { + this.expr(e.left); + this.expr(e.right); + const result = this.constNode( + op === ts.SyntaxKind.PlusEqualsToken ? this.top : this.prim("number"), + ); + if (op === ts.SyntaxKind.PlusEqualsToken) { + this.nodes[result]!.atoms.clear(); + this.add(result, this.prim("string")); + this.add(result, this.prim("number")); + } + this.assignTo(e.left, result); + return result; + } + if ( + op === ts.SyntaxKind.BarBarToken || + op === ts.SyntaxKind.QuestionQuestionToken || + op === ts.SyntaxKind.AmpersandAmpersandToken + ) { + const n = this.newNode(); + this.edge(this.expr(e.left), n); + this.edge(this.expr(e.right), n); + return n; + } + if (op === ts.SyntaxKind.CommaToken) { + this.expr(e.left); + return this.expr(e.right); + } + const l = this.expr(e.left); + const r = this.expr(e.right); + switch (op) { + case ts.SyntaxKind.EqualsEqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsEqualsToken: + case ts.SyntaxKind.EqualsEqualsToken: + case ts.SyntaxKind.ExclamationEqualsToken: + case ts.SyntaxKind.LessThanToken: + case ts.SyntaxKind.LessThanEqualsToken: + case ts.SyntaxKind.GreaterThanToken: + case ts.SyntaxKind.GreaterThanEqualsToken: + case ts.SyntaxKind.InstanceOfKeyword: + case ts.SyntaxKind.InKeyword: + if (op === ts.SyntaxKind.InKeyword) + this.ops.push({ kind: "computedRead", recv: r, name: null, node: e }); + return this.constNode(this.prim("boolean")); + case ts.SyntaxKind.PlusToken: { + const n = this.newNode(); + const strOrNum = (a: Atom): void => { + this.add( + n, + a.kind === "prim" && a.label === "number" ? this.prim("number") : this.prim("string"), + ); + if ( + a.kind === "prim" && + (a.label === "boolean" || a.label === "null" || a.label === "undefined") + ) + this.add(n, this.prim("number")); + if (a.kind === "top") this.add(n, this.prim("number")); + }; + this.on(l, strOrNum); + this.on(r, strOrNum); + return n; + } + default: + return this.constNode(this.prim("number")); + } + } + + assignTo(target: ts.Expression, value: number): void { + const t = unparen(target); + if (ts.isIdentifier(t)) { + const decl = this.resolve(t); + if (decl === undefined || decl.getSourceFile().isDeclarationFile) { + this.sink(value, `global write at ${locLabel(t)}`); + return; + } + this.edge(value, this.binding(decl)); + return; + } + if (ts.isPropertyAccessExpression(t)) { + const recv = this.expr(t.expression); + this.storeConstraint(recv, t.name.text, value, t); + this.ops.push({ + kind: t.name.text === "length" ? "lengthWrite" : "write", + recv, + name: t.name.text, + node: t, + }); + return; + } + if (ts.isElementAccessExpression(t)) { + const recv = this.expr(t.expression); + const key = unparen(t.argumentExpression); + if (ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key)) { + this.storeConstraint(recv, key.text, value, t); + this.ops.push({ kind: "write", recv, name: key.text, node: t }); + } else { + this.expr(t.argumentExpression); + this.computedStore(recv, value, t); + this.ops.push({ kind: "computedWrite", recv, name: null, node: t }); + } + return; + } + if (ts.isObjectLiteralExpression(t)) { + for (const p of list(t.properties)) { + if (ts.isPropertyAssignment(p)) { + const key = propertyNameText(p.name); + const n = this.newNode(); + if (key !== null) this.loadConstraint(value, key, n, p); + else this.add(n, this.top); + this.assignTo(p.initializer, n); + } else if (ts.isShorthandPropertyAssignment(p)) { + const n = this.newNode(); + const id = p.name as ts.Identifier; + this.loadConstraint(value, id.text, n, p); + this.assignTo(id, n); + } + } + return; + } + if (ts.isArrayLiteralExpression(t)) { + for (const el of list(t.elements)) { + if (ts.isOmittedExpression(el)) continue; + const n = this.newNode(); + this.iterateInto(value, n, el); + this.assignTo(ts.isSpreadElement(el) ? el.expression : el, n); + } + } + } + + // ---- loads and stores ------------------------------------------------------------ + + loadConstraint(recv: number, name: string, dst: number, site: Node): void { + this.on(recv, (a) => this.loadAtom(a, name, dst, site)); + } + + loadAtom(a: Atom, name: string, dst: number, site: Node): void { + switch (a.kind) { + case "top": + this.add(dst, this.top); + return; + case "prim": + if (name === "length" && a.label === "string") this.add(dst, this.prim("number")); + else if (a.label === "string" || a.label === "number" || a.label === "boolean") + this.add(dst, this.builtin(`${a.label}.${name}`, a)); + return; + case "instance": { + const decl = a.node!; + if (name === "constructor") { + this.add(dst, this.classAtoms(decl).cls); + return; + } + const member = this.lookupMember(decl, name, false); + if (member?.kind === "method") + this.add( + dst, + this.fnAtom(member.method!, this.ownerOf(member.method!), this.receiverCtx(a)), + ); + if (member !== null && member.get !== undefined) { + const g = this.functionInfo(member.get, this.ownerOf(member.get), this.receiverCtx(a)); + this.activate(g); + this.add(g.thisNode, a); + this.edge(g.ret, dst); + this.edgeFor(site, g); + return; + } + if (this.classChain(decl).some((c) => this.baseClass(c) === undefined)) + this.add(dst, this.top); + this.edge(this.field(a, name), dst); + return; + } + case "class": { + const decl = a.node!; + if (name === "prototype") { + this.add(dst, this.top); + return; + } + if (name === "name") { + this.add(dst, this.prim("string")); + return; + } + const member = this.lookupMember(decl, name, true); + if (member?.kind === "method") + this.add( + dst, + this.fnAtom(member.method!, this.ownerOf(member.method!), this.receiverCtx(a)), + ); + if (member?.get !== undefined) { + const g = this.functionInfo(member.get, this.ownerOf(member.get), this.receiverCtx(a)); + this.activate(g); + this.add(g.thisNode, a); + this.edge(g.ret, dst); + return; + } + // static fields of the class and its bases + for (const c of this.classChain(decl)) { + const cls = this.classAtoms(c).cls; + this.edge(this.field(cls, name), dst); + } + return; + } + case "function": + if (name === "call" || name === "apply" || name === "bind") + this.add(dst, this.builtin(`function.${name}`, a)); + else if (name === "prototype") this.add(dst, this.top); + else if (name === "name") this.add(dst, this.prim("string")); + else if (name === "length") this.add(dst, this.prim("number")); + else this.edge(this.field(a, name), dst); + return; + case "array": + if (name === "length") this.add(dst, this.prim("number")); + else if (/^\d+$/.test(name)) this.edge(this.element(a), dst); + else if (ARRAY_METHODS.has(name)) this.add(dst, this.builtin(`array.${name}`, a)); + else this.edge(this.field(a, name), dst); + return; + case "object": + this.edge(this.field(a, name), dst); + this.edge(this.anyField(a), dst); + if (name === "constructor" || name === "hasOwnProperty" || name === "toString") + this.add(dst, this.builtin(`object.${name}`, a)); + return; + case "dictionary": + this.edge(this.anyField(a), dst); + this.edge(this.field(a, name), dst); + return; + case "promise": + if (name === "then" || name === "catch" || name === "finally") + this.add(dst, this.builtin(`promise.${name}`, a)); + return; + case "collection": + if (name === "size") this.add(dst, this.prim("number")); + else this.add(dst, this.builtin(`collection.${name}`, a)); + return; + case "builtin": + this.loadBuiltin(a, name, dst); + return; + } + void site; + } + + ownerOf(member: Node): Node | null { + const p = member.parent; + return p !== undefined && ts.isClassLikeDeclaration(p) ? p : null; + } + + loadBuiltin(a: Atom, name: string, dst: number): void { + const path = a.path ?? ""; + if ( + path === "Number" && + (name === "MAX_SAFE_INTEGER" || + name === "MIN_SAFE_INTEGER" || + name === "EPSILON" || + name === "MAX_VALUE" || + name === "MIN_VALUE" || + name === "POSITIVE_INFINITY" || + name === "NEGATIVE_INFINITY" || + name === "NaN") + ) + this.add(dst, this.prim("number")); + else if (path === "Math" && /^[A-Z0-9_]+$/.test(name)) this.add(dst, this.prim("number")); + else if (path === "Symbol") this.add(dst, this.prim("symbol")); + else if (path === "Error.instance" || path === "error") { + if (name === "message" || name === "stack" || name === "name") + this.add(dst, this.prim("string")); + else this.add(dst, this.top); + } else if ( + a.receiver === undefined && + MODELED_GLOBALS.has(path.split(".")[0]!) && + path.split(".").length < 4 + ) + this.add(dst, this.builtin(`${path}.${name}`)); + else if (path === "RegExp.instance") this.add(dst, this.builtin(`regexp.${name}`, a)); + else this.add(dst, this.top); + } + + storeConstraint(recv: number, name: string, value: number, site: Node): void { + this.on(recv, (a) => { + switch (a.kind) { + case "top": + this.noteTopWrite(name, site); + this.sink(value, `stored into ⊤.${name} at ${locLabel(site)}`); + return; + case "instance": { + const member = this.lookupMember(a.node!, name, false); + if (member?.set !== undefined) { + const s = this.functionInfo(member.set, this.ownerOf(member.set), this.receiverCtx(a)); + this.activate(s); + this.add(s.thisNode, a); + if (s.params[0] !== undefined) this.edge(value, s.params[0]); + return; + } + this.edge(value, this.field(a, name)); + return; + } + case "class": + case "function": + case "object": + case "dictionary": + this.edge(value, this.field(a, name)); + return; + case "array": + if (/^\d+$/.test(name)) this.edge(value, this.element(a)); + return; + case "prim": + return; + default: + this.sink(value, `stored into ${a.label} at ${locLabel(site)}`); + } + }); + } + + computedLoad(recv: number, dst: number, site: Node): void { + this.on(recv, (a) => { + switch (a.kind) { + case "top": + this.add(dst, this.top); + return; + case "array": + case "collection": + this.edge(this.element(a), dst); + return; + case "prim": + if (a.label === "string") this.add(dst, this.prim("string")); + return; + case "object": + case "dictionary": + case "instance": + case "class": + case "function": { + // every field, now and later + const seen = new Set(); + const copy = (): void => { + for (const [name, id] of this.fields.get(a) ?? new Map()) { + if (seen.has(name)) continue; + seen.add(name); + this.edge(id, dst); + } + }; + copy(); + this.lateCopies.push(copy); + if (a.kind === "instance" || a.kind === "class") this.add(dst, this.top); + return; + } + default: + this.add(dst, this.top); + } + void site; + }); + } + + computedStore(recv: number, value: number, site: Node): void { + this.on(recv, (a) => { + switch (a.kind) { + case "top": + this.noteTopWrite(null, site); + this.sink(value, `stored into ⊤[k] at ${locLabel(site)}`); + return; + case "array": + this.edge(value, this.element(a)); + return; + case "prim": + return; + default: + this.edge(value, this.anyField(a)); + } + }); + } + + // ---- calls --------------------------------------------------------------------------- + + args(e: ts.CallExpression | ts.NewExpression): { args: number[]; spread: boolean } { + const out: number[] = []; + let spread = false; + for (const a of list(e.arguments)) { + if (ts.isSpreadElement(a)) { + const inner = unparen(a.expression); + if (ts.isIdentifier(inner) && inner.text === "arguments") { + // `f(...arguments)`: forward the enclosing function's parameters. + const fn = this.enclosingNonArrow(); + if (fn !== null && out.length === 0) { + if (fn.argPos === null) fn.argPos = [0, 1, 2, 3, 4, 5].map(() => this.newNode()); + out.push(...fn.argPos); + continue; + } + } + spread = true; + out.push(this.expr(a.expression)); + continue; + } + out.push(this.expr(a)); + } + return { args: out, spread }; + } + + call(e: ts.CallExpression): number { + this.noteSite(e); + const callee = unparen(e.expression); + const dst = this.newNode(); + if (callee.kind === ts.SyntaxKind.SuperKeyword) { + const { args, spread } = this.args(e); + const fn = this.enclosingNonArrow(); + const owner = fn?.ownerClass ?? null; + const base = owner !== null ? this.baseClass(owner) : undefined; + if (fn !== null && base) { + this.on(fn.thisNode, (inst) => { + if (inst.kind === "instance") this.callConstructorChain(base, inst, args, spread, e); + }); + } else if (base === undefined) + for (const a of args) this.sink(a, `super(...) at ${locLabel(e)}`); + return dst; + } + if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) { + const recvExpr = unparen(callee.expression); + const { args, spread } = this.args(e); + if (recvExpr.kind === ts.SyntaxKind.SuperKeyword && ts.isPropertyAccessExpression(callee)) { + // super.m(...): the base class member with the current receiver. + const fn = this.enclosingNonArrow(); + const owner = fn?.ownerClass ?? null; + const base = owner !== null ? this.baseClass(owner) : undefined; + if (fn !== null && base) { + const member = this.lookupMember(base, callee.name.text, isStatic(fn.node)); + if (member?.method !== undefined) { + this.callFunction( + this.functionInfo(member.method, this.ownerOf(member.method), fn.ctx), + fn.thisNode, + args, + spread, + dst, + e, + ); + } + } + return dst; + } + const recv = this.expr(recvExpr); + let name: string | null = null; + if (ts.isPropertyAccessExpression(callee)) name = callee.name.text; + else { + const k = unparen(callee.argumentExpression); + if (ts.isStringLiteral(k) || ts.isNoSubstitutionTemplateLiteral(k)) name = k.text; + else this.expr(callee.argumentExpression); + } + if (name !== null) this.ops.push({ kind: "read", recv, name, node: callee }); + // Per receiver atom: the member it resolves to runs with that atom as `this`. + const member = name; + this.on(recv, (a) => { + const fnNode = this.newNode(); + const self = this.constNode(a); + if (member !== null) this.loadAtom(a, member, fnNode, callee); + else this.computedLoad(self, fnNode, callee); + this.invoke(fnNode, self, args, spread, dst, e); + }); + return dst; + } + const calleeNode = this.expr(callee); + const { args, spread } = this.args(e); + this.invoke(calleeNode, null, args, spread, dst, e); + return dst; + } + + invoke( + callee: number, + recv: number | null, + args: number[], + spread: boolean, + dst: number, + site: ts.CallExpression, + ): void { + const undefNode = recv === null ? this.constNode(this.prim("undefined")) : recv; + this.on(callee, (a) => { + switch (a.kind) { + case "function": + this.callFunction(this.infoOfAtom(a)!, undefNode, args, spread, dst, site); + return; + case "builtin": + this.callBuiltin(a, args, spread, dst, site, recv); + return; + case "class": + return; // calling a class throws + case "prim": + return; + default: + for (const x of args) this.sink(x, `argument of ⊤ call at ${locLabel(site)}`); + if (recv !== null && a.kind === "top") + this.sink(recv, `receiver of ⊤ call at ${locLabel(site)}`); + this.add(dst, this.top); + } + }); + } + + newExpr(e: ts.NewExpression): number { + this.noteSite(e); + const dst = this.newNode(); + const callee = this.expr(e.expression); + const { args, spread } = this.args(e); + this.on(callee, (a) => { + switch (a.kind) { + case "class": + this.construct(a.node!, args, spread, dst, e); + return; + case "function": { + // An ES5 constructor function: one instance atom per function. + const inst = this.atoms.intern(`fninst:${this.nodeKey(a.node!)}`, () => ({ + kind: "object", + label: `new ${a.label}`, + node: a.node, + })); + this.add(dst, inst); + const info = this.infoOfAtom(a); + if (info !== undefined) { + this.activate(info); + this.add(info.thisNode, inst); + this.bindArgs(info, args, spread); + this.edge(info.ret, dst); + } + return; + } + case "builtin": + this.constructBuiltin(a.path ?? "", args, dst, e); + return; + default: + for (const x of args) this.sink(x, `argument of ⊤ constructor at ${locLabel(e)}`); + this.add(dst, this.top); + } + }); + return dst; + } + + constructBuiltin(path: string, args: number[], dst: number, site: Node): void { + switch (path) { + case "Map": + case "Set": + case "WeakMap": + case "WeakSet": { + const c = this.atoms.intern(`coll:${this.nodeKey(site)}`, () => ({ + kind: "collection", + label: `${path}@${locLabel(site)}`, + node: site, + })); + this.add(dst, c); + if (args[0] !== undefined) this.iterateInto(args[0], this.element(c), site); + return; + } + case "Error": + case "TypeError": + case "RangeError": + case "SyntaxError": + this.add(dst, this.builtin("Error.instance")); + return; + case "Promise": { + const p = this.promiseAtom(site, locLabel(site)); + this.add(this.field(p, "value"), this.top); + this.add(dst, p); + if (args[0] !== undefined) { + const resolver = this.constNode(this.builtin("promise.resolver")); + this.on(args[0], (f) => { + if (f.kind === "function") + this.callFunction( + this.infoOfAtom(f)!, + null, + [resolver, resolver], + false, + this.newNode(), + site, + ); + }); + } + return; + } + case "Date": + this.add(dst, this.builtin("Date.instance")); + return; + case "RegExp": + this.add(dst, this.builtin("RegExp.instance")); + return; + case "Array": { + this.add(dst, this.syntheticArray(site, "Array")); + return; + } + default: + for (const x of args) this.sink(x, `argument of new ${path} at ${locLabel(site)}`); + this.add(dst, this.top); + } + } + + /** Builtin call summaries; anything unmodeled makes its arguments escape. */ + callBuiltin( + a: Atom, + args: number[], + spread: boolean, + dst: number, + site: ts.CallExpression, + recvNode: number | null, + ): void { + const path = a.path ?? ""; + const recv = a.receiver; + const arg = (i: number): number => args[i] ?? this.constNode(this.prim("undefined")); + const prim = (p: string): void => this.add(dst, this.prim(p)); + const callback = (i: number, cbArgs: number[], ret: number | null): void => { + if (args[i] === undefined) return; + this.on(args[i]!, (f) => { + if (f.kind === "function") { + const saved = this.forceLoop; + this.forceLoop = LOOP_CALLBACK_METHODS.has(path.slice(path.indexOf(".") + 1)); + this.callFunction(this.infoOfAtom(f)!, null, cbArgs, false, ret ?? this.newNode(), site); + this.forceLoop = saved; + } else if (f.kind === "top" || f.kind === "builtin") { + for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${locLabel(site)}`); + if (ret !== null) this.add(ret, this.top); + } + }); + }; + const escapeAll = (): void => { + for (const x of args) this.sink(x, `argument of ${path} at ${locLabel(site)}`); + this.add(dst, this.top); + }; + // Methods of builtin receivers. + if (recv !== undefined) { + const method = path.slice(path.indexOf(".") + 1); + if (path.startsWith("array.")) { + const el = this.element(recv); + const num = this.constNode(this.prim("number")); + const recvN = this.constNode(recv); + switch (method) { + case "push": + case "unshift": + for (const x of args) { + if (spread) this.iterateInto(x, el, site); + else this.edge(x, el); + } + prim("number"); + return; + case "pop": + case "shift": + case "at": + this.edge(el, dst); + this.add(dst, this.prim("undefined")); + return; + case "find": + case "findLast": + callback(0, [el, num, recvN], null); + this.edge(el, dst); + this.add(dst, this.prim("undefined")); + return; + case "forEach": + callback(0, [el, num, recvN], null); + prim("undefined"); + return; + case "some": + case "every": + case "includes": + callback(0, [el, num, recvN], null); + prim("boolean"); + return; + case "findIndex": + case "findLastIndex": + case "indexOf": + case "lastIndexOf": + callback(0, [el, num, recvN], null); + prim("number"); + return; + case "join": + case "toString": + prim("string"); + return; + case "map": + case "flatMap": { + const out = this.syntheticArray(site, method); + const outEl = this.element(out); + if (method === "map") callback(0, [el, num, recvN], outEl); + else { + const r = this.newNode(); + callback(0, [el, num, recvN], r); + this.iterateInto(r, outEl, site); + } + this.add(dst, out); + return; + } + case "filter": + case "slice": + case "concat": + case "reverse": + case "sort": + case "splice": + case "toSorted": + case "toReversed": + case "flat": { + if (method === "filter") callback(0, [el, num, recvN], null); + if (method === "sort" || method === "toSorted") callback(0, [el, el], null); + if (method === "reverse" || method === "sort") { + this.add(dst, recv); + return; + } + const out = this.syntheticArray(site, method); + this.edge(el, this.element(out)); + if (method === "concat") + for (const x of args) this.iterateInto(x, this.element(out), site); + if (method === "splice") for (const x of args.slice(2)) this.edge(x, el); + this.add(dst, out); + return; + } + case "reduce": + case "reduceRight": { + const acc = this.newNode(); + if (args[1] !== undefined) this.edge(args[1], acc); + else this.edge(el, acc); + callback(0, [acc, el, num, recvN], acc); + this.edge(acc, dst); + return; + } + case "keys": + case "values": + case "entries": { + const out = this.syntheticArray(site, method); + if (method === "values") this.edge(el, this.element(out)); + else if (method === "keys") this.add(this.element(out), this.prim("number")); + else { + const pair = this.syntheticArray(site, "entry"); + this.add(this.element(pair), this.prim("number")); + this.edge(el, this.element(pair)); + this.add(this.element(out), pair); + } + this.add(dst, out); + return; + } + case "fill": + for (const x of args) this.edge(x, el); + this.add(dst, recv); + return; + } + escapeAll(); + this.escape(recv, `array.${method} at ${locLabel(site)}`); + return; + } + if (path.startsWith("string.") || path.startsWith("number.") || path.startsWith("boolean.")) { + const r = STRING_METHOD_RESULTS.get(method); + if (r === "array") { + const out = this.syntheticArray(site, method); + this.add(this.element(out), this.prim("string")); + this.add(this.element(out), this.prim("undefined")); + this.add(dst, out); + this.add(dst, this.prim("null")); + } else if (r !== undefined) prim(r); + else if (method === "replace" || method === "replaceAll") prim("string"); + else escapeAll(); + if (method === "replace" || method === "replaceAll") + callback(1, [this.constNode(this.prim("string"))], null); + return; + } + if (path.startsWith("regexp.")) { + if (method === "test") prim("boolean"); + else if (method === "exec") { + const out = this.syntheticArray(site, "exec"); + this.add(this.element(out), this.prim("string")); + this.add(dst, out); + prim("null"); + } else escapeAll(); + return; + } + if (path.startsWith("promise.")) { + const out = this.promiseAtom(site, locLabel(site)); + const value = this.field(recv, "value"); + const ret = this.newNode(); + if (method === "then") { + callback(0, [value], ret); + callback(1, [this.constNode(this.top)], ret); + } else if (method === "catch") { + callback(0, [this.constNode(this.top)], ret); + this.edge(value, ret); + } else { + callback(0, [], null); + this.edge(value, ret); + } + this.awaitInto(ret, this.field(out, "value")); + this.add(dst, out); + return; + } + if (path.startsWith("collection.")) { + const el = this.element(recv); + switch (method) { + case "add": + case "set": + for (const x of args) this.edge(x, el); + this.add(dst, recv); + return; + case "get": + this.edge(el, dst); + prim("undefined"); + return; + case "has": + case "delete": + prim("boolean"); + return; + case "clear": + prim("undefined"); + return; + case "forEach": + callback(0, [el, el], null); + prim("undefined"); + return; + case "keys": + case "values": + case "entries": { + const out = this.syntheticArray(site, method); + this.edge(el, this.element(out)); + if (method === "entries") { + const pair = this.syntheticArray(site, "entry"); + this.edge(el, this.element(pair)); + this.nodes[this.element(out)]!.atoms.clear(); + this.add(this.element(out), pair); + } + this.add(dst, out); + return; + } + } + escapeAll(); + return; + } + if (path.startsWith("function.")) { + if (recv.kind !== "function" || recv.node === undefined) { + escapeAll(); + return; + } + if (method === "bind") { + const info = this.infoOfAtom(recv); + if (info !== undefined && args[0] !== undefined && !info.isArrow) + this.edge(args[0], info.thisNode); + if (info !== undefined && args.length > 1) { + this.activate(info); + for (const p of info.params) this.add(p, this.top); + for (const x of args.slice(1)) this.sink(x, `bound argument at ${locLabel(site)}`); + } + this.add(dst, recv); + return; + } + if (method === "call") { + this.callFunction( + this.infoOfAtom(recv)!, + args[0] ?? this.constNode(this.prim("undefined")), + args.slice(1), + spread, + dst, + site, + ); + return; + } + // apply: arguments from an array + const info = this.infoOfAtom(recv); + if (info !== undefined) { + this.activate(info); + if (args[0] !== undefined && !info.isArrow) this.edge(args[0], info.thisNode); + for (const p of info.params) + if (args[1] !== undefined) this.iterateInto(args[1], p, site); + this.edge(info.ret, dst); + } + return; + } + if (path.startsWith("object.")) { + if (method === "hasOwnProperty") prim("boolean"); + else if (method === "toString") prim("string"); + else escapeAll(); + return; + } + escapeAll(); + return; + } + // `Object.prototype.m.call(o, …)`-style calls of builtin prototype methods. + if (/^(Object|Array|String|Function)\.prototype\.\w+\.(call|apply)$/.test(path)) { + const m = path.split(".")[2]!; + if (m === "hasOwnProperty" || m === "propertyIsEnumerable" || m === "isPrototypeOf") + prim("boolean"); + else if (m === "toString") prim("string"); + else escapeAll(); + return; + } + switch (path) { + case "Object.keys": + case "Object.getOwnPropertyNames": { + const out = this.syntheticArray(site, "keys"); + this.add(this.element(out), this.prim("string")); + this.add(dst, out); + return; + } + case "Object.values": + case "Object.entries": { + const out = this.syntheticArray(site, path === "Object.values" ? "values" : "entries"); + const vals = this.newNode(); + if (args[0] !== undefined) this.computedLoad(args[0], vals, site); + if (path === "Object.values") this.edge(vals, this.element(out)); + else { + const pair = this.syntheticArray(site, "entry"); + this.add(this.element(pair), this.prim("string")); + this.edge(vals, this.element(pair)); + this.add(this.element(out), pair); + } + this.add(dst, out); + return; + } + case "Object.freeze": + case "Object.seal": + case "Object.preventExtensions": + this.ops.push({ + kind: path === "Object.freeze" ? "freeze" : "seal", + recv: arg(0), + name: null, + node: site, + }); + this.edge(arg(0), dst); + return; + case "Object.isFrozen": + case "Object.isSealed": + case "Object.isExtensible": + case "Array.isArray": + case "Number.isInteger": + case "Number.isNaN": + case "Number.isFinite": + case "Number.isSafeInteger": + case "isNaN": + case "isFinite": + prim("boolean"); + return; + case "Object.assign": { + this.ops.push({ kind: "assign", recv: arg(0), name: null, node: site }); + this.on(arg(0), (t) => { + for (const s of args.slice(1)) this.copyFields(s, t, site); + if (t.kind === "top") + for (const s of args.slice(1)) + this.sink(s, `Object.assign into ⊤ at ${locLabel(site)}`); + }); + this.edge(arg(0), dst); + return; + } + case "Object.defineProperty": + case "Object.defineProperties": + case "Reflect.defineProperty": + this.ops.push({ kind: "defineProperty", recv: arg(0), name: null, node: site }); + this.sink(arg(2), `property descriptor at ${locLabel(site)}`); + this.sink(arg(1), `property descriptor at ${locLabel(site)}`); + this.edge(arg(0), dst); + return; + case "Object.setPrototypeOf": + case "Reflect.setPrototypeOf": + this.ops.push({ kind: "setPrototypeOf", recv: arg(0), name: null, node: site }); + this.sink(arg(1), `prototype at ${locLabel(site)}`); + this.edge(arg(0), dst); + return; + case "Object.create": { + const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ + kind: "dictionary", + label: `Object.create@${locLabel(site)}`, + node: site, + })); + this.add(dst, d); + return; + } + case "Object.fromEntries": { + const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ + kind: "dictionary", + label: `fromEntries@${locLabel(site)}`, + node: site, + })); + const pairs = this.newNode(); + if (args[0] !== undefined) this.iterateInto(args[0], pairs, site); + const vals = this.newNode(); + this.iterateInto(pairs, vals, site); + this.edge(vals, this.anyField(d)); + this.add(dst, d); + return; + } + case "Object.getPrototypeOf": + case "Reflect.getPrototypeOf": + this.add(dst, this.top); + return; + case "Object.is": + prim("boolean"); + return; + case "Array.from": + case "Array.of": { + const out = this.syntheticArray(site, path); + if (path === "Array.of") for (const x of args) this.edge(x, this.element(out)); + else if (args[1] !== undefined) { + const els = this.newNode(); + if (args[0] !== undefined) this.iterateInto(args[0], els, site); + callback(1, [els, this.constNode(this.prim("number"))], this.element(out)); + } else if (args[0] !== undefined) this.iterateInto(args[0], this.element(out), site); + this.add(dst, out); + return; + } + case "JSON.parse": + this.add(dst, this.top); + return; + case "JSON.stringify": + prim("string"); + prim("undefined"); + return; + case "String": + case "encodeURIComponent": + case "decodeURIComponent": + case "encodeURI": + case "decodeURI": + prim("string"); + return; + case "Number": + case "parseInt": + case "parseFloat": + case "Number.parseInt": + case "Number.parseFloat": + prim("number"); + return; + case "Boolean": + prim("boolean"); + return; + case "Symbol": + case "Symbol.for": + prim("symbol"); + return; + case "BigInt": + prim("bigint"); + return; + case "Promise.resolve": { + const p = this.promiseAtom(site, locLabel(site)); + if (args[0] !== undefined) this.awaitInto(args[0], this.field(p, "value")); + else this.add(this.field(p, "value"), this.prim("undefined")); + this.add(dst, p); + return; + } + case "Promise.reject": { + const p = this.promiseAtom(site, locLabel(site)); + this.add(dst, p); + if (args[0] !== undefined) this.sink(args[0], `rejection at ${locLabel(site)}`); + return; + } + case "Promise.all": + case "Promise.allSettled": + case "Promise.race": + case "Promise.any": { + const p = this.promiseAtom(site, locLabel(site)); + const items = this.newNode(); + if (args[0] !== undefined) this.iterateInto(args[0], items, site); + if (path === "Promise.all") { + const out = this.syntheticArray(site, "all"); + this.awaitInto(items, this.element(out)); + this.add(this.field(p, "value"), out); + } else if (path === "Promise.race" || path === "Promise.any") + this.awaitInto(items, this.field(p, "value")); + else this.add(this.field(p, "value"), this.top); + this.add(dst, p); + return; + } + case "promise.resolver": + for (const x of args) this.sink(x, `promise resolution at ${locLabel(site)}`); + return; + } + if (path.startsWith("console.")) { + prim("undefined"); + return; + } + if (path.startsWith("Math.")) { + prim("number"); + return; + } + if (path === "Date.now") { + prim("number"); + return; + } + if (path.startsWith("Date.instance") || path === "Error.instance") { + escapeAll(); + return; + } + void recvNode; + escapeAll(); + } + + /** Re-run deferred field copies until nothing new flows. */ + finish(): void { + for (;;) { + const before = this.steps; + for (const c of this.lateCopies) c(); + this.solve(); + if (this.steps === before || this.budgetExceeded) return; + } + } +} diff --git a/packages/compiler/src/frontend/shape-inference/domain.ts b/packages/compiler/src/frontend/shape-inference/domain.ts new file mode 100644 index 000000000..36978d4c9 --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/domain.ts @@ -0,0 +1,153 @@ +/* Shape inference (results/design-shape-inference/DESIGN.md), stage S1: + * the abstract domain of the allocation-site points-to analysis. + * + * An abstract value is a set of atoms. An atom names a set of runtime + * values: a primitive kind, an allocation site (object or array literal, or + * a value a builtin creates at one call site), the instances of one class, + * one class value, one function (closure site), a builtin object, or ⊤ — + * every value the analysis does not track. Value nodes hold atom sets and + * flow into each other along inclusion edges; the solver (analyze.ts) + * attaches load, store and call constraints that fire as atoms arrive. */ +import type * as ts from "../ts7/adapter.js"; + +export type AtomKind = + | "prim" + | "top" + | "object" + | "array" + | "instance" + | "class" + | "function" + | "builtin" + | "promise" + | "collection" + | "dictionary"; + +export interface Atom { + readonly id: number; + readonly kind: AtomKind; + /** Short display name, e.g. `string`, `ZodString`, `{status,value}@types.js:804`. */ + readonly label: string; + /** The syntax that creates the atom: the literal, class or function + * declaration, or the builtin call. Undefined for prims, ⊤ and builtins. */ + readonly node?: ts.Node | undefined; + /** builtin atoms: the dotted path (`Object.keys`, `array.push`). */ + readonly path?: string | undefined; + /** method atoms of builtin receivers: the receiver atom. */ + readonly receiver?: Atom | undefined; + /** function atoms: the analysis context of the function's clone (a + * method's receiver class), "" for the context-insensitive default. */ + readonly ctx?: string | undefined; +} + +export class AtomTable { + readonly atoms: Atom[] = []; + private readonly byKey = new Map(); + + intern(key: string, make: (id: number) => Omit): Atom { + let atom = this.byKey.get(key); + if (atom === undefined) { + const id = this.atoms.length; + atom = { id, ...make(id) }; + this.atoms.push(atom); + this.byKey.set(key, atom); + } + return atom; + } +} + +/** Object.prototype members: a read of one of these names on a plain + * object resolves on the prototype, never to an absent own key. */ +export const OBJECT_PROTOTYPE_MEMBERS: ReadonlySet = new Set([ + "constructor", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", + "toString", + "valueOf", + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "__proto__", +]); + +/** Global names the analysis models as builtin objects. Any other global is ⊤. */ +export const MODELED_GLOBALS: ReadonlySet = new Set([ + "Object", + "Array", + "JSON", + "Math", + "Number", + "String", + "Boolean", + "Symbol", + "Promise", + "Map", + "Set", + "WeakMap", + "WeakSet", + "Error", + "TypeError", + "RangeError", + "SyntaxError", + "Date", + "RegExp", + "console", + "Reflect", + "parseInt", + "parseFloat", + "isNaN", + "isFinite", + "encodeURIComponent", + "decodeURIComponent", + "encodeURI", + "decodeURI", + "BigInt", +]); + +/** Results of string (and number) methods, by name. Anything else is ⊤. */ +type MethodResult = "string" | "number" | "boolean" | "array"; +export const STRING_METHOD_RESULTS: ReadonlyMap = new Map< + string, + MethodResult +>([ + ["at", "string"], + ["charAt", "string"], + ["charCodeAt", "number"], + ["codePointAt", "number"], + ["concat", "string"], + ["endsWith", "boolean"], + ["includes", "boolean"], + ["indexOf", "number"], + ["lastIndexOf", "number"], + ["localeCompare", "number"], + ["match", "array"], + ["matchAll", "array"], + ["normalize", "string"], + ["padEnd", "string"], + ["padStart", "string"], + ["repeat", "string"], + ["replace", "string"], + ["replaceAll", "string"], + ["search", "number"], + ["slice", "string"], + ["split", "array"], + ["startsWith", "boolean"], + ["substring", "string"], + ["substr", "string"], + ["toLowerCase", "string"], + ["toUpperCase", "string"], + ["toLocaleLowerCase", "string"], + ["toLocaleUpperCase", "string"], + ["toString", "string"], + ["trim", "string"], + ["trimEnd", "string"], + ["trimStart", "string"], + ["valueOf", "string"], + ["test", "boolean"], + ["toFixed", "string"], + ["toPrecision", "string"], + ["toExponential", "string"], +]); diff --git a/packages/compiler/src/frontend/shape-inference/index.ts b/packages/compiler/src/frontend/shape-inference/index.ts new file mode 100644 index 000000000..88c2100ee --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/index.ts @@ -0,0 +1,21 @@ +/* Shape inference for untyped npm JavaScript + * (results/design-shape-inference/DESIGN.md). Stage S1: report only. */ +import type * as ts from "../ts7/adapter.js"; +import { analyzeShapes } from "./analyze.js"; +import { buildShapeReport } from "./report.js"; + +export { analyzeShapes } from "./analyze.js"; +export { buildShapeReport } from "./report.js"; + +/** SCRIPTC_DEBUG_SHAPES: `1` reports package code in hot functions, + * `verbose` every package site, `all` user files too, any other value only + * files whose path contains it. Writes to stderr; changes nothing else. */ +export function reportShapes(program: ts.Program, filter: string): void { + const start = performance.now(); + const analysis = analyzeShapes(program, program.getTypeChecker()); + const report = buildShapeReport(analysis, filter); + const ms = (performance.now() - start).toFixed(0); + process.stderr.write( + `[shapes] ${ms} ms\n${report.lines.map((l) => `[shapes] ${l}`).join("\n")}\n`, + ); +} diff --git a/packages/compiler/src/frontend/shape-inference/report.ts b/packages/compiler/src/frontend/shape-inference/report.ts new file mode 100644 index 000000000..c18d9eb56 --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/report.ts @@ -0,0 +1,374 @@ +/* Shape inference stage S1: the plan the analysis would hand the lowering, + * as a report (DESIGN.md §4.5, §5.5). Nothing here changes compilation. + * + * Per allocation site of package code, the verdict: + * P local — a native record would be exact: no reflection, no added + * keys, no computed writes, and its objects never escape; + * P escaping — exact as a record behind identity-keeping capsules: it + * escapes, but the census leaves every later operation + * answerable (fields untyped code may write become dyn); + * G — keeps today's dynamic object, with the rule that decided. + * Plus class field types, the census, and the hot functions' value types. */ +import * as ts from "../ts7/adapter.js"; +import { OBJECT_PROTOTYPE_MEMBERS, type Atom } from "./domain.js"; +import { locLabel, type FunctionInfo, type LiteralSite, type ShapeAnalysis } from "./analyze.js"; + +export interface SiteVerdict { + readonly site: LiteralSite; + readonly label: string; + readonly verdict: "P-local" | "P-escaping" | "G"; + readonly reasons: string[]; + /** P-escaping: fields untyped code may write (they become dyn slots). */ + readonly writableByTop: string[]; + readonly freezable: boolean; + readonly brand: boolean; +} + +export interface ShapeReport { + readonly sites: SiteVerdict[]; + readonly hot: Set; + readonly lines: string[]; +} + +const MAX_ATOMS_SHOWN = 6; + +function fieldKeys(fields: ReadonlyMap | undefined): string[] { + return fields === undefined ? [] : [...fields.keys()]; +} + +function fieldEntries(fields: ReadonlyMap | undefined): [string, number][] { + return fields === undefined ? [] : [...fields.entries()]; +} + +export function describeValue(analysis: ShapeAnalysis, node: number): string { + const atoms = [...analysis.nodes[node]!.atoms]; + if (atoms.length === 0) return "∅"; + const labels = [...new Set(atoms.map((a) => a.label))]; + labels.sort((a, b) => (a === "⊤" ? 1 : b === "⊤" ? -1 : a.localeCompare(b))); + const shown = labels.slice(0, MAX_ATOMS_SHOWN).join(" | "); + if (labels.length <= MAX_ATOMS_SHOWN) return shown; + return `${shown} | +${labels.length - MAX_ATOMS_SHOWN}${labels.includes("⊤") ? " | ⊤" : ""}`; +} + +function atomsOf(analysis: ShapeAnalysis, node: number): Set { + return analysis.nodes[node]!.atoms; +} + +/** Functions reached from call sites in loops (or loop callbacks), and + * everything they call: the static hotness heuristic (DESIGN.md §8.1). */ +export function hotFunctions(analysis: ShapeAnalysis): Set { + const hot = new Set(); + const callees = new Map(); + const work: FunctionInfo[] = []; + for (const c of analysis.calls) { + let l = callees.get(c.caller); + if (l === undefined) callees.set(c.caller, (l = [])); + l.push(c.callee); + if (c.inLoop && !hot.has(c.callee)) { + hot.add(c.callee); + work.push(c.callee); + } + } + while (work.length > 0) { + const f = work.pop()!; + for (const g of callees.get(f) ?? []) { + if (!hot.has(g)) { + hot.add(g); + work.push(g); + } + } + } + return hot; +} + +export function buildShapeReport(analysis: ShapeAnalysis, filter: string): ShapeReport { + const lines: string[] = []; + const all = filter === "all" || filter === "verbose"; + const verbose = filter === "verbose"; + const fileFilter = filter !== "1" && !all ? filter : null; + const wanted = (n: ts.Node, inPackage: boolean): boolean => { + if (fileFilter !== null) return n.getSourceFile().fileName.includes(fileFilter); + return all || inPackage; + }; + + // Census facts: operations whose receiver may be ⊤ reach every escaped atom. + + // Index ops by the site atoms they can reach directly. + const byAtom = new Map(); + const topOps: typeof analysis.ops = []; + for (const o of analysis.ops) { + const atoms = atomsOf(analysis, o.recv); + for (const a of atoms) { + if (a.kind !== "object" && a.kind !== "array") continue; + let l = byAtom.get(a); + if (l === undefined) byAtom.set(a, (l = [])); + l.push(o); + } + if (atoms.has(analysis.top)) topOps.push(o); + } + + // Key-order groups: sites with the same key set and different orders need brands. + const orders = new Map>(); + for (const s of analysis.sites) { + if (s.kind !== "object" || s.keys === null) continue; + const set = [...s.keys].sort().join(","); + let o = orders.get(set); + if (o === undefined) orders.set(set, (o = new Set())); + o.add(s.keys.join(",")); + } + + const verdicts: SiteVerdict[] = []; + for (const s of analysis.sites) { + if (!s.inPackage && !all && fileFilter === null) continue; + const reasons: string[] = []; + const escapedWhy = analysis.escaped.get(s.atom); + const escapes = escapedWhy !== undefined; + if (s.hasAccessor) reasons.push("accessor in literal"); + if (s.hasProto) reasons.push(s.kind === "array" ? "hole in literal" : "__proto__ in literal"); + if (s.hasComputedKey) reasons.push("computed key in literal"); + for (const sp of s.spreads) + if (atomsOf(analysis, sp).has(analysis.top)) reasons.push("spread of ⊤"); + const direct = byAtom.get(s.atom) ?? []; + const ops = escapes ? [...direct, ...topOps] : direct; + const keys = new Set(s.keys ?? []); + const fieldsOf = analysis.fields.get(s.atom); + const knownKeys = + s.keys !== null ? keys : new Set([...fieldKeys(fieldsOf)].filter((k) => k !== "*")); + let freezable = false; + const seen = new Set(); + const note = (r: string): void => { + if (!seen.has(r)) { + seen.add(r); + reasons.push(r); + } + }; + for (const o of ops) { + const viaTop = !atomsOf(analysis, o.recv).has(s.atom); + const where = `${locLabel(o.node)}${viaTop ? " via ⊤" : ""}`; + switch (o.kind) { + case "delete": + case "defineProperty": + case "setPrototypeOf": + case "seal": + case "proxy": + note(`${o.kind} at ${where}`); + break; + case "assign": + note(`Object.assign target at ${where}`); + break; + case "freeze": + freezable = true; + break; + case "computedWrite": + if (!viaTop) + note(s.kind === "array" ? `index write at ${where}` : `computed write at ${where}`); + break; + case "lengthWrite": + if (s.kind === "array") note(`length write at ${where}`); + break; + case "write": + if (s.kind === "object" && !viaTop && o.name !== null && !knownKeys.has(o.name)) + note(`adds key '${o.name}' at ${where}`); + break; + case "read": + if ( + s.kind === "object" && + !underPromiseGuard(o.node) && + escapes && + o.name !== null && + !viaTop && + !knownKeys.has(o.name) && + !OBJECT_PROTOTYPE_MEMBERS.has(o.name) + ) + note(`reads absent key '${o.name}' at ${where} (escapes)`); + break; + default: + break; + } + } + let brand = false; + if (s.kind === "object" && s.keys !== null) { + const o = orders.get([...s.keys].sort().join(",")); + if (o !== undefined && o.size > 1) brand = true; + } + const writableByTop: string[] = []; + if (escapes) { + if (analysis.topComputedWrites.length > 0) writableByTop.push("*"); + else for (const k of knownKeys) if (analysis.topWriteNames.has(k)) writableByTop.push(k); + } + const verdict: SiteVerdict["verdict"] = + reasons.length > 0 ? "G" : escapes ? "P-escaping" : "P-local"; + const label = s.atom.label; + if (escapes) reasons.push(`escapes: ${escapedWhy}`); + verdicts.push({ site: s, label, verdict, reasons, writableByTop, freezable, brand }); + } + + const hot = hotFunctions(analysis); + const hotNodes = new Set([...hot].map((f) => f.node)); + + // ---- print ---- + const pkgFns = [...analysis.functions.values()].filter((f) => f.inPackage); + lines.push( + `analyzed ${analysis.files.length} files (${analysis.packageFiles} package JS), ${analysis.functions.size} functions (${pkgFns.length} in packages), ${analysis.atoms.atoms.length} atoms, ${analysis.nodes.length} value nodes, ${analysis.steps} steps${analysis.budgetExceeded ? " — BUDGET EXCEEDED, results incomplete" : ""}`, + ); + const topNames = [...analysis.topWriteNames.entries()].map( + ([n, node]) => `${n} (${locLabel(node)})`, + ); + lines.push( + `census: writes on ⊤ receivers: ${topNames.length === 0 ? "none" : topNames.slice(0, 20).join(", ")}${topNames.length > 20 ? ` +${topNames.length - 20}` : ""}`, + ); + lines.push( + `census: computed writes on ⊤ receivers: ${analysis.topComputedWrites.length === 0 ? "none" : analysis.topComputedWrites.slice(0, 8).map(locLabel).join(", ")}`, + ); + const topReflect = topOps.filter( + (o) => + o.kind !== "read" && + o.kind !== "computedRead" && + o.kind !== "write" && + o.kind !== "computedWrite", + ); + lines.push( + `census: reflection on ⊤ receivers: ${topReflect.length === 0 ? "none" : topReflect.map((o) => `${o.kind} (${locLabel(o.node)})`).join(", ")}`, + ); + const escapedPkg = [...analysis.escaped.keys()].filter( + (a) => + a.node !== undefined && + wanted(a.node, a.node.getSourceFile().fileName.includes("/node_modules/")), + ); + lines.push( + `escaped atoms: ${analysis.escaped.size} (${escapedPkg.length} shown below where relevant)`, + ); + const firstEscapes = [...analysis.escaped.entries()] + .filter(([, why]) => !why.startsWith("via ")) + .slice(0, 12); + for (const [atom, why] of firstEscapes) lines.push(` escape root: ${atom.label}: ${why}`); + + const counts = { "P-local": 0, "P-escaping": 0, G: 0 }; + const hotCounts = { "P-local": 0, "P-escaping": 0, G: 0 }; + const inHot = (n: ts.Node): boolean => { + let p: ts.Node | undefined = n.parent; + while (p !== undefined) { + if (hotNodes.has(p)) return true; + p = p.parent; + } + return false; + }; + for (const v of verdicts) { + counts[v.verdict]++; + if (inHot(v.site.node)) hotCounts[v.verdict]++; + } + lines.push( + `sites: ${verdicts.length} (P local ${counts["P-local"]}, P escaping ${counts["P-escaping"]}, G ${counts.G}); in hot functions: P local ${hotCounts["P-local"]}, P escaping ${hotCounts["P-escaping"]}, G ${hotCounts.G}`, + ); + for (const v of verdicts) { + if (!wanted(v.site.node, v.site.inPackage)) continue; + if (!verbose && !inHot(v.site.node)) continue; + const extra: string[] = []; + if (v.brand) extra.push("needs brand (key order)"); + if (v.freezable) extra.push("freezable"); + if (v.writableByTop.length > 0) extra.push(`⊤-writable: ${v.writableByTop.join(",")}`); + const fields = analysis.fields.get(v.site.atom); + const ftypes = + v.site.kind === "array" + ? `elem: ${analysis.elements.has(v.site.atom) ? describeValue(analysis, analysis.elements.get(v.site.atom)!) : "∅"}` + : fieldEntries(fields) + .filter(([k]) => k !== "*") + .map(([k, id]) => `${k}: ${describeValue(analysis, id)}`) + .join("; "); + lines.push( + `site ${v.label}${inHot(v.site.node) ? " [hot]" : ""}: ${v.verdict}${v.reasons.length > 0 ? ` — ${v.reasons.join("; ")}` : ""}${extra.length > 0 ? ` [${extra.join("; ")}]` : ""}`, + ); + if (ftypes.length > 0) lines.push(` ${ftypes}`); + } + + // Class fields. + for (const atom of analysis.atoms.atoms) { + if (atom.kind !== "instance" || atom.node === undefined) continue; + const inPackage = atom.node.getSourceFile().fileName.includes("/node_modules/"); + if (!wanted(atom.node, inPackage)) continue; + const fm = analysis.fields.get(atom); + if (fm === undefined || fm.size === 0) continue; + const esc = analysis.escaped.get(atom); + const parts = [...fm.entries()].map(([k, id]) => { + const w = + esc !== undefined && + !k.startsWith("#") && + (analysis.topComputedWrites.length > 0 || analysis.topWriteNames.has(k)) + ? " (⊤-writable)" + : ""; + return `${k}: ${describeValue(analysis, id)}${w}`; + }); + lines.push( + `class ${atom.label}${esc !== undefined ? ` (escapes: ${esc})` : ""}: ${parts.join("; ")}`, + ); + } + + // Hot functions. + const hotPkg = [...hot].filter((info) => wanted(info.node, info.inPackage)); + lines.push(`hot functions: ${hotPkg.length} (clones per receiver class counted separately)`); + for (const info of hotPkg) { + const n = info.node; + const params = info.params.map((p, i) => `p${i}=${describeValue(analysis, p)}`).join(", "); + const flags = [ + info.usesArguments ? "uses arguments" : "", + analysis.escaped.size > 0 && [...analysis.escaped.keys()].some((a) => a.node === n) + ? "escapes" + : "", + ].filter((x) => x !== ""); + lines.push( + `fn ${info.label}${flags.length > 0 ? ` [${flags.join(", ")}]` : ""}: this=${describeValue(analysis, info.thisNode)}${params.length > 0 ? `, ${params}` : ""}, ret=${describeValue(analysis, info.ret)}`, + ); + } + const why = process.env["SCRIPTC_DEBUG_SHAPES_WHY"]; + if (why !== undefined) { + for (const info of analysis.functions.values()) { + if (!info.label.startsWith(why)) continue; + for (const [what, node] of [ + ["ret", info.ret], + ...info.params.map((p, i) => [`p${i}`, p] as const), + ] as const) { + if (!analysis.nodes[node]!.atoms.has(analysis.top)) continue; + lines.push(`why ⊤ in ${info.label} ${what}:`); + let cur: number | string | undefined = node; + for (let i = 0; i < 40 && typeof cur === "number"; i++) { + lines.push(` ${analysis.nodeLabel.get(cur) ?? `node ${cur}`}`); + cur = analysis.topCause.get(cur); + } + if (typeof cur === "string") lines.push(` seed: ${cur}`); + } + } + } + return { sites: verdicts, hot, lines }; +} + +/** A read inside the true arm of a promise test (`isAsync(x)`, + * `x instanceof Promise`): flow-sensitive narrowing removes the record + * arms there. Stage S1's locals are flow-insensitive, so the report + * recognizes the guard syntactically. */ +function underPromiseGuard(node: ts.Node): boolean { + let child: ts.Node = node; + let p = node.parent; + while (p !== undefined && !ts.isFunctionLike(p)) { + let cond: ts.Expression | undefined; + if (ts.isIfStatement(p) && p.thenStatement === child) cond = p.expression; + if (ts.isConditionalExpression(p) && p.whenTrue === child) cond = p.condition; + if (cond !== undefined && isPromiseTest(cond)) return true; + child = p; + p = p.parent; + } + return false; +} + +function isPromiseTest(e: ts.Expression): boolean { + if (ts.isParenthesizedExpression(e)) return isPromiseTest(e.expression); + if (ts.isBinaryExpression(e)) { + if (e.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) + return ts.isIdentifier(e.right) && e.right.text === "Promise"; + if (e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) + return isPromiseTest(e.left) || isPromiseTest(e.right); + return false; + } + if (ts.isCallExpression(e) && ts.isIdentifier(e.expression)) + return /^is(Async|Promise|Thenable)$/.test(e.expression.text); + return false; +} diff --git a/packages/compiler/test/shape-inference.test.ts b/packages/compiler/test/shape-inference.test.ts new file mode 100644 index 000000000..3cd6e809d --- /dev/null +++ b/packages/compiler/test/shape-inference.test.ts @@ -0,0 +1,166 @@ +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { compile } from "../src/index.js"; + +// A package shaped like a validation library: schema classes with a virtual +// `_parse`, per-field context and result literals, a class holding the +// context, and an output object built with computed keys. +const packageSource = ` +export class Status { + constructor() { this.value = "valid"; } + dirty() { if (this.value === "valid") this.value = "dirty"; } +} +class LazyPath { + constructor(parent, data, key) { this.parent = parent; this.data = data; this.key = key; } + get path() { return [this.key]; } +} +export class Base { + constructor(def) { this._def = def; } + ctxOf(input) { return { common: input.parent.common, data: input.data, parent: input.parent }; } + parse(data) { + const root = { common: { issues: [] }, data, parent: null }; + const result = this._parse({ data, parent: root }); + return result.value; + } +} +export class Str extends Base { + _parse(input) { + const status = new Status(); + if (typeof input.data !== "string") status.dirty(); + return { status: status.value, value: input.data }; + } +} +export class Obj extends Base { + _parse(input) { + const ctx = this.ctxOf(input); + const pairs = []; + for (const key of Object.keys(this._def.shape)) { + const field = this._def.shape[key]; + pairs.push({ key, value: field._parse(new LazyPath(ctx, ctx.data[key], key)) }); + } + const out = {}; + for (const pair of pairs) out[pair.key] = pair.value.value; + return { status: "valid", value: out }; + } +} +export const str = () => new Str({ checks: [] }); +export const obj = (shape) => new Obj({ shape }); +export function leak(value) { globalThis.leaked = value; return value; } +export function reflect() { const o = { a: 1 }; delete o.a; return o; } +`; + +const packageTypes = ` +export declare class Base { parse(data: unknown): unknown; } +export declare class Str extends Base {} +export declare class Obj extends Base {} +export declare const str: () => Str; +export declare const obj: (shape: Record) => Obj; +export declare function leak(value: T): T; +export declare function reflect(): object; +`; + +const mainSource = ` +import { leak, obj, reflect, str } from "shapes-fixture"; +const schema = obj({ a: str(), b: str() }); +const input: unknown = JSON.parse('{"a":"x","b":"y"}'); +console.log(JSON.stringify(schema.parse(input))); +console.log(JSON.stringify(leak({ kept: 1 })), JSON.stringify(reflect())); +`; + +let dir: string; +let saved: string | undefined; +beforeEach(async () => { + dir = await mkdtemp(join(tmpdir(), "scriptc-shapes-")); + const pkg = join(dir, "node_modules", "shapes-fixture"); + await mkdir(pkg, { recursive: true }); + await writeFile( + join(pkg, "package.json"), + JSON.stringify({ + name: "shapes-fixture", + version: "1.0.0", + type: "module", + main: "index.js", + types: "index.d.ts", + }), + ); + await writeFile(join(pkg, "index.js"), packageSource); + await writeFile(join(pkg, "index.d.ts"), packageTypes); + await writeFile( + join(dir, "package.json"), + JSON.stringify({ type: "module", dependencies: { "shapes-fixture": "1.0.0" } }), + ); + await writeFile(join(dir, "main.ts"), mainSource); + saved = process.env["SCRIPTC_DEBUG_SHAPES"]; +}); +afterEach(async () => { + if (saved === undefined) delete process.env["SCRIPTC_DEBUG_SHAPES"]; + else process.env["SCRIPTC_DEBUG_SHAPES"] = saved; + vi.restoreAllMocks(); + await rm(dir, { recursive: true, force: true }); +}); + +async function report(): Promise { + process.env["SCRIPTC_DEBUG_SHAPES"] = "verbose"; + const lines: string[] = []; + vi.spyOn(process.stderr, "write").mockImplementation((chunk: string | Uint8Array) => { + const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); + for (const l of text.split("\n")) if (l.startsWith("[shapes] ")) lines.push(l.slice(9)); + return true; + }); + const entry = join(dir, "main.ts"); + await compile(entry, { + outDir: dir, + outPath: join(dir, "main.ir.json"), + outputKind: "ir", + npmStatic: ["shapes-fixture"], + }); + return lines; +} + +function site(lines: string[], label: RegExp): string { + const line = lines.find((l) => l.startsWith("site ") && label.test(l)); + if (line === undefined) throw new Error(`no site ${label}:\n${lines.join("\n")}`); + return line; +} + +test("S1 report: per-field temporaries are provable records, dynamic objects stay G", async () => { + const lines = await report(); + // result literals of both _parse implementations, the pair literal, the + // context and the root context never escape: native records would be exact + // the leaf schema's result: in a function called from a loop (hot) + expect(site(lines, /^site \{status,value\}@shapes-fixture\/index\.js:23 \[hot\]/)).toMatch( + /: P-local/, + ); + expect(site(lines, /^site \{status,value\}@shapes-fixture\/index\.js:36/)).toMatch(/: P-local/); + expect(site(lines, /^site \{key,value\}@/)).toMatch(/: P-local/); + expect(site(lines, /^site \{common,data,parent\}@shapes-fixture\/index\.js:12/)).toMatch( + /: P-local/, + ); + expect(site(lines, /^site \[\]@shapes-fixture\/index\.js:29/)).toMatch(/: P-local/); + // the output object is built with computed keys + expect(site(lines, /^site \{\}@shapes-fixture\/index\.js:34/)).toMatch(/: G — computed write/); + // a deleted key and a value stored into the global object + expect(site(lines, /^site \{a\}@/)).toMatch(/: G — delete at/); + expect(site(lines, /^site \{kept\}@main\.ts/)).toMatch(/: P-escaping — escapes: stored into ⊤/); + // receiver-typed dispatch: the field's validators are the schema classes + expect(lines.find((l) => l.startsWith("fn Str._parse@"))).toMatch(/p0=LazyPath/); + expect(lines.find((l) => l.startsWith("class LazyPath"))).toMatch( + /parent: \{common,data,parent\}@shapes-fixture\/index\.js:12;/, + ); + expect(lines.find((l) => l.startsWith("class Str"))).toMatch(/_def: \{checks\}@/); +}); + +test("S1 report is off by default and changes nothing", async () => { + delete process.env["SCRIPTC_DEBUG_SHAPES"]; + const writes = vi.spyOn(process.stderr, "write"); + const entry = join(dir, "main.ts"); + await compile(entry, { + outDir: dir, + outPath: join(dir, "main.ir.json"), + outputKind: "ir", + npmStatic: ["shapes-fixture"], + }); + expect(writes.mock.calls.some(([c]) => String(c).includes("[shapes]"))).toBe(false); +}); From c244ddc4c53b5746b7443c6dadbd42eea1f616f8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:18:19 -0500 Subject: [PATCH 078/395] Answer 'send', 'disconnect' and 'channel' in a child process from its IPC channel A child process has these members only when it was created with an IPC channel (fork, or an "ipc" stdio entry), and keeps them after the channel closes and after the child exits. The 'in' test on native handles answered true for every declared member, so a child spawned without IPC reported them and entered IPC-only branches. The three names now answer from the runtime: scr_child_ipc_present reports whether the child was created with a channel. Corpus 8290 tests a forked and a spawned child before and after disconnect and exit. --- .../compiler/src/backend/llvm/lib-process.ts | 7 +++++ .../lowering/expressions/relational.ts | 8 ++++++ .../compiler/src/ir/builtin-signatures.ts | 1 + .../8290-child-channel-members/main.ts.json | 6 ++++ packages/runtime/src/scr_child.c | 3 ++ packages/runtime/src/scr_child.h | 1 + .../corpus/8290-child-channel-members/main.ts | 28 +++++++++++++++++++ .../8290-child-channel-members/worker.ts | 1 + 8 files changed, 55 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8290-child-channel-members/main.ts.json create mode 100644 tests/corpus/8290-child-channel-members/main.ts create mode 100644 tests/corpus/8290-child-channel-members/worker.ts diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 3e8a9bff5..c44febada 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -118,6 +118,13 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr B.line(`call void @scr_child_on_error(ptr ${child.name}, ptr ${cb.name}, ptr @${adapter})`); return { name: "", type: e.type }; } + if (e.fn === "child.hasChannel") { + const child = host.emitExpr(e.args[0]!); + host.declare(`declare zeroext i1 @scr_child_ipc_present(ptr)`); + const out = B.tmp(); + B.line(`${out} = call i1 @scr_child_ipc_present(ptr ${child.name})`); + return { name: out, type: e.type }; + } if (e.fn === "child.connected") { const child = host.emitExpr(e.args[0]!); host.declare(`declare zeroext i1 @scr_child_ipc_connected(ptr)`); diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index edec5ba5f..9b74e3f10 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1762,6 +1762,11 @@ export function lowerInExpression( }; } } + // A child process has `send`, `disconnect` and `channel` only when it was + // created with an IPC channel (fork, or an "ipc" stdio entry), and keeps + // them after the channel closes: the runtime answers from the child. + if (recv.type.kind === "child" && CHILD_CHANNEL_MEMBERS.has(key)) + return { kind: "libCall", fn: "child.hasChannel", args: [recv], type: BOOL, loc }; // A native Node handle (an http.Server, a socket, a child process...): // a member Node's declarations give the class is on its prototype, so // `"closeAllConnections" in server` answers true. Undeclared names can @@ -2094,6 +2099,9 @@ const NATIVE_HANDLE_KINDS: ReadonlySet = new Set([ "http2Stream", ]); +/** Child process members that exist only on a child with an IPC channel. */ +const CHILD_CHANNEL_MEMBERS: ReadonlySet = new Set(["send", "disconnect", "channel"]); + /** Whether every constituent of `t` declares a required member `key` in * a declaration file (Node's @types/node). */ function nodeDeclaredMember(lowerer: Lowerer, t: ts.Type, key: string): boolean { diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 51a0ec778..0449be07f 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2570,6 +2570,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ result: CHILD_T, }, "child.connected": { argTypes: [CHILD_T], result: BOOL }, + "child.hasChannel": { argTypes: [CHILD_T], result: BOOL }, "child.send": { argTypes: [CHILD_T, STRING], result: BOOL }, "child.sendCb": { argTypes: [CHILD_T, STRING, null], result: BOOL }, "child.disconnect": { argTypes: [CHILD_T], result: VOID }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8290-child-channel-members/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8290-child-channel-members/main.ts.json new file mode 100644 index 000000000..3519c4e6c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8290-child-channel-members/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8290-child-channel-members/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_child.c b/packages/runtime/src/scr_child.c index df7543500..234b3c115 100644 --- a/packages/runtime/src/scr_child.c +++ b/packages/runtime/src/scr_child.c @@ -6456,6 +6456,9 @@ double scr_process_fork_target(double target_count) { return target; } +/* The child was created with an IPC channel; it keeps it until it is freed. */ +bool scr_child_ipc_present(ScrChild *c) { return c->ipc != NULL; } + bool scr_child_ipc_connected(ScrChild *c) { return c->ipc != NULL && c->ipc->connected; } diff --git a/packages/runtime/src/scr_child.h b/packages/runtime/src/scr_child.h index a5853ad59..8b0d4d3f1 100644 --- a/packages/runtime/src/scr_child.h +++ b/packages/runtime/src/scr_child.h @@ -176,6 +176,7 @@ void scr_child_release_v(void *p); void scr_child_on_exit(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildExitFn fn); void scr_child_on_close(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildExitFn fn); void scr_child_on_error(ScrChild *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn); +bool scr_child_ipc_present(ScrChild *c); bool scr_child_ipc_connected(ScrChild *c); bool scr_child_ipc_send(ScrChild *c, ScrStr *json); bool scr_child_ipc_send_cb(ScrChild *c, ScrStr *json, diff --git a/tests/corpus/8290-child-channel-members/main.ts b/tests/corpus/8290-child-channel-members/main.ts new file mode 100644 index 000000000..78ed52de1 --- /dev/null +++ b/tests/corpus/8290-child-channel-members/main.ts @@ -0,0 +1,28 @@ +import { fork, spawn } from "node:child_process"; + +// `send`, `disconnect` and `channel` exist only on a child created with an +// IPC channel, and stay after the channel closes; the other members are +// on every child. +const describe = (label: string, child: ReturnType): string => + `${label}: send ${"send" in child} disconnect ${"disconnect" in child} channel ${"channel" in child} kill ${"kill" in child} pid ${"pid" in child}`; + +const lines: string[] = []; +const plain = spawn("/bin/sh", ["-c", "exit 0"], { stdio: "ignore" }); +lines.push(describe("spawn", plain)); +const forked = fork(new URL("./worker.ts", import.meta.url), [], { + stdio: ["ignore", "ignore", "inherit", "ipc"], +}); +lines.push(describe("fork", forked)); +forked.once("message", () => { + forked.disconnect(); + lines.push(describe("fork after disconnect", forked)); +}); +forked.once("exit", () => { + lines.push(describe("fork after exit", forked)); +}); +plain.once("exit", () => { + lines.push(describe("spawn after exit", plain)); +}); +process.once("exit", () => { + console.log(lines.sort().join("\n")); +}); diff --git a/tests/corpus/8290-child-channel-members/worker.ts b/tests/corpus/8290-child-channel-members/worker.ts new file mode 100644 index 000000000..96401ea1a --- /dev/null +++ b/tests/corpus/8290-child-channel-members/worker.ts @@ -0,0 +1 @@ +process.send?.({ value: 1 }); From 587443428cfb1e2600fd642195731cd4e68814ba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:22:26 -0500 Subject: [PATCH 079/395] Link Linux executables with lld when the driver finds it Linux executables linked through the clang driver with its default linker, GNU ld. lld links the same objects in about half the time. - Native and Node-hosted Linux exe links pass -fuse-ld=lld when ld.lld is beside the driver's executable (where clang looks first) or on PATH, and the default driver is in use (no SCRIPTC_LINKER, no sanitizer link). - A link with lld that fails runs again with the driver's default linker, whose error is the one reported when that fails too. --- packages/compiler/src/backend/linker.ts | 14 ++- packages/compiler/src/backend/lld.test.ts | 61 ++++++++++++ packages/compiler/src/backend/lld.ts | 49 +++++++++ packages/compiler/src/native/compiler.test.ts | 99 ++++++++++++++++++- packages/compiler/src/native/compiler.ts | 20 +++- 5 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 packages/compiler/src/backend/lld.test.ts create mode 100644 packages/compiler/src/backend/lld.ts diff --git a/packages/compiler/src/backend/linker.ts b/packages/compiler/src/backend/linker.ts index af53f74df..a54956ed9 100644 --- a/packages/compiler/src/backend/linker.ts +++ b/packages/compiler/src/backend/linker.ts @@ -21,6 +21,7 @@ import { import { type NativeArtifactDependency } from "./native/contracts.js"; import { RuntimePackError, stageRuntimePackArtifacts } from "./runtime-pack.js"; import type { NativeLinkPlan } from "./link-plan.js"; +import { lldLinkerArgs } from "./lld.js"; import { createDarwinDebugSymbols, installDarwinDebugSymbols, @@ -240,8 +241,11 @@ export async function linkNativeExecutable( try { const staged = await stageRuntimePackArtifacts(plan.runtimePack); stagedRoot = staged.root; + // Linux links with lld when the default driver finds it. + const lld = explicitLinker === undefined ? lldLinkerArgs(plan.target.platform, linker) : []; const args = [ ...(explicitLinker === undefined ? plan.target.defaultLinkerArgs : []), + ...lld, ...plan.driverFlags, ...plan.inputs.map((input) => staged.replacements.get(input) ?? input), ...plan.systemLibraries.map((name) => `-l${name}`), @@ -276,7 +280,15 @@ export async function linkNativeExecutable( ...(await snapshotDependencies([...toolchain, ...additionalDependencyPaths])), ]) .catch(() => null); - await execFileAsync(linker, args); + await execFileAsync(linker, args).catch(async (error: unknown) => { + // A failed link with lld runs again with the driver's default linker, + // whose error (if any) is the one reported. + if (lld.length === 0) throw error; + await execFileAsync( + linker, + args.filter((arg) => !lld.includes(arg)), + ); + }); const output = await stat(privateOut); if (!output.isFile() || output.size === 0) throw new Error("linker produced no executable"); if (plan.darwinDebugSymbols) { diff --git a/packages/compiler/src/backend/lld.test.ts b/packages/compiler/src/backend/lld.test.ts new file mode 100644 index 000000000..b0a466cf6 --- /dev/null +++ b/packages/compiler/src/backend/lld.test.ts @@ -0,0 +1,61 @@ +import { chmodSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { driverFindsLld, lldLinkerArgs } from "./lld.js"; + +const directories: string[] = []; +afterEach(() => { + for (const path of directories.splice(0)) rmSync(path, { recursive: true, force: true }); +}); + +function tool(path: string): string { + writeFileSync(path, "#!/bin/sh\n"); + chmodSync(path, 0o755); + return path; +} + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "scriptc-lld-")); + directories.push(root); + const llvm = join(root, "llvm", "bin"); + const usr = join(root, "usr", "bin"); + const other = join(root, "other"); + for (const directory of [llvm, usr, other]) mkdirSync(directory, { recursive: true }); + return { root, llvm, usr, other }; +} + +test.skipIf(process.platform === "win32")( + "a driver finds ld.lld beside itself, beside its real executable, or on PATH", + () => { + const f = fixture(); + const clang = tool(join(f.llvm, "clang")); + // Without ld.lld anywhere. + expect(driverFindsLld(clang, { PATH: f.other })).toBe(false); + // Beside the driver. + tool(join(f.llvm, "ld.lld")); + expect(driverFindsLld(clang, { PATH: f.other })).toBe(true); + // A PATH entry that links to the driver: clang looks beside its real executable. + symlinkSync(clang, join(f.usr, "clang")); + expect(driverFindsLld("clang", { PATH: f.usr })).toBe(true); + // A driver elsewhere finds ld.lld on PATH, and not otherwise. + const gcc = tool(join(f.other, "gcc")); + expect(driverFindsLld(gcc, { PATH: f.other })).toBe(false); + expect(driverFindsLld(gcc, { PATH: [f.other, f.llvm].join(":") })).toBe(true); + expect(driverFindsLld("missing-driver", { PATH: f.llvm })).toBe(false); + }, +); + +test.skipIf(process.platform === "win32")( + "Linux links with the default driver use lld when the driver finds it", + () => { + const f = fixture(); + const clang = tool(join(f.llvm, "clang")); + tool(join(f.llvm, "ld.lld")); + const env = { PATH: f.other }; + expect(lldLinkerArgs("linux", clang, env)).toEqual(["-fuse-ld=lld"]); + expect(lldLinkerArgs("darwin", clang, env)).toEqual([]); + expect(lldLinkerArgs("windows", clang, env)).toEqual([]); + expect(lldLinkerArgs("linux", clang, { ...env, SCRIPTC_LINKER: clang })).toEqual([]); + }, +); diff --git a/packages/compiler/src/backend/lld.ts b/packages/compiler/src/backend/lld.ts new file mode 100644 index 000000000..790e07062 --- /dev/null +++ b/packages/compiler/src/backend/lld.ts @@ -0,0 +1,49 @@ +import { realpathSync, statSync } from "node:fs"; +import { delimiter, dirname, isAbsolute, join, resolve } from "node:path"; + +function isFile(path: string): boolean { + try { + return statSync(path).isFile(); + } catch { + return false; + } +} + +/** The executable `command` names: a path, or the first match on PATH. */ +function commandFile(command: string, env: NodeJS.ProcessEnv): string | null { + if (command.includes("/")) return isFile(command) ? resolve(command) : null; + for (const directory of (env["PATH"] ?? "/usr/bin:/bin").split(delimiter)) { + const candidate = join(directory || ".", command); + if (isFile(candidate)) return isAbsolute(candidate) ? candidate : resolve(candidate); + } + return null; +} + +/** + * Whether the compiler driver `driver` finds ld.lld for `-fuse-ld=lld`: in + * the directory of the driver's executable (where clang looks first) or on + * PATH. Linking still falls back to the driver's default linker when a link + * with lld fails. + */ +export function driverFindsLld(driver: string, env: NodeJS.ProcessEnv = process.env): boolean { + const file = commandFile(driver, env); + if (file === null) return false; + try { + for (const directory of new Set([dirname(file), dirname(realpathSync(file))])) + if (isFile(join(directory, "ld.lld"))) return true; + } catch { + return false; + } + return commandFile("ld.lld", env) !== null; +} + +/** `-fuse-ld=lld` for Linux links whose driver finds ld.lld. */ +export function lldLinkerArgs( + platform: string, + driver: string, + env: NodeJS.ProcessEnv = process.env, +): string[] { + return platform === "linux" && env["SCRIPTC_LINKER"] === undefined && driverFindsLld(driver, env) + ? ["-fuse-ld=lld"] + : []; +} diff --git a/packages/compiler/src/native/compiler.test.ts b/packages/compiler/src/native/compiler.test.ts index 7a1cee62a..a085356a0 100644 --- a/packages/compiler/src/native/compiler.test.ts +++ b/packages/compiler/src/native/compiler.test.ts @@ -1,5 +1,13 @@ import { execFileSync, spawnSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { afterEach, expect, test, vi } from "vitest"; @@ -7,7 +15,7 @@ import { NativeCompiler } from "./compiler.js"; import type { NativeToolchain } from "./toolchain.js"; import { runNativeFrontend } from "../frontend/pipeline-native.js"; import { emitNativeObject } from "../backend/native-tools.js"; -import { MACOS_ARM64_TARGET } from "../backend/targets.js"; +import { LINUX_X64_GNU_TARGET, MACOS_ARM64_TARGET } from "../backend/targets.js"; import { VOID, type IrModule } from "../ir/ir.js"; import { contentDigest, NativeCache } from "./cache.js"; import { NativeExecutableCache, openNativeExecutableCache } from "./executable-cache.js"; @@ -283,3 +291,90 @@ test.each([false, true])( expect(readdirSync(directory).sort()).toEqual(["output.o", "output.o.ll"]); }, ); + +test.skipIf(process.platform === "win32").each([false, true])( + "Linux links use lld when the driver finds it, and the default linker when lld fails (%s)", + (lldFails) => { + const directory = mkdtempSync(join("/tmp", "scriptc-native-lld-")); + directories.push(directory); + const bin = join(directory, "bin"); + mkdirSync(bin); + const clang = join(bin, "clang"); + writeFileSync(clang, "#!/bin/sh\n"); + chmodSync(clang, 0o755); + writeFileSync(join(bin, "ld.lld"), "#!/bin/sh\n"); + chmodSync(join(bin, "ld.lld"), 0o755); + const entry = join(directory, "entry.ts"); + const output = join(directory, "program"); + 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 }, + }), + }); + vi.mocked(emitNativeObject).mockImplementation((options) => { + writeFileSync(options.outputPath, "object"); + return [options.outputPath]; + }); + vi.mocked(openNativeExecutableCache).mockReturnValue(null); + const links: string[][] = []; + vi.mocked(execFileSync).mockImplementation((_command, args) => { + const list = args as string[]; + links.push(list); + if (lldFails && list.includes("-fuse-ld=lld")) throw new Error("lld failed"); + 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: "release", + strip: true, + }); + expect(result).toMatchObject({ ok: true, binaryPath: output }); + expect(links[0]).toContain("-fuse-ld=lld"); + expect(links.length).toBe(lldFails ? 2 : 1); + if (lldFails) expect(links[1]).not.toContain("-fuse-ld=lld"); + }, +); diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index a4582ec49..0188dccf7 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -65,6 +65,7 @@ import { import { splitLlvmLibraryProgram } from "../backend/llvm/split.js"; import { prepareNativeExecutable, prepareNativeLibrary } from "./prepare.js"; import { buildSanitizedRuntime, sanitizerDriver, sanitizerFlags } from "./sanitizer.js"; +import { lldLinkerArgs } from "../backend/lld.js"; import { openNativeExecutableCache } from "./executable-cache.js"; import { nativeFileIdentity } from "./file-identity.js"; @@ -494,8 +495,11 @@ export class NativeCompiler { : { windowsSubsystem: options.windowsSubsystem }), ...(profile?.generate === undefined ? {} : { profileGenerate: true }), }); + // Linux links with lld when the driver finds it. + const lld = options.sanitize ? [] : lldLinkerArgs(target.platform, toolchain.linker); const linkArgs = [ ...(options.sanitize ? ["-fsanitize=address"] : toolchain.linkerArgs), + ...lld, ...plan.driverFlags, ...plan.inputs, ...plan.systemLibraries.map((name) => `-l${name}`), @@ -506,8 +510,20 @@ export class NativeCompiler { timing("link-inputs"); // A successful dependency trace already produced this executable. // Keep its verified output instead of asking the linker to repeat it. - if (!cacheable) - runNativeTool(options.sanitize ? sanitizerDriver(toolchain) : toolchain.linker, linkArgs); + if (!cacheable) { + const driver = options.sanitize ? sanitizerDriver(toolchain) : toolchain.linker; + try { + runNativeTool(driver, linkArgs); + } catch (error) { + // A failed link with lld runs again with the driver's default + // linker, whose error (if any) is the one reported. + if (lld.length === 0) throw error; + runNativeTool( + driver, + linkArgs.filter((arg) => !lld.includes(arg)), + ); + } + } timing("native-link"); requireNativeArtifact(stagedOutput); if (target.platform === "darwin" && optimization === "dev" && !options.strip) { From a8c0fa772b9c14005f55e378df588059bed0cb0a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:28:53 -0500 Subject: [PATCH 080/395] Use fixed timestamps in the fs.cp preserveTimestamps test --- tests/corpus/5570-fs-cp-async.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/corpus/5570-fs-cp-async.ts b/tests/corpus/5570-fs-cp-async.ts index d83750095..23747bcb3 100644 --- a/tests/corpus/5570-fs-cp-async.ts +++ b/tests/corpus/5570-fs-cp-async.ts @@ -88,8 +88,13 @@ const stamp = new Date("2021-05-06T07:08:09.123Z"); fs.utimesSync("file.txt", stamp, stamp); await fsp.cp("file.txt", "p-stamped.txt", { preserveTimestamps: true }); console.log(new Date(fs.statSync("p-stamped.txt").mtimeMs).toISOString()); +// The source tree gets fixed times: its own times come from the clock, and +// whether a copy equals them depends on their sub-millisecond digits. +fs.utimesSync("src/a.txt", stamp, stamp); +fs.utimesSync("src", stamp, stamp); await fsp.cp("src", "p-stamped-dir", { recursive: true, preserveTimestamps: true }); -console.log(fs.statSync("p-stamped-dir").mtimeMs === fs.statSync("src").mtimeMs); +for (const path of ["p-stamped-dir", "p-stamped-dir/a.txt"]) + console.log(path, new Date(fs.statSync(path).mtimeMs).toISOString()); const rejected = await fsp.cp("missing", "x").then( () => "fulfilled", From 908d87d3e0bdcb3ba91e3d6ad21722bd9a819392 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:32:14 -0500 Subject: [PATCH 081/395] Key widened returns by their type-parameter bindings and skip self-calls Two review findings on the widened-return cache: - A nested function lowered under different type-parameter bindings reused the record layout of the first instance it was widened in. In `function outer(x: T) { const inner = () => ...; return inner(); }`, the closures of outer and outer returned the records of outer, and returning one threw a TypeError. A kept result now records the bindings it was mapped under (typeParamBindings, its checker-type and implicit-any twins, and whether an erased body is lowering) and answers only under the same ones. - A function that calls itself on one return cut its own widening, so its result was never kept and a chain of such functions forwarding to each other on two branches was scanned 2^depth times. A call of the function itself returns its own type, whose arms add nothing, so the scan now skips it. --- .../src/frontend/lowering/coercions/state.ts | 18 +++++- .../src/frontend/lowering/function-returns.ts | 25 ++++++++- .../compiler/test/widened-returns.test.ts | 55 ++++++++++++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index 27667197c..cbc577634 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -1,5 +1,6 @@ import type { IrType } from "../../../ir/ir.js"; import type * as ts from "../../ts7/adapter.js"; +import type { Lowerer } from "../lowerer.js"; /** Per-compilation conversion state. Recursive plans are scoped to the * active planning stack; only fully validated copy routes are memoized. */ @@ -24,6 +25,19 @@ export class CoercionState { * width-converting extraction (widthNarrowHelper). */ readonly widenedReturns = new Set(); /** widenReducedReturn's results by declaration and inferred return type, - * for the results computed without cutting a recursive widening. */ - readonly widenedReturnTypes = new Map>(); + * for the results computed without cutting a widening cycle through + * another function. Each keeps the type-parameter bindings it was mapped + * under and answers only under the same bindings. */ + readonly widenedReturnTypes = new Map>(); +} + +/** A widened inferred return type and the lowering state that mapped it + * (Lowerer.typeParamBindings and its checker-type and implicit-any twins, + * and whether an erased generic body is lowering). */ +export interface WidenedReturn { + type: IrType; + bindings: Lowerer["typeParamBindings"]; + tsBindings: Lowerer["typeParamTsBindings"]; + implicit: Lowerer["implicitParamTypes"]; + erasing: boolean; } diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index 0b87710e0..4d586fcc3 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -584,15 +584,30 @@ function widenReducedReturn( return mapped; } // Functions that forward to the same callees along several return - // branches read each callee's widened type once. + // branches read each callee's widened type once. A nested function + // lowered under other type-parameter bindings maps its returns again. const known = lowerer.coercions.widenedReturnTypes.get(decl)?.get(retTsType); - if (known !== undefined) return known; + const erasing = lowerer.erasingDepth > 0; + if ( + known !== undefined && + known.bindings === lowerer.typeParamBindings && + known.tsBindings === lowerer.typeParamTsBindings && + known.implicit === lowerer.implicitParamTypes && + known.erasing === erasing + ) + return known.type; const cutsBefore = wideningCuts; const widened = widenReturnArms(lowerer, decl, retTsType, mapped, arms, recordArms); if (wideningCuts === cutsBefore) { let byType = lowerer.coercions.widenedReturnTypes.get(decl); if (byType === undefined) lowerer.coercions.widenedReturnTypes.set(decl, (byType = new Map())); - byType.set(retTsType, widened); + byType.set(retTsType, { + type: widened, + bindings: lowerer.typeParamBindings, + tsBindings: lowerer.typeParamTsBindings, + implicit: lowerer.implicitParamTypes, + erasing, + }); } return widened; } @@ -638,6 +653,10 @@ function widenReturnArms( (callee as { body?: ts.Node }).body !== undefined && callee.type === undefined ) { + // A call of the function itself returns its own type, whose arms + // add nothing. Reading it would cut the widening and keep this + // result from being reused. + if (callee === decl) return; let t: IrType | null = null; widening.add(decl); try { diff --git a/packages/compiler/test/widened-returns.test.ts b/packages/compiler/test/widened-returns.test.ts index 63d24c1da..fe78c6b75 100644 --- a/packages/compiler/test/widened-returns.test.ts +++ b/packages/compiler/test/widened-returns.test.ts @@ -22,7 +22,7 @@ async function lowerModule(source: string): Promise { /** Functions that each forward to the previous one along two return * branches, above one whose inferred type the checker reduced. */ -function forwardingChain(depth: number): string { +function forwardingChain(depth: number, recursive = false): string { const lines = [ "export {};", "type Short = { status: string };", @@ -35,6 +35,7 @@ function forwardingChain(depth: number): string { for (let i = 1; i <= depth; i++) lines.push( `function f${i}(a: Short, b: Long, n: number) {`, + ...(recursive ? [` if (n > 20) return f${i}(a, b, n - 1);`] : []), ` if (n > 1) return f${i - 1}(a, b, n - 1);`, ` if (n > 0) return f${i - 1}(a, b, n + 1);`, " return a;", @@ -53,3 +54,55 @@ test("return widening reads each forwarded callee's type once", { timeout: 60_00 expect(top).toBeDefined(); expect(top!.returnType.kind).toBe("union"); }); + +test( + "return widening reads each forwarded callee's type once when the callees call themselves", + { timeout: 60_000 }, + async () => { + const mod = await lowerModule(forwardingChain(40, true)); + const top = mod.functions.find((f) => f.name === "f40"); + expect(top).toBeDefined(); + expect(top!.returnType.kind).toBe("union"); + }, +); + +test("a nested function's widened return follows each instance's type arguments", async () => { + const mod = await lowerModule( + [ + "export {};", + "function outer(x: T, n: number) {", + " const inner = () => {", + " if (n > 0) return { status: x, value: n };", + " return { status: x };", + " };", + " return inner();", + "}", + "console.log(JSON.stringify(outer(1, 1)));", + 'console.log(JSON.stringify(outer("s", 1)));', + "console.log(JSON.stringify(outer(true, 0)));", + ].join("\n"), + ); + const shapes = new Map((mod.records ?? []).map((r) => [r.id, r])); + const unions = new Map((mod.unions ?? []).map((u) => [u.id, u])); + // Each closure returns records whose `status` has the type its captured + // `x` has. + const closures = mod.functions.filter( + (f) => f.name.startsWith("%fn.") && !f.name.includes("adapt"), + ); + expect(closures.length).toBe(3); + for (const closure of closures) { + const captured = closure.captures?.find((c) => c.type.kind !== "f64")?.type.kind ?? "f64"; + const ret = closure.returnType; + expect(ret.kind).toBe("union"); + const arms = ret.kind === "union" ? (unions.get(ret.unionId)?.arms ?? []) : []; + expect(arms.length).toBe(2); + for (const arm of arms) { + expect(arm.kind).toBe("record"); + const status = + arm.kind === "record" + ? shapes.get(arm.shapeId)?.fields.find((f) => f.name === "status") + : undefined; + expect(status?.type.kind).toBe(captured); + } + } +}); From 3f03c90e864352a95c31857baf7143fd87829827 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:47:17 -0500 Subject: [PATCH 082/395] Keep versioned loops' slow copies free of speculative integer copies The copy of a dense-array, byte-window, counted, callback or typed-array versioned loop that runs when its guard fails no longer gives its loops speculative integer or int32 kind copies, which emitted each loop again for a path that rarely runs. Proven integer loops, which emit one copy, are kept in it. --- .../src/backend/llvm/emit-function.ts | 1 + .../src/backend/llvm/emit-statements.ts | 26 +++- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../test/speculative-loop-emission.test.ts | 44 +++++- .../7902-versioned-loop-slow-copies.ts.json | 6 + .../corpus/7902-versioned-loop-slow-copies.ts | 139 ++++++++++++++++++ 6 files changed, 211 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7902-versioned-loop-slow-copies.ts.json create mode 100644 tests/corpus/7902-versioned-loop-slow-copies.ts diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index e015c2d72..327d2019b 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -136,6 +136,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { emitter.integerTempsEnabled = emitter.debug === null && !fn.async && !fn.generator; beginFunctionLoops(emitter, fn, numericFn); emitter.speculativeLoopsEnabled = LOOPS.of(emitter).countedLoopsEnabled; + emitter.loopCopiesEnabled = true; emitter.speculativeFunction = fn.name; emitter.speculation = null; emitter.speculationResume = null; diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index d356bfa3d..794ba1798 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -994,7 +994,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { LOOPS.of(emitter).denseLoopsEnabled = false; try { B.startBlock(slow); - emitter.emitStmt(s); + emitSlowCopy(emitter, s); B.br(done); B.startBlock(fast); LOOPS.of(emitter).denseLoop = dense; @@ -1018,7 +1018,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { LOOPS.of(emitter).byteWindowsEnabled = false; try { B.startBlock(slow); - emitter.emitStmt(s); + emitSlowCopy(emitter, s); B.br(done); B.startBlock(fast); emitter.integerRanges = window.ranges; @@ -1072,7 +1072,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.condBr(safe, fast, slow); B.startBlock(slow); LOOPS.of(emitter).countedLoopsEnabled = false; - emitter.emitStmt(s); + emitSlowCopy(emitter, s); LOOPS.of(emitter).countedLoopsEnabled = true; B.br(countedJoin); B.startBlock(fast); @@ -2049,7 +2049,7 @@ function emitCallbackVersionedLoop( emitter.callbackVersioning.add(s); try { B.startBlock(slow); - emitter.emitStmt(s); + emitSlowCopy(emitter, s); B.br(done); B.startBlock(fast); for (const { ref } of bindings) emitter.knownCallbacks.add(ref.localId); @@ -2062,6 +2062,20 @@ function emitCallbackVersionedLoop( B.startBlock(done); } +/** The copy of a versioned loop that runs when its guard fails. Its loops + * get no speculative or int32 kind copies, which would emit each loop again + * for a path that rarely runs. Proven integer loops, which emit one copy, + * are kept. */ +function emitSlowCopy(emitter: LlEmitter, s: IrStmt): void { + const copies = emitter.loopCopiesEnabled; + emitter.loopCopiesEnabled = false; + try { + emitter.emitStmt(s); + } finally { + emitter.loopCopiesEnabled = copies; + } +} + /** Entries in a row whose fast copy bailed before a speculative loop site * stops trying it. */ const SPECULATION_BAILS = 8; @@ -2089,7 +2103,7 @@ function emitVersionedLoop( LOOPS.of(emitter).bytesLoopVersioning.add(s); try { B.startBlock(slow); - emitter.emitStmt(s); + emitSlowCopy(emitter, s); B.br(done); B.startBlock(fast); emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); @@ -2120,6 +2134,7 @@ export function emitLoopCopies( const plan = allowed && emitter.speculativeLoopsEnabled && + emitter.loopCopiesEnabled && emitter.speculation === null && emitter.speculationResume === null ? planSpeculativeLoop( @@ -2147,6 +2162,7 @@ export function emitLoopCopies( const kinds = allowed && emitter.speculativeLoopsEnabled && + emitter.loopCopiesEnabled && emitter.int32Slots.checkedInt32Arrays.size > 0 && emitter.int32Kinds === null && emitter.speculation === null && diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6729da197..e6ec6e702 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1461,6 +1461,9 @@ export class LlEmitter { integerTempsEnabled = false; /** Speculative integer loops (speculative-integers.ts) run in this function. */ speculativeLoopsEnabled = false; + /** False inside the copy of a versioned loop that runs when its guard + * fails: loops there get no speculative or int32 kind copies. */ + loopCopiesEnabled = true; /** The fast copy of a speculative integer loop being emitted: its * integer-computed writes and where each resumes in the ordinary copy. */ speculation: { diff --git a/packages/compiler/test/speculative-loop-emission.test.ts b/packages/compiler/test/speculative-loop-emission.test.ts index a81348102..3f6aef158 100644 --- a/packages/compiler/test/speculative-loop-emission.test.ts +++ b/packages/compiler/test/speculative-loop-emission.test.ts @@ -47,9 +47,8 @@ test("a failed check finishes its iteration outside the ordinary copy", async () const llvm = await emitLlvm(source); const fn = /^define internal double @sc_bf_scaled\([^]*?^}/m.exec(llvm)?.[0] ?? ""; const order = labels(fn); - // The loop may have several speculative sites (one in each copy of a - // dense-array loop). Each site emits its ordinary copy, its third copy - // and its fast copy in that order. + // Each speculative site emits its ordinary copy, its third copy and its + // fast copy in that order. const slows = order.flatMap((l, i) => (l.startsWith("spec.slow") ? [i] : [])); expect(slows.length).toBeGreaterThan(0); let resumes = 0; @@ -91,3 +90,42 @@ test("a loop site stops entering its fast copy after repeated bails", async () = expect(fn).toMatch(new RegExp(`call i8 @llvm\\.uadd\\.sat\\.i8\\(i8 %\\S+, i8 1\\)`)); expect(fn).toContain(`store atomic i8 0, ptr ${site} unordered, align 1`); }); + +test("the slow copy of a dense-array loop has no speculative copy", async () => { + const llvm = await emitLlvm(source); + const fn = /^define internal double @sc_bf_scaled\([^]*?^}/m.exec(llvm)?.[0] ?? ""; + const order = labels(fn); + const slow = order.findIndex((l) => l.startsWith("dense.loop.slow")); + const fast = order.findIndex((l) => l.startsWith("dense.loop.fast")); + expect(slow).toBeGreaterThanOrEqual(0); + expect(fast).toBeGreaterThan(slow); + // The slow copy runs when the guard fails; only the fast copy speculates. + expect(order.slice(slow, fast).filter((l) => l.startsWith("spec."))).toEqual([]); + expect(order.slice(fast).some((l) => l.startsWith("spec.fast"))).toBe(true); + expect(llvm.match(/^@sc_spec_bails_\d+ = /gm)).toHaveLength(1); +}); + +test("the slow copy of a typed-array version keeps its proven integer loop", async () => { + // The program can start a worker, so typed-array loops are versioned on + // thread ownership. + const llvm = await emitLlvm(` +import { Worker, isMainThread } from "node:worker_threads"; +function count(bytes: Uint8Array): number { + let lines = 0; + for (let i = 0; i < bytes.length; i++) if (bytes[i] === 10) lines++; + return lines; +} +if (isMainThread) { + if (process.argv[2] === "spawn") new Worker(new URL(import.meta.url)); + console.log(count(new Uint8Array([10, 1, 10]))); +} +`); + const fn = /^define internal double @sc_bf_\w*count\([^]*?^}/m.exec(llvm)?.[0] ?? ""; + const order = labels(fn); + const slow = order.findIndex((l) => l.startsWith("bytes.version.slow")); + const fast = order.findIndex((l) => l.startsWith("bytes.version.fast")); + expect(slow).toBeGreaterThanOrEqual(0); + expect(fast).toBeGreaterThan(slow); + expect(order.slice(slow, fast).some((l) => l.startsWith("integer.leave"))).toBe(true); + expect(order.slice(fast).some((l) => l.startsWith("integer.leave"))).toBe(true); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7902-versioned-loop-slow-copies.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7902-versioned-loop-slow-copies.ts.json new file mode 100644 index 000000000..0147937af --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7902-versioned-loop-slow-copies.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7902-versioned-loop-slow-copies.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7902-versioned-loop-slow-copies.ts b/tests/corpus/7902-versioned-loop-slow-copies.ts new file mode 100644 index 000000000..91b769f47 --- /dev/null +++ b/tests/corpus/7902-versioned-loop-slow-copies.ts @@ -0,0 +1,139 @@ +// Loops whose versioning guard can fail, so they run the ordinary copy: +// arrays whose holes were filled later, counted loops that read past the +// end, arrays that change length inside the loop, typed-array windows past +// the end, fractional and NaN bounds, and callbacks that are replaced. The +// loops keep integer counters and sums that would also suit integer +// speculation, with values past 2^31 and 2^53, negative zero and fractions. + +function sum(values: number[]): string { + let total = 0; + let count = 0; + let seed = 12345; + let grow = 1; + for (let i = 0; i < values.length; i++) { + total += values[i]!; + count++; + seed = (seed * 1103515245 + 12345) % 2147483648; + grow = grow * 7 + i; + } + return `${total} ${count} ${seed} ${grow}`; +} + +function product(values: number[], limit: number): string { + let p = 1; + let steps = 0; + for (let i = 0; i < limit; i++) { + p *= values[i] ?? 1; + steps += 3; + } + return `${p} ${steps}`; +} + +function shrinking(values: number[]): string { + let total = 0; + for (let i = 0; i < values.length; i++) { + total += values[i]! * i; + if (values[i]! % 3 === 0) values.pop(); + } + return `${total} ${values.length}`; +} + +function windowSum(a: Int32Array, from: number, to: number): string { + let total = 0; + let mix = 1; + for (let i = from; i < to; i++) { + total += a[i] ?? -1; + mix = (mix * 31 + 17) % 65521; + } + return `${total} ${mix}`; +} + +function bytes(a: Uint8Array, start: number, n: number): string { + let hash = 0; + let steps = 0; + for (let i = start; i < start + n; i++) { + hash = (hash * 31 + (a[i] ?? 7)) | 0; + steps++; + } + return `${hash} ${steps}`; +} + +function counted(values: Float64Array, n: number, step: number): string { + let total = 0; + let odd = 0; + for (let i = 0; i < n; i += step) { + values[i] = i * 2; + total += i; + odd = odd * 2 + 1; + } + return `${total} ${odd} ${values.join()}`; +} + +function mapped(values: number[], f: (x: number) => number): string { + let total = 0; + let k = 0; + for (let i = 0; i < values.length; i++) { + total += f(values[i]!); + k = k * 10 + 1; + } + return `${total} ${k}`; +} + +function lastNegativeZero(values: number[]): string { + let last = 0; + let zero = 0; + for (let i = 0; i < values.length; i++) { + last = values[i]! * -1; + zero = zero * -1; + } + return `${Object.is(last, -0) ? "-0" : String(last)} ${Object.is(zero, -0) ? "-0" : zero}`; +} + +/** An array written out of order: index 2 first, then the hole at 1. */ +function filledLate(): number[] { + const values: number[] = [1]; + values[2] = 3; + values[1] = 2; + return values; +} + +console.log(sum([1, 2, 3, 4, 5])); +console.log(sum(filledLate())); +console.log(sum([1, 2.5, 3])); +console.log(sum([2147483647, 2147483647, 2147483647])); +console.log(sum([9007199254740991, 2, 2])); +console.log(sum(Array.from({ length: 40 }, (_, i) => (i % 2 === 0 ? i : -i)))); + +console.log(product([1, 2, 3, 4], 4)); +console.log(product([1, 2, 3, 4], 6)); +console.log(product([65536, 65536, 65536], 3)); +console.log(product([0, -1], 2), product([-1, 0], 2)); + +console.log(shrinking([1, 2, 3, 4, 5, 6, 7, 8, 9])); +console.log(shrinking([3, 3, 3, 3])); + +const ints = new Int32Array([10, 20, 30, 40]); +console.log(windowSum(ints, 0, 4), windowSum(ints, 2, 7), windowSum(ints, -2, 2)); +console.log(windowSum(ints, 0.5, 3.5), windowSum(ints, 0, NaN)); + +const text = new Uint8Array([104, 101, 108, 108, 111]); +console.log(bytes(text, 0, 5), bytes(text, 3, 5), bytes(text, -1, 3)); + +console.log(counted(new Float64Array(12), 10, 1), counted(new Float64Array(12), 10.5, 1)); +console.log(counted(new Float64Array(12), 10, 2.5), counted(new Float64Array(4), NaN, 1)); +console.log(counted(new Float64Array(4), 8, 1), counted(new Float64Array(4), 3, 0.75)); +console.log(counted(new Float64Array(60), 60, 1).split(" ").slice(0, 2).join(" ")); + +let calls = 0; +const double = (x: number): number => { + calls++; + return x * 2; +}; +const half = (x: number): number => { + calls++; + return x / 2; +}; +console.log(mapped([1, 2, 3], double), mapped([1, 2, 3], half), calls); +console.log(mapped(filledLate(), double), mapped([1, 2.5], (x) => x * x), calls); + +console.log(lastNegativeZero([1, 2, 0]), lastNegativeZero([1, 2, 3]), lastNegativeZero([])); From 40faf6a9f1b2cf2beb2e3dee7c10abf1cc735466 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 14:16:32 -0500 Subject: [PATCH 083/395] Collect the native compiler profile on the release commit The release workflow's new cli-profile job runs scripts/refresh-cli-profile.mjs on Linux x64 and uploads the profile. Every platform's native CLI build uses it through SCRIPTC_NATIVE_PROFILE_USE, and the GitHub release attaches it as scriptc--cli.profdata.gz. When the job fails, the CLI builds use the committed profile. build-native-cli.mts accepts a gzip-compressed profile in SCRIPTC_NATIVE_PROFILE_USE when the name ends in .gz. refresh-cli-profile.mjs creates the directory of --output before it starts. --- .github/workflows/release.yml | 90 ++++++++++++++++++++++++++++++++- RELEASING.md | 2 +- scripts/build-native-cli.mts | 12 +++-- scripts/refresh-cli-profile.mjs | 19 ++++--- 4 files changed, 109 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fff1931ae..799337e0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,10 +100,59 @@ jobs: if (receiptRun) appendFileSync(process.env.GITHUB_OUTPUT, `receipt_run=${receiptRun}\n`); NODE - build-native-packages: - name: Build native package (${{ matrix.platform }}) + # 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. + cli-profile: + name: Collect the native compiler profile needs: check-release if: needs.check-release.outputs.should_release == 'true' && needs.check-release.outputs.reuse_artifacts != 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 45 + continue-on-error: true + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: pnpm + - uses: ./.github/actions/llvm-linux + - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/zig + - uses: ./.github/actions/native-packages + with: + target: linux-x64-gnu + - run: pnpm --filter @scriptc/compiler --filter scriptc build + - name: Check out tsc-ts + run: | + git clone --quiet --depth 1 https://github.com/vercel-labs/tsc-ts "$RUNNER_TEMP/tsc-ts" + echo "tsc-ts $(git -C "$RUNNER_TEMP/tsc-ts" rev-parse HEAD)" + npm ci --prefix "$RUNNER_TEMP/tsc-ts" + - 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" + - uses: actions/upload-artifact@v4 + with: + name: cli-profile + path: ${{ runner.temp }}/cli-profile/cli.profdata.gz + retention-days: 7 + if-no-files-found: error + + build-native-packages: + name: Build native package (${{ matrix.platform }}) + needs: [check-release, cli-profile] + # Runs when the profile job fails; the CLI builds then use the committed profile. + if: >- + !cancelled() && needs.check-release.outputs.should_release == 'true' && + needs.check-release.outputs.reuse_artifacts != 'true' strategy: fail-fast: false matrix: @@ -243,6 +292,28 @@ jobs: - name: Build host helper for cross-libc bootstrap if: matrix.seed_helper != '' run: pnpm --filter @scriptc/${{ matrix.seed_helper }} build:native + - name: Download the profile collected on this commit + id: cli-profile + if: matrix.cli != '' + continue-on-error: true + uses: actions/download-artifact@v4 + 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. + - name: Select the native compiler profile + if: matrix.cli != '' + shell: bash + env: + COLLECTED: ${{ steps.cli-profile.outcome == 'success' && format('{0}/cli-profile/cli.profdata.gz', runner.temp) || '' }} + run: | + if [ -n "$COLLECTED" ]; then + echo "SCRIPTC_NATIVE_PROFILE_USE=$COLLECTED" >> "$GITHUB_ENV" + echo "The native CLI builds with the profile collected on $GITHUB_SHA" + else + echo "::warning::No profile was collected on this commit; the native CLI builds with the committed profile" + fi - name: Build native CLI distribution if: matrix.cli != '' env: @@ -558,6 +629,21 @@ jobs: - name: Package standalone compilers run: node scripts/package-native-cli.mjs packages /tmp/scriptc-release-assets + # The profile the native CLIs were built with. Runs that reuse native + # builds from before the cli-profile job, or whose profile job failed, + # have none; their CLIs used the committed profile. + - name: Download the native compiler profile + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN: ${{ needs.check-release.outputs.artifact_run }} + VERSION: ${{ needs.check-release.outputs.version }} + run: | + if gh run download "$RUN" --repo "$GITHUB_REPOSITORY" --name cli-profile --dir "$RUNNER_TEMP/cli-profile"; then + mv "$RUNNER_TEMP/cli-profile/cli.profdata.gz" "/tmp/scriptc-release-assets/scriptc-$VERSION-cli.profdata.gz" + else + echo "Run $RUN has no cli-profile artifact" + fi + - name: Create GitHub Release run: | VERSION="${{ needs.check-release.outputs.version }}" diff --git a/RELEASING.md b/RELEASING.md index b6c5239ee..42bfe75ad 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -16,7 +16,7 @@ CI (`.github/workflows/release.yml`) compares the version in `packages/cli/packa The release job builds each pinned LLVM helper, precompiled runtime pack, and native CLI on its matching host. The native CLI bundles its TypeScript checker, LLVM helper, runtime pack, declarations, and comptime evaluator. npm postinstall installs the matching native executable; Node is needed for npm installation but not native compilation or execution. The user's toolchain performs the final platform link. Sanitized builds also use it to instrument program LLVM and compile the C runtime. Platform packages publish ahead of `@scriptc/compiler` and `scriptc`. The GitHub release attaches standalone compiler archives, their SHA-256 checksums, and the surface manifest after npm publication succeeds. -Release builds of the native CLI optimize the compiler with the committed profile `packages/compiler/native/cli.profdata` on every platform. Code the profile does not describe, or describes from older compiler sources, is optimized as without a profile. Refresh the profile on a quiet Linux x64 host when compiler code has changed substantially since the last refresh: `node scripts/refresh-cli-profile.mjs --tsc-ts ` 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. Commit the new profile on its own. +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. 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/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index e539cea1d..0b26b0965 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -17,8 +17,9 @@ const sanitize = process.env["SCRIPTC_SAN"] === "1"; // SCRIPTC_NATIVE_PROFILE_GENERATE= builds a compiler that writes a // raw profile per run (scripts/refresh-cli-profile.mjs). Optimized builds // otherwise use the committed profile, or the indexed profile that -// SCRIPTC_NATIVE_PROFILE_USE names; an empty SCRIPTC_NATIVE_PROFILE_USE -// builds without one. +// SCRIPTC_NATIVE_PROFILE_USE names (gzip-compressed when the name ends in +// .gz, as refresh-cli-profile.mjs writes it); an empty +// SCRIPTC_NATIVE_PROFILE_USE builds without one. const profileGenerate = process.env["SCRIPTC_NATIVE_PROFILE_GENERATE"] || undefined; const committedProfile = join(root, "packages/compiler/native/cli.profdata.gz"); const requestedProfile = process.env["SCRIPTC_NATIVE_PROFILE_USE"]; @@ -45,10 +46,11 @@ const seed = join(dirname(output), ".scriptc", basename(output) + "-seed"); for (const dir of [bin, lib, seed]) mkdirSync(dir, { recursive: true }); let profileUse: string | undefined; if (useProfile) { - profileUse = requestedProfile; - if (profileUse === undefined) { + profileUse = requestedProfile ?? committedProfile; + if (profileUse.endsWith(".gz")) { + const compressed = profileUse; profileUse = join(seed, "cli.profdata"); - writeFileSync(profileUse, gunzipSync(readFileSync(committedProfile))); + writeFileSync(profileUse, gunzipSync(readFileSync(compressed))); } } const compiler = process.env["SCRIPTC_CC"] ?? target.defaultLinker; diff --git a/scripts/refresh-cli-profile.mjs b/scripts/refresh-cli-profile.mjs index e9554e911..1285b5503 100644 --- a/scripts/refresh-cli-profile.mjs +++ b/scripts/refresh-cli-profile.mjs @@ -1,6 +1,8 @@ #!/usr/bin/env node -/* Refreshes packages/compiler/native/cli.profdata.gz, the profile that release - * builds of the native compiler optimize with (scripts/build-native-cli.mts). +/* Refreshes packages/compiler/native/cli.profdata.gz, the profile that builds + * of the native compiler optimize with (scripts/build-native-cli.mts). The + * release workflow's cli-profile job runs it with --output on the release + * commit, and release builds use that profile instead of the committed one. * * Builds an instrumented native compiler for this Linux host, runs it on the * training builds below, and merges the raw profiles it writes into one @@ -26,7 +28,7 @@ import { writeFileSync, } from "node:fs"; import { cpus, tmpdir } from "node:os"; -import { join, resolve } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; import { constants, gzipSync } from "node:zlib"; @@ -55,6 +57,9 @@ const tscTsPackage = join(tscTs, "packages/tsc-ts"); if (!existsSync(join(tscTsPackage, "src/main.ts"))) throw new Error(`${tscTs} is not a tsc-ts checkout with installed dependencies`); const jobs = Number(values.jobs); +// The output's directory is created up front, before the instrumented build. +const profileOutput = resolve(values.output); +mkdirSync(dirname(profileOutput), { recursive: true }); const work = resolve(values.work ?? mkdtempSync(join(tmpdir(), "scriptc-cli-profile-"))); const raw = join(work, "raw"); const builds = join(work, "builds"); @@ -196,10 +201,12 @@ const merged = spawnSync(scriptc, ["merge-profiles", "--sparse", "-o", indexed, }); if (merged.status !== 0) throw new Error("merge-profiles failed"); // The indexed profile compresses to about an eighth of its size. -const output = resolve(values.output); -writeFileSync(output, gzipSync(readFileSync(indexed), { level: constants.Z_BEST_COMPRESSION })); +writeFileSync( + profileOutput, + gzipSync(readFileSync(indexed), { level: constants.Z_BEST_COMPRESSION }), +); console.log( - `wrote ${output} (${statSync(output).size} bytes; ${statSync(indexed).size} uncompressed)`, + `wrote ${profileOutput} (${statSync(profileOutput).size} bytes; ${statSync(indexed).size} uncompressed)`, ); // A work directory the caller named stays for inspection. if (values.work === undefined) rmSync(work, { recursive: true, force: true }); From 55740d094a6b461abb886be66f395a577fe21205 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 14:33:02 -0500 Subject: [PATCH 084/395] Resolve corpus promises 7503 and 7504 with an explicit undefined tsgo checks JavaScript calls to an untyped Promise's resolve against its one parameter, so resolve() with no argument is an error there while tsc-ts accepts it. The two programs pass undefined, which both checkers accept and which settles the promise the same way. --- tests/corpus/7503-pipeline-untyped-sources.mjs | 10 +++++----- tests/corpus/7504-pipeline-untyped-source-errors.mjs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/corpus/7503-pipeline-untyped-sources.mjs b/tests/corpus/7503-pipeline-untyped-sources.mjs index 3dbc90eee..e676fd607 100644 --- a/tests/corpus/7503-pipeline-untyped-sources.mjs +++ b/tests/corpus/7503-pipeline-untyped-sources.mjs @@ -58,7 +58,7 @@ async function run(name) { queueMicrotask(() => log.push(`${name} microtask`)); pipeline(src, sink(name, objectMode), (err) => { log.push(`${name} done ${err ? err.code ?? err.message : err}`); - resolve(); + resolve(undefined); }); log.push(`${name} after call`); }); @@ -72,7 +72,7 @@ await new Promise((resolve) => src2, new Transform({ transform(c, _e, cb) { cb(null, c.toString().toUpperCase()); } }), sink("transform"), - (err) => { log.push(`transform done ${err}`); resolve(); }, + (err) => { log.push(`transform done ${err}`); resolve(undefined); }, ), ); const src3 = new Set(["f1", "f2"]); @@ -81,7 +81,7 @@ await new Promise((resolve) => src3, async function* (source) { for await (const c of source) yield c + "!"; }, sink("fnstage"), - (err) => { log.push(`fnstage done ${err}`); resolve(); }, + (err) => { log.push(`fnstage done ${err}`); resolve(undefined); }, ), ); // promises form @@ -92,11 +92,11 @@ log.push("promises resolved"); function* bad() { yield "ok"; throw new Error("source failed"); } const src5 = bad(); await new Promise((resolve) => - pipeline(src5, sink("bad"), (err) => { log.push(`bad done ${err && err.message}`); resolve(); }), + pipeline(src5, sink("bad"), (err) => { log.push(`bad done ${err && err.message}`); resolve(undefined); }), ); // a source of non-string values into a byte-mode writable const src6 = [1, 2]; await new Promise((resolve) => - pipeline(src6, sink("numbers"), (err) => { log.push(`numbers done ${err && err.code}`); resolve(); }), + pipeline(src6, sink("numbers"), (err) => { log.push(`numbers done ${err && err.code}`); resolve(undefined); }), ); console.log(log.join("\n")); diff --git a/tests/corpus/7504-pipeline-untyped-source-errors.mjs b/tests/corpus/7504-pipeline-untyped-source-errors.mjs index fcbfa9d60..9c677c011 100644 --- a/tests/corpus/7504-pipeline-untyped-source-errors.mjs +++ b/tests/corpus/7504-pipeline-untyped-source-errors.mjs @@ -30,7 +30,7 @@ await new Promise((resolve) => { }); pipeline(pick(counted("f", 5)), dst, (err) => { console.log("fail done", err && err.message); - resolve(); + resolve(undefined); }); }); @@ -45,7 +45,7 @@ await new Promise((resolve) => { }); pipeline(pick(counted("b", 3)), dst, (err) => { console.log("slow done", err); - resolve(); + resolve(undefined); }); }); @@ -85,6 +85,6 @@ await new Promise((resolve) => { pipeline( pick(new Map([["k", 1]])), new Writable({ objectMode: true, write(c, _e, cb) { console.log("map write", JSON.stringify(c)); cb(); } }), - (err) => { console.log("map done", err); resolve(); }, + (err) => { console.log("map done", err); resolve(undefined); }, ); }); From ab341be202cfcb5d56c802f994045c0c0c45a141 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:19:33 -0500 Subject: [PATCH 085/395] Settle BigInt quotients from the top limbs, and vectorize limb shifts A quotient-only BigInt division with a one-limb quotient returns Knuth's estimate without a pass over the operands when the top limbs prove it: with rhat * B + u0 - qhat * d0 at least qhat, a - qhat * b stays positive whatever the lower limbs hold, since they subtract less than qhat * B^(n-2). Otherwise one pass settles the estimate as before. The add and subtract loops process eight limbs a pass, which rebuilds the carry half as often. Left and right shifts, including the operand normalization of the general division, form each limb from two source limbs in a restrict-qualified helper with no dependency between limbs, so the loops vectorize. --- packages/runtime/src/scr_bigint.c | 60 +++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/packages/runtime/src/scr_bigint.c b/packages/runtime/src/scr_bigint.c index 5af2eafaa..69b7009fc 100644 --- a/packages/runtime/src/scr_bigint.c +++ b/packages/runtime/src/scr_bigint.c @@ -269,11 +269,15 @@ static inline bi_limb bi_subc(bi_limb x, bi_limb y, bi_limb *borrow) { static bi_limb bi_add_n(bi_limb *r, const bi_limb *a, const bi_limb *b, size_t n) { bi_limb carry = 0; size_t i = 0; - for (; i + 4 <= n; i += 4) { + for (; i + 8 <= n; i += 8) { r[i] = bi_addc(a[i], b[i], &carry); r[i + 1] = bi_addc(a[i + 1], b[i + 1], &carry); r[i + 2] = bi_addc(a[i + 2], b[i + 2], &carry); r[i + 3] = bi_addc(a[i + 3], b[i + 3], &carry); + r[i + 4] = bi_addc(a[i + 4], b[i + 4], &carry); + r[i + 5] = bi_addc(a[i + 5], b[i + 5], &carry); + r[i + 6] = bi_addc(a[i + 6], b[i + 6], &carry); + r[i + 7] = bi_addc(a[i + 7], b[i + 7], &carry); } for (; i < n; i++) r[i] = bi_addc(a[i], b[i], &carry); return carry; @@ -283,11 +287,15 @@ static bi_limb bi_add_n(bi_limb *r, const bi_limb *a, const bi_limb *b, size_t n static bi_limb bi_sub_n(bi_limb *r, const bi_limb *a, const bi_limb *b, size_t n) { bi_limb borrow = 0; size_t i = 0; - for (; i + 4 <= n; i += 4) { + for (; i + 8 <= n; i += 8) { r[i] = bi_subc(a[i], b[i], &borrow); r[i + 1] = bi_subc(a[i + 1], b[i + 1], &borrow); r[i + 2] = bi_subc(a[i + 2], b[i + 2], &borrow); r[i + 3] = bi_subc(a[i + 3], b[i + 3], &borrow); + r[i + 4] = bi_subc(a[i + 4], b[i + 4], &borrow); + r[i + 5] = bi_subc(a[i + 5], b[i + 5], &borrow); + r[i + 6] = bi_subc(a[i + 6], b[i + 6], &borrow); + r[i + 7] = bi_subc(a[i + 7], b[i + 7], &borrow); } for (; i < n; i++) r[i] = bi_subc(a[i], b[i], &borrow); return borrow; @@ -409,6 +417,23 @@ static bi_limb bi_submul1(bi_limb *r, const bi_limb *a, size_t n, bi_limb m) { return carry; } +/* y[0..n) = the low n limbs of x << shift, for 0 < shift < BI_LIMB_BITS + * and y disjoint from x. Each limb is formed from two source limbs with no + * dependency between limbs, so the loop vectorizes. */ +static void bi_lshift(bi_limb *restrict y, const bi_limb *restrict x, size_t n, unsigned shift) { + unsigned back = BI_LIMB_BITS - shift; + y[0] = x[0] << shift; + for (size_t i = 1; i < n; i++) y[i] = (x[i] << shift) | (x[i - 1] >> back); +} + +/* y[0..n) = x[0..n) >> shift, for 0 < shift < BI_LIMB_BITS and y disjoint + * from x; like bi_lshift, each limb reads two source limbs. */ +static void bi_rshift(bi_limb *restrict y, const bi_limb *restrict x, size_t n, unsigned shift) { + unsigned back = BI_LIMB_BITS - shift; + for (size_t i = 0; i + 1 < n; i++) y[i] = (x[i] >> shift) | (x[i + 1] << back); + y[n - 1] = x[n - 1] >> shift; +} + /* Schoolbook product: r[0..an+bn) = a * b, r disjoint from a and b. */ static void bi_mul_school(bi_limb *r, const bi_limb *a, size_t an, const bi_limb *b, size_t bn) { r[an] = bi_mul1(r, a, an, b[0], 0); @@ -589,11 +614,9 @@ static void bi_divmod_knuth(bi_limb *q, bi_limb *r, const bi_limb *a, size_t an, bi_limb *un = bi_limbs(an + 1 + bn); bi_limb *vn = un + an + 1; if (s) { - for (size_t i = bn - 1; i > 0; i--) vn[i] = (b[i] << s) | (b[i - 1] >> (BI_LIMB_BITS - s)); - vn[0] = b[0] << s; + bi_lshift(vn, b, bn, s); + bi_lshift(un, a, an, s); un[an] = a[an - 1] >> (BI_LIMB_BITS - s); - for (size_t i = an - 1; i > 0; i--) un[i] = (a[i] << s) | (a[i - 1] >> (BI_LIMB_BITS - s)); - un[0] = a[0] << s; } else { memcpy(vn, b, bn * sizeof(bi_limb)); memcpy(un, a, an * sizeof(bi_limb)); @@ -631,8 +654,7 @@ static void bi_divmod_knuth(bi_limb *q, bi_limb *r, const bi_limb *a, size_t an, } if (r) { if (s) { - for (size_t i = 0; i + 1 < bn; i++) r[i] = (un[i] >> s) | (un[i + 1] << (BI_LIMB_BITS - s)); - r[bn - 1] = un[bn - 1] >> s; + bi_rshift(r, un, bn, s); } else { memcpy(r, un, bn * sizeof(bi_limb)); } @@ -674,6 +696,15 @@ static bi_limb bi_div_qlimb(bi_limb *r, const bi_limb *a, const bi_limb *b, size rhat += d1; rhat_big = rhat < d1; } + /* Without a remainder, the top limbs decide when they leave room: + * a - qhat * b >= (rhat * B + u0 - qhat * d0) * B^(n-2) - qhat * B^(n-2) + * as normalized, so a margin of at least qhat proves qhat. */ + if (!r && !rhat_big) { + bi_wide top = ((bi_wide)rhat << BI_LIMB_BITS) | u0, sub = (bi_wide)qhat * d0; + if (top - sub >= qhat) return qhat; + } else if (!r) { + return qhat; /* rhat >= B: the remainder exceeds B^(n-1) >= qhat * b's low part */ + } /* a - qhat * b: negative when the product's high limb or the borrow * reaches past a's n limbs */ bi_limb carry = 0, borrow = 0; @@ -926,13 +957,8 @@ static ScrBigInt *bi_shl_abs(const ScrBigInt *a, size_t count, int sign) { if (!shift) { memcpy(r->limb + words, a->limb, a->len * sizeof(bi_limb)); } else { - bi_limb carry = 0; - for (size_t i = 0; i < a->len; i++) { - bi_limb v = a->limb[i]; - r->limb[i + words] = (v << shift) | carry; - carry = v >> (BI_LIMB_BITS - shift); - } - if (grow) r->limb[n] = carry; + bi_lshift(r->limb + words, a->limb, a->len, shift); + if (grow) r->limb[n] = a->limb[a->len - 1] >> (BI_LIMB_BITS - shift); } r->len = n + grow; r->sign = sign; @@ -955,9 +981,7 @@ static ScrBigInt *bi_shr_abs(const ScrBigInt *a, size_t count, int sign, bool *d if (!shift) { memcpy(r->limb, a->limb + words, n * sizeof(bi_limb)); } else { - for (size_t i = 0; i + 1 < n; i++) - r->limb[i] = (a->limb[i + words] >> shift) | (a->limb[i + words + 1] << (BI_LIMB_BITS - shift)); - r->limb[n - 1] = a->limb[a->len - 1] >> shift; + bi_rshift(r->limb, a->limb + words, n, shift); } return bi_finish(r, n, sign); } From 9b6a25f57e48507b2819867c03f3efc0d557ad4f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:20:53 -0500 Subject: [PATCH 086/395] Resolve each package file's module edges once when pruning npm re-exports planNpmStaticReexports visited a file again each time another of its export names was demanded. Every visit resolved the file's import and export declarations again and walked its whole syntax tree for import() and require() calls. The purity check kept only pure trees and the failing root, so once a walk reached an impure file, the next namespace re-export walked the same files again. Each file's edges are now collected once per plan; the calls come from moduleSourceCandidates, which fork and worker discovery already fill. A repeated visit re-checks only the file's namespace re-exports, the only edges whose pruning depends on the demand. Purity is decided per strongly connected component of the module graph and kept for every file, and a walk that reaches an impure file marks every file on its stack impure. The pruned re-exports and the module order are unchanged. --- .../compiler/src/frontend/npm-static-prune.ts | 266 ++++++++++-------- 1 file changed, 154 insertions(+), 112 deletions(-) diff --git a/packages/compiler/src/frontend/npm-static-prune.ts b/packages/compiler/src/frontend/npm-static-prune.ts index 84a58d561..f02a6e0f6 100644 --- a/packages/compiler/src/frontend/npm-static-prune.ts +++ b/packages/compiler/src/frontend/npm-static-prune.ts @@ -11,6 +11,7 @@ import { npmStaticPackageOfPath } from "./npm-static.js"; import { trackedReadFile } from "./input-tracker.js"; import { npmPackageNameOf } from "./workspace-registry.js"; import { typeOnlyNamedImport } from "./type-only-imports.js"; +import { moduleSourceCandidates } from "./module-source-candidates.js"; export function isPrunedNpmReexport(program: ts.Program, stmt: ts.ExportDeclaration): boolean { return program.analysis.prunedNpmReexports.has(stmt); @@ -117,6 +118,21 @@ function pureExternalPackage( type Demand = Set | null; // null requests the entire namespace +/** One module edge of a package file, resolved once per plan. */ +interface ModuleEdge { + spec: string; + dep: ts.SourceFile | null; +} + +/** A file's runtime module edges: its import declarations with the names + * they request (null: the whole module), its `export … from` declarations, + * and its `import()` and `require()` calls with one string argument. */ +interface ModuleEdges { + imports: (ModuleEdge & { names: readonly string[] | null })[]; + reexports: (ModuleEdge & { stmt: ts.ExportDeclaration })[]; + calls: ModuleEdge[]; +} + /** Package files outside the demanded module graph are absent from the * executable program. All ordinary project files retain their preflight * behavior, including files not imported by the entry. */ @@ -151,6 +167,70 @@ export function planNpmStaticReexports( } }; + // The demand walk visits a file again whenever another of its names is + // requested, and every purity check walks the files it reaches: both read + // these edges instead of resolving the file's syntax each time. + const edgeMemo = new Map(); + const edgesOf = (sf: ts.SourceFile): ModuleEdges => { + const cached = edgeMemo.get(sf); + if (cached !== undefined) return cached; + const edges: ModuleEdges = { imports: [], reexports: [], calls: [] }; + for (const stmt of sf.statements.nodes) { + if (ts.isImportDeclaration(stmt)) { + if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue; + const clause = stmt.importClause; + if (clause?.phaseModifier === ts.SyntaxKind.TypeKeyword) continue; + const spec = stmt.moduleSpecifier.text; + let names: string[] | null = null; + if ( + clause !== undefined && + (clause.namedBindings === undefined || + !( + ts.isNamespaceImport(clause.namedBindings) || + (ts.isNamedImports(clause.namedBindings) && + clause.namedBindings.elements.nodes.length === 0) + )) && + // `import { type A }` still loads the module, like `import {}`. + !typeOnlyNamedImport(stmt) + ) { + names = []; + if (clause.name !== undefined) names.push("default"); + if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { + for (const element of clause.namedBindings.elements.nodes) { + if (!element.isTypeOnly) names.push((element.propertyName ?? element.name).text); + } + } + } + edges.imports.push({ spec, dep: resolveEdge(sf, spec), names }); + } else if (ts.isExportDeclaration(stmt)) { + // `export { type A } from` still loads the module under Node's + // type stripping; only `export type … from` erases. + if ( + stmt.isTypeOnly || + stmt.moduleSpecifier === undefined || + !ts.isStringLiteral(stmt.moduleSpecifier) + ) + continue; + const spec = stmt.moduleSpecifier.text; + edges.reexports.push({ spec, dep: resolveEdge(sf, spec), stmt }); + } + } + for (const node of moduleSourceCandidates(program, sf).calls) { + if (node.arguments.nodes.length !== 1) continue; + const arg = node.arguments.nodes[0]; + if (arg === undefined || !ts.isStringLiteralLike(arg)) continue; + const kind = + node.expression.kind === ts.SyntaxKind.ImportKeyword + ? "import" + : ts.isIdentifier(node.expression) && node.expression.text === "require" + ? "require" + : null; + if (kind !== null) edges.calls.push({ spec: arg.text, dep: resolveEdge(sf, arg.text, kind) }); + } + edgeMemo.set(sf, edges); + return edges; + }; + for (const sf of files) { if (npmStaticPackageOfPath(sf.fileName) === null) request(sf, null); } @@ -178,57 +258,65 @@ export function planNpmStaticReexports( return false; } }; + // An edge this program does not resolve: the conservative + // whole-dependency promise of the external package it names. + const externalPure = (sf: ts.SourceFile, spec: string): boolean => { + const resolved = resolveBareModule(sf.fileName, spec, "js-only"); + const pkg = resolved === null ? null : npmPackageNameOf(resolved.typesFile); + const root = + pkg === null || resolved === null + ? null + : packageRootJsonPath(resolved.typesFile, pkg, packageJsons); + return root !== null && pureExternalPackage(root, packageJsons); + }; // Check the actual imported closure: metadata for one file never vouches // for its side-effectful descendants, even across a package or a cycle. - const pureModuleTree = (root: ts.SourceFile): boolean => { - const cached = pureMemo.get(root); - if (cached !== undefined) return cached; - const visited = new Set(); - const visit = (sf: ts.SourceFile): boolean => { - if (visited.has(sf)) return true; - if (pureMemo.get(sf) === false || !filePure(sf)) return false; - visited.add(sf); - const edges: { spec: string; kind?: "import" | "require" }[] = []; - for (const stmt of sf.statements.nodes) { - if ( - ts.isImportDeclaration(stmt) && - stmt.importClause?.phaseModifier !== ts.SyntaxKind.TypeKeyword && - ts.isStringLiteral(stmt.moduleSpecifier) - ) - edges.push({ spec: stmt.moduleSpecifier.text }); - else if ( - ts.isExportDeclaration(stmt) && - !stmt.isTypeOnly && - stmt.moduleSpecifier && - ts.isStringLiteral(stmt.moduleSpecifier) - ) - edges.push({ spec: stmt.moduleSpecifier.text }); + // A file is pure when every file it reaches is. The walk finds the + // strongly connected components of the module graph (Tarjan): a finished + // component is pure as a whole. On the first impure file the walk stops, + // and every file still on its stack reaches that file, so each is impure. + const order = new Map(); + const stack: ts.SourceFile[] = []; + /** The lowest order the file's walk reaches (Infinity for a finished + * component), or -1 once it reaches an impure file. */ + const visit = (sf: ts.SourceFile): number => { + const known = pureMemo.get(sf); + if (known !== undefined) return known ? Infinity : -1; + const seen = order.get(sf); + if (seen !== undefined) return seen; + if (!filePure(sf)) { + pureMemo.set(sf, false); + return -1; + } + const own = order.size; + order.set(sf, own); + stack.push(sf); + let low = own; + const edges = edgesOf(sf); + for (const list of [edges.imports, edges.reexports, edges.calls]) { + for (const edge of list) { + const reached = + edge.dep !== null ? visit(edge.dep) : externalPure(sf, edge.spec) ? low : -1; + if (reached < 0) return -1; + if (reached < low) low = reached; } - ts.walkPreorder(sf, (node) => { - if (!ts.isCallExpression(node) || node.arguments.nodes.length !== 1) return; - const arg = node.arguments.nodes[0]; - if (!arg || !ts.isStringLiteralLike(arg)) return; - if (node.expression.kind === ts.SyntaxKind.ImportKeyword) - edges.push({ spec: arg.text, kind: "import" }); - else if (ts.isIdentifier(node.expression) && node.expression.text === "require") - edges.push({ spec: arg.text, kind: "require" }); - }); - return edges.every((edge) => { - const dep = resolveEdge(sf, edge.spec, edge.kind); - if (dep !== null) return visit(dep); - const resolved = resolveBareModule(sf.fileName, edge.spec, "js-only"); - const pkg = resolved === null ? null : npmPackageNameOf(resolved.typesFile); - const root = - pkg === null || resolved === null - ? null - : packageRootJsonPath(resolved.typesFile, pkg, packageJsons); - return root !== null && pureExternalPackage(root, packageJsons); - }); - }; - const pure = visit(root); - if (pure) for (const sf of visited) pureMemo.set(sf, true); - else pureMemo.set(root, false); - return pure; + } + if (low === own) { + for (;;) { + const member = stack.pop()!; + order.delete(member); + pureMemo.set(member, true); + if (member === sf) break; + } + } + return low; + }; + const pureModuleTree = (root: ts.SourceFile): boolean => { + if (visit(root) >= 0) return true; + for (const sf of stack) pureMemo.set(sf, false); + stack.length = 0; + order.clear(); + return false; }; const canPrune = ( sf: ts.SourceFile, @@ -245,78 +333,32 @@ export function planNpmStaticReexports( return names !== undefined && names !== null && !names.has(stmt.exportClause.name.text); }; + // A file is queued again only when its demand grows. Its imports and + // calls request the same modules every time; only a namespace re-export + // the new demand names stops being prunable. + const walked = new Set(); while (queue.length > 0) { const sf = queue.shift()!; - for (const stmt of sf.statements.nodes) { - if (ts.isImportDeclaration(stmt)) { - if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue; - const clause = stmt.importClause; - if (clause?.phaseModifier === ts.SyntaxKind.TypeKeyword) continue; - const dep = resolveEdge(sf, stmt.moduleSpecifier.text); - if ( - clause === undefined || - (clause.namedBindings !== undefined && - (ts.isNamespaceImport(clause.namedBindings) || - (ts.isNamedImports(clause.namedBindings) && - clause.namedBindings.elements.nodes.length === 0))) || - // `import { type A }` still loads the module, like `import {}`. - typeOnlyNamedImport(stmt) - ) { - request(dep, null); - } else { - if (clause.name !== undefined) request(dep, "default"); - if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) { - for (const element of clause.namedBindings.elements.nodes) { - if (!element.isTypeOnly) request(dep, (element.propertyName ?? element.name).text); - } - } - } - } else if (ts.isExportDeclaration(stmt)) { - // `export { type A } from` still loads the module under Node's - // type stripping; only `export type … from` erases. - if ( - stmt.isTypeOnly || - stmt.moduleSpecifier === undefined || - !ts.isStringLiteral(stmt.moduleSpecifier) - ) - continue; - const dep = resolveEdge(sf, stmt.moduleSpecifier.text); - if (canPrune(sf, stmt, dep)) continue; - request(dep, null); + const edges = edgesOf(sf); + const first = !walked.has(sf); + walked.add(sf); + if (first) { + for (const edge of edges.imports) { + if (edge.names === null) request(edge.dep, null); + else for (const name of edge.names) request(edge.dep, name); } } - ts.walkPreorder(sf, (node) => { - if (!ts.isCallExpression(node) || node.arguments.nodes.length !== 1) return undefined; - const arg = node.arguments.nodes[0]; - if (arg === undefined || !ts.isStringLiteralLike(arg)) return undefined; - if ( - node.expression.kind === ts.SyntaxKind.ImportKeyword || - (ts.isIdentifier(node.expression) && node.expression.text === "require") - ) { - request( - resolveEdge( - sf, - arg.text, - node.expression.kind === ts.SyntaxKind.ImportKeyword ? "import" : "require", - ), - null, - ); - } - return undefined; - }); + for (const edge of edges.reexports) { + if (canPrune(sf, edge.stmt, edge.dep)) continue; + request(edge.dep, null); + } + if (first) for (const edge of edges.calls) request(edge.dep, null); } const pruned = new Set(); for (const sf of demanded.keys()) { - for (const stmt of sf.statements.nodes) { - if ( - !ts.isExportDeclaration(stmt) || - stmt.moduleSpecifier === undefined || - !ts.isStringLiteral(stmt.moduleSpecifier) - ) - continue; - if (canPrune(sf, stmt, resolveEdge(sf, stmt.moduleSpecifier.text))) pruned.add(stmt); - } + for (const edge of edgesOf(sf).reexports) + if (canPrune(sf, edge.stmt, edge.dep)) pruned.add(edge.stmt); } program.analysis.prunedNpmReexports.clear(); for (const stmt of pruned) program.analysis.prunedNpmReexports.add(stmt); From e5d386add79ad909c1b5d25a06ae1f2b2eed6acb Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:27:31 -0500 Subject: [PATCH 087/395] List caches and open pull requests in full before pruning --- .github/workflows/prune-caches.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/prune-caches.yml b/.github/workflows/prune-caches.yml index 008baaa43..1509d56ce 100644 --- a/.github/workflows/prune-caches.yml +++ b/.github/workflows/prune-caches.yml @@ -24,13 +24,18 @@ jobs: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} run: | - open=" $(gh api --paginate "repos/$REPO/pulls?state=open&per_page=100" --jq '.[].number' | tr '\n' ' ') " - gh api --paginate "repos/$REPO/actions/caches?per_page=100" \ - --jq '.actions_caches[] | select(.ref | startswith("refs/pull/")) | "\(.id) \(.ref)"' | + set -euo pipefail + # Read both lists in full before deleting anything: deleting while the cache + # listing pages shifts later pages, and a failed request must stop the job + # instead of leaving open pull requests out of the list. + open=$(gh api --paginate "repos/$REPO/pulls?state=open&per_page=100" --jq '.[].number') + caches=$(gh api --paginate "repos/$REPO/actions/caches?per_page=100" \ + --jq '.actions_caches[] | select(.ref | startswith("refs/pull/")) | "\(.id) \(.ref)"') while read -r id ref; do + [ -n "$id" ] || continue number=$(echo "$ref" | cut -d/ -f3) - case "$open" in - *" $number "*) ;; - *) gh api --method DELETE "repos/$REPO/actions/caches/$id" >/dev/null && echo "deleted cache $id ($ref)" ;; - esac - done + if ! grep -qx "$number" <<<"$open"; then + gh api --method DELETE "repos/$REPO/actions/caches/$id" >/dev/null + echo "deleted cache $id ($ref)" + fi + done <<<"$caches" From dd57655bb14bbc36cc72790b0ca1ef572d572be1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:38:16 -0500 Subject: [PATCH 088/395] Parse npm-static declaration and package files only when a rewrite can apply Loading a program with --npm-static parses package files on their own before the checker reads them: - The declaration closure of each named package was walked twice, once for class overloads and once for class properties, and the one-entry parse cache could not serve the second walk. One walk now reads both from each parse. - The nullable class field and Array.find return rewrites parsed every package JavaScript file. They now parse only files whose text contains `class` and `null` (or `find`), or a Unicode escape that could spell them. - The JSDoc namepath rewrite parsed every file containing `#` or `~`. It now parses only files with a namepath segment joined by one of them (`a#b`, `a~b`), the pattern the rewrite replaces. The rewritten sources are unchanged. --- packages/compiler/src/frontend/program.ts | 43 ++++++++-------------- packages/compiler/src/frontend/services.ts | 13 ++++++- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 8e582af89..5a3f3d20c 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -488,43 +488,33 @@ function visitNpmStaticDeclarationClosure( visit(entryPath); } -function npmStaticDeclarationOverloadsOf( +/** The overloads and properties of the classes a package's declaration + * closure declares, read from one parse of each declaration file. */ +function npmStaticDeclarationMetadataOf( services: FrontendServices, entryPath: string, -): NpmStaticDeclarationOverloads { - const classes = new Map>(); +): { overloads: NpmStaticDeclarationOverloads; properties: NpmStaticDeclarationProperties } { + const overloads = new Map>(); + const properties = new Map>(); visitNpmStaticDeclarationClosure(services, entryPath, (path, source) => { - for (const [className, methods] of parseNpmStaticDeclarationOverloads( - services.parse(path, source, "ts"), - )) { + const sourceFile = services.parse(path, source, "ts"); + for (const [className, methods] of parseNpmStaticDeclarationOverloads(sourceFile)) { const target = - classes.get(className) ?? new Map(); - classes.set(className, target); + overloads.get(className) ?? new Map(); + overloads.set(className, target); for (const [methodName, signatures] of methods) { if (!target.has(methodName)) target.set(methodName, signatures); } } - }); - return classes; -} - -function npmStaticDeclarationPropertiesOf( - services: FrontendServices, - entryPath: string, -): NpmStaticDeclarationProperties { - const classes = new Map>(); - visitNpmStaticDeclarationClosure(services, entryPath, (path, source) => { - for (const [className, properties] of parseNpmStaticDeclarationProperties( - services.parse(path, source, "ts"), - )) { - const target = classes.get(className) ?? new Map(); - classes.set(className, target); - for (const [propertyName, type] of properties) { + for (const [className, fields] of parseNpmStaticDeclarationProperties(sourceFile)) { + const target = properties.get(className) ?? new Map(); + properties.set(className, target); + for (const [propertyName, type] of fields) { if (!target.has(propertyName)) target.set(propertyName, type); } } }); - return classes; + return { overloads, properties }; } /** See LoadResult.startupCrash: Node's exact error message, the IR error @@ -1096,8 +1086,7 @@ export function loadProgram( ) { continue; } - const overloads = npmStaticDeclarationOverloadsOf(services, resolved.typesFile); - const properties = npmStaticDeclarationPropertiesOf(services, resolved.typesFile); + const { overloads, properties } = npmStaticDeclarationMetadataOf(services, resolved.typesFile); const classNames = new Set([...overloads.keys(), ...properties.keys()]); if (classNames.size === 0) continue; const runtime = resolveBareModule(entryPath, pkg, "js-only"); diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index 14b366bfa..a0acb5818 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -20,6 +20,12 @@ import * as syntax from "./npm-static-declaration-syntax.js"; import { normalizeCjsExportsVar } from "./npm-static-cjs-exports-var.js"; import type { SourceFile } from "./ts7/ast-types.js"; +/** Whether the source can contain `word` as a keyword or identifier: it + * holds the word itself or a Unicode escape that might spell it. */ +function spells(source: string, word: string): boolean { + return source.includes(word) || source.includes("\\u"); +} + /** Evaluate an isolated, self-contained TypeScript callback under a finite * budget. Hosts supply execution; shared lowering owns capture checks and * verifies the returned value before baking it into the program. */ @@ -150,13 +156,18 @@ export class FrontendServices { return normalizeCjsExportsVar(this.parse(path, source, "js")); } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { + // Rewrites `null`-initialized fields of class declarations. + if (!spells(source, "class") || !spells(source, "null")) return null; return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); } jsDocNamepaths(path: string, source: string): string | null { - if (!source.includes("~") && !source.includes("#")) return null; + // A namepath segment joined by `#` or `~` (applyNpmStaticJsDocNamepaths). + if (!/[$\w][#~][$A-Z_a-z]/.test(source)) return null; return syntax.applyNpmStaticJsDocNamepaths(this.parse(path, source, "js"), source); } findReturnWidening(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { + // Widens `find` calls returned by methods of class declarations. + if (!spells(source, "class") || !spells(source, "find")) return null; return syntax.applyNpmStaticFindReturnWidening(this.parse(path, source, "js"), source); } declarationProperties( From d4fcd787a8f6f11b3ea4a7be786b06f7d06f799c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:38:37 -0500 Subject: [PATCH 089/395] Reference the unwinder weakly from the runtime's raise scr_unwind.c is linked into every Linux executable, and its references to _Unwind_ForcedUnwind and _Unwind_GetCFA pulled the unwinder out of libgcc_eh (or Zig's libunwind) even for programs that never raise, which grew a static hello world past its size ceiling. The references are now weak: programs that raise reference the personality routine from their landing pads, and its unit links the unwinder in. --- packages/runtime/src/scr_unwind.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_unwind.c b/packages/runtime/src/scr_unwind.c index 9bdf2dbd2..50474e82e 100644 --- a/packages/runtime/src/scr_unwind.c +++ b/packages/runtime/src/scr_unwind.c @@ -10,6 +10,13 @@ #include #include +/* Weak, so the runtime unit alone does not link the unwinder into every + * program: programs that raise reference the personality routine from + * their landing pads, and its unit references the unwinder's entry points, + * which link it in. */ +#pragma weak _Unwind_ForcedUnwind +#pragma weak _Unwind_GetCFA + #if defined(__SANITIZE_ADDRESS__) #define SCR_UNWIND_ASAN 1 #elif defined(__has_feature) @@ -69,7 +76,7 @@ void scr_unwind_raise(void) { #ifdef SCR_UNWIND_ASAN scr_unwind_low = (uintptr_t)__builtin_frame_address(0); #endif - _Unwind_ForcedUnwind(exc, scr_unwind_stop, NULL); + if (_Unwind_ForcedUnwind != NULL) _Unwind_ForcedUnwind(exc, scr_unwind_stop, NULL); fputs("scriptc: the native unwinder could not raise an exception\n", stderr); abort(); } From d837036f87a64711db49b138fc5dd009b788aebe Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 11:27:25 -0700 Subject: [PATCH 090/395] Report constant object graphs in untyped JavaScript (static objects, stage 1) SCRIPTC_DEBUG_SHAPES=const reports the constant object graphs shape inference finds (DESIGN-constant-objects.md, stage 1): objects created by code that runs at most once (module top level, IIFEs, functions reached only through one call site from such code) whose fields are written only there, that never meet a reflective or computed write, and that never escape. Namespaces (`var ns = ns || {}; ns.util = {}`), revealing modules, `module.exports` tables, and ES5 constructors' prototype objects qualify. Each near miss names its rule: allocated repeatedly, created by an init factory (one object per call site), escapes, reflective or computed write, literal form, written after init, or lazy cache. The report counts the member reads and calls a later stage can fold. The analysis now models ES5 constructors (a function's prototype object, instances reading through it) and links CommonJS require() and export bindings to the analyzed module's exports, which it previously read as nothing. Code generation is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/shape-inference/analyze.ts | 111 ++++- .../shape-inference/constant-graphs.ts | 421 ++++++++++++++++++ .../src/frontend/shape-inference/index.ts | 11 +- .../compiler/test/constant-graphs.test.ts | 106 +++++ 4 files changed, 642 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/src/frontend/shape-inference/constant-graphs.ts create mode 100644 packages/compiler/test/constant-graphs.test.ts diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 55e0e0107..ee07098e7 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -100,6 +100,8 @@ export interface CallEdge { readonly caller: FunctionInfo | null; readonly callee: FunctionInfo; readonly inLoop: boolean; + /** The call (or `new`, getter read, builtin callback) site. */ + readonly site: Node; } export interface ShapeAnalysis { @@ -123,6 +125,10 @@ export interface ShapeAnalysis { readonly steps: number; readonly topCause: ReadonlyMap; readonly nodeLabel: ReadonlyMap; + /** Functions unknown code may call (their atoms escaped). */ + readonly escapedFunctions: ReadonlySet; + /** ES5 constructors: each function's default prototype object. */ + readonly protoOf: ReadonlyMap; } /** Array.prototype members: other names on an array are own properties. */ @@ -298,6 +304,7 @@ class Analyzer { caller: ctx?.caller ?? null, callee, inLoop: (ctx?.inLoop ?? false) || this.forceLoop, + site, }); } @@ -340,6 +347,8 @@ class Analyzer { steps: this.steps, topCause: this.topCause, nodeLabel: this.nodeLabel, + escapedFunctions: this.escapedFunctionCalls, + protoOf: this.protoOf, }; } @@ -413,6 +422,25 @@ class Analyzer { return a; } + /** ES5 constructor functions: the function's default prototype object, + * and which function each `new F()` instance atom belongs to. */ + readonly protoOf = new Map(); + readonly fnOfInstance = new Map(); + protoAtom(fn: Atom): Atom { + let p = this.protoOf.get(fn); + if (p === undefined) { + p = this.atoms.intern(`proto:${this.nodeKey(fn.node!)}:${fn.ctx ?? ""}`, () => ({ + kind: "object", + label: `${fn.label}.prototype`, + node: fn.node, + })); + this.protoOf.set(fn, p); + this.add(this.field(p, "constructor"), fn); + if (this.escaped.has(fn)) this.escape(p, `via ${fn.label}`); + } + return p; + } + builtin(path: string, receiver?: Atom): Atom { const key = receiver === undefined ? `builtin:${path}` : `builtin:${path}@${receiver.id}`; return this.atoms.intern(key, () => ({ kind: "builtin", label: path, path, receiver })); @@ -477,6 +505,11 @@ class Analyzer { if (this.escaped.has(atom)) return; this.escaped.set(atom, reason); const via = `via ${atom.label}`; + // Unknown code reaches a constructor's prototype through the function + // and through its instances. + const ctor = atom.kind === "function" ? atom : this.fnOfInstance.get(atom); + if (ctor !== undefined && ctor.node !== undefined && (atom !== ctor || this.protoOf.has(ctor))) + this.escape(this.protoAtom(ctor), via); const fm = this.fields.get(atom); if (fm !== undefined) { for (const [name, id] of fm) { @@ -1424,6 +1457,15 @@ class Analyzer { ) return this.constNode(this.top); if (ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) return this.namespaceNode(decl); + // A CommonJS export the checker resolved an import or require binding + // to (`module.exports = { name }`, `exports.name = v`): the member of the + // module's exports. + const exported = cjsExportName(decl); + if (exported !== null && decl.getSourceFile() !== id.getSourceFile()) { + const dst = this.newNode(); + this.loadConstraint(this.namespaceNode(decl.getSourceFile()), exported, dst, id); + return dst; + } return this.narrowed(id, decl, this.binding(decl)); } @@ -1531,6 +1573,9 @@ class Analyzer { } private readonly namespaces = new Map(); + /** Module namespaces: a CommonJS module's `module.exports` value is the + * namespace's `exports` field, and member reads see its members too. */ + private readonly namespaceAtoms = new Set(); /** `import * as ns`: an object whose fields are the module's exports. */ namespaceNode(mod: Node): number { const existing = this.namespaces.get(mod); @@ -1542,6 +1587,7 @@ class Analyzer { label: `namespace ${shortFile((mod as ts.SourceFile).fileName ?? "")}`, node: mod, })); + this.namespaceAtoms.add(atom); this.add(n, atom); const sym = this.checker.getSymbolAtLocation(mod); if (sym !== undefined) { @@ -1983,8 +2029,11 @@ class Analyzer { case "function": if (name === "call" || name === "apply" || name === "bind") this.add(dst, this.builtin(`function.${name}`, a)); - else if (name === "prototype") this.add(dst, this.top); - else if (name === "name") this.add(dst, this.prim("string")); + else if (name === "prototype") { + // The default prototype object, or what the program assigns. + this.add(dst, this.protoAtom(a)); + this.edge(this.field(a, "prototype"), dst); + } else if (name === "name") this.add(dst, this.prim("string")); else if (name === "length") this.add(dst, this.prim("number")); else this.edge(this.field(a, name), dst); return; @@ -1994,12 +2043,28 @@ class Analyzer { else if (ARRAY_METHODS.has(name)) this.add(dst, this.builtin(`array.${name}`, a)); else this.edge(this.field(a, name), dst); return; - case "object": + case "object": { this.edge(this.field(a, name), dst); this.edge(this.anyField(a), dst); + if (this.namespaceAtoms.has(a) && name !== "exports") { + this.loadConstraint(this.field(a, "exports"), name, dst, site); + } + // `new F()` instances read through F's prototype objects. + const fn = this.fnOfInstance.get(a); + if (fn !== undefined) { + const protos = this.newNode(); + this.add(protos, this.protoAtom(fn)); + this.edge(this.field(fn, "prototype"), protos); + this.on(protos, (proto) => { + if (proto.kind === "object") this.edge(this.field(proto, name), dst); + else if (proto.kind === "top") this.add(dst, this.top); + }); + return; + } if (name === "constructor" || name === "hasOwnProperty" || name === "toString") this.add(dst, this.builtin(`object.${name}`, a)); return; + } case "dictionary": this.edge(this.anyField(a), dst); this.edge(this.field(a, name), dst); @@ -2190,6 +2255,27 @@ class Analyzer { for (const a of args) this.sink(a, `super(...) at ${locLabel(e)}`); return dst; } + // CommonJS `require("")` of an analyzed module: its exports (the + // module's namespace, which holds `module.exports` as its `exports` + // field and the checker's view of its exports). + if (ts.isIdentifier(callee) && callee.text === "require" && e.arguments.nodes.length === 1) { + const spec = e.arguments.nodes[0]!; + if (ts.isStringLiteral(spec)) { + let target: Node | undefined; + try { + const sym = this.checker.getSymbolAtLocation(spec); + target = sym?.valueDeclaration ?? sym?.declarations[0]; + } catch { + target = undefined; + } + if (target !== undefined && ts.isSourceFile(target) && this.files.includes(target)) { + const ns = this.namespaceNode(target); + this.edge(ns, dst); + this.on(ns, (a) => this.edge(this.field(a, "exports"), dst)); + return dst; + } + } + } if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) { const recvExpr = unparen(callee.expression); const { args, spread } = this.args(e); @@ -2286,6 +2372,7 @@ class Analyzer { label: `new ${a.label}`, node: a.node, })); + this.fnOfInstance.set(inst, a); this.add(dst, inst); const info = this.infoOfAtom(a); if (info !== undefined) { @@ -2862,3 +2949,21 @@ class Analyzer { } } } + +/** The export name of a CommonJS export declaration the checker resolves + * bindings to: a member of the `module.exports` literal, or the target of + * `exports.name =` / `module.exports.name =`. */ +function cjsExportName(decl: Node): string | null { + if (ts.isShorthandPropertyAssignment(decl) || ts.isPropertyAssignment(decl)) { + const n = decl.name; + return ts.isIdentifier(n) || ts.isStringLiteral(n) ? n.text : null; + } + if (ts.isPropertyAccessExpression(decl) && ts.isIdentifier(decl.name)) return decl.name.text; + if ( + ts.isBinaryExpression(decl) && + ts.isPropertyAccessExpression(decl.left) && + ts.isIdentifier(decl.left.name) + ) + return decl.left.name.text; + return null; +} diff --git a/packages/compiler/src/frontend/shape-inference/constant-graphs.ts b/packages/compiler/src/frontend/shape-inference/constant-graphs.ts new file mode 100644 index 000000000..dffa8d17d --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/constant-graphs.ts @@ -0,0 +1,421 @@ +/* Static-object specialization, stage 1 (DESIGN-constant-objects.md): + * constant object graphs. + * + * An object literal is a SINGLETON when the code that creates it runs at + * most once: at module top level, or in a function every call path to which + * starts at module evaluation through exactly one call site outside loops + * (IIFEs, revealing modules, init helpers). A singleton's field is CONSTANT + * when every store to it runs in such once-only code, no reflective or + * computed write reaches the object, and the object never escapes to code + * the analysis cannot see. A constant object has only constant fields and a + * fixed key set; constant objects whose constant fields point at each other + * form a constant graph (`ns`, `ns.util`, `ns.util.trim`). + * + * This stage computes the graphs and reports them, with the rule that + * demoted every near miss, and the reads and calls a later stage could fold. + * Nothing reaches code generation. */ +import * as ts from "../ts7/adapter.js"; +import type { Atom } from "./domain.js"; +import type { FunctionInfo, Op, ShapeAnalysis } from "./analyze.js"; +import { npmStaticPackageOfPath } from "../npm-static.js"; + +type Node = ts.Node; + +export interface ConstantField { + readonly key: string; + /** Distinct atoms the field may hold. */ + readonly values: readonly Atom[]; + /** Stores (the literal's own member included). */ + readonly stores: number; +} + +/** An object the analysis tracks as one allocation: an object literal, a + * function object (a namespace or an ES5 constructor), or a constructor's + * default prototype object. */ +export interface Candidate { + readonly atom: Atom; + /** The syntax that creates it (the literal or the function). */ + readonly node: Node; + readonly kind: "literal" | "function" | "prototype"; + /** Keys the creating syntax itself defines. */ + readonly keys: readonly string[]; + /** Whether the creating syntax is a plain data literal. */ + readonly plain: boolean; + readonly inPackage: boolean; +} + +export interface ConstantObject { + readonly site: Candidate; + readonly fields: readonly ConstantField[]; + readonly frozen: boolean; +} + +export interface NearMiss { + readonly site: Candidate; + /** Null when the whole object was demoted. */ + readonly key: string | null; + readonly rule: + | "init-factory" + | "allocated-repeatedly" + | "escapes" + | "reflective-write" + | "computed-write" + | "literal-form" + | "written-after-init" + | "lazy-cache"; + readonly detail: string; +} + +export interface ConstantGraphs { + readonly objects: readonly ConstantObject[]; + readonly nearMisses: readonly NearMiss[]; + /** Reads `e.k` whose receiver is exactly one constant object and whose + * field is constant, and the subset that are callees. */ + readonly foldableReads: number; + readonly foldableCalls: number; + /** Reads of a constant field through a receiver that may also be other + * values. */ + readonly mixedReads: number; + readonly once: (fn: Node) => boolean; +} + +function isLoop(n: Node): boolean { + return ( + ts.isForStatement(n) || + ts.isForInStatement(n) || + ts.isForOfStatement(n) || + ts.isWhileStatement(n) || + ts.isDoStatement(n) + ); +} + +/** The function (or source file) whose body contains `n`. */ +function ownerFunction(n: Node): Node { + for (let p = n.parent; p !== undefined; p = p.parent) { + if (ts.isSourceFile(p)) return p; + if (ts.isFunctionLike(p) && (p as { body?: Node }).body !== undefined) return p; + } + return n.getSourceFile(); +} + +/** Whether `n` sits in a loop of its own function's body. */ +function inLoop(n: Node, owner: Node): boolean { + for (let p = n.parent; p !== undefined && p !== owner; p = p.parent) if (isLoop(p)) return true; + return false; +} + +function loc(n: Node): string { + const sf = n.getSourceFile(); + const file = sf.fileName.split("/node_modules/").pop() ?? sf.fileName; + return `${file.split("/").slice(-2).join("/")}:${sf.getLineAndCharacterOfPosition(ts.tokenStartOfNode(n)).line + 1}`; +} + +/** `x.k || (x.k = v)`, `x.k ??= v`, `x.k ||= v`, `if (!x.k) x.k = v`. */ +function isCacheStore(write: Node): boolean { + let assign: Node | undefined = write.parent; + while (assign !== undefined && ts.isParenthesizedExpression(assign)) assign = assign.parent; + if (assign === undefined || !ts.isBinaryExpression(assign)) return false; + const op = assign.operatorToken.kind; + if (op === ts.SyntaxKind.BarBarEqualsToken || op === ts.SyntaxKind.QuestionQuestionEqualsToken) + return true; + if (op !== ts.SyntaxKind.EqualsToken) return false; + const text = write.getText(); + let up: Node | undefined = assign.parent; + while (up !== undefined && ts.isParenthesizedExpression(up)) up = up.parent; + if ( + up !== undefined && + ts.isBinaryExpression(up) && + (up.operatorToken.kind === ts.SyntaxKind.BarBarToken || + up.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) && + up.left.getText() === text + ) + return true; + for (let p: Node | undefined = assign.parent; p !== undefined; p = p.parent) { + if (ts.isIfStatement(p)) { + const cond = p.expression.getText().replace(/\s+/g, ""); + return cond === `!${text}` || cond === `${text}===undefined` || cond === `${text}==null`; + } + if (ts.isFunctionLike(p) || ts.isSourceFile(p)) break; + } + return false; +} + +const OBJECT_DEMOTING: ReadonlySet = new Set([ + "computedWrite", + "delete", + "defineProperty", + "setPrototypeOf", + "assign", + "proxy", +]); + +export function computeConstantGraphs(analysis: ShapeAnalysis): ConstantGraphs { + const infosByNode = new Map(); + for (const info of analysis.functions.values()) { + let l = infosByNode.get(info.node); + if (l === undefined) infosByNode.set(info.node, (l = [])); + l.push(info); + } + const edgesTo = new Map(); + for (const e of analysis.calls) { + let l = edgesTo.get(e.callee.node); + if (l === undefined) edgesTo.set(e.callee.node, (l = [])); + l.push(e); + } + const escapedNodes = new Set([...analysis.escapedFunctions].map((f) => f.node)); + + const onceMemo = new Map(); + const once = (fn: Node): boolean => { + if (ts.isSourceFile(fn)) return true; + const known = onceMemo.get(fn); + if (known !== undefined) return known; + onceMemo.set(fn, false); // a cycle is not once-only + let result = false; + const edges = edgesTo.get(fn) ?? []; + if ( + infosByNode.has(fn) && + !escapedNodes.has(fn) && + edges.length > 0 && + new Set(edges.map((e) => e.site)).size === 1 + ) { + result = edges.every( + (e) => + !e.inLoop && e.caller !== null && !inLoop(e.site, e.caller.node) && once(e.caller.node), + ); + } + onceMemo.set(fn, result); + return result; + }; + + // Whether every call of a function comes from once-only code outside + // loops, through any number of call sites: an init factory, whose + // allocations are one object per call site (schema builders called at + // module initialization). + const initOnlyMemo = new Map(); + const initOnly = (fn: Node): boolean => { + if (ts.isSourceFile(fn)) return true; + const known = initOnlyMemo.get(fn); + if (known !== undefined) return known; + initOnlyMemo.set(fn, false); + const edges = edgesTo.get(fn) ?? []; + const result = + infosByNode.has(fn) && + !escapedNodes.has(fn) && + edges.length > 0 && + edges.every( + (e) => + !e.inLoop && + e.caller !== null && + !inLoop(e.site, e.caller.node) && + (once(e.caller.node) || initOnly(e.caller.node)), + ); + initOnlyMemo.set(fn, result); + return result; + }; + + const opsByAtom = new Map(); + for (const op of analysis.ops) { + for (const a of analysis.nodes[op.recv]!.atoms) { + let l = opsByAtom.get(a); + if (l === undefined) opsByAtom.set(a, (l = [])); + l.push(op); + } + } + + const objects: ConstantObject[] = []; + const nearMisses: NearMiss[] = []; + const constantKeys = new Map>(); + const candidates: Candidate[] = []; + for (const site of analysis.sites) { + if (site.kind !== "object") continue; + candidates.push({ + atom: site.atom, + node: site.node, + kind: "literal", + keys: site.keys ?? [], + plain: !( + site.hasAccessor || + site.hasComputedKey || + site.spreads.length > 0 || + site.keys === null + ), + inPackage: site.inPackage, + }); + } + const inPackage = (n: Node): boolean => + npmStaticPackageOfPath(n.getSourceFile().fileName) !== null; + for (const atom of analysis.atoms.atoms) { + if (atom.kind !== "function" || atom.node === undefined) continue; + const hasFields = (analysis.fields.get(atom)?.size ?? 0) > 0; + const proto = analysis.protoOf.get(atom); + if (!hasFields && proto === undefined) continue; + candidates.push({ + atom, + node: atom.node, + kind: "function", + keys: [], + plain: true, + inPackage: inPackage(atom.node), + }); + if (proto !== undefined) + candidates.push({ + atom: proto, + node: atom.node, + kind: "prototype", + keys: ["constructor"], + plain: true, + inPackage: inPackage(atom.node), + }); + } + for (const site of candidates) { + // A function object is created where its expression or declaration + // runs; its prototype with it. + const owner = ownerFunction(site.node); + if (!once(owner) || inLoop(site.node, owner)) { + const factory = !inLoop(site.node, owner) && initOnly(owner); + if (site.inPackage || factory) + nearMisses.push({ + site, + key: null, + rule: factory ? "init-factory" : "allocated-repeatedly", + detail: factory + ? `one object per call of ${loc(owner)} (${new Set((edgesTo.get(owner) ?? []).map((e) => e.site)).size} call sites)` + : `created in ${owner.kind === ts.SyntaxKind.SourceFile ? "a module loop" : loc(owner)}`, + }); + continue; + } + const escaped = analysis.escaped.get(site.atom); + if (escaped !== undefined) { + nearMisses.push({ site, key: null, rule: "escapes", detail: escaped }); + continue; + } + if (!site.plain) { + nearMisses.push({ + site, + key: null, + rule: "literal-form", + detail: "accessor, computed key or spread in the literal", + }); + continue; + } + const ops = opsByAtom.get(site.atom) ?? []; + const demoting = ops.find((op) => OBJECT_DEMOTING.has(op.kind)); + if (demoting !== undefined) { + nearMisses.push({ + site, + key: null, + rule: demoting.kind === "computedWrite" ? "computed-write" : "reflective-write", + detail: `${demoting.kind} at ${loc(demoting.node)}`, + }); + continue; + } + const frozen = ops.some((op) => op.kind === "freeze"); + const stores = new Map(); + for (const k of site.keys) stores.set(k, 1); + // A function's own data fields come only from stores. + const demotedKeys = new Map(); + for (const op of ops) { + // A `length` store on an object (not an array) is an ordinary write. + if ((op.kind !== "write" && op.kind !== "lengthWrite") || op.name === null) continue; + stores.set(op.name, (stores.get(op.name) ?? 0) + 1); + const fn = ownerFunction(op.node); + if (once(fn) && !inLoop(op.node, fn)) continue; + if (demotedKeys.has(op.name)) continue; + demotedKeys.set(op.name, { + site, + key: op.name, + rule: isCacheStore(op.node) ? "lazy-cache" : "written-after-init", + detail: loc(op.node), + }); + } + for (const miss of demotedKeys.values()) nearMisses.push(miss); + const fieldMap = analysis.fields.get(site.atom) ?? new Map(); + const fields: ConstantField[] = []; + const keys = new Set(); + for (const [key, count] of stores) { + if (demotedKeys.has(key)) continue; + const id = fieldMap.get(key); + const values = id === undefined ? [] : [...analysis.nodes[id]!.atoms]; + fields.push({ key, values, stores: count }); + keys.add(key); + } + constantKeys.set(site.atom, keys); + if (demotedKeys.size === 0) objects.push({ site, fields, frozen }); + } + + let foldableReads = 0, + foldableCalls = 0, + mixedReads = 0; + for (const op of analysis.ops) { + if (op.kind !== "read" || op.name === null) continue; + const atoms = analysis.nodes[op.recv]!.atoms; + const constant = [...atoms].filter((a) => constantKeys.get(a)?.has(op.name!) === true); + if (constant.length === 0) continue; + if (atoms.size === 1) { + foldableReads++; + const p = op.node.parent; + if (p !== undefined && ts.isCallExpression(p) && p.expression === op.node) foldableCalls++; + } else mixedReads++; + } + return { objects, nearMisses, foldableReads, foldableCalls, mixedReads, once }; +} + +/** Report lines (SCRIPTC_DEBUG_SHAPES=const): per package, the constant + * graphs with their roots, the near misses by rule, and the foldable reads. */ +export function constantGraphReport(analysis: ShapeAnalysis, graphs: ConstantGraphs): string[] { + const lines: string[] = []; + const pkgOf = (site: Candidate): string => + npmStaticPackageOfPath(site.node.getSourceFile().fileName) ?? ""; + const byPkg = new Map(); + const entry = (pkg: string) => { + let e = byPkg.get(pkg); + if (e === undefined) byPkg.set(pkg, (e = { objects: [], misses: [] })); + return e; + }; + for (const o of graphs.objects) entry(pkgOf(o.site)).objects.push(o); + for (const m of graphs.nearMisses) entry(pkgOf(m.site)).misses.push(m); + const constantAtoms = new Set(graphs.objects.map((o) => o.site.atom)); + const referenced = new Set(); + for (const o of graphs.objects) + for (const f of o.fields) + if (f.values.length === 1 && constantAtoms.has(f.values[0]!)) referenced.add(f.values[0]!); + const describe = (o: ConstantObject, depth: number, seen: Set): string => { + const parts = o.fields.map((f) => { + if (f.values.length !== 1) + return `${f.key}: ${f.values.length === 0 ? "?" : f.values.map((a) => a.label).join("|")}`; + const v = f.values[0]!; + const child = graphs.objects.find((x) => x.site.atom === v); + if (child !== undefined && depth < 3 && !seen.has(v)) { + seen.add(v); + return `${f.key}: ${describe(child, depth + 1, seen)}`; + } + return `${f.key}: ${v.kind === "function" && child === undefined ? "fn" : v.label}`; + }); + const shown = parts.length > 8 ? [...parts.slice(0, 8), `… ${parts.length - 8} more`] : parts; + return `{${shown.join(", ")}}${o.frozen ? " (frozen)" : ""}`; + }; + lines.push( + `analysis ${analysis.steps} steps${analysis.budgetExceeded ? " (BUDGET EXCEEDED: facts incomplete)" : ""}, ${analysis.files.length} files`, + ); + lines.push( + `constant objects ${graphs.objects.length}, near misses ${graphs.nearMisses.length}; foldable reads ${graphs.foldableReads} (calls ${graphs.foldableCalls}), mixed-receiver reads ${graphs.mixedReads}`, + ); + for (const [pkg, e] of [...byPkg].sort((a, b) => a[0].localeCompare(b[0]))) { + const fields = e.objects.reduce((n, o) => n + o.fields.length, 0); + const rules = new Map(); + for (const m of e.misses) rules.set(m.rule, (rules.get(m.rule) ?? 0) + 1); + lines.push( + `${pkg}: ${e.objects.length} constant objects (${fields} fields); near misses ${[...rules].map(([r, n]) => `${r} ${n}`).join(", ") || "none"}`, + ); + for (const o of e.objects) { + if (referenced.has(o.site.atom)) continue; + lines.push(` graph ${loc(o.site.node)} ${describe(o, 0, new Set([o.site.atom]))}`); + } + for (const m of e.misses.filter((x) => x.rule !== "allocated-repeatedly").slice(0, 40)) { + lines.push( + ` near miss ${loc(m.site.node)}${m.key === null ? "" : ` .${m.key}`}: ${m.rule} (${m.detail})`, + ); + } + } + return lines; +} diff --git a/packages/compiler/src/frontend/shape-inference/index.ts b/packages/compiler/src/frontend/shape-inference/index.ts index 88c2100ee..402a788b1 100644 --- a/packages/compiler/src/frontend/shape-inference/index.ts +++ b/packages/compiler/src/frontend/shape-inference/index.ts @@ -3,6 +3,7 @@ import type * as ts from "../ts7/adapter.js"; import { analyzeShapes } from "./analyze.js"; import { buildShapeReport } from "./report.js"; +import { computeConstantGraphs, constantGraphReport } from "./constant-graphs.js"; export { analyzeShapes } from "./analyze.js"; export { buildShapeReport } from "./report.js"; @@ -13,9 +14,11 @@ export { buildShapeReport } from "./report.js"; export function reportShapes(program: ts.Program, filter: string): void { const start = performance.now(); const analysis = analyzeShapes(program, program.getTypeChecker()); - const report = buildShapeReport(analysis, filter); + // `const`: the constant object graphs (DESIGN-constant-objects.md, stage 1). + const lines = + filter === "const" + ? constantGraphReport(analysis, computeConstantGraphs(analysis)) + : buildShapeReport(analysis, filter).lines; const ms = (performance.now() - start).toFixed(0); - process.stderr.write( - `[shapes] ${ms} ms\n${report.lines.map((l) => `[shapes] ${l}`).join("\n")}\n`, - ); + process.stderr.write(`[shapes] ${ms} ms\n${lines.map((l) => `[shapes] ${l}`).join("\n")}\n`); } diff --git a/packages/compiler/test/constant-graphs.test.ts b/packages/compiler/test/constant-graphs.test.ts new file mode 100644 index 000000000..258c83b08 --- /dev/null +++ b/packages/compiler/test/constant-graphs.test.ts @@ -0,0 +1,106 @@ +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { compile } from "../src/index.js"; + +// A library in the pre-module idiom: guarded namespace declarations, a +// revealing module, an ES5 constructor with prototype methods, and the +// cases that keep an object from being constant. +const packageSource = ` +var lib = lib || {}; +lib.math = lib.math || {}; +lib.math.TAU = 6.283185307179586; +lib.math.half = function (x) { return x / 2; }; +lib.text = (function () { + function upper(s) { return s.toUpperCase(); } + return { upper: upper }; +})(); +lib.Point = function (x, y) { this.x = x; this.y = y; }; +lib.Point.prototype.norm = function () { return Math.sqrt(this.x * this.x + this.y * this.y); }; +lib.counter = { hits: 0 }; +lib.bump = function () { lib.counter.hits++; }; +lib.cache = { table: null }; +lib.lookup = function () { return lib.cache.table || (lib.cache.table = [1, 2, 3]); }; +lib.scratch = { a: 1, b: 2 }; +delete lib.scratch.b; +lib.shared = { value: 1 }; +globalThis.sharedFromLib = lib.shared; +function option(name) { return { name: name }; } +lib.options = { first: option("a"), second: option("b") }; +module.exports = { lib }; +`; + +const mainSource = ` +const { lib } = require("ns-fixture"); +let total = 0; +for (let i = 0; i < 3; i++) { + total += lib.math.half(i) + new lib.Point(i, i).norm() + lib.lookup().length; + lib.bump(); +} +console.log(total, lib.text.upper("x"), lib.counter.hits); +`; + +let dir: string; +let saved: string | undefined; +beforeEach(async () => { + dir = await mkdtemp(join(tmpdir(), "scriptc-constant-graphs-")); + const pkg = join(dir, "node_modules", "ns-fixture"); + await mkdir(pkg, { recursive: true }); + await writeFile( + join(pkg, "package.json"), + JSON.stringify({ name: "ns-fixture", version: "1.0.0", main: "index.js", types: "index.d.ts" }), + ); + await writeFile(join(pkg, "index.js"), packageSource); + await writeFile(join(pkg, "index.d.ts"), "declare const lib: any;\nexport { lib };\n"); + await writeFile( + join(dir, "package.json"), + JSON.stringify({ dependencies: { "ns-fixture": "1.0.0" } }), + ); + await writeFile(join(dir, "main.cjs"), mainSource); + saved = process.env["SCRIPTC_DEBUG_SHAPES"]; +}); +afterEach(async () => { + if (saved === undefined) delete process.env["SCRIPTC_DEBUG_SHAPES"]; + else process.env["SCRIPTC_DEBUG_SHAPES"] = saved; + vi.restoreAllMocks(); + await rm(dir, { recursive: true, force: true }); +}); + +async function report(): Promise { + process.env["SCRIPTC_DEBUG_SHAPES"] = "const"; + const lines: string[] = []; + vi.spyOn(process.stderr, "write").mockImplementation((chunk: string | Uint8Array) => { + const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); + for (const l of text.split("\n")) if (l.startsWith("[shapes] ")) lines.push(l.slice(9)); + return true; + }); + await compile(join(dir, "main.cjs"), { + outDir: dir, + outPath: join(dir, "main.ir.json"), + outputKind: "ir", + npmStatic: ["ns-fixture"], + }); + return lines; +} + +test("constant graphs: namespaces, revealing modules and prototypes, with near misses", async () => { + const lines = await report(); + const text = lines.join("\n"); + // The namespace graph: guarded declarations, the revealing module's + // table, and the constructor function. + expect(text).toMatch( + /graph ns-fixture\/index\.js:\d+ \{lib: \{math: \{TAU: number, half: fn\}, text: \{upper: fn\}/, + ); + // The constructor's prototype object, built by member stores at init. + expect(text).toMatch(/graph ns-fixture\/index\.js:\d+ \{constructor: \{\}, norm: fn\}/); + // A field written by a function called from a loop. + expect(text).toMatch(/near miss ns-fixture\/index\.js:12 \.hits: written-after-init/); + // A lazily filled cache. + expect(text).toMatch(/near miss ns-fixture\/index\.js:14 \.table: lazy-cache/); + // A deleted key, and an object stored into the global object. + expect(text).toMatch(/near miss ns-fixture\/index\.js:16: reflective-write \(delete/); + expect(text).toMatch(/near miss ns-fixture\/index\.js:18: escapes/); + // A factory called from two init sites: one object per call site. + expect(text).toMatch(/near miss ns-fixture\/index\.js:20: init-factory \(.*2 call sites\)/); +}); From 5e7338ad2c5ef69402c9133bf5f18e520e40adcb Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:56:52 -0500 Subject: [PATCH 091/395] Scan checker reply strings with many escapes in one pass scriptc_ts7_json_value_end searched for the next quote again after each backslash escape in a string, so a string with many escapes was scanned once per escape. The quote it found now stays the candidate end until an escape skips it, and each byte of a string is scanned once by each search. A test scans strings of 2,000,000 newline, backslash and quote escapes, alone and inside an object, and compares the ends with jsonValueEnd's; before the change it does not finish within 60 s. --- packages/compiler/native/ts7-process.c | 6 +- .../test/native-json-value-end.test.ts | 75 +++++++++++++------ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index 3333cf4e8..eb90f8bd3 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -418,9 +418,10 @@ int32_t scriptc_ts7_json_value_end(const uint8_t *bytes, size_t size, uint32_t s uint8_t byte = bytes[index]; if (byte == '"') { index++; + // The quote found stays the candidate end until an escape skips it, so + // every byte of the string is scanned once by each search. + const uint8_t *quote = index < size ? memchr(bytes + index, '"', size - index) : NULL; for (;;) { - if (index >= size) return -1; - const uint8_t *quote = memchr(bytes + index, '"', size - index); if (quote == NULL) return -1; const uint8_t *escape = memchr(bytes + index, '\\', (size_t)(quote - (bytes + index))); if (escape == NULL) { @@ -428,6 +429,7 @@ int32_t scriptc_ts7_json_value_end(const uint8_t *bytes, size_t size, uint32_t s break; } index = (size_t)(escape - bytes) + 2; + if (bytes + index > quote) quote = index < size ? memchr(bytes + index, '"', size - index) : NULL; } index++; if (depth == 0) return (int32_t)index; diff --git a/packages/compiler/test/native-json-value-end.test.ts b/packages/compiler/test/native-json-value-end.test.ts index 9d9cc3950..0818fbab6 100644 --- a/packages/compiler/test/native-json-value-end.test.ts +++ b/packages/compiler/test/native-json-value-end.test.ts @@ -76,31 +76,45 @@ function cases(): { bytes: Uint8Array; start: number }[] { return out; } +/** The native scanner's results for `all`, from a harness built in `dir`. */ +function nativeEnds( + dir: string, + all: { bytes: Uint8Array; start: number }[], + timeout?: number, +): number[] { + const harness = join(dir, "scan" + (process.platform === "win32" ? ".exe" : "")); + execFileSync("clang", [ + "-std=c11", + "-Wall", + "-Wextra", + "-Werror", + ...(process.env["SCRIPTC_SAN"] === "1" ? ["-fsanitize=address,undefined"] : []), + join(sources, "ts7-json.test.c"), + join(sources, "ts7-process.c"), + "-o", + harness, + ]); + const chunks: Buffer[] = []; + for (const { bytes, start } of all) { + const header = Buffer.alloc(8); + header.writeUInt32LE(bytes.length, 0); + header.writeUInt32LE(start, 4); + chunks.push(header, Buffer.from(bytes)); + } + const output = execFileSync(harness, { + input: Buffer.concat(chunks), + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + ...(timeout === undefined ? {} : { timeout }), + }); + return output.trim().split(/\r?\n/).map(Number); +} + test("the native scanner finds the value ends jsonValueEnd finds", () => { const dir = mkdtempSync(join(tempRoot, "scriptc-ts7-json-")); try { - const harness = join(dir, "scan" + (process.platform === "win32" ? ".exe" : "")); - execFileSync("clang", [ - "-std=c11", - "-Wall", - "-Wextra", - "-Werror", - ...(process.env["SCRIPTC_SAN"] === "1" ? ["-fsanitize=address,undefined"] : []), - join(sources, "ts7-json.test.c"), - join(sources, "ts7-process.c"), - "-o", - harness, - ]); const all = cases(); - const chunks: Buffer[] = []; - for (const { bytes, start } of all) { - const header = Buffer.alloc(8); - header.writeUInt32LE(bytes.length, 0); - header.writeUInt32LE(start, 4); - chunks.push(header, Buffer.from(bytes)); - } - const output = execFileSync(harness, { input: Buffer.concat(chunks), encoding: "utf8" }); - const actual = output.trim().split(/\r?\n/).map(Number); + const actual = nativeEnds(dir, all); expect(actual.length).toBe(all.length); for (let k = 0; k < all.length; k++) { const { bytes, start } = all[k]!; @@ -116,3 +130,22 @@ test("the native scanner finds the value ends jsonValueEnd finds", () => { rmSync(dir, { recursive: true, force: true }); } }); + +test("the native scanner reads strings with many escapes in one pass", () => { + const dir = mkdtempSync(join(tempRoot, "scriptc-ts7-json-")); + try { + // Strings of 2,000,000 escapes each: newlines, backslashes and quotes, + // alone and as a member of an object. + const strings = ["\\n", "\\\\", '\\"'].map((escape) => `"${escape.repeat(2_000_000)}"`); + const all = [ + ...strings.map((text) => ({ bytes: Buffer.from(text, "utf8"), start: 0 })), + ...strings.map((text) => ({ bytes: Buffer.from(`{"value":${text}},1`, "utf8"), start: 0 })), + ]; + // A scan that searched for the closing quote again after each escape + // would take minutes here. + const actual = nativeEnds(dir, all, 60_000); + expect(actual).toEqual(all.map(({ bytes, start }) => expectedEnd(bytes, start))); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); From ebeec4818d680f09278c51aea8ae5a9126dda032 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:58:14 -0500 Subject: [PATCH 092/395] Prove 32-bit products exact only below 2^53 The exactness test for a product's 32-bit form multiplied the operands' magnitude bounds in a double and accepted a result of at most 2^53. A bound of 2^53 + 1 (3 * 3002399751580331) rounds to 2^53 there, so a product the double rounds took the integer multiply's low bits: `(3 * 3002399751580331) | 0` printed 1 where Node prints 0. The test is now strict: a bound below 2^53 is exact, and no bound of 2^53 or more rounds below it. --- .../src/backend/llvm/wrapping-sums.ts | 8 +++--- .../8195-wrapping-products-near-2-53.ts.json | 6 +++++ .../8195-wrapping-products-near-2-53.ts | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json create mode 100644 tests/corpus/8195-wrapping-products-near-2-53.ts diff --git a/packages/compiler/src/backend/llvm/wrapping-sums.ts b/packages/compiler/src/backend/llvm/wrapping-sums.ts index e93d66909..5b487d809 100644 --- a/packages/compiler/src/backend/llvm/wrapping-sums.ts +++ b/packages/compiler/src/backend/llvm/wrapping-sums.ts @@ -176,12 +176,14 @@ export function emitWrappingSum( } /** Whether every product of a value in `left` and one in `right` is an - * integer of magnitude at most 2^53, which the double product holds - * exactly. */ + * integer of magnitude below 2^53, which the double product holds exactly. + * The bound's own product is computed in a double: a bound of at least + * 2^53 never rounds below 2^53, while 2^53 + 1 (3 * 3002399751580331) + * rounds to 2^53, so the test is strict. */ function exactProduct(left: IntegerRange, right: IntegerRange): boolean { const magnitude = (range: IntegerRange): number => Math.max(Math.abs(range.min), Math.abs(range.max)); - return magnitude(left) * magnitude(right) <= 2 ** 53; + return magnitude(left) * magnitude(right) < 2 ** 53; } /** The number locals of `fn` that only hold exact integers (never -0) or NaN, with the range of diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json new file mode 100644 index 000000000..234bcebd5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8195-wrapping-products-near-2-53.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8195-wrapping-products-near-2-53.ts b/tests/corpus/8195-wrapping-products-near-2-53.ts new file mode 100644 index 000000000..c42104290 --- /dev/null +++ b/tests/corpus/8195-wrapping-products-near-2-53.ts @@ -0,0 +1,25 @@ +// Products that reach bitwise operators with operands whose ranges are +// known, around 2^53: below it the double product is exact, and from it on +// the double product rounds, so the 32-bit result comes from the rounded +// value (3 * 3002399751580331 is 2^53 + 1, which rounds to 2^53). +const pairs: [number, number][] = [ + [3, 3002399751580331], + [-3, 3002399751580331], + [2 ** 26, 2 ** 27], + [94906265, 94906267], + [94906267, 94906267], + [7, 1286742750677284], + [65535, 137443147777], +]; +for (const [a, b] of pairs) console.log(a, b, (a * b) | 0, (a * b) >>> 0, (a * b) ^ 1, (a * b) << 3); + +// The same products with literal operands, which the compiler sees whole. +console.log((3 * 3002399751580331) | 0, (3 * 3002399751580331) >>> 0); +console.log((-3 * 3002399751580331) | 0, (2 ** 26 * 2 ** 27) | 0); +console.log((94906267 * 94906267) | 0, (94906265 * 94906267) >>> 0); + +// A counted loop whose counter multiplies a constant past 2^53. +const step = 1286742750677284; +let h = 0; +for (let k = 5; k < 9; k++) h = (h + ((k * step) | 0)) | 0; +console.log(h); From d813fdafd32dfc999ca38754585f26d40acb34ae Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:00:06 -0500 Subject: [PATCH 093/395] Compare termination signals and resolve the Node checkout path in the test/parallel runner A run that a signal ends has a null exit code, so the runner recorded a pass when Node and the executable ended by different signals, or when one ended by a signal and the other with an exit code compared as null. The verdict now compares both the exit code and the signal (verdict.mjs), and records Node's signal in nodeExit as it does for the executable. A relative --node path also made the minimal lane's fixtures symlink relative to the copied tree, where it does not resolve. The checkout path is now resolved first. tests/harness/node-parallel-runner.test.ts covers both: the termination comparison, and a relative --node path whose fixtures resolve from the minimal tree. --- tests/harness/node-parallel-runner.test.ts | 52 ++++++++++++++++++++++ tests/node-parallel/README.md | 2 +- tests/node-parallel/run.mjs | 13 +++--- tests/node-parallel/verdict.mjs | 13 ++++++ 4 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 tests/harness/node-parallel-runner.test.ts create mode 100644 tests/node-parallel/verdict.mjs diff --git a/tests/harness/node-parallel-runner.test.ts b/tests/harness/node-parallel-runner.test.ts new file mode 100644 index 000000000..a829c4d78 --- /dev/null +++ b/tests/harness/node-parallel-runner.test.ts @@ -0,0 +1,52 @@ +import { spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, relative, resolve } from "node:path"; +import { afterAll, expect, test } from "vitest"; +// @ts-expect-error -- a plain ESM helper of the runner script +import { sameTermination, termination } from "../node-parallel/verdict.mjs"; + +const repo = resolve(import.meta.dirname, "../.."); +const scratch = mkdtempSync(join(tmpdir(), "scriptc-node-parallel-")); +afterAll(() => rmSync(scratch, { recursive: true, force: true })); + +test("runs that end by different signals, or by a signal and an exit, differ", () => { + expect(sameTermination({ code: 0, signal: null }, { code: 0, signal: null })).toBe(true); + expect( + sameTermination({ code: null, signal: "SIGABRT" }, { code: null, signal: "SIGABRT" }), + ).toBe(true); + expect( + sameTermination({ code: null, signal: "SIGABRT" }, { code: null, signal: "SIGSEGV" }), + ).toBe(false); + expect(sameTermination({ code: 1, signal: null }, { code: null, signal: "SIGKILL" })).toBe(false); + expect(sameTermination({ code: 0, signal: null }, { code: 1, signal: null })).toBe(false); + expect(termination({ code: null, signal: "SIGSEGV" })).toBe("SIGSEGV"); + expect(termination({ code: 0, signal: null })).toBe(0); +}); + +test("a relative --node path links the checkout's fixtures into the minimal tree", () => { + const checkout = join(scratch, "node"); + mkdirSync(join(checkout, "test", "parallel"), { recursive: true }); + mkdirSync(join(checkout, "test", "fixtures"), { recursive: true }); + mkdirSync(join(checkout, "test", "common"), { recursive: true }); + writeFileSync(join(checkout, "test", "fixtures", "marker.txt"), "fixture\n"); + writeFileSync(join(checkout, "test", "common", "fixtures.js"), "module.exports = {};\n"); + const out = join(scratch, "out"); + const result = spawnSync( + process.execPath, + [ + join(repo, "tests/node-parallel/run.mjs"), + "--node", + relative(scratch, checkout), + "--pattern", + "^no-such-test$", + "--out", + out, + ], + { cwd: scratch, encoding: "utf8" }, + ); + expect(result.status, result.stderr).toBe(0); + const linked = join(out, "tree", "test", "fixtures", "marker.txt"); + expect(existsSync(linked)).toBe(true); + expect(readFileSync(linked, "utf8")).toBe("fixture\n"); +}); diff --git a/tests/node-parallel/README.md b/tests/node-parallel/README.md index 35af64464..c8ac12134 100644 --- a/tests/node-parallel/README.md +++ b/tests/node-parallel/README.md @@ -1,6 +1,6 @@ # Node test/parallel files -`run.mjs` builds files from a Node.js checkout's `test/parallel` with scriptc, runs each executable in that directory, and compares its exit status with Node's. It records a verdict per file: `pass` (same exit status), `fail` (different exit status), or `refused` (the build failed), with the first error line. +`run.mjs` builds files from a Node.js checkout's `test/parallel` with scriptc, runs each executable in that directory, and compares its exit status with Node's. It records a verdict per file: `pass` (the same exit code, or the same terminating signal), `fail` (a different exit code or signal), or `refused` (the build failed), with the first error line. ```bash git clone --depth 1 --branch v24.22.0 https://github.com/nodejs/node /path/to/node diff --git a/tests/node-parallel/run.mjs b/tests/node-parallel/run.mjs index 4f2230c6b..e7a1e3cec 100644 --- a/tests/node-parallel/run.mjs +++ b/tests/node-parallel/run.mjs @@ -14,6 +14,7 @@ import { import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; +import { sameTermination, termination } from "./verdict.mjs"; const here = dirname(fileURLToPath(import.meta.url)); const repo = resolve(here, "../.."); @@ -33,9 +34,9 @@ if (!values.node || !values.pattern || !["minimal", "node"].includes(values.comm ); process.exit(2); } -const nodeTest = existsSync(join(values.node, "test", "parallel")) - ? join(values.node, "test") - : values.node; +// Absolute, so the minimal lane's fixtures symlink resolves from the tree. +const nodeRoot = resolve(values.node); +const nodeTest = existsSync(join(nodeRoot, "test", "parallel")) ? join(nodeRoot, "test") : nodeRoot; const pattern = new RegExp(values.pattern); const out = resolve(values.out); mkdirSync(out, { recursive: true }); @@ -96,15 +97,15 @@ async function one(file) { cwd: dir, timeout: 900_000, }); - const result = { file, flags: flags.join(" "), nodeExit: node.code }; + const result = { file, flags: flags.join(" "), nodeExit: termination(node) }; if (build.code !== 0) { result.verdict = "refused"; result.cause = firstCause(build.stdout + build.stderr); return result; } const native = await run(exe, [], { cwd: dir, timeout: 120_000 }); - result.exit = native.code ?? native.signal; - result.verdict = native.code === node.code ? "pass" : "fail"; + result.exit = termination(native); + result.verdict = sameTermination(node, native) ? "pass" : "fail"; if (result.verdict !== "pass") result.cause = firstCause(native.stderr + native.stdout); return result; } diff --git a/tests/node-parallel/verdict.mjs b/tests/node-parallel/verdict.mjs new file mode 100644 index 000000000..392238f44 --- /dev/null +++ b/tests/node-parallel/verdict.mjs @@ -0,0 +1,13 @@ +// How two runs of one test file compare (run.mjs). + +/** Whether Node and the executable ended the same way: the same exit code, + * or the same terminating signal (an exit code is null when a signal ended + * the process). */ +export function sameTermination(node, native) { + return node.code === native.code && node.signal === native.signal; +} + +/** An exit code, or the signal that ended the process. */ +export function termination(result) { + return result.code ?? result.signal; +} From 190fb8d58e75f0165e7be2b973bc32a802a3aac8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:01:33 -0500 Subject: [PATCH 094/395] Mix weak-collection hashes in 64 bits on 32-bit targets uintptr_t is 32 bits on wasm32, where the weak-collection hashes shifted it by 33 and 56 bits. The hashes now widen keys, owners and kinds to uint64_t before mixing; 64-bit targets compute the same values as before. --- packages/runtime/src/scr_json.c | 11 +++++---- packages/runtime/test/json.test.ts | 31 ++++++++++++++++++++++++- tests/harness/wasi-differential.test.ts | 3 +++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index afe3315de..7df866a4c 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -16157,16 +16157,19 @@ typedef struct { void *key; ScrWeakEntry *head; } ScrWeakKeySlot; static SCR_TL ScrWeakKeySlot *scr_weak_keys; static SCR_TL size_t scr_weak_keys_mask, scr_weak_keys_used; -static inline size_t scr_weak_mix(uintptr_t bits) { +/* Mixed in 64 bits on every target: uintptr_t is 32 bits on wasm32, where + * these shifts would exceed its width. */ +static inline size_t scr_weak_mix(uint64_t bits) { bits ^= bits >> 33; - bits *= 0xff51afd7ed558ccdull; + bits *= UINT64_C(0xff51afd7ed558ccd); bits ^= bits >> 29; return (size_t)bits; } static inline size_t scr_weak_entry_hash(const ScrWeakMap *owner, const void *key, unsigned kind) { - return scr_weak_mix((uintptr_t)key ^ ((uintptr_t)owner * 0x9E3779B97F4A7C15ull) ^ ((uintptr_t)kind << 56)); + return scr_weak_mix((uint64_t)(uintptr_t)key ^ ((uint64_t)(uintptr_t)owner * UINT64_C(0x9E3779B97F4A7C15)) ^ + ((uint64_t)kind << 56)); } -static inline size_t scr_weak_key_hash(const void *key) { return scr_weak_mix((uintptr_t)key); } +static inline size_t scr_weak_key_hash(const void *key) { return scr_weak_mix((uint64_t)(uintptr_t)key); } static void scr_weak_index_put(ScrWeakEntry *entry) { size_t j = scr_weak_entry_hash(entry->owner, entry->key, entry->kind) & scr_weak_index_mask; diff --git a/packages/runtime/test/json.test.ts b/packages/runtime/test/json.test.ts index 3315cb415..f83f5ad65 100644 --- a/packages/runtime/test/json.test.ts +++ b/packages/runtime/test/json.test.ts @@ -1,4 +1,4 @@ -import { execFile } from "node:child_process"; +import { execFile, execFileSync } from "node:child_process"; import { mkdir } from "node:fs/promises"; import { join } from "node:path"; import { promisify } from "node:util"; @@ -85,3 +85,32 @@ test("json runtime: parser, dyn, dynCheck failure path, stringify buffer", async ].join("\n"), ); }); + +function zigOnPath(): boolean { + try { + execFileSync("zig", ["version"], { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +// 32-bit uintptr_t: the weak-collection hashes mix in 64 bits, so no shift +// exceeds the width of its operand on wasm32. +test.skipIf(!zigOnPath())("json runtime builds for wasm32 without oversized shifts", async () => { + await execFileAsync("zig", [ + "cc", + "-target", + "wasm32-wasi", + "-std=c11", + "-O2", + "-D_GNU_SOURCE", + "-D_WASI_EMULATED_SIGNAL", + "-D_WASI_EMULATED_PROCESS_CLOCKS", + "-Werror=shift-count-overflow", + "-c", + "-o", + join(testDir, "build", "scr_json.wasm32.o"), + join(testDir, "../src/scr_json.c"), + ]); +}); diff --git a/tests/harness/wasi-differential.test.ts b/tests/harness/wasi-differential.test.ts index 5d814b384..66c2934f2 100644 --- a/tests/harness/wasi-differential.test.ts +++ b/tests/harness/wasi-differential.test.ts @@ -313,6 +313,9 @@ describe.skipIf(!zigOnPath())("wasm32-wasi differential", () => { // ThreadSlots blocks and pages hold 32-bit pointers. "5727-thread-slots-basics.ts", "5729-thread-slots-values.ts", + // Weak-collection hashes mix 32-bit pointers in 64 bits. + "4591-weak-keys-union-records.ts", + "7742-weak-collections-many-keys.ts", ])("the LLVM target runs %s byte-identically to Node", async (fixture) => { const entry = join(repoRoot, "tests/corpus", fixture); const outDir = await mkdtemp("/tmp/scriptc-wasi-"); From 1d579ea5a2ba93fb7073e0656720cc9fe91e12e9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:05:19 -0500 Subject: [PATCH 095/395] Check the identity of the open file a runtime pack artifact is read from Staging stat'ed an artifact's path, read the path, and stat'ed it again. When the path was a symbolic link switched to another file of the same size during the read and back before the second stat, both identities were those of the verified file, so the bytes of the other file were staged without hashing. Staging now opens the artifact once and takes both identities from the open file with fstat, so they describe the file the bytes come from. A test switches the artifact's link to a same-sized file during the read and checks that the staged copy has the verified bytes. --- .../src/backend/runtime-pack-native.test.ts | 50 ++++++++++++++++++- .../src/backend/runtime-pack-native.ts | 20 ++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/backend/runtime-pack-native.test.ts b/packages/compiler/src/backend/runtime-pack-native.test.ts index 723deba39..293d814b8 100644 --- a/packages/compiler/src/backend/runtime-pack-native.test.ts +++ b/packages/compiler/src/backend/runtime-pack-native.test.ts @@ -1,5 +1,14 @@ import { createHash } from "node:crypto"; -import { mkdirSync, readFileSync, renameSync, statSync, utimesSync, writeFileSync } from "node:fs"; +import { + mkdirSync, + readFileSync, + renameSync, + statSync, + symlinkSync, + unlinkSync, + utimesSync, + writeFileSync, +} from "node:fs"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -21,6 +30,10 @@ vi.mock("node:crypto", async (original) => { const crypto = await original(); return { ...crypto, createHash: vi.fn(crypto.createHash) }; }); +vi.mock("node:fs", async (original) => { + const fs = await original(); + return { ...fs, readFileSync: vi.fn(fs.readFileSync) }; +}); const dirs: string[] = []; afterEach(async () => @@ -192,3 +205,38 @@ test("staging does not hash an unchanged artifact again", async () => { expect(hashes() - start).toBe(firstStage - 1); expect(readFileSync(staged.runtimeObjects[0]!, "utf8")).toBe("base object"); }); + +test.skipIf(process.platform === "win32")( + "staging reads an artifact's bytes from the file whose identity it checks", + async () => { + const f = await packFixture(); + // The artifact path is a symbolic link to the verified file. + const real = f.object + ".real"; + renameSync(f.object, real); + symlinkSync(real, f.object); + const selection = f.select(); + f.stage(selection); + // During the next read the link names another file of the same size, + // and it names the verified file again before the read ends. + const other = f.object + ".other"; + writeFileSync(other, "x".repeat(statSync(real).size)); + const read = vi.mocked(readFileSync); + const original = read.getMockImplementation()!; + read.mockImplementation(((source: Parameters[0], ...rest: unknown[]) => { + if (typeof source === "string" && !source.endsWith("base.o")) + return (original as (...args: unknown[]) => unknown)(source, ...rest); + read.mockImplementation(original); + unlinkSync(f.object); + symlinkSync(other, f.object); + try { + return (original as (...args: unknown[]) => unknown)(source, ...rest); + } finally { + unlinkSync(f.object); + symlinkSync(real, f.object); + } + }) as typeof readFileSync); + const staged = f.stage(selection); + expect(read.getMockImplementation()).toBe(original); + expect(readFileSync(staged.runtimeObjects[0]!, "utf8")).toBe("base object"); + }, +); diff --git a/packages/compiler/src/backend/runtime-pack-native.ts b/packages/compiler/src/backend/runtime-pack-native.ts index f51b24221..0fff34455 100644 --- a/packages/compiler/src/backend/runtime-pack-native.ts +++ b/packages/compiler/src/backend/runtime-pack-native.ts @@ -9,11 +9,13 @@ import { closeSync, constants, existsSync, + fstatSync, mkdirSync, openSync, readFileSync, statSync, writeSync, + type Stats, } from "node:fs"; import { dirname, join } from "node:path"; import type { NativeLinkFeatures } from "./native-link-info.js"; @@ -62,7 +64,10 @@ interface FileIdentity { } function fileIdentity(path: string): FileIdentity { - const info = statSync(path); + return statsIdentity(statSync(path)); +} + +function statsIdentity(info: Stats): FileIdentity { return { dev: info.dev, ino: info.ino, @@ -256,9 +261,16 @@ function stageArtifact( let before: FileIdentity; let after: FileIdentity; try { - before = fileIdentity(source); - bytes = readFileSync(source); - after = fileIdentity(source); + // The identities are those of the open file the bytes come from, also + // when the path is a symbolic link that changes during the read. + const input = openSync(source, constants.O_RDONLY); + try { + before = statsIdentity(fstatSync(input)); + bytes = readFileSync(input); + after = statsIdentity(fstatSync(input)); + } finally { + closeSync(input); + } } catch { throw new RuntimePackError(`runtime pack artifact is missing: ${artifact.path}`, "invalid"); } From 70f565177e1823c1a67853b9fe46da50e8572fae Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:05:36 -0500 Subject: [PATCH 096/395] Count profile-annotated functions when the annotation runs The match report counted the functions with profile counts after the whole pipeline, so an annotated function that inlining or dead code elimination later removed was not counted, while the annotation's mismatch and missing-function warnings were. A pass-instrumentation callback now counts the annotated functions right after PGOInstrumentationUse runs, in the one-module and the partitioned pipelines. A new test builds helpers that reach the annotation and are then inlined into main and deleted, and checks that the report counts them. --- native/llvm-codegen/src/emit.cpp | 23 +++- packages/compiler/test/native-profile.test.ts | 100 +++++++++++++++++- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 35d7ce4f0..14bcd1522 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -296,6 +296,7 @@ std::optional inspectProfile(const EmitOptions &Options) { // Owns the analysis managers a pipeline needs for one module. struct Pipeline { + PassInstrumentationCallbacks Callbacks; LoopAnalysisManager LAM; FunctionAnalysisManager FAM; CGSCCAnalysisManager CGAM; @@ -304,13 +305,26 @@ struct Pipeline { Pipeline(TargetMachine &Machine, OptimizationLevel Level, std::optional Profile = std::nullopt) - : PB(&Machine, tuningFor(Level), std::move(Profile)) { + : PB(&Machine, tuningFor(Level), std::move(Profile), &Callbacks) { PB.registerModuleAnalyses(MAM); PB.registerCGSCCAnalyses(CGAM); PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); } + + // Adds to Count the functions the profile annotation gave counts to, as + // soon as that pass has run: inlining and dead code elimination later in + // the pipeline delete annotated functions. + void countAnnotated(std::atomic &Count) { + Callbacks.registerAfterPassCallback( + [&Count](StringRef PassID, Any IR, const PreservedAnalyses &) { + if (PassID != "PGOInstrumentationUse") + return; + if (const auto *M = llvm::any_cast(&IR)) + Count += annotatedFunctions(**M); + }); + } }; std::optional verify(Module &M, StringRef Code) { @@ -499,9 +513,9 @@ emitModule(Module &M, TargetMachine &Machine, OptimizationLevel Level, M.getContext().setDiagnosticHandler( std::make_unique(Match)); Pipeline P(Machine, Level, std::move(Profile)); - P.PB.buildPerModuleDefaultPipeline(Level).run(M, P.MAM); if (Annotating) - Match.Annotated += annotatedFunctions(M); + P.countAnnotated(Match.Annotated); + P.PB.buildPerModuleDefaultPipeline(Level).run(M, P.MAM); if (std::optional Failure = verify(M, "post_optimization_verification_failed")) return Failure; @@ -716,6 +730,8 @@ compileOptimizedPartitions(const EmitOptions &Options, // Profile instrumentation or annotation happens here, before the thin // link; the post-link optimization reads the annotated weights. Pipeline P(*Machine, Level, profileOptions(Options)); + if (Annotating) + P.countAnnotated(PartitionMatch.Annotated); ModulePassManager MPM = P.PB.buildThinLTOPreLinkDefaultPipeline(Level); MPM.addPass(BitcodeWriterPass(Stream, /*ShouldPreserveUseListOrder=*/false, /*EmitSummaryIndex=*/true, @@ -723,7 +739,6 @@ compileOptimizedPartitions(const EmitOptions &Options, MPM.run(Mod, P.MAM); } if (Annotating) { - PartitionMatch.Annotated += annotatedFunctions(Mod); Match.add(PartitionMatch); // Stored before the summary, so a reused summary finds its counts. Cache.store(Key, "match", PartitionMatch.serialize()); diff --git a/packages/compiler/test/native-profile.test.ts b/packages/compiler/test/native-profile.test.ts index 53d077017..9d04739d0 100644 --- a/packages/compiler/test/native-profile.test.ts +++ b/packages/compiler/test/native-profile.test.ts @@ -110,6 +110,65 @@ function expectedExit(argc: number, edit = 0): number { return Number(total % 251n); } +const INLINED = 6; + +/** A program whose helpers are too large for the inliner that runs before the + * profile annotation and each called twice, so they reach the annotation, + * and which the profile-guided inliner then inlines into their hot call + * sites in `main` and deletes. */ +function inlinedProgram(): string { + const lines = ["; profiled program, helpers inlined after annotation"]; + for (let index = 0; index < INLINED; index++) { + lines.push( + `define internal i64 @g${index}(i64 %n) {`, + "entry:", + " %x0 = add i64 %n, " + index, + ); + for (let step = 1; step <= 40; step++) + lines.push(` %x${step} = ${step % 2 === 0 ? "xor" : "mul"} i64 %x${step - 1}, ${step + 3}`); + lines.push(" ret i64 %x40", "}"); + } + lines.push( + "define i32 @main(i32 %argc, ptr %argv) {", + "entry:", + " %wide = sext i32 %argc to i64", + " %limit = mul i64 %wide, 1000", + " br label %loop", + "loop:", + " %i = phi i64 [ 0, %entry ], [ %next, %loop ]", + " %total = phi i64 [ 0, %entry ], [ %grown, %loop ]", + ...Array.from({ length: INLINED }, (_, index) => [ + ` %a${index} = call i64 @g${index}(i64 %i)`, + ` %b${index} = call i64 @g${index}(i64 %a${index})`, + ` %t${index} = add i64 ${index === 0 ? "%total" : `%t${index - 1}`}, %b${index}`, + ]).flat(), + ` %grown = add i64 %t${INLINED - 1}, 0`, + " %next = add i64 %i, 1", + " %again = icmp ult i64 %next, %limit", + " br i1 %again, label %loop, label %exit", + "exit:", + " %low = urem i64 %grown, 251", + " %code = trunc i64 %low to i32", + " ret i32 %code", + "}", + ); + return lines.join("\n") + "\n"; +} + +function inlinedExit(argc: number): number { + const mask = (1n << 64n) - 1n; + const g = (index: number, n: bigint) => { + let x = (n + BigInt(index)) & mask; + for (let step = 1; step <= 40; step++) + x = step % 2 === 0 ? x ^ BigInt(step + 3) : (x * BigInt(step + 3)) & mask; + return x; + }; + let total = 0n; + for (let i = 0n; i < BigInt(argc * 1000); i++) + for (let index = 0; index < INLINED; index++) total = (total + g(index, g(index, i))) & mask; + return Number(total % 251n); +} + type EmitOptions = { outputs: number; name: string; @@ -119,12 +178,17 @@ type EmitOptions = { }; async function emit(dir: string, source: string, options: EmitOptions): Promise { + return (await emitReporting(dir, source, options)).outputs; +} + +/** `emit`, also returning the helper's stdout (its profile match report). */ +async function emitReporting(dir: string, source: string, options: EmitOptions) { const input = join(dir, `${options.name}.ll`); await writeFile(input, source); const outputs = Array.from({ length: options.outputs }, (_, index) => join(dir, `${options.name}.${index}.o`), ); - await execFileAsync(helper!, [ + const { stdout } = await execFileAsync(helper!, [ "emit", "--input", input, @@ -145,7 +209,7 @@ async function emit(dir: string, source: string, options: EmitOptions): Promise< ...(options.use === undefined ? [] : ["--profile-use", options.use]), ...(options.cache === undefined ? [] : ["--cache-dir", options.cache]), ]); - return outputs; + return { outputs, stdout }; } async function link(dir: string, objects: string[], name: string, instrumented = false) { @@ -181,12 +245,18 @@ async function merge(output: string, inputs: string[]) { /** An instrumented build of `source` run with 1 and 3 arguments, its raw * profiles merged into one indexed profile. */ -async function collect(dir: string, source: string, outputs: number, name: string) { +async function collect( + dir: string, + source: string, + outputs: number, + name: string, + expected: (argc: number) => number = expectedExit, +) { const raw = join(dir, `${name}-raw`); const objects = await emit(dir, source, { outputs, name, generate: join(raw, "%p.profraw") }); const executable = await link(dir, objects, name, true); - expect(await run(executable, 1)).toBe(expectedExit(1)); - expect(await run(executable, 3)).toBe(expectedExit(3)); + expect(await run(executable, 1)).toBe(expected(1)); + expect(await run(executable, 3)).toBe(expected(3)); const profiles = (await readdir(raw)).map((file) => join(raw, file)); expect(profiles).toHaveLength(2); const profile = join(dir, `${name}.profdata`); @@ -286,6 +356,26 @@ describe.runIf(helper !== null)("LLVM profile-guided optimization", () => { expect(await run(await link(dir, objects, "edited"), 2)).toBe(expectedExit(2, 7)); }); + test("the match report counts annotated functions that inlining removes", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-profile-match-")); + dirs.push(dir); + const { profile } = await collect(dir, inlinedProgram(), 1, "train", inlinedExit); + const { outputs, stdout } = await emitReporting(dir, inlinedProgram(), { + outputs: 1, + name: "guided", + use: profile, + }); + expect(await run(await link(dir, outputs, "guided"), 2)).toBe(inlinedExit(2)); + // Every g and main received counts, though optimization then + // inlined the helpers into main and deleted them. + expect(JSON.parse(stdout.trim().split("\n").at(-1)!)).toEqual({ + ok: true, + profile: { annotated: INLINED + 1, mismatched: 0, missing: 0, unknown_partitions: 0 }, + }); + const symbols = await execFileAsync("nm", [outputs[0]!]); + expect(symbols.stdout).not.toMatch(/\bg\d\b/); + }); + test("the profile keys the partition cache", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-profile-cache-")); dirs.push(dir); From 530bd9534ffc45ca8a29db5c971b533c407d55d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:10:00 -0500 Subject: [PATCH 097/395] Await the values of untyped sync pipeline sources An untyped pipeline source that is a sync iterable passed each value on as it came, so a promise reached the next stage as the promise: a byte-mode destination refused it and an object-mode one received it. for await over a sync iterator, which Node's pipeline pumps it with, awaits each value. The source's generator now awaits a value that is an object (a promise included) before yielding it, and a rejection closes the source, as Node's async-from-sync iterator does. --- .../src/frontend/lowering/lower-stream.ts | 44 ++++++++++++++++++- .../7504-pipeline-untyped-source-errors.mjs | 27 +++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 72bea5dd7..b519ce378 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3947,11 +3947,13 @@ function lowerDynIterableSource( const syncId = frame.local("sync", BOOL, false); const nextId = frame.local("next", DYN, false); const stepId = frame.local("step", DYN, true); + const valueId = frame.local("value", DYN, true); const activeId = frame.local("active", BOOL, true); const closeId = frame.local("close", DYN, false); const it = (): IrExpr => varRef(itId, DYN, loc); const sync = (): IrExpr => varRef(syncId, BOOL, loc); const step = (): IrExpr => varRef(stepId, DYN, loc); + const value = (): IrExpr => varRef(valueId, DYN, loc); const get = (obj: IrExpr, name: string): IrExpr => ({ kind: "dynKeyGet", value: obj, @@ -4011,6 +4013,7 @@ function lowerDynIterableSource( }, { kind: "varDecl", localId: nextId, init: get(it(), "next"), loc }, { kind: "varDecl", localId: stepId, init: dynUndefinedExpr(loc), loc }, + { kind: "varDecl", localId: valueId, init: dynUndefinedExpr(loc), loc }, { kind: "varDecl", localId: activeId, init: boolLit(false, loc), loc }, { kind: "tryCatch", @@ -4055,9 +4058,48 @@ function lowerDynIterableSource( loc, }, setActive(true), + { kind: "assign", localId: valueId, value: get(step(), "value"), loc }, + // for await over a sync iterator awaits each value + // (CreateAsyncFromSyncIterator): a promise or a thenable + // passes on its result, and a rejection closes the iterator + { + kind: "if", + cond: { + kind: "logical", + op: "&&", + left: sync(), + right: { + kind: "logical", + op: "||", + left: { kind: "dynTest", test: "promise", value: value(), type: BOOL, loc }, + right: { + kind: "logical", + op: "&&", + left: { + kind: "dynTest", + test: "nullish", + negated: true, + value: value(), + type: BOOL, + loc, + }, + right: { kind: "dynTest", test: "object", value: value(), type: BOOL, loc }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + then: [{ kind: "assign", localId: valueId, value: awaitDyn(value()), loc }], + else_: null, + loc, + }, { kind: "exprStmt", - expr: { kind: "yieldExpr", value: get(step(), "value"), type: VOID, loc }, + expr: { kind: "yieldExpr", value: value(), type: VOID, loc }, loc, }, ], diff --git a/tests/corpus/7504-pipeline-untyped-source-errors.mjs b/tests/corpus/7504-pipeline-untyped-source-errors.mjs index 9c677c011..3ee984547 100644 --- a/tests/corpus/7504-pipeline-untyped-source-errors.mjs +++ b/tests/corpus/7504-pipeline-untyped-source-errors.mjs @@ -1,6 +1,7 @@ // pipeline() over untyped iterable sources: a destination that fails closes // the source, backpressure waits for drain, an abort signal stops the pump, -// and async generator and Map sources reach object-mode destinations. +// async generator and Map sources reach object-mode destinations, and a +// sync source's promise values are awaited. import { pipeline, Writable, PassThrough } from "node:stream"; import { pipeline as pipelineP } from "node:stream/promises"; @@ -88,3 +89,27 @@ await new Promise((resolve) => { (err) => { console.log("map done", err); resolve(undefined); }, ); }); + +// sync sources whose values are promises: each value is awaited, as +// for await over a sync iterator does, and a rejection closes the source +await pipelineP( + pick([Promise.resolve("p1"), "p2", new Promise((r) => setTimeout(() => r("p3"), 1))]), + new Writable({ write(c, _e, cb) { console.log("byte write", String(c)); cb(); } }), +); +function* promised() { + try { + yield Promise.resolve({ n: 1 }); + yield Promise.reject(new Error("value rejected")); + yield "never"; + } finally { + console.log("promised finally"); + } +} +try { + await pipelineP( + pick(promised()), + new Writable({ objectMode: true, write(c, _e, cb) { console.log("object write", JSON.stringify(c)); cb(); } }), + ); +} catch (err) { + console.log("promised rejected", err.message); +} From 317162af78f367342ff8a06b46779f9c4e2b5e9d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:13:05 -0500 Subject: [PATCH 098/395] Count repeated calls through constructors, apply and per-value callbacks The constant-graph pass treats a function whose call edges all run once at initialization as init-only. Some calls recorded no edge or a once-only one: - `new F()` of an ES5 constructor and `f.apply(thisArg, args)` bound the arguments without a call edge, so a function also called directly once was init-only even when a loop constructed or applied it. - Callbacks of Array.from, toSorted, replace and replaceAll run once per element, comparison or match, but those methods were missing from the repeating callback set. - JSON.parse revivers, JSON.stringify replacers and toJSON methods run once per value; their edges at the JSON call site now count as repeated, including toJSON calls resolved after the call's handling. Objects these functions write or allocate were reported as constant, and their reads as foldable. constant-graphs.test.ts covers each case. --- .../src/frontend/shape-inference/analyze.ts | 15 +++++++- .../compiler/test/constant-graphs.test.ts | 37 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 98ec87099..8b1101da2 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -252,6 +252,10 @@ const LOOP_CALLBACK_METHODS: ReadonlySet = new Set([ "reduceRight", "flatMap", "sort", + "toSorted", + "from", + "replace", + "replaceAll", ]); export function analyzeShapes(program: ts.Program, checker: ts.TypeChecker): ShapeAnalysis { @@ -298,12 +302,17 @@ class Analyzer { this.siteCtx.set(site, { caller: this.currentFn, inLoop: this.loopDepth > 0 }); } + /** Builtin calls that invoke their callbacks once per value + * (JSON.parse revivers, JSON.stringify replacers and toJSON methods), + * which may run after the call's own handling. */ + private readonly repeatingSites = new Set(); + edgeFor(site: Node, callee: FunctionInfo): void { const ctx = this.siteCtx.get(site); this.calls.push({ caller: ctx?.caller ?? null, callee, - inLoop: (ctx?.inLoop ?? false) || this.forceLoop, + inLoop: (ctx?.inLoop ?? false) || this.forceLoop || this.repeatingSites.has(site), site, }); } @@ -2547,6 +2556,7 @@ class Analyzer { this.add(info.thisNode, inst); this.bindArgs(info, args, spread); this.edge(info.ret, dst); + this.edgeFor(e, info); } return; } @@ -2894,6 +2904,7 @@ class Analyzer { for (const p of info.params) if (args[1] !== undefined) this.iterateInto(args[1], p, site); this.edge(info.ret, dst); + this.edgeFor(site, info); } return; } @@ -3034,6 +3045,7 @@ class Analyzer { // result. const revived = this.newNode(); this.add(revived, this.top); + this.repeatingSites.add(site); if (args[1] !== undefined) this.on(args[1], (f) => { if (f.kind === "function") @@ -3057,6 +3069,7 @@ class Analyzer { // Their results are serialized in turn. const serialized = this.newNode(); this.childrenInto(serialized); + this.repeatingSites.add(site); this.edge(arg(0), serialized); const key = this.constNode(this.prim("string")); this.on(serialized, (v) => this.callToJSON(v, key, serialized, site)); diff --git a/packages/compiler/test/constant-graphs.test.ts b/packages/compiler/test/constant-graphs.test.ts index 258c83b08..a89276ce7 100644 --- a/packages/compiler/test/constant-graphs.test.ts +++ b/packages/compiler/test/constant-graphs.test.ts @@ -104,3 +104,40 @@ test("constant graphs: namespaces, revealing modules and prototypes, with near m // A factory called from two init sites: one object per call site. expect(text).toMatch(/near miss ns-fixture\/index\.js:20: init-factory \(.*2 call sites\)/); }); + +// Functions that one call site runs repeatedly: an ES5 constructor and an +// apply call in a loop, and builtin callbacks that run once per element, +// match or parsed value. +const repeatedSource = ` +var lib = {}; +lib.newState = { v: 0 }; +function bumpNew() { lib.newState.v++; } +bumpNew(); +lib.applyState = { v: 0 }; +function bumpApply() { lib.applyState.v++; } +bumpApply(); +lib.repeat = function () { for (var i = 0; i < 3; i++) { new bumpNew(); bumpApply.apply(null, []); } }; +lib.seen = { match: null, pair: null, mapped: null, key: null }; +lib.replaced = "a-b".replace(/\\\\w/g, function (m) { lib.seen.match = { m: m }; return m; }); +lib.sorted = [3, 1, 2].toSorted(function (a, b) { lib.seen.pair = { a: a }; return a - b; }); +lib.mapped = Array.from([1, 2], function (x) { lib.seen.mapped = { x: x }; return x; }); +lib.revived = JSON.parse('{"a":{"b":1}}', function (k, v) { lib.seen.key = { k: k }; return v; }); +module.exports = { lib }; +`; + +test("constant graphs: constructors, apply calls and callbacks that one site runs repeatedly", async () => { + await writeFile(join(dir, "node_modules", "ns-fixture", "index.js"), repeatedSource); + await writeFile( + join(dir, "main.cjs"), + 'const { lib } = require("ns-fixture");\nlib.repeat();\nconsole.log(lib.newState.v, lib.applyState.v, lib.replaced, lib.sorted, lib.mapped, lib.revived);\n', + ); + const text = (await report()).join("\n"); + // `new bumpNew()` and `bumpApply.apply(...)` in a loop write after init. + expect(text).toMatch(/near miss ns-fixture\/index\.js:3 \.v: written-after-init/); + expect(text).toMatch(/near miss ns-fixture\/index\.js:6 \.v: written-after-init/); + // Each callback runs once per match, comparison, element or parsed value. + for (const field of ["match", "pair", "mapped", "key"]) + expect(text).toMatch( + new RegExp(`near miss ns-fixture/index\\.js:10 \\.${field}: written-after-init`), + ); +}); From 8f3c2c28d625015a93bb15acbc3cdb5fb1c20d9a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:14:15 -0500 Subject: [PATCH 099/395] Count comparator calls in AArch64 and Mach-O assembly The string comparison emission test matched only x86-64 `call` instructions to unprefixed symbols, so on AArch64 and macOS hosts it counted no calls and failed its positive-count check. It now also matches AArch64 `bl` and Mach-O's leading underscore, and it runs for the host and for aarch64-linux-gnu and x86_64-macos assembly, so every host checks all three spellings. --- tests/harness/string-compare-emission.test.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts index 2654f56ba..1a8fdddf6 100644 --- a/tests/harness/string-compare-emission.test.ts +++ b/tests/harness/string-compare-emission.test.ts @@ -11,17 +11,23 @@ import { compile } from "@scriptc/compiler"; const repoRoot = join(import.meta.dirname, "../.."); const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); -async function emittedAsm(name: string, source: string): Promise { - const key = createHash("sha256").update(source).digest("hex").slice(0, 16); +async function emittedAsm(name: string, source: string, target: string): Promise { + const key = createHash("sha256").update(target).update(source).digest("hex").slice(0, 16); const outDir = join(cacheDir, `string-compare-${key}`); mkdirSync(outDir, { recursive: true }); const file = join(outDir, `${name}.ts`); writeFileSync(file, source); + const saved = process.env["SCRIPTC_TARGET"]; + if (target === "host") delete process.env["SCRIPTC_TARGET"]; + else process.env["SCRIPTC_TARGET"] = target; const result = await compile(file, { outPath: join(outDir, name), outDir, backend: "llvm", outputKind: "asm", + }).finally(() => { + if (saved === undefined) delete process.env["SCRIPTC_TARGET"]; + else process.env["SCRIPTC_TARGET"] = saved; }); if (!result.ok) throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); @@ -36,12 +42,23 @@ words.sort(order); console.log(words.join(",")); `; +// Calls to the comparison runtime: `call`/`callq` on x86-64 and `bl` on +// AArch64, with Mach-O's leading underscore on macOS. const compareCalls = (asm: string): number => - asm.match(/\bcall[a-z]*\s+(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g)?.length ?? 0; + asm.match( + /\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g, + )?.length ?? 0; -test("a three-way string comparator compares its pair once", async () => { - const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"))); - const both = compareCalls(await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"))); - expect(once).toBeGreaterThan(0); - expect(both).toBe(once); -}); +// The host's assembly, and two other targets' so that every host checks the +// x86-64, AArch64 and Mach-O spellings. +test.each(["host", "aarch64-linux-gnu", "x86_64-macos"])( + "a three-way string comparator compares its pair once (%s)", + async (target) => { + const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"), target)); + const both = compareCalls( + await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"), target), + ); + expect(once).toBeGreaterThan(0); + expect(both).toBe(once); + }, +); From c98c1fe345a988113032d425cc02434ab1090f48 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:15:53 -0500 Subject: [PATCH 100/395] Test powers of two exactly before treating a product as exact The product of two exact integers was treated as exact everywhere when one of them was a constant power of two, tested with Number.isInteger(Math.log2). Math.log2 rounds to 52 for 2^52 - 1, so a product by 2^52 - 1 that passes 2^53 was compared as its integer instead of its rounded double. The test now compares 2 ** Math.round(Math.log2(value)) with the value. Corpus 7983 adds products by 2^52 - 1 compared with products by 4. --- .../compiler/src/backend/llvm/integer-values.ts | 4 +++- tests/corpus/7983-integer-compares.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/backend/llvm/integer-values.ts b/packages/compiler/src/backend/llvm/integer-values.ts index 497eb5b0d..5b8935036 100644 --- a/packages/compiler/src/backend/llvm/integer-values.ts +++ b/packages/compiler/src/backend/llvm/integer-values.ts @@ -143,7 +143,9 @@ export function wideArithmetic( function powerOfTwo(integer: LlInteger | null): boolean { if (integer === null || integer.range.min !== integer.range.max) return false; const value = Math.abs(integer.range.min); - return value >= 1 && value <= 2 ** 52 && Number.isInteger(Math.log2(value)); + // Math.log2 can round to an integer for a value that is not a power of + // two (2^52 - 1), so the candidate power is compared with the value. + return value >= 1 && value <= 2 ** 52 && 2 ** Math.round(Math.log2(value)) === value; } /** The i64 operands of a number compare that gives the doubles' result: diff --git a/tests/corpus/7983-integer-compares.ts b/tests/corpus/7983-integer-compares.ts index 1114df8a8..2603a39a1 100644 --- a/tests/corpus/7983-integer-compares.ts +++ b/tests/corpus/7983-integer-compares.ts @@ -123,6 +123,21 @@ function scaled(i: number, end: number): string { .join(""); } +function roundedProducts(): string { + // 3 * (2^52 - 1) is 13510798882111485, which rounds to 13510798882111484 = + // 4 * 3377699720527871: the products' doubles are equal and the integers + // are not. A product by 4 never rounds. + let equal = 0; + let below = 0; + for (let i = 1; i <= 8; i++) { + for (let j = 3377699720527869; j <= 3377699720527873; j++) { + if (i * 4503599627370495 === j * 4) equal++; + if (i * 4503599627370495 < j * 4) below++; + } + } + return `${equal} ${below}`; +} + const a = new Int32Array([5, -3, 2147483647, -2147483648, 5, 0]); const b = new Uint8Array([0, 255, 7, 128, 5, 0]); const c = new Uint32Array([4294967295, 2147483648, 2147483647, 0, 1, 4294967295]); @@ -148,3 +163,4 @@ console.log(descend(3, 100), descend(0, 1), descend(5, 0)); for (const i of [0, 1, p52 / 2 - 1, p52 / 2, p52 / 1024, p52 / 2 + 1, p52, -p52]) { console.log(scaled(i, safe), scaled(i, safe - 1), scaled(i, 2 ** 51), scaled(i, 4)); } +console.log(roundedProducts()); From 7405e93e5647e7020248608d47d4934a65105429 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:16:23 -0500 Subject: [PATCH 101/395] Convert compareArray's message only when the arrays differ --- tests/harness/test262.test.ts | 16 ++++++++++++++++ tests/test262/README.md | 2 +- tests/test262/harness.ts | 11 ++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tests/harness/test262.test.ts b/tests/harness/test262.test.ts index fd35a08fb..678685103 100644 --- a/tests/harness/test262.test.ts +++ b/tests/harness/test262.test.ts @@ -278,6 +278,22 @@ assert.compareArray([object, fn], [object, fn]); status: "fail", source: "assert.sameValue(1, 2, false);", }, + { + name: "passing assertions do not convert their message", + status: "pass", + source: ` +const message = { toString() { throw new Error("converted"); } }; +assert(true, message); +assert.sameValue(1, 1, message); +assert.notSameValue(1, 2, message); +assert.compareArray([1, 2], [1, 2], message); +`, + }, + { + name: "array mismatch with an object message", + status: "fail", + source: 'assert.compareArray([1], [2], { toString() { return "differs"; } });', + }, ]; describe(`Test262 host assertion contract${shardSuffix()}`, () => { diff --git a/tests/test262/README.md b/tests/test262/README.md index 5474fa2c3..231e074e6 100644 --- a/tests/test262/README.md +++ b/tests/test262/README.md @@ -32,7 +32,7 @@ The current profile is `static-adapted-v6`. It adapts strict scripts to standalo The unmodified upstream assertion helper currently reaches scriptc's JavaScript function-expando refusal. Test262 explicitly permits host implementations of harness functions in its [interpretation rules](https://github.com/tc39/test262/blob/7ab7fafa0003f73fc85c1b95d88094d33f7eb8bd/INTERPRETING.md#host-defined-functions). `harness.ts` supplies `assert`, `assert.sameValue`, `assert.notSameValue`, `assert.compareArray`, `assert.throws` for selected Error constructors, `$DONE`, and `Test262Error`. Scalar SameValue and scalar array-element comparisons use the statically compiled `node:assert/strict` implementation. Contract tests compare successful and failing assertions with the original Test262 helpers under Node, including NaN, signed zero, null/undefined, differing primitive types, and premature termination. -Conversion at a native function boundary can copy references, so one object, array, or function may reach the adapter as two distinct references. A copy cannot merge two references into one. `assert.sameValue`, `assert.notSameValue`, and `assert.compareArray` elements therefore treat identical references as the same value and a reference as different from every primitive. Two distinct references terminate with a distinct harness-refusal result, which tests cannot catch and turn into a pass. Assertion messages may be any value; the adapter converts them with `String`, as the upstream helpers do. Sparse array comparisons are not yet handled faithfully across that boundary. The adapted `propertyHelper.js` admits direct `verifyProperty` calls with an inline descriptor literal or `undefined`; reference-valued comparisons follow the same identity rule, while other property helpers and descriptor expressions remain excluded. The adapted `testTypedArray.js` keeps the upstream constructor lists, argument factories and `testWith*` helpers. It omits the `TypedArray` (%TypedArray%) binding, so tests that name it are excluded. Its resizable and immutable buffer factories run only where `ArrayBuffer.prototype` provides `resize` and `transferToImmutable`, as the upstream feature checks intend. Contract tests compare its successful and failing uses with the upstream helper under Node. Assertion aliases, mutation, reflection, additional assertion methods, and other `includes` helpers remain excluded. +Conversion at a native function boundary can copy references, so one object, array, or function may reach the adapter as two distinct references. A copy cannot merge two references into one. `assert.sameValue`, `assert.notSameValue`, and `assert.compareArray` elements therefore treat identical references as the same value and a reference as different from every primitive. Two distinct references terminate with a distinct harness-refusal result, which tests cannot catch and turn into a pass. Assertion messages may be any value; the adapter converts them with `String` when an assertion fails, as the upstream `assert`, `assert.sameValue`, `assert.notSameValue` and `assert.compareArray` do. Sparse array comparisons are not yet handled faithfully across that boundary. The adapted `propertyHelper.js` admits direct `verifyProperty` calls with an inline descriptor literal or `undefined`; reference-valued comparisons follow the same identity rule, while other property helpers and descriptor expressions remain excluded. The adapted `testTypedArray.js` keeps the upstream constructor lists, argument factories and `testWith*` helpers. It omits the `TypedArray` (%TypedArray%) binding, so tests that name it are excluded. Its resizable and immutable buffer factories run only where `ArrayBuffer.prototype` provides `resize` and `transferToImmutable`, as the upstream feature checks intend. Contract tests compare its successful and failing uses with the upstream helper under Node. Assertion aliases, mutation, reflection, additional assertion methods, and other `includes` helpers remain excluded. Raw tests, module variants, agents, resolution negatives, and runtime negatives remain excluded. Parse negatives pass only when a source-matched syntax or vetted JavaScript early-error diagnostic appears; TypeScript type errors and harness-only failures never satisfy a negative expectation. `$262`, dynamic evaluation, observable script-global state, and other unsupported host requirements are excluded as well. The runtime and compiler continue to enforce their existing refusal boundaries for admitted source. diff --git a/tests/test262/harness.ts b/tests/test262/harness.ts index b478b4555..dce1be99c 100644 --- a/tests/test262/harness.ts +++ b/tests/test262/harness.ts @@ -79,18 +79,19 @@ export namespace assert { if (outcome !== "pass") throw new Test262Error(text(message, "Expected exception")); } + /** Converts `message` only when the contents differ, as the upstream helper does. */ export function compareArray(actual: unknown, expected: unknown, message?: unknown): void { - const failure = text(message, "Expected matching array contents"); if (!Array.isArray(actual) || !Array.isArray(expected)) { scalar(actual); scalar(expected); - throw new Test262Error(failure); + throw new Test262Error(text(message, "Expected matching array contents")); } - if (actual.length !== expected.length) throw new Test262Error(failure); - for (let i = 0; i < actual.length; i++) { + let matches = actual.length === expected.length; + for (let i = 0; matches && i < actual.length; i++) { const left: unknown = i in actual ? actual[i] : undefined; const right: unknown = i in expected ? expected[i] : undefined; - if (!same(left, right)) throw new Test262Error(failure); + matches = same(left, right); } + if (!matches) throw new Test262Error(text(message, "Expected matching array contents")); } } From af33be8046e3a3833d44dcf63880082cccea4257 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:16:49 -0500 Subject: [PATCH 102/395] Treat a collection exported through an export list as escaping A reference in `export { m }` resolved to the export's alias symbol, not to the local binding, so the inference did not see the export and typed the collection from its local uses. An importing module then used it as an untyped Map and failed to compile (SC9001). An unrenamed export specifier now resolves to the binding it exports, which refuses the inference as `export const` does. Corpus 7985 adds `export { listed, renamed as alias }`. --- .../src/frontend/lowering/js-collection-types.ts | 12 +++++++++++- packages/compiler/test/js-collection-types.test.ts | 2 ++ tests/corpus/7985-untyped-collection-types.mjs | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/js-collection-types.ts b/packages/compiler/src/frontend/lowering/js-collection-types.ts index 8820ade4a..a4c182523 100644 --- a/packages/compiler/src/frontend/lowering/js-collection-types.ts +++ b/packages/compiler/src/frontend/lowering/js-collection-types.ts @@ -225,7 +225,17 @@ function referencedSymbol(lowerer: Lowerer, id: ts.Identifier): ts.Symbol | unde const parent = id.parent; if (parent !== undefined && ts.isShorthandPropertyAssignment(parent) && parent.name === id) return lowerer.checker.getShorthandAssignmentValueSymbol(parent); - return lowerer.checker.getSymbolAtLocation(id); + const symbol = lowerer.checker.getSymbolAtLocation(id); + // `export { m }` names the export, an alias of the local binding. + if ( + symbol !== undefined && + parent !== undefined && + ts.isExportSpecifier(parent) && + parent.propertyName === undefined && + (symbol.flags & ts.SymbolFlags.Alias) !== 0 + ) + return lowerer.checker.getAliasedSymbol(symbol); + return symbol; } /** Records a reference's use, or answers false when the reference is not diff --git a/packages/compiler/test/js-collection-types.test.ts b/packages/compiler/test/js-collection-types.test.ts index 88e2006b7..6a6f65798 100644 --- a/packages/compiler/test/js-collection-types.test.ts +++ b/packages/compiler/test/js-collection-types.test.ts @@ -54,6 +54,8 @@ test("untyped JavaScript collections take the types their uses prove, and only t "spread", "unread", "exported", + "listed", + "renamed", ]) expect(typed(types.get(untyped)!), untyped).toBe(false); } finally { diff --git a/tests/corpus/7985-untyped-collection-types.mjs b/tests/corpus/7985-untyped-collection-types.mjs index a60a2ea04..de4e581c2 100644 --- a/tests/corpus/7985-untyped-collection-types.mjs +++ b/tests/corpus/7985-untyped-collection-types.mjs @@ -114,3 +114,12 @@ console.log(unread.size, unread.get("none")); export const exported = new Map(); exported.set("e", 1); console.log(exported.get("e")); +// Exported through an export list, under its own name or another: an +// importer sees an untyped Map. +const listed = new Map(); +listed.set("l", 1); +console.log(listed.get("l")); +const renamed = new Map(); +renamed.set("r", 1); +console.log(renamed.get("r")); +export { listed, renamed as alias }; From a538ff42f0ab99581f2e65a00a03510781090b19 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:19:16 -0500 Subject: [PATCH 103/395] Read only value slots in lastIndexOf with an element-typed needle lastIndexOf with a needle of the element type read every non-hole index as the element type. An undefined or foreign element written through a dynamic view (or a hole that toSorted turned into undefined) has no element-typed payload, so the read aborted with "array index N is undefined". The search now reads an index only when it holds a value, and skips undefined and foreign elements, as Node's strict equality does for a string or number needle. --- .../lowering/containers/array-methods.ts | 32 ++++++++++++------- .../6068-foreign-elements-through-views.ts | 23 +++++++++++++ 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 4ef203edd..0e8b4fd84 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -1788,17 +1788,27 @@ function arraySearchHelper( }, loc, }; - const visitPresent: IrStmt[] = [ - { kind: "varDecl", localId: "value.0", init: value, loc }, - { - kind: "if", - cond: match, - then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }], - else_: null, - loc, - }, - advance, - ]; + const found: IrStmt = { + kind: "if", + cond: directNeedle ? equal : match, + then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }], + else_: null, + loc, + }; + // A direct needle reads only value slots: an undefined or foreign + // element written through a dynamic view has no element-typed payload. + const visitPresent: IrStmt[] = directNeedle + ? [ + { + kind: "if", + cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc }, + then: [{ kind: "varDecl", localId: "value.0", init: value, loc }, found], + else_: null, + loc, + }, + advance, + ] + : [{ kind: "varDecl", localId: "value.0", init: value, loc }, found, advance]; const skipHole: IrStmt[] = method === "lastIndexOf" ? [advance] diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 5181d7fe5..1b23f6fa7 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -177,3 +177,26 @@ holderView.push({ }, }); console.log(JSON.stringify(holder), JSON.stringify(holder)); + +// toSorted keeps the full length and reads holes as undefined; indexOf, +// lastIndexOf and includes skip the undefined and foreign elements. +const searched: string[] = ["b", "a"]; +const searchedView: unknown[] = searched; +searchedView[9] = 4; +searchedView[5] = undefined; +console.log(searched.indexOf("b"), searched.lastIndexOf("b"), searched.lastIndexOf("a", 8)); +console.log(searched.includes("a"), searched.lastIndexOf("z")); +const searchedSorted = searched.toSorted(); +console.log(searchedSorted.length, searchedSorted.slice(0, 4), searchedSorted.lastIndexOf("b")); +console.log(searchedSorted.indexOf("a"), searchedSorted.includes("b")); +const counts: number[] = [3, 1]; +const countsView: unknown[] = counts; +countsView[6] = "x"; +countsView[4] = 2; +const countsSorted = counts.toSorted(); +console.log(countsSorted.length, countsSorted, countsSorted.lastIndexOf(3), counts.lastIndexOf(2)); +const far: number[] = [5]; +const farView: unknown[] = far; +farView[70000] = "z"; +const farSorted = far.toSorted(); +console.log(farSorted.length, farSorted.slice(0, 3), farSorted.slice(69999), farSorted.lastIndexOf(5)); From 49d99d776493120858fef3a2971471f344deeb1e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:20:33 -0500 Subject: [PATCH 104/395] Rewrite tests of held character locals inside other optional reads An optional read tested against a one-unit literal is replaced by a test of its code unit, built from the read's receiver and index. When that receiver or index tested a held character local, as in `src[c === "a" ? pos + 1 : pos] === "b"`, the inner test was kept as it was and still compared the local as a string|undefined union while the local's binding became a number, which failed IR validation. The same held for a held local's own initializer. The receiver, index and temps of both reads are now rewritten first. --- .../compiler/src/ir/char-unit-locals.test.ts | 26 +++++++++++++++++++ packages/compiler/src/ir/char-unit-locals.ts | 18 +++++++++++-- .../corpus/7304-string-optional-char-reads.ts | 14 ++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/ir/char-unit-locals.test.ts b/packages/compiler/src/ir/char-unit-locals.test.ts index 378a7f0df..2cce2f32b 100644 --- a/packages/compiler/src/ir/char-unit-locals.test.ts +++ b/packages/compiler/src/ir/char-unit-locals.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { expect, test } from "vitest"; import { compile, deserializeModule } from "../index.js"; import { holdCharLocalsAsUnits } from "./char-unit-locals.js"; +import { validateModule } from "./validate.js"; import type { IrFunction, IrModule } from "./ir.js"; import { everyStmtList } from "./traverse.js"; @@ -166,3 +167,28 @@ console.log(kept("a b", 1), kept("ab", 0), kept("", 3), longer("ab", 0), twice(" // The local's own test still reads it as a union. expect(unionTests(fn(after, "kept"))).toContain("unionEq"); }); + +test("a held local tested inside another read's index or receiver becomes its unit", async () => { + const mod = holdCharLocalsAsUnits( + await lower(` +function count(src: string, other: string): number { + let n = 0; + for (let pos = 0; pos <= src.length; pos += 1.5) { + const c = src[pos]; + if (src[c === "a" ? pos + 1 : pos] === "b") n++; + if ((c === "x" ? other : src)[pos] === "y") n += 2; + const d = src[c === "c" ? pos - 1 : pos]; + if (d === "c") n += 10; + } + return n; +} +console.log(count("abcab", "xyz"), count("cab", "yy"), count("", "")); +`), + ); + const count = fn(mod, "count"); + expect(validateModule(mod)).toEqual([]); + expect(count.locals.find((l) => l.name === "c")!.type).toEqual({ kind: "f64" }); + expect(count.locals.find((l) => l.name === "d")!.type).toEqual({ kind: "f64" }); + // No test of either local reads it as a union. + expect(unionTests(count)).not.toContain("unionEq"); +}); diff --git a/packages/compiler/src/ir/char-unit-locals.ts b/packages/compiler/src/ir/char-unit-locals.ts index d8be86f08..03636fa87 100644 --- a/packages/compiler/src/ir/char-unit-locals.ts +++ b/packages/compiler/src/ir/char-unit-locals.ts @@ -421,18 +421,32 @@ function rewriteFunction(fn: IrFunction): IrFunction { const read = optionalCharRead(operand)!; if (read.plain || !optional.twice) { changed = true; - return optional.test(() => unitRead(read, operand.loc)); + const rewritten = rewriteRead(read); + return optional.test(() => unitRead(rewritten, operand.loc)); } } return mapExprChildren(e, rewriteExpr, rewriteStmt); }; + // A read's receiver and index may test other held locals. + const rewriteRead = (read: OptionalCharRead): OptionalCharRead => { + const [str, idx] = read.temps; + return { + ...read, + temps: [ + { ...str, init: rewriteExpr(str.init!) }, + { ...idx, init: rewriteExpr(idx.init!) }, + ], + receiver: rewriteExpr(read.receiver), + index: rewriteExpr(read.index), + }; + }; const rewriteStmt = (s: IrStmt): IrStmt => { if (s.kind === "varDecl" && held.has(s.localId) && s.init) { changed = true; const source = copyOf.get(s.localId); if (source !== undefined) return { ...s, init: unitRef(source, s.init.loc) }; const read = reads.get(s.localId); - if (read) return { ...s, init: unitRead(read, s.init.loc) }; + if (read) return { ...s, init: unitRead(rewriteRead(read), s.init.loc) }; if (s.init.kind === "strIntrinsic") return { ...s, diff --git a/tests/corpus/7304-string-optional-char-reads.ts b/tests/corpus/7304-string-optional-char-reads.ts index 13222219d..6e47abb75 100644 --- a/tests/corpus/7304-string-optional-char-reads.ts +++ b/tests/corpus/7304-string-optional-char-reads.ts @@ -119,3 +119,17 @@ for (const p of programs) console.log(JSON.stringify(p), JSON.stringify(new Lexe let text = ""; for (let k = 0; k < 30; k++) text += (k % 4 === 0 ? "==" : "w" + k) + " "; console.log(new Lexer(text).tokens().length, new Lexer(text).tokens().slice(0, 6).join(",")); + +// A character local tested inside another read's index or receiver. +function nested(src: string, other: string): number { + let n = 0; + for (let pos = 0; pos <= src.length; pos += 1.5) { + const c = src[pos]; + if (src[c === "a" ? pos + 1 : pos] === "b") n++; + if ((c === "x" ? other : src)[pos] === "y") n += 2; + const d = src[c === "c" ? pos - 1 : pos]; + if (d === "c") n += 10; + } + return n; +} +console.log(nested("abcab", "xyz"), nested("cab", "yy"), nested("xyxy", "yyyy"), nested("", "")); From 0c6ed44227b893057e75e5868d4b67d4d4df27eb Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:21:32 -0500 Subject: [PATCH 105/395] Match a nested union in full after a getter runs in a trusted build A union without recursion builds its matched arm without a visit session, so its getters and proxy traps ran without marking the conversion. A nested union then took its arm from the earlier match, even when the getter returned another value. A getter or trap that runs during a trusted build outside a session now marks it as well, and the outermost trusted build clears the mark when it ends. --- packages/runtime/src/scr_json.c | 24 ++++++++++++++++------ tests/harness/dyncheck.test.ts | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 2f9c9884e..b0f4c99c4 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -10824,9 +10824,10 @@ static SCR_TL const ScrDyn *scr_dyn_visit_held; /* Set while a union's builder builds the arm it matched in full * (scr_dyn_trust_enter). */ static SCR_TL bool scr_dyn_trusted; -/* Set once a getter or proxy trap has run during this session. Their code - * can return a value the match never saw or change one it saw, so no build - * in the rest of the session trusts an earlier match. */ +/* Set once a getter or proxy trap has run during this session, or during + * a trusted build outside any session. Their code can return a value the + * match never saw or change one it saw, so no build in the rest of the + * session, or of the outermost trusted build, trusts an earlier match. */ static SCR_TL bool scr_dyn_session_ran_code; /* Tables up to this many slots stay allocated for the next session. */ @@ -10978,7 +10979,10 @@ static void scr_dyn_visit_resume(const ScrDynVisitState *saved) { /* Calls a getter for a member read, outside any visit session. */ static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) { - if (scr_dyn_visit_depth == 0) return scr_dyn_call(getter, NULL, 0, "getter"); + if (scr_dyn_visit_depth == 0) { + if (scr_dyn_trusted) scr_dyn_session_ran_code = true; + return scr_dyn_call(getter, NULL, 0, "getter"); + } ScrDynVisitState saved; scr_dyn_visit_suspend(&saved); ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter"); @@ -10988,7 +10992,10 @@ static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) { /* A proxy's get trap for a member read, outside any visit session. */ static ScrDyn *scr_dyn_visit_proxy_get(const ScrDyn *d, ScrStr *name) { - if (scr_dyn_visit_depth == 0) return scr_dyn_proxy_get(d, name); + if (scr_dyn_visit_depth == 0) { + if (scr_dyn_trusted) scr_dyn_session_ran_code = true; + return scr_dyn_proxy_get(d, name); + } ScrDynVisitState saved; scr_dyn_visit_suspend(&saved); ScrDyn *result = scr_dyn_proxy_get(d, name); @@ -11002,7 +11009,12 @@ bool scr_dyn_trust_enter(void) { return previous; } -void scr_dyn_trust_exit(bool previous) { scr_dyn_trusted = previous; } +void scr_dyn_trust_exit(bool previous) { + scr_dyn_trusted = previous; + /* The outermost trusted build outside a session ends: later conversions + * may trust their own matches again. A session clears it on its exit. */ + if (!previous && scr_dyn_visit_depth == 0) scr_dyn_session_ran_code = false; +} bool scr_dyn_trusted_build(void) { return scr_dyn_trusted && !scr_dyn_session_ran_code; } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index eb0ea1a7f..f38d8237d 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -650,6 +650,42 @@ console.log("getter calls", calls); ]); }); + test("nested unions: a getter's value in a non-recursive union's build is matched in full", async () => { + // A union without recursion builds without a visit session. The getter + // still runs user code during the trusted build, so the nested union + // matches its second answer in full, and the failure names that union. + const r = await compileAndRun( + "flat-discriminated-getter", + `type Inner = { tag: "x"; n: number } | { tag: "y"; s: string }; +type Outer = { kind: "a"; inner: Inner } | { kind: "b"; other: number }; +const v = JSON.parse('{"kind":"a"}') as unknown; +let calls = 0; +Object.defineProperty(v, "inner", { + enumerable: true, + get() { + calls++; + return calls === 1 ? JSON.parse('{"tag":"x","n":1}') : JSON.parse('{"tag":"x","n":"bad"}'); + }, +}); +try { + const o = v as Outer; + console.log("ok", JSON.stringify(o)); +} catch (err) { + console.log((err as Error).message); +} +console.log("getter calls", calls); +const plain = JSON.parse('{"kind":"a","inner":{"tag":"y","s":"z"}}') as Outer; +console.log(JSON.stringify(plain)); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout.trimEnd().split("\n")).toEqual([ + "expected object | object at $.inner, got object", + "getter calls 2", + '{"kind":"a","inner":{"tag":"y","s":"z"}}', + ]); + }); + test("array where a record is expected names the mismatch at the root", async () => { const r = await compileAndRun( "root-kind", From e00d3fd9116431b8ce2ac4e52358aea7b51ccde2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:22:03 -0500 Subject: [PATCH 106/395] Check comparison operands and walk instanceof chains as Node does - The assert comparison methods read as function values (equal, strictEqual, deepEqual, partialDeepStrictEqual and their negations) throw Node's ERR_MISSING_ARGS TypeError, `The "actual" and "expected" arguments must be specified`, when called with fewer than two arguments, where `[assert.equal][0]()` passed. - Untyped `instanceof Object` starts at the value's prototype, so Object.prototype itself, whose prototype is null, is not an Object. - Untyped `instanceof Function` follows the whole prototype chain through objects and functions: an object whose prototype is a function, or a function whose prototype was set to such an object, is a Function. --- packages/runtime/src/scr_assert.c | 11 ++++ packages/runtime/src/scr_json.c | 64 ++++++++++++++++--- .../8078-instanceof-object-function.cjs | 22 +++++++ tests/corpus/8190-assert-function-values.cjs | 15 +++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/packages/runtime/src/scr_assert.c b/packages/runtime/src/scr_assert.c index 738a7729c..bf5d1c875 100644 --- a/packages/runtime/src/scr_assert.c +++ b/packages/runtime/src/scr_assert.c @@ -3066,8 +3066,18 @@ static ScrDyn *scr_av_fail(ScrClosure *c, ScrDyn *const *args, size_t argc) { return scr_av_done(msg); } +/* The comparison methods take both operands: Node's ERR_MISSING_ARGS + * TypeError otherwise. False with it pending. */ +static bool scr_av_operands(size_t argc) { + if (argc >= 2) return true; + static const char text[] = "The \"actual\" and \"expected\" arguments must be specified"; + scr_throw_error_msg_code(SCR_ERR_TYPE, text, sizeof text - 1, "ERR_MISSING_ARGS"); + return false; +} + /* equal / notEqual: Node's loose comparison, NaN equal to NaN. */ static ScrDyn *scr_av_loose(ScrDyn *const *args, size_t argc, bool negated) { + if (!scr_av_operands(argc)) return NULL; const ScrDyn *a = scr_av_arg(args, argc, 0), *b = scr_av_arg(args, argc, 1); bool nan = a->kind == SCR_DYN_NUM && b->kind == SCR_DYN_NUM && isnan(a->v.num) && isnan(b->v.num); bool equal = nan || scr_dyn_abstract_eq(a, b); @@ -3097,6 +3107,7 @@ static ScrDyn *scr_av_not_equal(ScrClosure *c, ScrDyn *const *a, size_t n) { enum { SCR_AV_STRICT, SCR_AV_DEEP_STRICT, SCR_AV_DEEP, SCR_AV_PARTIAL }; static ScrDyn *scr_av_compare(ScrDyn *const *args, size_t argc, int form, bool negated) { + if (!scr_av_operands(argc)) return NULL; ScrDyn *a = (ScrDyn *)scr_av_arg(args, argc, 0), *b = (ScrDyn *)scr_av_arg(args, argc, 1); ScrStr *msg; bool has; diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index ecc49e8cf..5414611b8 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -4684,12 +4684,18 @@ static bool scr_dyn_instanceof_object(const ScrDyn *value) { default: break; } + /* Object.prototype is on the chain from the value's own prototype on: + * Object.prototype itself has a null prototype, so it is not an Object. */ ScrDyn *current = scr_dyn_retain((ScrDyn *)value); - for (;;) { - if (current == scr_builtin_object_prototype) { + for (bool first = true;; first = false) { + if (!first && current == scr_builtin_object_prototype) { scr_dyn_release(current); return true; } + if (current == scr_builtin_object_prototype) { + scr_dyn_release(current); + return false; + } ScrDyn *next = NULL; if (current->kind == SCR_DYN_FUNC) { /* A function's prototype is Function.prototype, whose own is @@ -4719,15 +4725,53 @@ static bool scr_dyn_instanceof_object(const ScrDyn *value) { } } -/* `value instanceof Function`: every function, unless the program set its - * prototype to null or to a non-function. */ +/* `value instanceof Function`: whether a function is on the value's + * prototype chain. A function's own prototype is Function.prototype unless + * the program set it, so the walk ends true at the first function that + * kept its prototype; it follows set prototypes through functions and + * objects alike (`Object.create(function () {})` is a Function instance), + * and ends false at null or at a value without a chain the runtime walks. + * False with the exception pending when a prototype read throws. */ static bool scr_dyn_instanceof_function(const ScrDyn *value) { - if (value->kind != SCR_DYN_FUNC) return false; - if (!scr_closure_identity(value->v.fn.clo)->props) return true; - ScrDyn *table = scr_dyn_fn_properties((ScrDyn *)value); - bool result = table->prototype ? scr_dyn_instanceof_function(table->prototype) : !table->null_proto; - scr_dyn_release(table); - return result; + ScrDyn *current = NULL; + if (value->kind == SCR_DYN_FUNC) { + if (!scr_closure_identity(value->v.fn.clo)->props) return true; + ScrDyn *table = scr_dyn_fn_properties((ScrDyn *)value); + current = table->prototype ? scr_dyn_retain(table->prototype) : NULL; + bool unset = !table->prototype && !table->null_proto; + scr_dyn_release(table); + if (unset) return true; + } else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR) { + current = scr_dyn_get_prototype((ScrDyn *)value); + if (!current) return false; /* the read threw: pending */ + } + while (current && current->kind != SCR_DYN_NULL) { + ScrDyn *next = NULL; + if (current->kind == SCR_DYN_FUNC) { + if (!scr_closure_identity(current->v.fn.clo)->props) { + scr_dyn_release(current); + return true; + } + ScrDyn *table = scr_dyn_fn_properties(current); + bool unset = !table->prototype && !table->null_proto; + next = table->prototype ? scr_dyn_retain(table->prototype) : NULL; + scr_dyn_release(table); + if (unset) { + scr_dyn_release(current); + return true; + } + } else if (current->kind == SCR_DYN_OBJ || current->kind == SCR_DYN_ARR) { + next = scr_dyn_get_prototype(current); + if (!next) { + scr_dyn_release(current); + return false; /* the read threw: pending */ + } + } + scr_dyn_release(current); + current = next; + } + if (current) scr_dyn_release(current); + return false; } bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const ScrStr *refusal) { diff --git a/tests/corpus/8078-instanceof-object-function.cjs b/tests/corpus/8078-instanceof-object-function.cjs index 9a72de00f..22f212269 100644 --- a/tests/corpus/8078-instanceof-object-function.cjs +++ b/tests/corpus/8078-instanceof-object-function.cjs @@ -53,3 +53,25 @@ function describe(value) { return value instanceof Function ? 'function' : 'object'; } console.log([nullProto, {}, describe, 0].map(describe).join(', ')); + +// Prototype chains through functions and objects, and Object.prototype +// itself, whose own prototype is null. +const base = function base() {}; +const fromFunction = {}; +Object.setPrototypeOf(fromFunction, base); +const throughObject = {}; +Object.setPrototypeOf(throughObject, fromFunction); +const nullRooted = function nullRooted() {}; +Object.setPrototypeOf(nullRooted, null); +const reparented = function reparented() {}; +Object.setPrototypeOf(reparented, fromFunction); +const chains = { + objectPrototype: Object.getPrototypeOf({}), + fromFunction, + throughObject, + nullRooted, + reparented, +}; +for (const [label, value] of Object.entries(chains)) { + console.log(`${label}: Object ${value instanceof Object}, Function ${value instanceof Function}`); +} diff --git a/tests/corpus/8190-assert-function-values.cjs b/tests/corpus/8190-assert-function-values.cjs index b91434899..f27bf22dc 100644 --- a/tests/corpus/8190-assert-function-values.cjs +++ b/tests/corpus/8190-assert-function-values.cjs @@ -28,3 +28,18 @@ const checks = [assert.ok, assert.fail, assert.strictEqual]; checks.forEach((check, i) => console.log(i, typeof check, check === checks[i], check.name)); [1, 'x', true].forEach((value) => assert.ok(value)); console.log('done'); + +// The comparison methods need both operands, as Node's ERR_MISSING_ARGS +// TypeError says, also through function values. +const comparisons = [assert.equal, assert.notEqual, assert.strictEqual, assert.notStrictEqual, + assert.deepEqual, assert.notDeepEqual, assert.deepStrictEqual, assert.notDeepStrictEqual]; +for (const compare of comparisons) { + for (const args of [[], [1]]) { + try { + compare(...args); + console.log(compare.name, args.length, 'passed'); + } catch (e) { + console.log(compare.name, args.length, e.name, e.code, e.message); + } + } +} From e7c7a458106c6bf5fb9f538eba159c0822dd510d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:24:50 -0500 Subject: [PATCH 107/395] Convert a read length once in fs.read and fs.readSync fs.read and fs.readSync with arguments the compiler could not type validated the length with `length |= 0` and then converted it again for the read. A length with a valueOf method ran valueOf twice, and the read used the second result. Node converts the length once. scr_fs_read_validate now returns the converted length, and both reads use it. --- packages/runtime/src/scr_bytes_io.c | 23 ++++++++++++------- .../8273-fs-read-write-argument-checks.cjs | 17 +++++++++++--- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/runtime/src/scr_bytes_io.c b/packages/runtime/src/scr_bytes_io.c index bebc2a479..daeb47c60 100644 --- a/packages/runtime/src/scr_bytes_io.c +++ b/packages/runtime/src/scr_bytes_io.c @@ -635,7 +635,8 @@ void scr_fs_cb_schedule(const ScrDyn *callback, ScrDyn *result, const ScrDyn *bu #endif } -static bool scr_fs_read_validate(const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *); +static bool scr_fs_read_validate(const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *, const ScrDyn *, + double *); static bool scr_fs_encoding_chk(const ScrDyn *); static bool scr_fs_int_range_chk(const ScrDyn *, const char *, double, double, const char *); static bool scr_fs_length_int32(const ScrDyn *, double *); @@ -904,7 +905,12 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc } if (!scr_fs_cb_chk(callback, "cb")) return NULL; } - if (!strcmp(op, "read") && count >= 4 && !scr_fs_read_validate(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4))) return NULL; + /* The read length after `length |= 0`, converted once (a valueOf call + * runs once, as in Node). */ + double readLength = 0; + if (!strcmp(op, "read") && count >= 4 && + !scr_fs_read_validate(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), &readLength)) + return NULL; /* write checks its fd first, then the buffer form's callback, offset and * length (validateOffsetLengthWrite), or the string form's data and * callback. */ @@ -1121,7 +1127,7 @@ static ScrDyn *scr_fs_cb_invoke(ScrStr *member, ScrDyn *const *args, size_t argc double offset = ownedBuffer ? scr_fs_cb_option_number(options, "offset", 0) : scr_fs_cb_number(ARG(2), "offset", 0); double length = bytes->len - offset; if (ownedBuffer) length = scr_fs_cb_option_number(options, "length", length); - else if (readOp && count >= 4) scr_fs_length_int32(ARG(3), &length); + else if (readOp && count >= 4) length = readLength; else length = scr_fs_cb_number(ARG(3), "length", length); double position = ownedBuffer ? scr_fs_cb_option_number(options, "position", -1) : scr_fs_cb_number(ARG(4), "position", -1); if (!scr_exc_pending()) result = scr_dyn_new_num(readOp ? scr_fs_read_sync(fd, bytes, offset, length, position) : scr_fs_write_sync(fd, bytes, offset, length, position)); @@ -1973,9 +1979,10 @@ static bool scr_fs_bigint_position_chk(ScrBigInt *position, double length) { * Node's order: the fd, the buffer, the offset (an integer >= 0), the * length as `length |= 0`, the position (validatePosition, against that * length), then for a non-empty read the empty-buffer and - * validateOffsetLengthRead checks. */ + * validateOffsetLengthRead checks. The converted length goes to + * *length_out when it is not NULL, so the read does not convert again. */ static bool scr_fs_read_validate(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, - const ScrDyn *length, const ScrDyn *position) { + const ScrDyn *length, const ScrDyn *position, double *length_out) { if (fd->kind != SCR_DYN_NUM) { scr_dyn_arg_type_fail("fd", "of type number", fd); return false; @@ -1995,6 +2002,7 @@ static bool scr_fs_read_validate(const ScrDyn *fd, const ScrDyn *buffer, const S } double count = 0; if (!scr_fs_length_int32(length, &count)) return false; + if (length_out) *length_out = count; if (!scr_fs_dyn_absent(position)) { if (position->kind == SCR_DYN_BIGINT) { if (!scr_fs_bigint_position_chk(position->v.bigint, count)) return false; @@ -2034,7 +2042,7 @@ static bool scr_fs_read_validate(const ScrDyn *fd, const ScrDyn *buffer, const S void scr_fs_read_chk(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset, const ScrDyn *length, const ScrDyn *position, const ScrStr *fence) { - if (scr_fs_read_validate(fd, buffer, offset, length, position)) scr_throw_lowering_fence(fence); + if (scr_fs_read_validate(fd, buffer, offset, length, position, NULL)) scr_throw_lowering_fence(fence); } /* createReadStream/createWriteStream(path, options?): getOptions' @@ -2090,9 +2098,8 @@ double scr_fs_read_sync_dyn(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer); return 0; } - if (!scr_fs_read_validate(fd, buffer, offset, length, position)) return 0; double count = 0; - if (!scr_fs_length_int32(length, &count)) return 0; + if (!scr_fs_read_validate(fd, buffer, offset, length, position, &count)) return 0; if (count == 0) return 0; double at = scr_fs_dyn_absent(position) ? -1 : position->kind == SCR_DYN_BIGINT ? scr_bigint_to_f64(position->v.bigint) diff --git a/tests/corpus/8273-fs-read-write-argument-checks.cjs b/tests/corpus/8273-fs-read-write-argument-checks.cjs index 623d5286f..b27564837 100644 --- a/tests/corpus/8273-fs-read-write-argument-checks.cjs +++ b/tests/corpus/8273-fs-read-write-argument-checks.cjs @@ -1,7 +1,8 @@ 'use strict'; // fs.read, fs.readSync and fs.write check their arguments in Node's order // and with Node's errors: the fd, the buffer, the offset, `length |= 0`, -// number and bigint positions, and the buffer window. +// number and bigint positions, and the buffer window. A length with a +// valueOf method is converted once. const fs = require('fs'); const path = require('path'); const os = require('os'); @@ -48,6 +49,11 @@ attempt('readSync offset 4', () => fs.readSync(fd, Buffer.alloc(3), 4, 1, 0)); attempt('readSync buffer number', () => fs.readSync(fd, 3, 0, 1, 0)); attempt('readSync fd null', () => fs.readSync(null, Buffer.alloc(3), 0, 1, 0)); console.log(fs.readSync(fd, Buffer.alloc(3), 0, 0.5, 0), fs.readSync(fd, Buffer.alloc(3), 0, 2.5, 0)); +let syncCalls = 0; +const syncLength = { valueOf: () => (++syncCalls === 1 ? 2 : 3) }; +const syncTarget = Buffer.alloc(3); +const syncCount = fs.readSync(fd, syncTarget, 0, syncLength, 0); +console.log('readSync valueOf', syncCount, syncCalls, syncTarget.toString('utf8', 0, syncCount)); attempt('write fd null', () => fs.write(null, Buffer.alloc(1), 0, 1, noop)); attempt('write fd "1"', () => fs.write('1', 'x', noop)); @@ -63,7 +69,12 @@ fs.read(fd, Buffer.alloc(2), 0, 2, 2n, (error, count, buffer) => { console.log('read 2n', error, count, buffer.toString()); fs.write(fd, Buffer.from('XY'), 0, 2, 0, (error2, written) => { console.log('wrote', error2, written, fs.readFileSync(file, 'utf8')); - fs.closeSync(fd); - fs.rmSync(dir, { recursive: true }); + let calls = 0; + const length = { valueOf: () => (++calls === 1 ? 1 : 3) }; + fs.read(fd, Buffer.alloc(3), 0, length, 0, (error3, count3, buffer3) => { + console.log('read valueOf', error3, count3, calls, buffer3.toString('utf8', 0, count3)); + fs.closeSync(fd); + fs.rmSync(dir, { recursive: true }); + }); }); }); From bffd887145a81541b05cd6a1361c074c88f0f57c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:26:20 -0500 Subject: [PATCH 108/395] Keep the native receiver for private accessors and private method values A JavaScript prototype method value runs over the call-site receiver when its private members are fields (brand-checked reads and writes) or private methods it calls. A private accessor, or a private method read as a value, would read a public property of the same name, so such a method keeps its native receiver, as before. A super method call with spread arguments over the call-site receiver now spreads them into the dynamic call instead of rejecting them. --- .../frontend/lowering/class-method-values.ts | 34 ++++++++++++- .../src/frontend/lowering/lower-classes.ts | 11 ++++- .../7621-class-methods-foreign-receivers.mjs | 49 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index af2ef51d5..ab9101cf7 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -383,12 +383,29 @@ function reflectedMethodValue( // base has a prototype object. Other uses of `super` keep the native // receiver. const superCalls = !staticMethod && info.base !== null && prototypeChainViewable(info.base); + // Over a dynamic receiver, private fields are brand-checked and read, and private methods + // brand-checked and called; a private accessor, or a private method read as a value, would become a + // lookup of its name. + const privateAccessors = new Set(); + const privateMethods = new Set(); + for (const other of declaration.members.nodes) + if (other.name !== undefined && ts.isPrivateIdentifier(other.name)) { + if (ts.isAccessor(other)) privateAccessors.add(other.name.text); + else if (ts.isMethodDeclaration(other)) privateMethods.add(other.name.text); + } ts.walkPreorder(member, (node) => { if (node.kind === ts.SyntaxKind.SuperKeyword && !(superCalls && isSuperMethodCallee(node))) needsNativeReceiver = true; + if (!ts.isPrivateIdentifier(node)) return; // Private members on a static method's receiver (the class value) keep - // the native receiver; prototype methods brand-check theirs. - if (staticMethod && ts.isPrivateIdentifier(node)) needsNativeReceiver = true; + // the native receiver; prototype methods brand-check theirs, except + // private accessors and private methods used other than by a call. + if ( + staticMethod || + privateAccessors.has(node.text) || + (privateMethods.has(node.text) && !isPrivateMethodCallee(node)) + ) + needsNativeReceiver = true; }); if (needsNativeReceiver) return null; // The method reads itself as a value inside its own body: it is the @@ -477,6 +494,19 @@ function prototypeChainViewable(base: ClassInfo): boolean { } /** `super` as the receiver of a called member: `super.m(...)`. */ +/** Whether `node`, a private name, is the method a call calls (`x.#m(...)`). */ +function isPrivateMethodCallee(node: ts.Node): boolean { + const access = node.parent; + return ( + access !== undefined && + ts.isPropertyAccessExpression(access) && + access.name === node && + access.parent !== undefined && + ts.isCallExpression(access.parent) && + access.parent.expression === access + ); +} + function isSuperMethodCallee(node: ts.Node): boolean { const access = node.parent; return ( diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index db8f35f95..2abbe81ca 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -9402,12 +9402,21 @@ export function lowerSuperMethodCall( type: DYN, loc, }; + // Spread arguments are spread into the dynamic call, as in a dynamic method call. + const spreads: { arg: number; what: string }[] = []; + const args = call.arguments.nodes.map((arg, index) => { + if (!ts.isSpreadElement(arg)) return lowerer.lowerExprExpecting(arg, DYN); + const spelling = ts.getTextOfNode(arg.expression); + spreads.push({ arg: index, what: spelling }); + return checkedIterableSpread(lowerer, lowerer.lowerExpr(arg.expression), spelling, loc); + }); const value: IrExpr = { kind: "dynCall", callee, receiver: lowerer.coerceToExpected(thisRef, DYN), calleeName: ts.getTextOfNode(access), - args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + args, + ...(spreads.length > 0 ? { spreads } : {}), type: DYN, loc, }; diff --git a/tests/corpus/7621-class-methods-foreign-receivers.mjs b/tests/corpus/7621-class-methods-foreign-receivers.mjs index 8e4feedbd..70826a192 100644 --- a/tests/corpus/7621-class-methods-foreign-receivers.mjs +++ b/tests/corpus/7621-class-methods-foreign-receivers.mjs @@ -167,3 +167,52 @@ console.log( JSON.stringify(meshClone.geometry), typeof copyValue, ); + +// Methods that use a private accessor or return a private method keep their own receiver, as values too. +class Vault { + #v = 1; + get #value() { + return this.#v * 10; + } + set #value(x) { + this.#v = x; + } + #hidden() { + return "hidden " + this.#v; + } + read() { + return this.#value; + } + bump() { + this.#value = this.#v + 1; + return this.#v; + } + hiddenValue() { + return this.#hidden; + } +} +const vault = new Vault(); +const readVault = Vault.prototype.read; +const bumpVault = Vault.prototype.bump; +const hiddenOf = Vault.prototype.hiddenValue; +console.log(readVault.call(vault), bumpVault.call(vault), readVault.call(vault), typeof hiddenOf.call(vault)); +console.log(hiddenOf.call(vault).call(vault)); +try { + readVault.call({}); +} catch (error) { + console.log(error.constructor.name); +} + +// A method value whose body calls super with spread arguments. +class Summer { + add(...xs) { + return xs.reduce((sum, x) => sum + x, 0); + } +} +class Doubler extends Summer { + add(...xs) { + return super.add(...xs) * 2; + } +} +const addValue = Doubler.prototype.add; +console.log(addValue.call(new Doubler(), 1, 2, 3), addValue.apply(new Doubler(), [4, 5])); From 49964dc9970f39538c2d471783936f5a25dc5b57 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:26:38 -0500 Subject: [PATCH 109/395] Read prototype methods stored under computed keys on ES5 instances A member read on a `new F()` instance looked up the name only among the fields of F's prototype objects. A method stored under a computed key (`F.prototype[name] = fn`) is kept in the prototype's unknown-key field, so the read found nothing: the method's body was never visited, and the objects it writes stayed constant in the report. The read now also takes the prototype's unknown-key field, as reads of the object's own fields do. --- .../src/frontend/shape-inference/analyze.ts | 8 +++++-- .../compiler/test/constant-graphs.test.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 8b1101da2..19d3cac22 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -2190,8 +2190,12 @@ class Analyzer { this.add(protos, this.protoAtom(fn)); this.edge(this.field(fn, "prototype"), protos); this.on(protos, (proto) => { - if (proto.kind === "object") this.edge(this.field(proto, name), dst); - else if (proto.kind === "top") this.add(dst, this.top); + if (proto.kind === "object") { + // A method stored under a computed key (`F.prototype[k] = fn`) + // may be any name. + this.edge(this.field(proto, name), dst); + this.edge(this.anyField(proto), dst); + } else if (proto.kind === "top") this.add(dst, this.top); }); return; } diff --git a/packages/compiler/test/constant-graphs.test.ts b/packages/compiler/test/constant-graphs.test.ts index a89276ce7..a7cbe6118 100644 --- a/packages/compiler/test/constant-graphs.test.ts +++ b/packages/compiler/test/constant-graphs.test.ts @@ -141,3 +141,24 @@ test("constant graphs: constructors, apply calls and callbacks that one site run new RegExp(`near miss ns-fixture/index\\.js:10 \\.${field}: written-after-init`), ); }); + +// A prototype method stored under a computed key and called on instances. +const computedMethodSource = ` +var lib = {}; +lib.touched = { count: 0 }; +lib.Widget = function () {}; +var methodName = ["to", "uch"].join(""); +lib.Widget.prototype[methodName] = function () { lib.touched.count++; }; +lib.useWidget = function () { new lib.Widget().touch(); }; +module.exports = { lib }; +`; + +test("constant graphs: prototype methods stored under computed keys run on their instances", async () => { + await writeFile(join(dir, "node_modules", "ns-fixture", "index.js"), computedMethodSource); + await writeFile( + join(dir, "main.cjs"), + 'const { lib } = require("ns-fixture");\nfor (let i = 0; i < 3; i++) lib.useWidget();\nconsole.log(lib.touched.count);\n', + ); + const text = (await report()).join("\n"); + expect(text).toMatch(/near miss ns-fixture\/index\.js:3 \.count: written-after-init/); +}); From f645e8e7022e8710631cbf641b99e00d4b55f0a3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:27:15 -0500 Subject: [PATCH 110/395] Run a prototype's constructor getter on the instance An instance's `constructor` read that reaches a class prototype's own property now reads it with the instance as the receiver (Reflect.get), so an accessor's getter sees the instance as `this`, as in Node, instead of the prototype. --- .../lowering/class-dynamic-dispatch.ts | 24 +++++++++++++++---- tests/corpus/8260-class-prototype-views.mjs | 23 ++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index fd6b8c941..db1ed1c8c 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -4235,25 +4235,35 @@ export class ClassDynamicDispatch { * (prototypeReflectedFlag, set when the view initializes): then the * program deleted the property, and the read continues to the base * class's prototype and past the last one to Object.prototype, as in - * Node. */ + * Node. A prototype's own `constructor` is read with the instance + * `receiver` as `this`, so an accessor's getter runs on the instance. */ private inheritedConstructor( lowerer: Lowerer, info: ClassInfo, key: IrExpr, + receiver: IrExpr, loc: SrcLoc, ): IrExpr { const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl!), DYN); const prototype = hasClassPrototypeData(info) ? classPrototypeData(lowerer, info, loc) : null; if (!prototype) return cls; const base = info.base; + const read = (): IrExpr => ({ + kind: "libCall", + fn: "dyn.reflectGet", + args: [prototype, lowerer.coerceToExpected(key, DYN), receiver], + type: DYN, + loc, + }); + // Past the last class prototype, the read continues to Object.prototype's data property. const next: IrExpr = base && !base.builtinError && base.decl && !base.generic && !base.localClass - ? this.inheritedConstructor(lowerer, base, key, loc) + ? this.inheritedConstructor(lowerer, base, key, receiver, loc) : { kind: "dynKeyGet", value: prototype, key, type: DYN, loc }; return { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [prototype, key], type: BOOL, loc }, - then: { kind: "dynKeyGet", value: prototype, key, type: DYN, loc }, + then: read(), else_: { kind: "ternary", cond: varRef(prototypeReflectedFlag(lowerer, info), BOOL, loc), @@ -6045,7 +6055,13 @@ export class ClassDynamicDispatch { type: DYN, loc, }, - else_: this.inheritedConstructor(lowerer, info, key, loc), + else_: this.inheritedConstructor( + lowerer, + info, + key, + lowerer.coerceToExpected(receiver, DYN), + loc, + ), type: DYN, loc, }, diff --git a/tests/corpus/8260-class-prototype-views.mjs b/tests/corpus/8260-class-prototype-views.mjs index 35e55f5f4..0f7b080bf 100644 --- a/tests/corpus/8260-class-prototype-views.mjs +++ b/tests/corpus/8260-class-prototype-views.mjs @@ -153,3 +153,26 @@ delete Object.getPrototypeOf(new Root()).constructor; console.log(constructorNames(), new Middle().constructor === Object); Middle.prototype.constructor = function Named() {}; console.log(constructorNames()); + +// A reflected prototype whose constructor is an accessor: an instance's read runs the getter with the +// instance as `this`, also where the instance's class inherits it. +class Facet { + constructor(tag) { + this.tag = tag; + } +} +class Tile extends Facet {} +class Brick extends Tile {} +const facets = [new Tile("s"), new Brick("c"), new Facet("p")]; +const facetTags = () => + facets.map((facet) => (typeof facet.constructor === "string" ? facet.constructor : facet.constructor.name)).join(","); +Object.defineProperty(Tile.prototype, "constructor", { + get() { + return "on " + this.tag; + }, + configurable: true, +}); +delete Object.getPrototypeOf(new Brick("x")).constructor; +console.log(facetTags()); +delete Object.getPrototypeOf(new Tile("x")).constructor; +console.log(facetTags()); From e22cdf358bcd2a67909e3e905e68312370e7928b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:28:43 -0500 Subject: [PATCH 111/395] Decode an untyped stream unshift's string chunk with its encoding `s.unshift("e9", "hex")` on a native stream held as an untyped value dropped the encoding argument and put the string's UTF-8 bytes at the front. A byte-mode stream now takes a string chunk with an encoding as the bytes Buffer.from(chunk, encoding) holds, as Node's readableAddChunkUnshift converts it, and an unknown encoding throws ERR_UNKNOWN_ENCODING. Object-mode streams keep the string as the entry. --- packages/runtime/src/scr_stream.c | 25 ++++++++++++++++---- tests/corpus/8232-native-streams-untyped.cjs | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 33382363b..552fef146 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -8101,10 +8101,25 @@ static ScrDyn *scr_stream_dyn_push(ScrStream *stream, ScrDyn *const *args, size_ return scr_exc_pending() ? NULL : scr_dyn_new_bool(more); } -/* unshift(chunk): whether the stream takes more data afterwards, Node's - * canPushMore. */ -static ScrDyn *scr_stream_dyn_unshift(ScrStream *stream, const ScrDyn *chunk) { - scr_stream_unshift_dyn(stream, chunk); +/* unshift(chunk[, encoding]): a byte-mode stream takes a string chunk with + * an encoding as the bytes Buffer.from(chunk, encoding) holds, as Node's + * readableAddChunkUnshift converts it; an unknown encoding throws + * ERR_UNKNOWN_ENCODING. Answers whether the stream takes more data + * afterwards, Node's canPushMore. */ +static ScrDyn *scr_stream_dyn_unshift(ScrStream *stream, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = argc ? args[0] : scr_dyn_undefined(); + ScrDyn *encoding = argc > 1 ? args[1] : scr_dyn_undefined(); + if (chunk->kind == SCR_DYN_STR && encoding->kind == SCR_DYN_STR && + !(stream->st->has_r && stream->st->r.object_entries)) { + ScrStr *canonical = scr_bytes_normalize_encoding(encoding->v.str); + if (!canonical) return NULL; + ScrBytes *b = scr_stream_buffer(scr_bytes_from_str(chunk->v.str, canonical)); + scr_str_release(canonical); + scr_stream_unshift(stream, b); + scr_bytes_release(b); + } else { + scr_stream_unshift_dyn(stream, chunk); + } if (scr_exc_pending()) return NULL; ScrStreamState *st = stream->st; bool more = chunk->kind != SCR_DYN_NULL && st->has_r && !st->destroyed && !st->errored && @@ -8169,7 +8184,7 @@ static ScrDyn *scr_stream_dyn_call(ScrStream *stream, ScrDyn *self, const char * } } else if (!strcmp(method, "eventNames")) result = scr_stream_dyn_event_names(stream); else if (!strcmp(method, "push")) result = scr_stream_dyn_push(stream, args, argc); - else if (!strcmp(method, "unshift")) result = scr_stream_dyn_unshift(stream, first); + else if (!strcmp(method, "unshift")) result = scr_stream_dyn_unshift(stream, args, argc); else if (!strcmp(method, "setDefaultEncoding")) { ScrStream *returned = scr_stream_set_default_encoding(stream, first); if (returned) scr_stream_release(returned); diff --git a/tests/corpus/8232-native-streams-untyped.cjs b/tests/corpus/8232-native-streams-untyped.cjs index b70017b5a..fb1c1f0c1 100644 --- a/tests/corpus/8232-native-streams-untyped.cjs +++ b/tests/corpus/8232-native-streams-untyped.cjs @@ -54,3 +54,27 @@ const w = new Writable({ }); w.on("finish", () => console.log("finish")); drain(w); + +// unshift(string, encoding) puts the decoded bytes at the front; an +// unknown encoding throws. +const bytes = new Readable({ read() {} }); +const parts = []; +bytes.on("data", (c) => parts.push(c.toString("hex"))); +bytes.on("end", () => console.log("unshift bytes", parts.join("|"))); +(function (s) { + console.log(s.unshift("e9", "hex"), s.unshift("aGk=", "base64"), s.unshift("é", "latin1"), s.unshift("x", "utf8")); + try { + s.unshift("x", "bogus"); + } catch (e) { + console.log(e.code, e.message); + } + s.push(null); +})(bytes); +const text = new Readable({ read() {} }); +text.setEncoding("utf8"); +text.on("data", (c) => console.log("unshift text", JSON.stringify(c))); +(function (s) { + s.unshift("c3a9", "hex"); + s.unshift("41", "hex"); + s.push(null); +})(text); From d9fdd81ee92ed8b60dbceb6a311843e119207059 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:32:40 -0500 Subject: [PATCH 112/395] Construct X509Certificate bound through a require() member `const X = require("crypto").X509Certificate; new X(data)` now resolves to the builtin class, so the standard-library constructor fence reported SC2020 for it before the X509Certificate lowering ran. That lowering now runs ahead of the fence. Corpus 8231 constructs a certificate through the binding. --- .../src/frontend/lowering/lower-classes.ts | 189 +++++++++--------- .../8231-builtin-member-binding-new.cjs | 27 ++- 2 files changed, 122 insertions(+), 94 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 8bd5e57d7..fc89dc6b1 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -10122,6 +10122,102 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const agent = lowerHttpAgentNew(lowerer, expr); if (agent) return agent; } + // `new crypto.X509Certificate(data)` — the Dirent-style data record: + // the certificate's lowered members (fingerprint — the SHA-1 of the + // DER, uppercase colon-separated — plus the validFrom/validTo + // validity window in Node's ASN1_TIME_print shape) compute AT + // CONSTRUCTION, when Node parses too, so unparseable input throws + // Node's exact PEM error here (ERR_OSSL_PEM_NO_START_LINE) and the + // handle never exists. Both import forms (`crypto.X509Certificate` + // through the namespace, named `X509Certificate`); Buffer input only + // — the readFileSync idiom. mapType interns the matching record, so + // locals and the composed member reads all flow. The data argument + // feeds THREE field computations, so construction goes through an + // interned helper whose parameter evaluates it exactly once. + // It runs before the standard-library constructor fence below, which + // also sees `const X = require("crypto").X509Certificate` as the + // builtin class. + { + const callee = expr.expression; + const isX509 = + (ts.isPropertyAccessExpression(callee) && + callee.name.text === "X509Certificate" && + lowerer.builtinNamespaceModuleOf(callee.expression) === "crypto") || + (ts.isIdentifier(callee) && + (() => { + const bi = lowerer.builtinImportOf(callee); + return bi?.module === "crypto" && bi.member === "X509Certificate"; + })()); + if (isX509) { + const args: readonly ts.Expression[] = expr.arguments?.nodes ?? []; + if (args.length !== 1) { + lowerer.noLowering( + "X509Certificate with this argument shape", + expr, + "the supported form is new X509Certificate(readFileSync(path))", + ); + } + const data = lowerer.lowerExpr(args[0]!); + const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; + if (!isBytes && data.type.kind !== "string") { + lowerer.noLowering( + `X509Certificate over '${lowerer.fmt(data.type)}' data`, + args[0]!, + "pass the certificate Buffer or PEM string (an fs.readFileSync result)", + ); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (t?.kind !== "record") lowerer.badType(expr, lowerer.typeOf(expr)); + const key = `x509.record:${isBytes ? "bytes" : "str"}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%x509.record", key); + lowerer.valueHelpers.set(key, helper); + const dataT = data.type; + const dRef: IrExpr = { kind: "varRef", localId: "d.0", type: dataT, loc }; + const field = ( + name: string, + fn: + | "crypto.x509Fingerprint" + | "crypto.x509FingerprintStr" + | "crypto.x509ValidFrom" + | "crypto.x509ValidFromStr" + | "crypto.x509ValidTo" + | "crypto.x509ValidToStr", + ): { name: string; value: IrExpr } => ({ + name, + value: { kind: "libCall", fn, args: [dRef], type: STRING, loc }, + }); + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "d.0", name: "d", type: dataT }], + returnType: t, + locals: [{ id: "d.0", name: "d", type: dataT, mutable: false }], + body: [ + { + kind: "return", + value: { + kind: "recordLit", + fields: [ + field( + "fingerprint", + isBytes ? "crypto.x509Fingerprint" : "crypto.x509FingerprintStr", + ), + field("validFrom", isBytes ? "crypto.x509ValidFrom" : "crypto.x509ValidFromStr"), + field("validTo", isBytes ? "crypto.x509ValidTo" : "crypto.x509ValidToStr"), + ], + type: t, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [data], type: t, loc }; + } + } if ( ts.isIdentifier(expr.expression) || (ts.isPropertyAccessExpression(expr.expression) && @@ -10907,99 +11003,6 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { ); } } - // `new crypto.X509Certificate(data)` — the Dirent-style data record: - // the certificate's lowered members (fingerprint — the SHA-1 of the - // DER, uppercase colon-separated — plus the validFrom/validTo - // validity window in Node's ASN1_TIME_print shape) compute AT - // CONSTRUCTION, when Node parses too, so unparseable input throws - // Node's exact PEM error here (ERR_OSSL_PEM_NO_START_LINE) and the - // handle never exists. Both import forms (`crypto.X509Certificate` - // through the namespace, named `X509Certificate`); Buffer input only - // — the readFileSync idiom. mapType interns the matching record, so - // locals and the composed member reads all flow. The data argument - // feeds THREE field computations, so construction goes through an - // interned helper whose parameter evaluates it exactly once. - { - const callee = expr.expression; - const isX509 = - (ts.isPropertyAccessExpression(callee) && - callee.name.text === "X509Certificate" && - lowerer.builtinNamespaceModuleOf(callee.expression) === "crypto") || - (ts.isIdentifier(callee) && - (() => { - const bi = lowerer.builtinImportOf(callee); - return bi?.module === "crypto" && bi.member === "X509Certificate"; - })()); - if (isX509) { - const args: readonly ts.Expression[] = expr.arguments?.nodes ?? []; - if (args.length !== 1) { - lowerer.noLowering( - "X509Certificate with this argument shape", - expr, - "the supported form is new X509Certificate(readFileSync(path))", - ); - } - const data = lowerer.lowerExpr(args[0]!); - const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; - if (!isBytes && data.type.kind !== "string") { - lowerer.noLowering( - `X509Certificate over '${lowerer.fmt(data.type)}' data`, - args[0]!, - "pass the certificate Buffer or PEM string (an fs.readFileSync result)", - ); - } - const t = lowerer.mapTypeOf(lowerer.typeOf(expr)); - if (t?.kind !== "record") lowerer.badType(expr, lowerer.typeOf(expr)); - const key = `x509.record:${isBytes ? "bytes" : "str"}`; - let helper = lowerer.valueHelpers.get(key); - if (!helper) { - helper = keyedHelperName(lowerer, "%x509.record", key); - lowerer.valueHelpers.set(key, helper); - const dataT = data.type; - const dRef: IrExpr = { kind: "varRef", localId: "d.0", type: dataT, loc }; - const field = ( - name: string, - fn: - | "crypto.x509Fingerprint" - | "crypto.x509FingerprintStr" - | "crypto.x509ValidFrom" - | "crypto.x509ValidFromStr" - | "crypto.x509ValidTo" - | "crypto.x509ValidToStr", - ): { name: string; value: IrExpr } => ({ - name, - value: { kind: "libCall", fn, args: [dRef], type: STRING, loc }, - }); - lowerer.liftedFns.push({ - name: helper, - params: [{ localId: "d.0", name: "d", type: dataT }], - returnType: t, - locals: [{ id: "d.0", name: "d", type: dataT, mutable: false }], - body: [ - { - kind: "return", - value: { - kind: "recordLit", - fields: [ - field( - "fingerprint", - isBytes ? "crypto.x509Fingerprint" : "crypto.x509FingerprintStr", - ), - field("validFrom", isBytes ? "crypto.x509ValidFrom" : "crypto.x509ValidFromStr"), - field("validTo", isBytes ? "crypto.x509ValidTo" : "crypto.x509ValidToStr"), - ], - type: t, - loc, - }, - loc, - }, - ], - loc, - }); - } - return { kind: "call", callee: helper, args: [data], type: t, loc }; - } - } // `new X(...)` through a class VALUE (a classval-typed binding, array // element, map read, param): the newValue dispatch through the class // object's construct thunk. Arguments complete against the STATIC diff --git a/tests/corpus/8231-builtin-member-binding-new.cjs b/tests/corpus/8231-builtin-member-binding-new.cjs index 1549425f7..df1b2ebb3 100644 --- a/tests/corpus/8231-builtin-member-binding-new.cjs +++ b/tests/corpus/8231-builtin-member-binding-new.cjs @@ -1,12 +1,14 @@ // `new` of a builtin class bound through a member of a require() result // (`const SD = require("string_decoder").StringDecoder`, the spelling of // Node's own tests): the binding resolves to the class, as the -// destructured `const { StringDecoder } = require(...)` does. +// destructured `const { StringDecoder } = require(...)` does, also for the +// X509Certificate record. "use strict"; const SD = require("string_decoder").StringDecoder; const Readable = require("stream").Readable; const PassThrough = require("stream").PassThrough; const Transform = require("stream").Transform; +const X509 = require("crypto").X509Certificate; const decoder = new SD("utf8"); console.log(decoder.encoding); @@ -33,3 +35,26 @@ const upper = new Transform({ }); upper.on("data", (chunk) => console.log("transform", String(chunk))); upper.end("shout"); + +// The X509Certificate record constructs through the same binding. +const PEM = `-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIUMsBCFtPck8TATMJzfikgW963q3AwDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNdHNuYXRpdmUtdGVzdDAgFw0yNjA3MTcxNzUyMTFaGA8y +MTI2MDYyMzE3NTIxMVowGDEWMBQGA1UEAwwNdHNuYXRpdmUtdGVzdDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALRspe2XjtKvNU7Ws5JfqzmTCPmWtfw0 +1Uha271hNp/30VstOxWLbpqPrXDvIwJMN4o8CRp92Y1hcmS36m9QS5qfXJFw+n+S +SWGpFdMNvp6hro0OoNBA+anuKWoTsMyHSwe8ND0fyobQbgAXJ0+snmuK0E2ii+CM +IoUqYbK57nHeyHfISLupgrRoNGXjDHv4v+RSr3MJDag6BLA+/yVjkBOBOT6kaE4K +oyNXYYsfFxGnB/4pDSanV/UsfJ4wHXkVn18ndZHIzTsZMUkV4BecF8UaMrP+u8NI +c5wce0uWLFFEneK4q15iVvk9TaY0g1Uj69ryRT3L58W7RYYuHu7cVB0CAwEAAaNT +MFEwHQYDVR0OBBYEFLT2GqaTmxDhntPxMWaUZvA3s9V5MB8GA1UdIwQYMBaAFLT2 +GqaTmxDhntPxMWaUZvA3s9V5MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAFcTLVJIyhbIKHfOnGM5T+Kzu5IoDlqzr+Rdx/0IKFLVDqEA9ihEme07 +K/d246pOW5LBceVDY2eMikDpIOjYPEoe9C+Ftv5zrCaDw1wtXrq8a7/eS5B+3TzL +k8c1SVVDPIbmViAZNRmnkwCPBsIF9xIfMFhxz4BTJrm/Lt2Hv1GcRBQx/yRK89sj +JiGnsTna//4mPGRGYw+dRCq4tbyr8HbBKdJmbzOCb4CPKfwAMKUpd4e0Ao7jh/BU +EML7B3BEzv5Jw/BT7B39wAfDyhq32PnRnZefF+7pAmpkklHbckBtZtWn02zwQazv +5z62tszZee2i4BouJiyFnb30F6Pebs4= +-----END CERTIFICATE-----`; +const cert = new X509(PEM); +console.log(cert.fingerprint, cert.validFrom, cert.validTo); From 54f12d73dccf1b2cb62bb36ac920e7b8c4e0f5d0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:37:07 -0500 Subject: [PATCH 113/395] Use built-in typed-array species, copy slices after species creation, and read element descriptors - A built-in typed-array constructor set as a view's `constructor` is its own species, as the getter it inherits from %TypedArray% returns it: `u8.constructor = Float64Array; u8.map(f)` makes a Float64Array. An own Symbol.species on the constructor still wins. - TypedArray.prototype.slice with a species constructor creates the result first and then copies the source's current elements, up to its current length: a constructor that changes the source is seen. - Object.defineProperty on a typed-array element reads the descriptor as ToPropertyDescriptor does, through its getters and in its order, and throws a TypeError for a primitive descriptor or an accessor with a value. The generic object path shares the same reader. --- packages/runtime/src/scr_json.c | 176 ++++++++++++------ .../7753-typed-array-named-properties.js | 23 +++ 2 files changed, 140 insertions(+), 59 deletions(-) diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index be63e1436..f28160d42 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5024,6 +5024,29 @@ static double *dyn_bytes_values(const ScrBytes *b, size_t n) { return values; } +static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value); +static ScrDynEntry *scr_dyn_symbol_entry(const ScrDyn *receiver, const ScrDyn *key, bool own); + +/* Whether a typed-array constructor has a Symbol.species of its own, + * shadowing the one it inherits from %TypedArray%. */ +static bool scr_bytes_ctor_own_species(const ScrDyn *constructor) { + ScrDyn *table = scr_dyn_symbol_receiver(constructor); + bool own = table && scr_dyn_symbol_entry(table, scr_species_symbol, true); + scr_dyn_release(table); + return own; +} + +/* A relative index (ToIntegerOrInfinity, negatives from the end), clamped + * to [0, len], as scr_bytes_slice computes it. */ +static size_t scr_bytes_rel_index_of(double i, size_t len) { + if (i != i) return 0; + double t = trunc(i); + if (t < 0) t += (double)len; + if (t < 0) return 0; + if (t > (double)len) return len; + return (size_t)t; +} + /* SpeciesConstructor of a typed array: NULL (and no exception) for the * default constructor of its kind, else the species constructor (+1). * Only a `constructor` the program set on the view can differ. */ @@ -5044,6 +5067,13 @@ static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { return NULL; } ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + if (species && species->kind == SCR_DYN_UNDEF && scr_bytes_ctor_elem(constructor) >= 0 && + !scr_bytes_ctor_own_species(constructor)) { + /* A built-in typed-array constructor inherits %TypedArray%'s species + * getter, which returns the constructor itself. */ + scr_dyn_release(species); + species = scr_dyn_retain(constructor); + } scr_dyn_release(constructor); if (scr_exc_pending()) { scr_dyn_release(species); *failed = true; return NULL; } if (!species || species->kind == SCR_DYN_UNDEF || species->kind == SCR_DYN_NULL || @@ -5096,33 +5126,40 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar bool failed; ScrDyn *species = recv->buffer ? NULL : scr_bytes_species(recv, &failed); if (!recv->buffer && failed) return NULL; + if (species && !subarray) { + /* TypedArraySpeciesCreate runs first: the constructor can change the + * source, and the copy reads its elements as they are afterwards, + * up to its current length. */ + size_t from = scr_bytes_rel_index_of(startD, blen), to = scr_bytes_rel_index_of(endD, blen); + size_t count = to > from ? to - from : 0; + ScrDyn *result = scr_bytes_static_create(species, count); + scr_dyn_release(species); + if (!result) return NULL; + size_t end = to < bytes->len ? to : bytes->len; + for (size_t k = from, n = 0; k < end; k++, n++) + scr_bytes_set(result->v.bytes, (double)n, scr_bytes_get(bytes, (double)k)); + return result; + } ScrBytes *out = subarray ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); if (!species) { ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); scr_bytes_release(out); return d; } - ScrDyn *result; - if (subarray) { - /* The species constructor views the same buffer. */ - ScrDyn *ctor_args = scr_dyn_new_arr(); - scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); - scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); - scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); - ScrStr *what = scr_str_new("TypedArray species", 18); - result = scr_dyn_construct(species, ctor_args, what); - scr_str_release(what); - scr_dyn_release(ctor_args); - if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { - scr_dyn_release(result); - result = NULL; - static const char message[] = "this is not a typed array."; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); - } - } else { - result = scr_bytes_static_create(species, out->len); - for (size_t i = 0; result && i < out->len; i++) - scr_bytes_set(result->v.bytes, (double)i, scr_bytes_get(out, (double)i)); + /* A subarray's species constructor views the same buffer. */ + ScrDyn *ctor_args = scr_dyn_new_arr(); + scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); + ScrStr *what = scr_str_new("TypedArray species", 18); + ScrDyn *result = scr_dyn_construct(species, ctor_args, what); + scr_str_release(what); + scr_dyn_release(ctor_args); + if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { + scr_dyn_release(result); + result = NULL; + static const char message[] = "this is not a typed array."; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } scr_dyn_release(species); scr_bytes_release(out); @@ -6382,6 +6419,45 @@ bool scr_dyn_same_value(const ScrDyn *left, const ScrDyn *right) { static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) { for (size_t i = 0; i < 6; i++) scr_dyn_release(fields[i]); } + +/* ToPropertyDescriptor: the present fields of `descriptor` (enumerable, + * configurable, value, writable, get, set; +1 each, NULL when absent), + * read in that order through any getters. False with a TypeError pending + * for a primitive descriptor, a getter's exception, or an invalid + * combination; every field is then released. */ +static bool scr_dyn_descriptor_fields_read(const ScrDyn *descriptor, ScrDyn **fields) { + for (size_t i = 0; i < 6; i++) fields[i] = NULL; + if (descriptor->kind != SCR_DYN_OBJ) { + static const char msg[] = "Property description must be an object"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"}; + static const size_t lengths[] = {10, 12, 5, 8, 3, 3}; + for (size_t i = 0; i < 6; i++) { + if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue; + fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]); + if (!fields[i]) { + scr_dyn_descriptor_fields_drop(fields); + return false; + } + } + ScrDyn *get = fields[4], *set = fields[5]; + if ((get || set) && (fields[2] || fields[3])) { + scr_dyn_descriptor_fields_drop(fields); + static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) || + (set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) { + scr_dyn_descriptor_fields_drop(fields); + static const char msg[] = "Getter and setter must be functions"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + return true; +} static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key); static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key); @@ -8374,12 +8450,19 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) if (numeric) { /* [[DefineOwnProperty]] of an element: a valid index and a data * descriptor that keeps it writable, enumerable and configurable. */ + /* The descriptor is read first (ToPropertyDescriptor), through its + * getters, and must be an object. */ + ScrDyn *fields[6]; + if (!scr_dyn_descriptor_fields_read(descriptor, fields)) { + scr_str_release(name); + return NULL; + } + const ScrDyn *enumerable = fields[0], *configurable = fields[1], *value = fields[2]; + const ScrDyn *writable = fields[3]; + bool accessor = fields[4] || fields[5]; bool valid = !signbit(index) && trunc(index) == index && index < (double)target->v.bytes->len; - const ScrDyn *configurable = scr_dyn_obj_get(descriptor, "configurable", 12); - const ScrDyn *enumerable = scr_dyn_obj_get(descriptor, "enumerable", 10); - const ScrDyn *writable = scr_dyn_obj_get(descriptor, "writable", 8); - bool accessor = scr_dyn_obj_get(descriptor, "get", 3) || scr_dyn_obj_get(descriptor, "set", 3); if (!valid) { + scr_dyn_descriptor_fields_drop(fields); scr_str_release(name); static const char message[] = "Invalid typed array index"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); @@ -8387,6 +8470,7 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) } if (accessor || (configurable && !scr_dyn_truthy(configurable)) || (enumerable && !scr_dyn_truthy(enumerable)) || (writable && !scr_dyn_truthy(writable))) { + scr_dyn_descriptor_fields_drop(fields); ScrJsonBuf message; scr_jb_init(&message); scr_jb_puts(&message, "Cannot redefine property: "); @@ -8396,13 +8480,15 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) return NULL; } scr_str_release(name); - const ScrDyn *value = scr_dyn_obj_get(descriptor, "value", 5); double number; + bool stored = true; if (value) { - if (!scr_dyn_number_coerce_js(value, &number)) return NULL; - scr_bytes_set(target->v.bytes, index, number); + stored = scr_dyn_number_coerce_js(value, &number); + /* The conversion can run user code; the index is checked again. */ + if (stored && index < (double)target->v.bytes->len) scr_bytes_set(target->v.bytes, index, number); } - return scr_dyn_retain(target); + scr_dyn_descriptor_fields_drop(fields); + return stored ? scr_dyn_retain(target) : NULL; } bool fenced = scr_bytes_protocol_name(name) && !scr_bytes_has_named(target, name); if (fenced) scr_bytes_protocol_fence(name->data, name->len); @@ -8420,44 +8506,16 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) } ScrStr *name = scr_dyn_property_key(key); if (!name) return NULL; - if (descriptor->kind != SCR_DYN_OBJ) { + ScrDyn *fields[6]; + if (!scr_dyn_descriptor_fields_read(descriptor, fields)) { scr_str_release(name); - static const char msg[] = "Property description must be an object"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); return NULL; } - static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"}; - static const size_t lengths[] = {10, 12, 5, 8, 3, 3}; - ScrDyn *fields[6] = {NULL}; - for (size_t i = 0; i < 6; i++) { - if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue; - fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]); - if (!fields[i]) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - return NULL; - } - } ScrDyn *e = fields[0], *c = fields[1], *value = fields[2]; ScrDyn *w = fields[3], *get = fields[4], *set = fields[5]; ScrDynEntry *prior = scr_dyn_entry(target, name); bool accessor_descriptor = get || set; bool data_descriptor = value || w; - if (accessor_descriptor && data_descriptor) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); - return NULL; - } - if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) || - (set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - static const char msg[] = "Getter and setter must be functions"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); - return NULL; - } bool accessor = accessor_descriptor || (!data_descriptor && prior && prior->accessor); bool writable = w ? scr_dyn_truthy(w) : prior && !prior->accessor && !accessor ? prior->writable : false; bool enumerable = e ? scr_dyn_truthy(e) : prior ? prior->enumerable : false; diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js index b49f100b5..449e78ac7 100644 --- a/tests/corpus/7753-typed-array-named-properties.js +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -23,6 +23,17 @@ function expandos(TA) { try { Object.defineProperty(a, "7", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } try { Object.defineProperty(a, "-0", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } try { Object.defineProperty(a, "0", { get() { return 1; } }); } catch (e) { console.log(e.constructor.name, e.message); } + // An element's descriptor is read through its getters, and must be an object. + const got = []; + Object.defineProperty(a, "1", { get value() { got.push("value"); return 6; }, get enumerable() { got.push("enumerable"); return true; } }); + console.log(a[1], got); + for (const bad of [5, "x", undefined]) { + try { Object.defineProperty(a, "0", bad); } catch (e) { console.log(e.constructor.name); } + } + try { Object.defineProperty(a, "9", 5); } catch (e) { console.log(e.constructor.name); } + try { Object.defineProperty(a, "0", { get configurable() { throw new RangeError("read"); } }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "0", { value: 1, get: undefined }); } catch (e) { console.log(e.constructor.name); } + console.log(a[0], a[1]); let reads = 0; Object.defineProperty(a, "g", { get() { reads++; return this.length; }, enumerable: true }); console.log(a.g, reads, new TA(0)); @@ -56,6 +67,18 @@ function species(TA, Other) { const u = new TA([1, 2, 3]); u.constructor = { [Symbol.species]: Uint16Array }; console.log(u.filter(() => true), u.subarray(1), u.subarray(1).constructor === Uint16Array); + // A built-in constructor set as `constructor` is its own species. + const w = new TA([1, 2, 3]); + w.constructor = Float64Array; + const wm = w.map((v) => v / 2), ws = w.slice(1); + console.log(wm, wm.constructor === Float64Array, ws, ws.constructor === Float64Array, w.filter((v) => v > 1)); + w.constructor = TA; + console.log(w.map((v) => v / 2).constructor === TA); + // slice copies the source after the species constructor ran. + const x = new TA([1, 2, 3, 4]); + x.constructor = {}; + x.constructor[Symbol.species] = function (n) { x[1] = 40; x[2] = 30; return new Other(n); }; + console.log(x.slice(1, 3), x.slice(-1), x.slice(2, 1)); } function reflection(TA) { From 9b64c7388d7b675a645ae09099e955feea03fb55 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:38:12 -0500 Subject: [PATCH 114/395] Resume hoisted-call writes reached from earlier bails, and shift only by exact powers of two - A speculated write with hoisted calls resumes over its stored call results only when its own check failed. Reached in the resume copy from an earlier write's failed check, its calls never ran on that iteration, so it now runs as written: the calls happen once and the arithmetic reads their fresh results. - A literal divisor is a power of two only when it is exactly 2^k with k in [0, 52]. Math.log2 rounds 2^52 + 1 to 52, so `Math.floor(v / (2^52 + 1))` was emitted as a shift by 52. - A helper expands only when every call in its body is of another helper that expands, so an expansion never moves a call with effects ahead of an argument read. Hoisting already refused such writes; the classification now says so itself. --- packages/compiler/src/backend/llvm/emitter.ts | 37 ++++++++++----- .../backend/llvm/speculative-integers.test.ts | 30 ++++++++++++ .../src/backend/llvm/speculative-integers.ts | 29 ++++++++---- tests/corpus/7757-speculative-key-chains.ts | 47 +++++++++++++++++++ 4 files changed, 124 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 4080084de..eb1fa4157 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1457,14 +1457,20 @@ export class LlEmitter { /** What speculative loop planning may assume here (SpeculationContext). */ speculationContext(): SpeculationContext { + const inlinable = (callee: string): InlinableFunction | undefined => { + if (!this.inlinableFunctions.has(callee)) { + // Unset while its body is checked: a recursive helper is not inlinable. + this.inlinableFunctions.set(callee, undefined); + const fn = this.fnByName.get(callee); + this.inlinableFunctions.set( + callee, + fn ? inlinableNumberFunction(fn, inlinable) : undefined, + ); + } + return this.inlinableFunctions.get(callee); + }; return { - inlinable: (callee) => { - if (!this.inlinableFunctions.has(callee)) { - const fn = this.fnByName.get(callee); - this.inlinableFunctions.set(callee, fn ? inlinableNumberFunction(fn) : undefined); - } - return this.inlinableFunctions.get(callee); - }, + inlinable, unaliased: (id) => { const local = this.currentLocals.get(id); return ( @@ -5626,15 +5632,24 @@ export class LlEmitter { if (resume && !resume.placed && !this.B.isTerminated()) { // The fast copy's failed checks re-execute this statement here. resume.placed = true; - this.B.br(resume.label); - this.B.startBlock(resume.label); - // A write with hoisted leaves re-executes its arithmetic over the - // leaves' stored doubles: each leaf ran once, in the fast copy. + // A write with hoisted leaves resumed from its own failed check + // re-executes its arithmetic over the leaves' stored doubles: each + // leaf ran once, in the fast copy. Reached from an earlier statement + // of this copy (a failed check before it), its leaves never ran, so + // it runs as written. const rewritten = this.speculationResumeValues?.get(s); if (rewritten !== undefined && rewritten.leaves.length > 0) { + const join = this.B.newLabel("spec.resume.join"); + this.emitStmt(s); + if (!this.B.isTerminated()) this.B.br(join); + this.B.startBlock(resume.label); this.emitStmt(withSpeculativeValue(s, rewritten.value)); + if (!this.B.isTerminated()) this.B.br(join); + this.B.startBlock(join); return; } + this.B.br(resume.label); + this.B.startBlock(resume.label); } if (this.speculation?.writes.has(s)) { this.emitSpeculativeWrite(s, this.speculation.resume.get(s)); diff --git a/packages/compiler/src/backend/llvm/speculative-integers.test.ts b/packages/compiler/src/backend/llvm/speculative-integers.test.ts index 86055b1b6..151fab7c4 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.test.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.test.ts @@ -339,6 +339,36 @@ test("Math.floor needs a power-of-two divisor, and expansion pure arguments", () body: [{ kind: "exprStmt", expr: call("log", []), loc }], }; expect(inlinableNumberFunction(effectful as never)).toBeUndefined(); + // A divisor next to a power of two is not one, though Math.log2 rounds it to 52. + const near = (d: number): IrExpr => libCall("math.floor", [bin("/", ref("a"), num(d))]); + expect(rewriteSpeculable(near(2 ** 52), {})).not.toBeNull(); + expect(rewriteSpeculable(near(2 ** 52 + 1), {})).toBeNull(); + expect(rewriteSpeculable(near(2 ** 53), {})).toBeNull(); + expect(rewriteSpeculable(near(0.5), {})).toBeNull(); +}); + +test("a helper's own calls must be of helpers that expand too", () => { + // `function outer(v, n) { return trim(v, n) + other(v); }` + const outer = { + ...(trim as object), + name: "outer", + body: [ + { + kind: "return", + value: bin("+", call("trim", [ref("v"), ref("n")]), call("other", [ref("v")])), + loc, + }, + ], + }; + const helpers = (callee: string) => + callee === "trim" ? inlinableNumberFunction(trim) : undefined; + expect(inlinableNumberFunction(outer as never)).toBeUndefined(); + expect(inlinableNumberFunction(outer as never, helpers)).toBeUndefined(); + const pure = { + ...(outer as object), + body: [{ kind: "return", value: bin("*", call("trim", [ref("v"), ref("n")]), num(2)), loc }], + }; + expect(inlinableNumberFunction(pure as never, helpers)).toBeDefined(); }); test("other leaves are hoisted only when no call can change the write's locals", () => { diff --git a/packages/compiler/src/backend/llvm/speculative-integers.ts b/packages/compiler/src/backend/llvm/speculative-integers.ts index 69066a5b4..1d33a71ce 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.ts @@ -86,8 +86,12 @@ const MAX_INLINE_NODES = 64; /** A function that only returns arithmetic over its number parameters: * `function trim(v, n) { return v - Math.floor(v / Math.pow(2, n)) * Math.pow(2, n); }`. * Its call evaluates nothing but its arguments, so a speculated write may - * expand it in place. */ -export function inlinableNumberFunction(fn: IrFunction): InlinableFunction | undefined { + * expand it in place. A call in its body must be of another such function + * (`resolve`): any other call could change what the expansion reads. */ +export function inlinableNumberFunction( + fn: IrFunction, + resolve?: (callee: string) => InlinableFunction | undefined, +): InlinableFunction | undefined { if ( fn.async || fn.generator || @@ -126,7 +130,7 @@ export function inlinableNumberFunction(fn: IrFunction): InlinableFunction | und case "libCall": return (e.fn === "math.floor" || e.fn === "math.pow") && e.args.every(pure); case "call": - return e.args.every(pure); + return resolve?.(e.callee) !== undefined && e.args.every(pure); default: return false; } @@ -175,13 +179,18 @@ function substitute(e: IrExpr, args: ReadonlyMap): IrExpr { } } +/** k when `value` is exactly 2^k with k in [0, 52], else null. + * Math.log2 rounds: 2^52 + 1 gives 52, so the power is compared back. */ +function exactPowerOfTwo(value: number): number | null { + if (!(value >= 1)) return null; + const k = Math.round(Math.log2(value)); + return k >= 0 && k <= 52 && 2 ** k === value ? k : null; +} + /** Whether `e` is a power of two speculation can shift by: * `Math.pow(2, n)` or a literal 2^k with k in [0, 52]. */ function powerOfTwo(e: IrExpr): boolean { - if (e.kind === "numLit") { - const k = Math.log2(e.value); - return Number.isInteger(k) && k >= 0 && k <= 52; - } + if (e.kind === "numLit") return exactPowerOfTwo(e.value) !== null; return ( e.kind === "libCall" && e.fn === "math.pow" && @@ -1022,7 +1031,11 @@ function exponentOf( reads: ReadonlyMap, integral: boolean, ): string { - if (e.kind === "numLit") return String(Math.log2(e.value)); + if (e.kind === "numLit") { + const k = exactPowerOfTwo(e.value); + if (k === null) throw new InternalCompilerError("llvm emitter bug: speculative power of two"); + return String(k); + } if (e.kind !== "libCall") throw new InternalCompilerError("llvm emitter bug: speculative power of two"); const n = emitSpeculativeInteger(host, e.args[1]!, bail, reads, integral); diff --git a/tests/corpus/7757-speculative-key-chains.ts b/tests/corpus/7757-speculative-key-chains.ts index bdbd12e1e..206b53827 100644 --- a/tests/corpus/7757-speculative-key-chains.ts +++ b/tests/corpus/7757-speculative-key-chains.ts @@ -54,6 +54,49 @@ function hoisted(n: number): number { return acc; } +// A check that fails before a write with a hoisted call: that write still +// makes its call on the iteration that bails. +function tally(x: number): number { + calls++; + return x * 2 + 1; +} +function chained(n: number): number { + let a = 1; + let b = 1; + for (let i = 0; i < n; i++) { + a = a * 3 + i; + b = (b * 7 + tally(i) + (a % 11)) % 1000003; + } + return b; +} + +// A divisor next to a power of two is not one: 2^52 + 1 divides as itself. +function nearPowers(start: number, steps: number): number[] { + const out: number[] = []; + let v = start; + for (let i = 0; i < steps; i++) { + v = Math.floor(v / 4503599627370497) * 4 + Math.floor(v / 4503599627370496) + i; + out.push(v); + } + return out; +} + +// A helper that calls a function with effects is not expanded: its +// argument is read before the call runs. +let level = 0; +function raise(): number { + level += 100; + return 1; +} +function lifted(x: number): number { + return raise() + x; +} +function effectfulHelper(n: number): number { + let acc = 0; + for (let i = 0; i < n; i++) acc = (acc * 3 + lifted(level)) % 1000003; + return acc; +} + const codes = new Uint8Array(5000); let seed = 7; for (let i = 0; i < codes.length; i++) { @@ -65,3 +108,7 @@ console.log(shifts(1000, 3, 6), shifts(-1000, 2, 6), shifts(5, 53, 3), shifts(7. console.log(hoisted(20), calls); const short = codes.subarray(0, 3); console.log(chain(short, 2), chain(new Uint8Array(0), 2)); +calls = 0; +console.log(chained(34), chained(60), calls); +console.log(nearPowers(4503599627370496, 3), nearPowers(9007199254740991, 2)); +console.log(effectfulHelper(10), level); From 02ec0e86f607ce56a9a2241bb13aab08e25f23e6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:54:24 -0500 Subject: [PATCH 115/395] Address review findings in the npm and CommonJS interop batch - Intl.Segmenter accepts an options object literal made only of `granularity` entries again, including the empty `{}` main accepted; with none the segmenter is a grapheme segmenter, and a repeated key takes its last value, as in JavaScript. - Namespace reads of a CommonJS module refuse a name that any module it re-exports assigns inside a function, not only the module itself: Node's namespace keeps the values from the end of evaluation. - The named-import link check treats `module.exports` as an export every CommonJS module provides, as Node 24 does, instead of throwing its missing-export SyntaxError. - The npm cases cjs-esm-named and cjs-lexer-aligned, which the batch no longer refuses at build time, are listed with the run-time SC1090 their CommonJS exports-object reads report. --- .../src/frontend/lowering/lower-classes.ts | 29 ++++++++++++------- .../compiler/src/frontend/lowering/lowerer.ts | 14 ++++++++- packages/compiler/src/frontend/program.ts | 27 +++++++++++++++-- .../main.ts | 10 ++++--- .../node_modules/text-width/index.d.ts | 1 + .../node_modules/text-width/index.js | 5 ++++ .../cjs-namespace-reexports/impl.cjs | 6 ++++ .../cjs-namespace-reexports/main.mjs | 7 +++++ .../cjs-namespace-reexports/package.json | 1 + .../cjs-namespace-reexports/wrapper.cjs | 2 ++ tests/harness/coverage.test.ts | 16 ++++++++++ tests/harness/npm.test.ts | 4 +-- 12 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 tests/coverage-fixtures/cjs-namespace-reexports/impl.cjs create mode 100644 tests/coverage-fixtures/cjs-namespace-reexports/main.mjs create mode 100644 tests/coverage-fixtures/cjs-namespace-reexports/package.json create mode 100644 tests/coverage-fixtures/cjs-namespace-reexports/wrapper.cjs diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index e50466114..80ad97090 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -9950,23 +9950,30 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const options = args[1]; let granularity = "grapheme"; if (options && !undefinedArg(options)) { - const properties = ts.isObjectLiteralExpression(options) ? options.properties.nodes : null; - const property = properties?.length === 1 ? properties[0] : undefined; - if ( - property === undefined || - !ts.isPropertyAssignment(property) || - !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || - property.name.text !== "granularity" || - !ts.isStringLiteral(property.initializer) || - !granularities.includes(property.initializer.text) - ) { + // An object literal of `granularity` entries (none: the default; a + // repeated key: the last one, as in JavaScript). + if (!ts.isObjectLiteralExpression(options)) lowerer.noLowering( "Intl.Segmenter options", options, 'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported', ); + for (const property of options.properties.nodes) { + if ( + !ts.isPropertyAssignment(property) || + !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || + property.name.text !== "granularity" || + !ts.isStringLiteral(property.initializer) || + !granularities.includes(property.initializer.text) + ) { + lowerer.noLowering( + "Intl.Segmenter options", + options, + 'the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported', + ); + } + granularity = property.initializer.text; } - granularity = property.initializer.text; } // Explicit undefined expressions can carry effects; retain argument order. const stmts: IrStmt[] = args.map((arg) => { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 59dda3d08..b62c024f0 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -270,6 +270,7 @@ import { cjsExportAssignmentOf, cjsExportDiscardReason, cjsExportsAssignedInFunctions, + cjsReexportedModules, cjsNamespaceExportNames, fallbackDtsPath, isCheckedJsSourceFile, @@ -4016,9 +4017,20 @@ export class Lowerer { } { let namespace = this.cjsNamespaces.get(dep); if (namespace === undefined) { + // A re-exported name reads the module that assigns it, so the writes + // after evaluation of every re-exported module count too. + let assignedLater: Set | "all" = new Set(); + for (const mod of cjsReexportedModules(this.program, dep)) { + const later = cjsExportsAssignedInFunctions(mod); + if (later === "all") { + assignedLater = "all"; + break; + } + for (const name of later) assignedLater.add(name); + } namespace = { names: cjsNamespaceExportNames(this.program, dep), - assignedLater: cjsExportsAssignedInFunctions(dep), + assignedLater, }; this.cjsNamespaces.set(dep, namespace); } diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index ce5f3c2bc..755eff830 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -6403,6 +6403,25 @@ function lexWithAccessorNames(sf: ts.SourceFile): ReturnType([dep]); + const order = [dep]; + for (let i = 0; i < order.length; i++) { + const mod = order[i]!; + for (const spec of lexWithAccessorNames(mod).reexports) { + const next = resolveDep(mod, spec); + if (next !== null && !seen.has(next)) { + seen.add(next); + order.push(next); + } + } + } + return order; +} + export function cjsNamespaceExportNames(program: ts.Program, dep: ts.SourceFile): Set { const names = cjsVisibleNames(dep, lexWithAccessorNames, cjsReexportDep7(program), new Map()); return new Set([...names, "default", "module.exports"]); @@ -6504,10 +6523,12 @@ function cjsNamedImportLinkCheck( }; const resolveCjsDep = cjsReexportDep7(program); const visible = (dep: ts.SourceFile, name: string): boolean => - // `default` is the module.exports binding itself — always provided. - // The program already owns native source ASTs; reuse them without - // starting another syntax session or reparsing their source text. + // `default` and `module.exports` are the module.exports binding itself + // — always provided. The program already owns native source ASTs; + // reuse them without starting another syntax session or reparsing + // their source text. name === "default" || + name === "module.exports" || cjsVisibleNames(dep, lexWithAccessorNames, resolveCjsDep, lexMemo).has(name); /** The statement's resolved LOCAL CommonJS dependency, when it is an diff --git a/tests/corpus/8217-intl-segmenter-word-construct/main.ts b/tests/corpus/8217-intl-segmenter-word-construct/main.ts index 5a2a42e08..d0c2247e7 100644 --- a/tests/corpus/8217-intl-segmenter-word-construct/main.ts +++ b/tests/corpus/8217-intl-segmenter-word-construct/main.ts @@ -1,8 +1,10 @@ // A package builds its segmenters at module load (pi-tui, grok-mermaid): -// a grapheme segmenter with a locale tag, and a word segmenter that only -// some paths use. Grapheme segmentation matches Node; the word segmenter -// constructs, and this program never segments with it. -import { columns, describe } from "text-width"; +// a grapheme segmenter with a locale tag, one with empty options, and a +// word segmenter that only some paths use. Grapheme segmentation matches +// Node; the word segmenter constructs, and this program never segments +// with it. +import { columns, describe, plainColumns } from "text-width"; console.log(describe()); console.log(columns("éa👍🏽🇫🇷"), columns("abc")); +console.log(plainColumns("a\u0301b\u{1F44D}\u{1F3FD}")); diff --git a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts index 471b4fd0a..89b16ddc0 100644 --- a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts +++ b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.d.ts @@ -1,2 +1,3 @@ export declare function columns(text: string): string; export declare function describe(): string; +export declare function plainColumns(text: string): number; diff --git a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js index d58b4ccbb..c7619bd81 100644 --- a/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js +++ b/tests/corpus/8217-intl-segmenter-word-construct/node_modules/text-width/index.js @@ -1,11 +1,16 @@ const graphemes = new Intl.Segmenter("en", { granularity: "grapheme" }); const words = new Intl.Segmenter(undefined, { granularity: "word" }); const sentences = new Intl.Segmenter(undefined, { granularity: "sentence" }); +const plain = new Intl.Segmenter(undefined, {}); export function columns(text) { return [...graphemes.segment(text)].map((part) => part.segment).join("|"); } +export function plainColumns(text) { + return [...plain.segment(text)].length; +} + export function describe() { return `${typeof words} ${typeof sentences}`; } diff --git a/tests/coverage-fixtures/cjs-namespace-reexports/impl.cjs b/tests/coverage-fixtures/cjs-namespace-reexports/impl.cjs new file mode 100644 index 000000000..3172d6685 --- /dev/null +++ b/tests/coverage-fixtures/cjs-namespace-reexports/impl.cjs @@ -0,0 +1,6 @@ +"use strict"; +exports.version = 1; +exports.current = null; +exports.set = (value) => { + exports.current = value; +}; diff --git a/tests/coverage-fixtures/cjs-namespace-reexports/main.mjs b/tests/coverage-fixtures/cjs-namespace-reexports/main.mjs new file mode 100644 index 000000000..2ce528d48 --- /dev/null +++ b/tests/coverage-fixtures/cjs-namespace-reexports/main.mjs @@ -0,0 +1,7 @@ +// A namespace import of a CommonJS wrapper that re-exports another module: +// Node's namespace keeps impl.cjs's values from the end of its evaluation, +// so `ns.current` reads null after `ns.set(5)`. +import * as ns from "./wrapper.cjs"; + +ns.set(5); +console.log(ns.current, ns.version); diff --git a/tests/coverage-fixtures/cjs-namespace-reexports/package.json b/tests/coverage-fixtures/cjs-namespace-reexports/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/coverage-fixtures/cjs-namespace-reexports/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/coverage-fixtures/cjs-namespace-reexports/wrapper.cjs b/tests/coverage-fixtures/cjs-namespace-reexports/wrapper.cjs new file mode 100644 index 000000000..ab637bab5 --- /dev/null +++ b/tests/coverage-fixtures/cjs-namespace-reexports/wrapper.cjs @@ -0,0 +1,2 @@ +"use strict"; +module.exports = require("./impl.cjs"); diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 5b541cc54..ffb30b1ce 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -553,6 +553,22 @@ test("CommonJS var and let exports that can change after the export keep their r expect(refused).toEqual(["counter", "tab", "twice"]); }); +test("CommonJS namespace reads of exports a re-exported module assigns later keep their refusal", () => { + // wrapper.cjs re-exports impl.cjs, whose set() assigns `current` after + // evaluation; `version` never changes. + const { coverage } = analyze(fixture("cjs-namespace-reexports/main.mjs")); + const refused = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])] + .map( + (d) => + /reads of '(\w+)' through namespace imports of CommonJS modules whose functions assign exports/.exec( + d.message, + )?.[1], + ) + .filter((name) => name !== undefined) + .sort(); + expect(refused).toEqual(["current"]); +}); + test("inferred JavaScript compiles natively", async () => { // The js-gap fixture turns noImplicitAny off. Its inferred parameter // uses checked native values alongside the JSDoc-typed neighbor. diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 27b27b8e3..d1bdcf190 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -38,8 +38,8 @@ const KNOWN: Record = { "assert-shims": { run: "SC1043" }, "buffer-shims": { run: "SC2020" }, "bytes-exit": { build: "SC2004" }, - "cjs-esm-named": { build: "SC2013" }, - "cjs-lexer-aligned": { build: "SC2013" }, + "cjs-esm-named": { run: "SC1090" }, + "cjs-lexer-aligned": { run: "SC1090" }, "commander-calc": { run: "SC1090", argvs: [["--help"]] }, "commander-calc-async": { run: "SC1090", argvs: [["--help"]] }, "create-require": { run: "SC1090" }, From 3d4aeddb82d94f78692c4fe125bee413c0dec9e3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:03:27 -0500 Subject: [PATCH 116/395] Keep the reply scanner in a constant object A native build of a program that installs the C scanner but decodes no reply, such as the self-hosted source parser and CommonJS checks, failed at startup with "Cannot access 'valueEnd' before initialization": the build left the module variable uninitialized because its only other use was the assignment in useNativeJsonValueEnd, which checks it first. The scanner is now a property of a constant object that the decoders and useNativeJsonValueEnd read, so it is initialized in every program. --- .../compiler/src/frontend/ts7/semantic-json.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/frontend/ts7/semantic-json.ts b/packages/compiler/src/frontend/ts7/semantic-json.ts index 1a4d93bd6..8913007d3 100644 --- a/packages/compiler/src/frontend/ts7/semantic-json.ts +++ b/packages/compiler/src/frontend/ts7/semantic-json.ts @@ -104,15 +104,20 @@ export function jsonValueEnd(bytes: Uint8Array, start: number): number { } /** The scanner the decoders find value ends with: jsonValueEnd, or the - * native compiler's scanner (useNativeJsonValueEnd). */ -let valueEnd: (bytes: Uint8Array, start: number) => number = jsonValueEnd; + * native compiler's scanner (useNativeJsonValueEnd). It is a property of a + * constant object: a program that installs the scanner but decodes no + * reply must still initialize it, and an assigned module variable with no + * other reader would be left uninitialized by a native build. */ +const scanner: { valueEnd: (bytes: Uint8Array, start: number) => number } = { + valueEnd: jsonValueEnd, +}; /** Has the decoders find value ends with `scan`, which returns what * jsonValueEnd returns, or -1 where jsonValueEnd throws. The native compiler * installs its C scanner (native/ts7-process.c), which skips the strings that * make up most reply bytes with memchr. */ export function useNativeJsonValueEnd(scan: (bytes: Uint8Array, start: number) => number): void { - valueEnd = (bytes, start) => { + scanner.valueEnd = (bytes, start) => { const end = scan(bytes, start); if (end < 0) malformedReply(); return end; @@ -174,7 +179,7 @@ export function decodeSemanticObject( ): R | null { const start = skipJsonSpace(bytes, 0); if (start === bytes.length) return null; - const end = valueEnd(bytes, start); + const end = scanner.valueEnd(bytes, start); if (skipJsonSpace(bytes, end) !== bytes.length) malformedReply(); return replyElement(bytes, start, end, cached, build); } @@ -190,7 +195,7 @@ export function decodeSemanticObjects( let index = skipJsonSpace(bytes, 0); if (index === bytes.length) return null; if (bytes[index] !== OPEN_BRACKET) { - const end = valueEnd(bytes, index); + const end = scanner.valueEnd(bytes, index); if ( skipJsonSpace(bytes, end) !== bytes.length || parseJsonSlice(bytes, index, end) !== null @@ -205,7 +210,7 @@ export function decodeSemanticObjects( return result; } for (;;) { - const end = valueEnd(bytes, index); + const end = scanner.valueEnd(bytes, index); if (end === index) malformedReply(); result.push(replyElement(bytes, index, end, cached, build)); index = skipJsonSpace(bytes, end); From 9406ba5b892b16b1675ba55fbfbcd5c1a762e514 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:04:14 -0500 Subject: [PATCH 117/395] Run row sums two rows per pass A loop nest that sums one term per row, for (let i = start; i < end; i++) { let t = 0; for (let j = from; j < to; j++) t += term; out[i] = t; } where the term only reads numbers, the counters and typed-array elements, now runs two rows per pass when both row bounds are int32, the start is at most the end, and the stored array shares no byte with any array the term reads. Each row keeps its order of additions, so each sum has the bits of the loop as written; an odd row count runs its last row as written, and any other case runs the nest as written. Element reads that do not depend on the row are read once per column for both rows. With two independent sums in the inner loop, neither waits on the other's addition, and LLVM computes both rows' terms in one vector. bytes.disjoint compares two typed arrays' element ranges in memory. --- packages/compiler/src/backend/llvm/emitter.ts | 19 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 34 +- .../compiler/src/ir/builtin-signatures.ts | 3 + packages/compiler/src/ir/row-pairs.test.ts | 147 ++++++ packages/compiler/src/ir/row-pairs.ts | 438 ++++++++++++++++++ .../tests/corpus/8194-row-pair-sums.ts.json | 6 + tests/corpus/8194-row-pair-sums.ts | 72 +++ 7 files changed, 710 insertions(+), 9 deletions(-) create mode 100644 packages/compiler/src/ir/row-pairs.test.ts create mode 100644 packages/compiler/src/ir/row-pairs.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8194-row-pair-sums.ts.json create mode 100644 tests/corpus/8194-row-pair-sums.ts diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index f6f9ea313..0009d5229 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -81,6 +81,7 @@ import { import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js"; import { versionLoopBoundParameters } from "../../ir/parameter-versions.js"; import { inlineExpressionFunctions } from "../../ir/expression-inlining.js"; +import { pairRowSums } from "../../ir/row-pairs.js"; import { allocatedLiteral, fillAllocationLength, @@ -565,14 +566,16 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { scalarizeNumericRecords( specializeCallbackCalls( specializeNumericCalls( - inlineExpressionFunctions( - foldConstantGlobals( - copyStableCaptures( - resolveSelfCaptures( - hoistFinalFieldReads( - freezeInitOnlyGlobals( - holdCharLocalsAsUnits( - dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + pairRowSums( + inlineExpressionFunctions( + foldConstantGlobals( + copyStableCaptures( + resolveSelfCaptures( + hoistFinalFieldReads( + freezeInitOnlyGlobals( + holdCharLocalsAsUnits( + dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + ), ), ), ), diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index d8ca5add8..f4ce9e405 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -10,7 +10,7 @@ import { FN_ATTRS, arrNewCall, traceArg, vAdapters } from "./shapes.js"; import { InternalCompilerError } from "../../errors.js"; import { MAY_THROW_LIB_FNS } from "../../ir/builtin-effects.js"; -import { F64, type IrExpr, type IrType } from "../../ir/ir.js"; +import { BYTES_ELEMENT_SIZE, F64, type IrExpr, type IrType } from "../../ir/ir.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import { emitJsLibCall } from "./jsval.js"; import type { LlvmEmitterContext, LibCallExpr, LibCallPrefix, LlValue } from "./expr-context.js"; @@ -936,6 +936,38 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { return { name: t, type: e.type }; } if (e.fn === "bytes.absent") return host.own({ name: "null", type: e.type }); + if (e.fn === "bytes.disjoint") { + // [data, data + len * size) of each view, compared as addresses. + const B = host.B; + const range = (arg: IrExpr): { start: string; end: string } => { + const value = host.emitReadReceiver(arg); + const size = arg.type.kind === "bytes" ? BYTES_ELEMENT_SIZE[arg.type.elem] : 1; + const dataSlot = B.tmp(), + data = B.tmp(), + lenSlot = B.tmp(), + len = B.tmp(), + bytes = B.tmp(), + start = B.tmp(), + end = B.tmp(); + B.line(`${dataSlot} = getelementptr inbounds %ScrBytes, ptr ${value.name}, i64 0, i32 3`); + B.line(`${data} = load ptr, ptr ${dataSlot}`); + B.line(`${lenSlot} = getelementptr inbounds %ScrBytes, ptr ${value.name}, i64 0, i32 1`); + B.line(`${len} = load ${host.sizeType}, ptr ${lenSlot}`); + B.line(`${bytes} = mul ${host.sizeType} ${len}, ${size}`); + B.line(`${start} = ptrtoint ptr ${data} to ${host.sizeType}`); + B.line(`${end} = add ${host.sizeType} ${start}, ${bytes}`); + return { start, end }; + }; + const left = range(e.args[0]!), + right = range(e.args[1]!); + const before = B.tmp(), + after = B.tmp(), + t = B.tmp(); + B.line(`${before} = icmp ule ${host.sizeType} ${left.end}, ${right.start}`); + B.line(`${after} = icmp ule ${host.sizeType} ${right.end}, ${left.start}`); + B.line(`${t} = or i1 ${before}, ${after}`); + return { name: t, type: e.type }; + } if (e.fn === "record.isFrozen") { // The frozen bit of the record's rc word (scr_rc.h SCR_RC_FROZEN) is // its second-highest bit; an immortal word sets the highest one too. diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1109f7f95..ef75b1b6d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4930,6 +4930,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * and crypto callback adapters pass NULL after an error. Borrowed arg; * never throws. */ "bytes.isAbsent": { argTypes: [BYTES_U8], result: BOOL }, + /** Whether two typed arrays share no byte: their element ranges in + * memory do not overlap. Borrowed args of any element kind; never throws. */ + "bytes.disjoint": { argTypes: [null, null], result: BOOL }, /** The absent Buffer result itself (NULL), where an error-path callback * body passes its undefined result on into a Buffer slot. Owned (+1); * never throws. */ diff --git a/packages/compiler/src/ir/row-pairs.test.ts b/packages/compiler/src/ir/row-pairs.test.ts new file mode 100644 index 000000000..f9db5b89d --- /dev/null +++ b/packages/compiler/src/ir/row-pairs.test.ts @@ -0,0 +1,147 @@ +import { expect, test } from "vitest"; +import { BOOL, F64, VOID, type IrExpr, type IrFunction, type IrModule, type IrStmt } from "./ir.js"; +import { pairRowSums } from "./row-pairs.js"; + +const loc = { file: "row-pairs.ts", start: 0, end: 0 }; +const BYTES = { kind: "bytes", elem: "f64" } as const; +const num = (value: number): IrExpr => ({ loc, kind: "numLit", value, type: F64 }); +const ref = (localId: string): IrExpr => ({ loc, kind: "varRef", localId, type: F64 }); +const bytes = (localId: string): IrExpr & { kind: "varRef" } => ({ + loc, + kind: "varRef", + localId, + type: BYTES, +}); +const bin = (op: "+" | "/" | "<", left: IrExpr, right: IrExpr): IrExpr => ({ + loc, + kind: "bin", + op, + left, + right, + type: op === "<" ? BOOL : F64, +}); +const step = (id: string): IrStmt => ({ + loc, + kind: "assign", + localId: id, + value: bin("+", ref(id), num(1)), +}); +const counted = ( + id: string, + start: IrExpr, + end: IrExpr, + body: IrStmt[], +): IrStmt & { kind: "for" } => ({ + loc, + kind: "for", + init: { loc, kind: "varDecl", localId: id, init: start }, + cond: bin("<", ref(id), end), + update: step(id), + body, +}); +const get = (array: string, index: IrExpr): IrExpr => ({ + loc, + kind: "bytesIntrinsic", + method: "get", + receiver: bytes(array), + args: [index], + type: F64, + invalidNaN: true, +}); + +/** for (i = start; i < end; i++) { t = 0; for (j = 0; j < n; j++) t += u[j] / (i + j + 1); v[i] = t } */ +function rowSum( + term: IrExpr = bin("/", get("u", ref("j")), bin("+", bin("+", ref("i"), ref("j")), num(1))), +) { + return counted("i", ref("start"), ref("end"), [ + { loc, kind: "varDecl", localId: "t", init: num(0) }, + counted("j", num(0), ref("n"), [ + { loc, kind: "assign", localId: "t", value: bin("+", ref("t"), term) }, + ]), + { loc, kind: "bytesSet", arr: bytes("v"), index: ref("i"), value: ref("t") }, + ]); +} + +const local = (id: string, type: IrExpr["type"] = F64) => ({ id, name: id, type, mutable: true }); +function mod(body: IrStmt[]): IrModule { + const fn: IrFunction = { + loc, + name: "rows", + params: [], + locals: [ + ...["i", "j", "t", "start", "end", "n"].map((id) => local(id)), + local("u", BYTES), + local("v", BYTES), + ], + returnType: VOID, + body, + }; + return { irVersion: 17, sourceFile: "row-pairs.ts", entry: "rows", functions: [fn] }; +} + +test("a row-sum nest runs two rows per pass behind its bounds and overlap test", () => { + const result = pairRowSums(mod([rowSum()])); + const top = result.functions[0]!.body[0] as IrStmt & { kind: "block" }; + expect(top.kind).toBe("block"); + const [go, from, branch, rest] = top.body as [ + IrStmt & { kind: "varDecl" }, + IrStmt & { kind: "varDecl" }, + IrStmt & { kind: "if" }, + IrStmt & { kind: "for" }, + ]; + // The test: int32 bounds, start <= end, and the stored array disjoint + // from the array the term reads. + expect(JSON.stringify(go.init)).toContain('"fn":"bytes.disjoint"'); + expect(branch.else_).toBeNull(); + const pairs = branch.then[0] as IrStmt & { kind: "for" }; + expect(pairs.body.filter((s) => s.kind === "bytesSet")).toHaveLength(2); + const inner = pairs.body[2] as IrStmt & { kind: "for" }; + // One read of u[j] for both rows, then the two sums. + expect(inner.body).toHaveLength(3); + expect(inner.body[0]).toMatchObject({ kind: "varDecl", init: { kind: "bytesIntrinsic" } }); + // The second row's term reads `row + 1`. + const row = pairs.init?.kind === "varDecl" ? pairs.init.localId : ""; + expect((inner.body[2] as IrStmt & { kind: "assign" }).value).toMatchObject({ + op: "+", + right: { + op: "/", + left: { kind: "varRef" }, + right: { + op: "+", + left: { + op: "+", + left: { op: "+", left: { kind: "varRef", localId: row }, right: { value: 1 } }, + }, + }, + }, + }); + // The loop as written runs from `end - ((end - start) & 1)` after the + // pairs, or from the start when the test failed: the nest's one other copy. + expect(from.init).toMatchObject({ + kind: "ternary", + then: { op: "-" }, + else_: { localId: "start" }, + }); + expect(rest.init).toMatchObject({ + kind: "varDecl", + localId: "i", + init: { localId: from.localId }, + }); + expect(top.body).toHaveLength(4); + expect( + result.functions[0]!.locals.map((l) => l.id).filter((id) => id.startsWith("%pair.")), + ).toHaveLength(7); +}); + +test("nests whose term calls, writes or reads the sum keep their loops", () => { + const calls: IrExpr = { loc, kind: "call", callee: "f", args: [ref("j")], type: F64 }; + const readsSum = bin("+", ref("t"), get("u", ref("j"))); + for (const term of [calls, readsSum]) { + const m = mod([rowSum(term)]); + expect(pairRowSums(m)).toBe(m); + } + const stored = rowSum(); + stored.body[2] = { loc, kind: "bytesSet", arr: bytes("v"), index: ref("j"), value: ref("t") }; + const m = mod([stored]); + expect(pairRowSums(m)).toBe(m); +}); diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts new file mode 100644 index 000000000..ed71166a8 --- /dev/null +++ b/packages/compiler/src/ir/row-pairs.ts @@ -0,0 +1,438 @@ +import { + BOOL, + F64, + type IrExpr, + type IrFunction, + type IrLocal, + type IrModule, + type IrStmt, +} from "./ir.js"; +import { everyExpr, everyExprChild, mapExprChildren, transformStmtList } from "./traverse.js"; + +/** Inner-loop expressions above this many nodes keep the loop as written. */ +const MAX_TERM_NODES = 64; + +/** The number operators a term may use: they run no other code and cannot + * throw. */ +const PURE_BINARY = new Set([ + "+", + "-", + "*", + "/", + "%", + "**", + "&", + "|", + "^", + "<<", + ">>", + ">>>", +]); + +/** A loop nest that sums one term per row: + * + * for (let i = start; i < end; i++) { + * let t = ; + * for (let j = from; j < to; j++) t = t + ; + * out[i] = t; + * } + */ +interface RowSum { + row: string; + start: IrExpr; + end: IrExpr; + sum: string; + init: IrExpr & { kind: "numLit" }; + inner: IrStmt & { kind: "for" }; + column: string; + term: IrExpr; + out: IrExpr & { kind: "varRef" }; + /** The typed arrays the term reads. */ + reads: (IrExpr & { kind: "varRef" })[]; +} + +function isRef(e: IrExpr | null | undefined, id: string): boolean { + return e?.kind === "varRef" && e.localId === id; +} + +/** `id = id + 1`, or `id++` as a statement. */ +function unitStep(update: IrStmt | null, id: string): boolean { + if (update?.kind === "exprStmt") + return update.expr.kind === "incDec" && update.expr.localId === id && update.expr.op === "+"; + return ( + update?.kind === "assign" && + update.localId === id && + update.value.kind === "bin" && + update.value.op === "+" && + isRef(update.value.left, id) && + update.value.right.kind === "numLit" && + update.value.right.value === 1 + ); +} + +/** A plain number local: unboxed and outside the temporal dead zone. */ +function plainNumber(locals: ReadonlyMap, id: string): boolean { + const local = locals.get(id); + return local !== undefined && local.type.kind === "f64" && !local.boxed && !local.tdz; +} + +function readsLocal(e: IrExpr, id: string): boolean { + return !everyExpr(e, { stmt: () => true, expr: (x) => !isRef(x, id) }); +} + +/** The row-sum shape of a `for` statement, or null. Bounds and receivers + * must be literals or locals the nest never writes. */ +function matchRowSum( + s: IrStmt & { kind: "for" }, + locals: ReadonlyMap, +): RowSum | null { + if (s.labels?.length || s.init?.kind !== "varDecl" || !s.init.init) return null; + const row = s.init.localId; + if (!plainNumber(locals, row) || !unitStep(s.update, row)) return null; + const cond = s.cond; + if (cond?.kind !== "bin" || cond.op !== "<" || !isRef(cond.left, row)) return null; + const start = s.init.init, + end = cond.right; + if (s.body.length !== 3) return null; + const [decl, inner, store] = s.body as [IrStmt, IrStmt, IrStmt]; + if (decl.kind !== "varDecl" || decl.init?.kind !== "numLit") return null; + const sum = decl.localId; + if (!plainNumber(locals, sum)) return null; + if (inner.kind !== "for" || inner.labels?.length) return null; + if (inner.init?.kind !== "varDecl" || !inner.init.init) return null; + const column = inner.init.localId; + if (!plainNumber(locals, column) || !unitStep(inner.update, column)) return null; + const innerCond = inner.cond; + if (innerCond?.kind !== "bin" || innerCond.op !== "<" || !isRef(innerCond.left, column)) + return null; + if (inner.body.length !== 1) return null; + const add = inner.body[0]!; + if ( + add.kind !== "assign" || + add.localId !== sum || + add.value.kind !== "bin" || + add.value.op !== "+" || + !isRef(add.value.left, sum) + ) + return null; + const term = add.value.right; + if ( + store.kind !== "bytesSet" || + store.arr.kind !== "varRef" || + store.arr.type.kind !== "bytes" || + !isRef(store.index, row) || + !isRef(store.value, sum) + ) + return null; + const out = store.arr; + // Values the nest reads but never writes: the row counter, the column + // counter and the sum are its only writes. + const written = new Set([row, sum, column]); + // A typed array's length is fixed, so a stable receiver's length is + // stable too. + const stable = (e: IrExpr): boolean => + e.kind === "numLit" || + (e.kind === "varRef" && + !written.has(e.localId) && + locals.get(e.localId) !== undefined && + !locals.get(e.localId)!.boxed && + !locals.get(e.localId)!.tdz) || + (e.kind === "bytesIntrinsic" && + e.method === "length" && + e.args.length === 0 && + e.receiver.kind === "varRef" && + e.receiver.type.kind === "bytes" && + stable(e.receiver)); + if (!stable(start) || !stable(end) || !stable(out)) return null; + if (!stable(inner.init.init) || !stable(innerCond.right)) return null; + const reads: (IrExpr & { kind: "varRef" })[] = []; + let nodes = 0; + const pure = (e: IrExpr): boolean => { + if (++nodes > MAX_TERM_NODES) return false; + switch (e.kind) { + case "numLit": + return e.type.kind === "f64"; + case "varRef": + return e.type.kind === "f64" && (e.localId === row || e.localId === column || stable(e)); + case "bin": + return e.type.kind === "f64" && PURE_BINARY.has(e.op) && pure(e.left) && pure(e.right); + case "unary": + return e.type.kind === "f64" && (e.op === "-" || e.op === "~") && pure(e.operand); + case "bytesIntrinsic": + if (stable(e)) return true; + if ( + e.method !== "get" || + e.args.length !== 1 || + e.receiver.kind !== "varRef" || + e.receiver.type.kind !== "bytes" || + !stable(e.receiver) + ) + return false; + reads.push(e.receiver); + return pure(e.args[0]!); + default: + return false; + } + }; + if (!pure(term) || readsLocal(term, sum)) return null; + return { row, start, end, sum, init: decl.init, inner, column, term, out, reads }; +} + +/** A fresh copy of `e` with `substitute` applied to its local reads and + * `replace` to the nodes it answers for. */ +function copy( + e: IrExpr, + substitute: (ref: IrExpr & { kind: "varRef" }) => IrExpr, + replace: (node: IrExpr) => IrExpr | null = () => null, +): IrExpr { + const replaced = replace(e); + if (replaced !== null) return replaced; + if (e.kind === "varRef") return substitute(e); + return mapExprChildren( + { ...e }, + (child) => copy(child, substitute, replace), + (stmt) => stmt, + ); +} + +const bin = ( + op: "+" | "-" | "&" | "|", + left: IrExpr, + right: IrExpr, + loc: IrExpr["loc"], +): IrExpr => ({ + kind: "bin", + op, + left, + right, + type: F64, + loc, +}); + +/** Runs the row sums of `match` two rows per pass when the test holds and + * as written otherwise. */ +function pairRows( + s: IrStmt & { kind: "for" }, + match: RowSum, + fresh: (name: string) => string, + done: Set, +): IrStmt { + const loc = s.loc; + const ref = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: F64, loc }); + const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); + const again = (e: IrExpr): IrExpr => copy(e, (r) => ({ ...r })); + const row = fresh("row"), + sum = fresh("sum"), + next = fresh("next"), + column = fresh("column"); + const atColumn = (r: IrExpr & { kind: "varRef" }): IrExpr => + r.localId === match.column ? { ...r, localId: column } : { ...r }; + // Element reads that do not depend on the row are read once per column + // and shared by both rows. + const shared = new Map(); + const elements: IrStmt[] = []; + const collect = (e: IrExpr): boolean => { + if (e.kind === "bytesIntrinsic" && e.method === "get" && !readsLocal(e, match.row)) { + const id = fresh("element"); + shared.set(e, id); + elements.push({ kind: "varDecl", localId: id, init: copy(e, atColumn), loc }); + return true; + } + return everyExprChild(e, collect, () => true); + }; + collect(match.term); + // The second row's term reads `row + 1`, so its counter sums stay visible + // to the counted-loop analysis. + const termAt = (rowExpr: () => IrExpr): IrExpr => + copy( + match.term, + (r) => (r.localId === match.row ? rowExpr() : atColumn(r)), + (node) => { + const id = shared.get(node); + return id === undefined ? null : { kind: "varRef", localId: id, type: node.type, loc }; + }, + ); + const innerCond = match.inner.cond as IrExpr & { kind: "bin" }; + const pairs: IrStmt = { + kind: "for", + init: { kind: "varDecl", localId: row, init: again(match.start), loc }, + cond: { + kind: "bin", + op: "<", + left: ref(row), + right: bin("-", again(match.end), num(1), loc), + type: BOOL, + loc, + }, + update: { kind: "assign", localId: row, value: bin("+", ref(row), num(2), loc), loc }, + body: [ + { kind: "varDecl", localId: sum, init: { ...match.init }, loc }, + { kind: "varDecl", localId: next, init: { ...match.init }, loc }, + { + kind: "for", + init: { + kind: "varDecl", + localId: column, + init: again((match.inner.init as IrStmt & { kind: "varDecl" }).init!), + loc, + }, + cond: { ...innerCond, left: ref(column), right: again(innerCond.right) }, + update: { kind: "assign", localId: column, value: bin("+", ref(column), num(1), loc), loc }, + body: [ + ...elements, + { + kind: "assign", + localId: sum, + value: bin( + "+", + ref(sum), + termAt(() => ref(row)), + loc, + ), + loc, + }, + { + kind: "assign", + localId: next, + value: bin( + "+", + ref(next), + termAt(() => bin("+", ref(row), num(1), loc)), + loc, + ), + loc, + }, + ], + loc, + }, + { kind: "bytesSet", arr: { ...match.out }, index: ref(row), value: ref(sum), loc }, + { + kind: "bytesSet", + arr: { ...match.out }, + index: bin("+", ref(row), num(1), loc), + value: ref(next), + loc, + }, + ], + loc, + }; + // Both bounds are int32, so `row < end - 1` is `row + 1 < end`, and the + // start is at most the end. Rows are independent when the written array + // shares no byte with an array the term reads. + const int32 = (e: IrExpr): IrExpr => ({ + kind: "bin", + op: "===", + left: bin("|", again(e), num(0), loc), + right: again(e), + type: BOOL, + loc, + }); + const tests: IrExpr[] = [ + int32(match.start), + int32(match.end), + { kind: "bin", op: "<=", left: again(match.start), right: again(match.end), type: BOOL, loc }, + ...match.reads.map((read): IrExpr => ({ + kind: "libCall", + fn: "bytes.disjoint", + args: [{ ...match.out }, { ...read }], + type: BOOL, + loc, + })), + ]; + const test = tests.reduce((left, right) => ({ + kind: "logical", + op: "&&", + left, + right, + type: BOOL, + loc, + })); + // The loop as written then runs the rows the pairs left: the last one when + // the count is odd (from `end - ((end - start) & 1)`), or all of them when + // the test failed. It is the nest's only other copy. + const go = fresh("go"), + from = fresh("from"); + const paired: IrExpr = { kind: "bin", op: "===", left: ref(go), right: num(1), type: BOOL, loc }; + const rest: IrStmt = { + ...s, + init: { kind: "varDecl", localId: match.row, init: ref(from), loc }, + }; + done.add(rest); + return { + kind: "block", + body: [ + { + kind: "varDecl", + localId: go, + init: { kind: "ternary", cond: test, then: num(1), else_: num(0), type: F64, loc }, + loc, + }, + { + kind: "varDecl", + localId: from, + init: { + kind: "ternary", + cond: paired, + then: bin( + "-", + again(match.end), + bin("&", bin("-", again(match.end), again(match.start), loc), num(1), loc), + loc, + ), + else_: again(match.start), + type: F64, + loc, + }, + loc, + }, + { kind: "if", cond: { ...paired }, then: [pairs], else_: null, loc }, + rest, + ], + loc, + }; +} + +/** Runs loop nests that sum one term per row (spectral-norm's `au`) two rows + * per pass. + * + * Each row's sum keeps its order of additions, so every sum has the bits + * the nest computes as written: the second row's term is the same + * expression of `row + 1`. The term only reads numbers, the counters and + * typed-array elements, so it runs no other code and cannot throw. The + * nest runs paired when both row bounds are int32 (so `row < end - 1` is + * `row + 1 < end`), the start is at most the end, and the array the rows + * store into shares no byte with any array the term reads, so no row reads + * another row's result. An odd row count runs its last row as written. + * Otherwise the nest runs as written. Two independent sums take turns in + * the inner loop, so neither waits on the other's addition. */ +export function pairRowSums(mod: IrModule): IrModule { + let changed = false; + const functions = mod.functions.map((fn): IrFunction => { + if (fn.async || fn.generator) return fn; + const locals = new Map(fn.locals.map((local) => [local.id, local])); + const added: IrLocal[] = []; + const ids = new Set(fn.locals.map((local) => local.id)); + const fresh = (name: string): string => { + let id = `%pair.${name}.${ids.size}`; + while (ids.has(id)) id += "_"; + ids.add(id); + added.push({ id, name: `%pair.${name}`, type: F64, mutable: true }); + return id; + }; + const paired = new Set(); + const body = transformStmtList(fn.body, { + expr: (e) => e, + stmt: (s) => { + if (s.kind !== "for" || paired.has(s)) return s; + const match = matchRowSum(s, locals); + if (match === null) return s; + paired.add(s); + return pairRows(s, match, fresh, paired); + }, + }); + if (added.length === 0) return fn; + changed = true; + return { ...fn, locals: [...fn.locals, ...added], body }; + }); + return changed ? { ...mod, functions } : mod; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8194-row-pair-sums.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8194-row-pair-sums.ts.json new file mode 100644 index 000000000..afb53a915 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8194-row-pair-sums.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8194-row-pair-sums.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8194-row-pair-sums.ts b/tests/corpus/8194-row-pair-sums.ts new file mode 100644 index 000000000..9aa22b9bd --- /dev/null +++ b/tests/corpus/8194-row-pair-sums.ts @@ -0,0 +1,72 @@ +// Row sums (`for i { t = 0; for j t += term; out[i] = t }`) over typed +// arrays, run with even and odd row counts, an empty range, fractional, +// negative-zero and out-of-int32 bounds, and output arrays that are the +// input array or overlap it in one buffer, so a row may read an earlier +// row's result. +function rows(u: Float64Array, v: Float64Array, start: number, end: number, n: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = 0; j < n; j++) t += u[j]! / ((((i + j) * (i + j + 1)) >>> 1) + i + 1); + v[i] = t; + } +} + +function weighted(u: Float32Array, w: Int32Array, v: Float64Array, start: number, end: number): void { + for (let i = start; i < end; i++) { + let t = 0.5; + for (let j = 0; j < u.length; j++) t += u[j]! * w[(i + j) % w.length]! - i * 0.25; + v[i] = t; + } +} + +const show = (label: string, v: Float64Array): void => { + const parts: string[] = []; + for (const x of v) parts.push(Object.is(x, -0) ? "-0" : x.toPrecision(17)); + console.log(label, parts.join(" ")); +}; + +const n = Number(process.argv[2] ?? 7); +const u = new Float64Array(n).map((_, k) => k + 1); +for (const [start, end] of [ + [0, n], + [0, n - 1], + [1, n], + [2, 2], + [3, 1], + [-0, 3], + [0.5, 4], + [0, 4.5], + [2 ** 31 - 3, 2 ** 31 + 1], + [NaN, 3], +]) { + const v = new Float64Array(n + 2); + rows(u, v, start!, end!, n); + show(`rows ${start} ${end}`, v); +} + +// The output is the input: each row reads the rows stored before it. +const same = new Float64Array(n).map((_, k) => k + 1); +rows(same, same, 0, n, n); +show("same", same); + +// Overlapping views of one buffer, offset by one element. +const buffer = new ArrayBuffer(8 * (n + 1)); +const lo = new Float64Array(buffer, 0, n).map((_, k) => k + 1); +const hi = new Float64Array(buffer, 8, n); +rows(lo, hi, 0, n, n); +show("overlap", new Float64Array(buffer)); + +// Disjoint views of one buffer. +const shared = new ArrayBuffer(16 * n); +const a = new Float64Array(shared, 0, n).map((_, k) => n - k); +const b = new Float64Array(shared, 8 * n, n); +rows(a, b, 0, n, n); +show("disjoint", b); + +const f = new Float32Array(n).map((_, k) => k / 3); +const w = new Int32Array([3, -1, 4, 1, -5]); +const out = new Float64Array(n); +weighted(f, w, out, 0, n); +show("weighted", out); +weighted(f, w, out, 1, n - 1); +show("weighted inner", out); From 01e6d55fc56e00e9ba91b55f29e021029d3289f3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:05:18 -0500 Subject: [PATCH 118/395] Keep optional-element arrays through nested conversions, union arms, rest packing and receiver reassignment - Checked conversions from dynamic values key their builders and matchers by the type with `?` after every `(T | undefined)[]`, at any depth. `number[][]` and `(number | undefined)[][]` share a typeKey, so the second conversion reused the first one's builder and rejected undefined elements. - The dynamic matcher of a `(T | undefined)[]` takes undefined elements, so a union with such an arm matches arrays that hold undefined. - An optional-element rest parameter packs tuple-spread values and plain arguments together as present undefined elements, each evaluated once in argument order, instead of converting each value to T. - The element-state scan counts an argument in a rest position that may be undefined, and a spread tuple with undefined elements, as undefined elements of the rest array, so reads in the callee check element states. - A union index read keeps the receiver in a temporary when the key can assign it, so `a[(a = other, 0)]` reads the array `a` held before the key. --- packages/compiler/src/backend/llvm/dyn.ts | 34 +++++++++++-- .../src/frontend/lowering/call-arguments.ts | 26 +++++++++- .../lowering/expressions/element-access.ts | 15 ++++-- .../compiler/src/frontend/lowering/lowerer.ts | 24 ++++++++- .../lowering/runtime-optional-elements.ts | 19 ++++++- .../8004-optional-element-conversions.ts.json | 6 +++ .../8004-optional-element-conversions.ts | 49 +++++++++++++++++++ .../8004-optional-element-conversions.ts.json | 1 + 8 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json create mode 100644 tests/corpus/8004-optional-element-conversions.ts create mode 100644 tests/harness/corpus-costs/8004-optional-element-conversions.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 57c8b5177..4ec710b28 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -66,6 +66,25 @@ import { recordMayKeepSource, recordSourceFieldOrder } from "./record-sources.js import type { WalkerHost } from "./walkers.js"; import type { LlStreamTypedRefAdapter } from "./expr-context.js"; +/** typeKey with `?` after every `(T | undefined)[]` (optionalElements), + * at any depth of arrays, Maps, Sets and promises: checked conversions to + * `number[][]` and `(number | undefined)[][]` need separate helpers, though + * the two types share a typeKey. */ +function checkedConversionKey(t: IrType): string { + switch (t.kind) { + case "array": + return `array<${checkedConversionKey(t.elem)}>${t.optionalElements ? "?" : ""}`; + case "map": + return `map<${checkedConversionKey(t.key)},${checkedConversionKey(t.value)}>`; + case "set": + return `set<${checkedConversionKey(t.elem)}>`; + case "promise": + return `promise<${checkedConversionKey(t.inner)}>`; + default: + return typeKey(t); + } +} + /** Whether to-dyn of a record shape keeps the record itself: an empty shape * and an event-listener record (a `handleEvent` function field) box their * identity. Every other shape, tuples included, copies its fields into a @@ -1060,7 +1079,7 @@ export class LlDyn { /** `sc_dm_(ptr d) -> i1` — does this dyn fit T? Never throws. */ dynMatchHelper(t: IrType, preserveRefs = false): string { - const key = typeKey(t); + const key = checkedConversionKey(t); const matcherKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynMatchers.get(matcherKey); if (existing) return existing; @@ -1375,7 +1394,7 @@ export class LlDyn { B.startBlock(l0); const n = this.lenOf(B, "%d"); const items = this.itemsOf(B, "%d"); - this.i64Loop(B, "dm.el", n, (i, brNext) => { + this.i64Loop(B, "dm.el", n, (i, brNext, next) => { this.host.declare(`declare zeroext i1 @scr_dyn_arr_has_index(ptr, ${this.S})`); const present = B.tmp(); B.line(`${present} = call zeroext i1 @scr_dyn_arr_has_index(ptr %d, ${this.S} ${i})`); @@ -1386,6 +1405,15 @@ export class LlDyn { brNext(); B.startBlock(valueLabel); const e = this.itemAt(B, items, i); + if (t.optionalElements) { + // A `(T | undefined)[]` stored as T elements takes undefined + // elements as well. + const undef = B.tmp(); + const checkLabel = B.newLabel("dm.check"); + B.line(`${undef} = icmp eq i32 ${this.kindOf(B, e)}, ${DYN_KIND.UNDEF}`); + B.condBr(undef, next, checkLabel); + B.startBlock(checkLabel); + } const ok = B.tmp(); B.line(`${ok} = call zeroext i1 @${m}(ptr ${e})`); const lOk = B.newLabel("dm.eo"); @@ -1798,7 +1826,7 @@ export class LlDyn { * TypeError and return a dummy with the pending flag set. */ dynCheckHelper(t: IrType, preserveRefs = false): string { // A `(T | undefined)[]` stored as T elements also takes undefined ones. - const key = t.kind === "array" && t.optionalElements ? `${typeKey(t)}?` : typeKey(t); + const key = checkedConversionKey(t); const builderKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynBuilders.get(builderKey); if (existing) return existing; diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 6da0cb337..b02d0b85c 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -28,7 +28,7 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; -import { lowerArraySpreadItems } from "./containers/array-construction.js"; +import { lowerArraySpreadItems, lowerArrayValueItems } from "./containers/array-construction.js"; import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( @@ -413,6 +413,30 @@ export function completeArgs( ); return out; } + // Tuple-spread values (pre-lowered) and plain arguments pack the same + // way. Each value evaluates once, in argument order. + if ( + restType.optionalElements === true && + !restNodes.some((a) => !isIr(a) && ts.isSpreadElement(a)) + ) { + const stmts: IrStmt[] = []; + const values = restNodes.map((a): IrExpr => { + if (isIr(a)) { + if (a.ir.kind !== "seqExpr") return a.ir; + stmts.push(...a.ir.stmts); + return a.ir.result; + } + const value = lowerer.lowerExpr(a as ts.Expression); + const saved = lowerer.declareHiddenLocal("%callArg", value.type); + stmts.push({ kind: "varDecl", localId: saved.id, init: value, loc: value.loc }); + return { kind: "varRef", localId: saved.id, type: value.type, loc: value.loc }; + }); + const items = lowerArrayValueItems(lowerer, values, restType.elem, restType, loc); + out.push( + stmts.length === 0 ? items : { kind: "seqExpr", stmts, result: items, type: restType, loc }, + ); + return out; + } // The rest pack is a fresh array per call; surplus SPREADS copy // their elements in (JS-exact — `f(a, ...xs, b, ...ys)` packs in // order, sources untouched). diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 70909ca7b..a2f2ebdb6 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -2471,14 +2471,21 @@ function optionalArmIndexRead( return null; if (unionArrayValueRead(lowerer, value, key, loc) === null) return null; const stmts: IrStmt[] = []; - const stable = (e: IrExpr, name: string): IrExpr => { - if (e.kind === "varRef" || e.kind === "numLit") return e; + const stable = (e: IrExpr, name: string, keep: boolean): IrExpr => { + if (keep) return e; const local = lowerer.declareHiddenLocal(name, e.type); stmts.push({ kind: "varDecl", localId: local.id, init: e, loc }); return varRef(local.id, e.type, loc); }; - const receiver = stable(value, "%indexReceiver"); - const index = stable(key, "%indexKey"); + // A receiver local stays a direct read only when the key cannot assign + // it (`a[(a = other, 0)]` reads the array `a` held before the key). + const plainKey = key.kind === "varRef" || key.kind === "numLit"; + const receiver = stable( + value, + "%indexReceiver", + value.kind === "numLit" || (value.kind === "varRef" && plainKey), + ); + const index = stable(key, "%indexKey", plainKey); const read = unionArrayValueRead(lowerer, receiver, index, loc)!; return stmts.length === 0 ? read : { kind: "seqExpr", stmts, result: read, type: read.type, loc }; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 87825d030..28288a5f1 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6546,8 +6546,20 @@ export class Lowerer { }, iterationMayYieldUndefined: (node) => { const type = typeOrNull(node); - if (type === null || this.checker.isArrayType(type) || this.checker.isTupleType(type)) - return false; + if (type === null || this.checker.isArrayType(type)) return false; + // A tuple yields its elements, in order. + if (this.checker.isTupleType(type)) + return panicSafe( + () => + this.checker + .getTypeArguments(type as ts.TypeReference) + .some((element) => + (element.isUnionType() ? ts.constituentTypes(element) : [element]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ), + ), + true, + ); // The first type argument of Set, Map and Set iterators, // generators and iterables is the value they yield. const element = @@ -6601,6 +6613,14 @@ export class Lowerer { const rest = sig.params.find((shape) => shape.mode === "rest"); return rest?.type.kind === "array" ? [typeKey(rest.type.elem)] : []; }, + restStart: (call) => { + const callee = peel(call.expression); + if (!ts.isIdentifier(callee) && !ts.isPropertyAccessExpression(callee)) return null; + const symbol = callableSymbolOf(callee); + const sig = symbol ? signatureBySymbol.get(symbol) : undefined; + const at = sig ? sig.params.findIndex((shape) => shape.mode === "rest") : -1; + return at >= 0 ? at : null; + }, markIterationBinding: (node, known) => { const symbol = symbolOf(node); if (!symbol) return false; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index fffe79a51..7561f4a65 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -31,6 +31,9 @@ export interface ElementStateHost { /** Element keys of the rest parameter that the spread arguments of * `call` fill: `[]` when the callee has none, null when it is unknown. */ restKeys(call: ts.CallExpression | ts.NewExpression): readonly string[] | null; + /** The argument index where the callee's rest parameter starts, or null + * when the callee has none or is unknown. */ + restStart?(call: ts.CallExpression | ts.NewExpression): number | null; /** Record that iterating `node`'s binding can observe `undefined`; * `known` when the iterated array's own facts (not only those shared by * its element ABI) say so. Returns true when the binding's record grew. */ @@ -518,14 +521,26 @@ export class ArrayElementStates { private scanCall(node: ts.CallExpression | ts.NewExpression): boolean { let changed = false; const args = node.arguments?.nodes ?? []; - // Spreading a sparse array into a rest parameter fills the holes. + // Spreading a sparse array, or a tuple with undefined elements, into a + // rest parameter fills the holes. if (args.some((arg) => ts.isSpreadElement(arg))) { for (const arg of args) { - if (!ts.isSpreadElement(arg) || !this.mayYieldUndefined(arg.expression)) continue; + if (!ts.isSpreadElement(arg) || !this.valueMayBeUndefined(arg)) continue; const rest = this.host.restKeys(node) ?? this.host.arrayKeys(arg.expression); if (this.addUndefined(rest)) changed = true; } } + // An argument in a rest position that may be undefined is a present + // undefined element of the rest array. + const restStart = this.host.restStart?.(node) ?? null; + if (restStart !== null) + for (const arg of args.slice(restStart)) + if ( + !ts.isSpreadElement(arg) && + this.host.mayBeUndefined(arg) && + this.addUndefined(this.host.restKeys(node)) + ) + changed = true; if (this.record(node)) changed = true; const callee = peel(node.expression); if (!ts.isPropertyAccessExpression(callee)) return changed; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json new file mode 100644 index 000000000..2a1bc7c66 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8004-optional-element-conversions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts new file mode 100644 index 000000000..2a394c331 --- /dev/null +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -0,0 +1,49 @@ +// `(T | undefined)[]` values built by checked conversions, rest parameters +// and union index reads: nested arrays converted beside their non-optional +// form, a union arm that is an optional-element array, undefined arguments +// and tuple spreads packed into a rest parameter, and an index expression +// that reassigns the receiver. + +const plainRows = JSON.parse("[[1, 2], [3]]") as number[][]; +console.log(plainRows); +const optionalSource: unknown = (JSON.parse("[[1, null], [null, 4]]") as unknown[][]).map((row) => + row.map((v) => (v === null ? undefined : v)), +); +const optionalRows = optionalSource as (number | undefined)[][]; +console.log(optionalRows, optionalRows[0]![1], optionalRows[1]!.length); + +const armSource: unknown = [1, undefined, 3]; +const arm = armSource as (number | undefined)[] | string; +console.log(typeof arm === "string" ? arm : arm.length, arm); +const stringArm = ["a", undefined] as unknown as (string | undefined)[] | number; +console.log(stringArm); +const textArm = "text" as unknown as (string | undefined)[] | string; +console.log(textArm); + +function flags(...values: (boolean | undefined)[]): string { + let text = ""; + for (const value of values) text += value === undefined ? "u" : value ? "t" : "f"; + return `${values.length}:${text}:${String(values[1])}`; +} +console.log(flags(true, undefined)); +const flagPair: [boolean, boolean | undefined] = [false, undefined]; +console.log(flags(...flagPair), flags(true, ...flagPair), flags(...flagPair, true)); +function label(first: string, ...rest: (string | undefined)[]): string { + return `${first}/${rest.length}/${rest.map((v) => v ?? "-").join("")}`; +} +const words: [string, string | undefined, string | undefined] = ["a", undefined, "c"]; +console.log(label(...words), label("b", undefined)); + +function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { + let a = start; + const read = a[((a = other), 0)]; + return `${String(read)} ${String(a[0])}`; +} +console.log(pick([1, undefined, 3], ["x", "y"]), pick([undefined, 2], ["z"])); +function counted(start: (number | undefined)[] | string[]): string { + let b = start; + let i = 1; + const read = b[((b = ["q"]), i--)]; + return `${String(read)} ${b.length} ${i}`; +} +console.log(counted([4, undefined]), counted(["s", "t"])); diff --git a/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json b/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json new file mode 100644 index 000000000..9f8eb9e05 --- /dev/null +++ b/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json @@ -0,0 +1 @@ +{ "plain": 4882, "san": 6379 } From 40adc2d1619c6598bc8600d79c93fb5455cbdef4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:06:14 -0500 Subject: [PATCH 119/395] Hoist a call only when its arguments write no binding A hoisted call runs before the rest of its write. An argument such as `i++` in `acc = acc * 3 + i + tally(i++)` would then move that write ahead of the write's earlier read of `i`, so such a call stays where it is and the write is not speculated. --- .../src/backend/llvm/speculative-integers.test.ts | 5 +++++ .../src/backend/llvm/speculative-integers.ts | 15 ++++++++++++++- tests/corpus/7757-speculative-key-chains.ts | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/backend/llvm/speculative-integers.test.ts b/packages/compiler/src/backend/llvm/speculative-integers.test.ts index 151fab7c4..1b82768d5 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.test.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.test.ts @@ -382,6 +382,11 @@ test("other leaves are hoisted only when no call can change the write's locals", const hoisted = rewriteSpeculable(value, { unaliased: () => true, leafId })!; expect(hoisted.leaves.map((leaf) => leaf.expr)).toEqual([call("noisy", [ref("i")])]); expect(rewriteSpeculable(value, { unaliased: (id) => id !== "acc", leafId })).toBeNull(); + // A call whose argument writes a local stays where it is. + const bump: IrExpr = { kind: "incDec", localId: "i", op: "+", prefix: false, type: F64, loc }; + expect( + rewriteSpeculable(bin("+", ref("i"), call("noisy", [bump])), { unaliased: () => true, leafId }), + ).toBeNull(); // Without leaf ids nothing is hoisted. expect(rewriteSpeculable(value, { unaliased: () => true })).toBeNull(); const write = assign("acc", value); diff --git a/packages/compiler/src/backend/llvm/speculative-integers.ts b/packages/compiler/src/backend/llvm/speculative-integers.ts index 1d33a71ce..07e5e3721 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.ts @@ -1,5 +1,5 @@ import type { IrExpr, IrFunction, IrStmt } from "../../ir/ir.js"; -import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; +import { everyExpr, everyExprChild, everyStmtChild } from "../../ir/traverse.js"; import { InternalCompilerError } from "../../errors.js"; import { f64Lit } from "./common.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; @@ -219,6 +219,15 @@ function isSpeculativeByteRead(e: IrExpr): boolean { ); } +/** Whether evaluating `e` assigns no binding: no `++`/`--`, no assignment + * expression, and no statement inside it. */ +function writesNoLocal(e: IrExpr): boolean { + return everyExpr(e, { + expr: (node) => node.kind !== "incDec" && node.kind !== "assignExpr", + stmt: () => false, + }); +} + /** Rewrite a write's value that isSpeculableValue refuses into one it can * speculate (SpeculativeValue), or null. Expansion and the new operations * (`Math.pow(2, n)`, `Math.floor(a / 2^n)`, byte reads) evaluate nothing @@ -295,6 +304,10 @@ export function rewriteSpeculable( // length, a Map read) are cheap where they stand, and would turn every // loop that adds one into a second copy. if (context.leafId === undefined || e.kind !== "call") return null; + // Hoisting runs the arguments before the rest of the write: one that + // assigns a local would move that write ahead of the write's earlier + // reads of it. + if (!e.args.every(writesNoLocal)) return null; const ref: IrExpr = { kind: "varRef", localId: context.leafId(), type: e.type, loc: e.loc }; leaves.push({ localId: (ref as { localId: string }).localId, expr: e, ref }); return ref; diff --git a/tests/corpus/7757-speculative-key-chains.ts b/tests/corpus/7757-speculative-key-chains.ts index 206b53827..1ef08a942 100644 --- a/tests/corpus/7757-speculative-key-chains.ts +++ b/tests/corpus/7757-speculative-key-chains.ts @@ -97,6 +97,15 @@ function effectfulHelper(n: number): number { return acc; } +// A call whose argument assigns a local the write also reads stays where it +// is: `i` is read before `i++` runs. +function stepped(n: number): number { + let acc = 1; + let i = 0; + while (i < n) acc = (acc * 3 + i + tally(i++)) % 1000003; + return acc; +} + const codes = new Uint8Array(5000); let seed = 7; for (let i = 0; i < codes.length; i++) { @@ -112,3 +121,4 @@ calls = 0; console.log(chained(34), chained(60), calls); console.log(nearPowers(4503599627370496, 3), nearPowers(9007199254740991, 2)); console.log(effectfulHelper(10), level); +console.log(stepped(40)); From 34ada100c47f24865125092cfaeb9bab7142a12b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:07:05 -0500 Subject: [PATCH 120/395] Format the string comparison emission test --- tests/harness/string-compare-emission.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts index 1a8fdddf6..b4219853e 100644 --- a/tests/harness/string-compare-emission.test.ts +++ b/tests/harness/string-compare-emission.test.ts @@ -45,9 +45,8 @@ console.log(words.join(",")); // Calls to the comparison runtime: `call`/`callq` on x86-64 and `bl` on // AArch64, with Mach-O's leading underscore on macOS. const compareCalls = (asm: string): number => - asm.match( - /\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g, - )?.length ?? 0; + asm.match(/\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g) + ?.length ?? 0; // The host's assembly, and two other targets' so that every host checks the // x86-64, AArch64 and Mach-O spellings. From 055c9ffa257d0f9ef63af5673d088c4804c997f4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:07:44 -0500 Subject: [PATCH 121/395] Evaluate the arguments of deferred require.resolve calls before they throw Preflight admits a createRequire loader's require.resolve call with a computed request inside a try block, but lowering refused the call, so in JavaScript source the whole statement became a runtime fence. The fence threw before the request expression ran, and before anything else in the statement: `try { where = load.resolve(candidate(name)); }` skipped candidate()'s side effects, so the catch clause saw a different state than in Node. Such calls (a computed request, another argument shape, or runtime-computed options, on require.resolve and require.resolve.paths) now evaluate their arguments in order and then throw the fence, as a computed require call does. The addon-probe fixture checks that a computed candidate path runs before each failed lookup. --- docs/content/limitations/native-addons.mdx | 5 +- .../lowering/builtins/module-resolution.ts | 81 ++++++++++++++----- tests/fixtures/npm-static/addon-probe-cli.ts | 3 +- .../node_modules/addon-probe/index.d.ts | 1 + .../node_modules/addon-probe/index.js | 24 ++++++ tests/harness/npm-static.test.ts | 6 +- 6 files changed, 95 insertions(+), 25 deletions(-) diff --git a/docs/content/limitations/native-addons.mdx b/docs/content/limitations/native-addons.mdx index 5fff3d7e8..8fa7f94bf 100644 --- a/docs/content/limitations/native-addons.mdx +++ b/docs/content/limitations/native-addons.mdx @@ -3,8 +3,9 @@ Direct `createRequire` calls to local addons are rejected at compile time. A package that requires an addon needs a JavaScript implementation that compiles, or a replacement through native FFI. A package that looks for an optional addon at run time compiles with `--npm-static` when the lookup is inside a `try` block. -There, a `createRequire` require call with a computed specifier throws an error where it runs, and its `require.resolve` read as a value is a function that throws when called. -Coverage lists both as deferred to runtime. +There, a `createRequire` require call with a computed specifier, and a `require.resolve` or `require.resolve.paths` call with a computed request, evaluate their arguments and then throw an error. +Its `require.resolve` read as a value is a function that throws when called. +Coverage lists these as deferred to runtime. The package's `catch` clause then runs as it does in Node.js when the addon is missing. Outside a `try` block, the same uses keep the package refused. diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index 42ecfdd50..107e1f793 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -193,6 +193,39 @@ export function createRequireResolveFenceValue( }; } +/** A createRequire use deferred to run time in JavaScript source: as in + * Node, the call's arguments are evaluated in order, then the call throws + * (here, the fence). Code before the call in the same statement runs, and + * the expression diverges, so nothing after it does. */ +function deferredCreateRequireThrow( + lowerer: Lowerer, + call: ts.CallExpression, + surface: string, + hint: string, + loc: SrcLoc, +): IrExpr { + const stmts: IrStmt[] = call.arguments.nodes.map((arg) => ({ + kind: "exprStmt", + expr: lowerer.lowerExpr(arg), + loc: locOf(arg), + })); + const type = lowerer.irTypeOf(call); + const slot = lowerer.declareHiddenLocal("%never", type); + slot.mutable = true; + stmts.push( + { kind: "varDecl", localId: slot.id, init: null, loc }, + createRequireFence(lowerer, surface, call, hint), + ); + return { + kind: "seqExpr", + stmts, + result: varRef(slot.id, type, loc), + diverges: true, + type, + loc, + }; +} + export function lowerCreateRequireCall( lowerer: Lowerer, call: ts.CallExpression, @@ -205,27 +238,7 @@ export function lowerCreateRequireCall( const hint = 'the compiled module graph is fixed at build time — the one lowered form is require(""); native addons are not loaded'; if (!isJsSourceFile(ts.getSourceFileOfNode(call))) lowerer.noLowering(surface, call, hint); - // Node evaluates the arguments, then the require throws. - const stmts: IrStmt[] = call.arguments.nodes.map((arg) => ({ - kind: "exprStmt", - expr: lowerer.lowerExpr(arg), - loc: locOf(arg), - })); - const type = lowerer.irTypeOf(call); - const slot = lowerer.declareHiddenLocal("%never", type); - slot.mutable = true; - stmts.push( - { kind: "varDecl", localId: slot.id, init: null, loc }, - createRequireFence(lowerer, surface, call, hint), - ); - return { - kind: "seqExpr", - stmts, - result: varRef(slot.id, type, loc), - diverges: true, - type, - loc, - }; + return deferredCreateRequireThrow(lowerer, call, surface, hint, loc); } const spec = cr.spec; if (spec === "node:ffi") { @@ -446,11 +459,30 @@ export function lowerRequireResolveCall(lowerer: Lowerer, call: ts.CallExpressio } const baseFile = requireResolverBaseFile(lowerer, receiver); if (baseFile === null) return null; + const surface = pathsCall ? "require.resolve.paths" : "require.resolve"; + // A createRequire loader's query that resolves only at run time, in + // JavaScript source: preflight admits it inside a try block + // (createRequireUse7 in program.ts). Its arguments run, then it throws. + const deferred = (what: string, hint: string): IrExpr | null => + call.questionDotToken === undefined && + createRequireCalleeFileOf(lowerer, receiver) !== null && + isJsSourceFile(ts.getSourceFileOfNode(call)) + ? deferredCreateRequireThrow( + lowerer, + call, + `createRequire's ${surface} ${what}`, + hint, + locOf(call), + ) + : null; + const resolveHint = `call it with a string literal (${surface}("")), which resolves at build time`; if ( call.questionDotToken !== undefined || call.arguments.nodes.length < 1 || call.arguments.nodes.length > (pathsCall ? 1 : 2) ) { + const fenced = deferred("with this argument shape", resolveHint); + if (fenced) return fenced; lowerer.noLowering( pathsCall ? "require.resolve.paths with this argument shape" @@ -460,6 +492,8 @@ export function lowerRequireResolveCall(lowerer: Lowerer, call: ts.CallExpressio } const specifier = staticString(call.arguments.nodes[0]); if (specifier === null) { + const fenced = deferred("with a computed request", resolveHint); + if (fenced) return fenced; lowerer.noLowering( `${pathsCall ? "require.resolve.paths" : "require.resolve"} with a runtime-computed request`, call.arguments.nodes[0]!, @@ -495,6 +529,11 @@ export function lowerRequireResolveCall(lowerer: Lowerer, call: ts.CallExpressio } const paths = staticResolveOptionPaths(call.arguments.nodes[1]); if (paths === null) { + const fenced = deferred( + "with runtime-computed options", + 'omit options or pass { paths: ["", ...] }', + ); + if (fenced) return fenced; lowerer.noLowering( "require.resolve with runtime-computed options", call.arguments.nodes[1]!, diff --git a/tests/fixtures/npm-static/addon-probe-cli.ts b/tests/fixtures/npm-static/addon-probe-cli.ts index 4515fb5bf..aa21df05d 100644 --- a/tests/fixtures/npm-static/addon-probe-cli.ts +++ b/tests/fixtures/npm-static/addon-probe-cli.ts @@ -1,5 +1,6 @@ -import { probe, resolveHelper } from "addon-probe"; +import { locate, probe, resolveHelper } from "addon-probe"; import { join } from "node:path"; console.log(join("a", "b"), [probe("clipboard"), probe("keys"), resolveHelper("addon-probe-helper")].join(",")); console.log(resolveHelper("other", (id) => `custom:${id}`)); +console.log(locate("helper")); diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts index cfacc4764..a9987d787 100644 --- a/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts @@ -1,2 +1,3 @@ export declare function probe(name: string): string; export declare function resolveHelper(name: string, resolvePackage?: (id: string) => string): string; +export declare function locate(name: string): string; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.js b/tests/fixtures/npm-static/node_modules/addon-probe/index.js index e65b1ad0e..2cc423359 100644 --- a/tests/fixtures/npm-static/node_modules/addon-probe/index.js +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.js @@ -26,3 +26,27 @@ export function resolveHelper(name, resolvePackage) { return "unresolved"; } } + +let probes = 0; +function candidate(name) { + probes++; + return `./${name}-${probes}.node`; +} + +// Resolves computed candidate paths: the arguments run before the lookup +// fails, as an assignment and as a statement of their own. +export function locate(name) { + let where = "none"; + try { + where = load.resolve(candidate(name)); + } catch { + where = `missing after ${probes}`; + } + try { + load.resolve(candidate(name)); + where += ",found"; + } catch { + where += `,missing after ${probes}`; + } + return where; +} diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 026da8f4e..4ddb6fe15 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -435,6 +435,8 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ).toEqual([ expect.stringContaining("'createRequire's require with a computed specifier"), expect.stringContaining("'createRequire's require.resolve as a value'"), + expect.stringContaining("'createRequire's require.resolve with a computed request'"), + expect.stringContaining("'createRequire's require.resolve with a computed request'"), ]); const outDir = join(cacheDir, "addon-probe", sanitize ? "san" : "plain", backend); const result = await compile(entry, { @@ -450,7 +452,9 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { runBinary(process.execPath, [entry]), runBinary(result.binaryPath, []), ]); - expect(nativeRes.stdout.toString()).toBe("a/b absent,absent,unresolved\ncustom:other\n"); + expect(nativeRes.stdout.toString()).toBe( + "a/b absent,absent,unresolved\ncustom:other\nmissing after 1,missing after 2\n", + ); expect(nativeRes.stdout).toEqual(nodeRes.stdout); expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); expect(nativeRes.exitCode).toBe(nodeRes.exitCode); From adbd9cd1e9b85fbe6b997e6ba2170b2f1d5a1858 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:18:30 -0500 Subject: [PATCH 122/395] Lower each closure argument of an implicit-any call once To key an implicit-any function's instance, storedImplicitArgumentType lowers a JavaScript arrow or function-expression argument to read its function type, then discards the value; the call's argument list then lowers the closure again. Every such closure was lowered twice, and a closure nested in another one four times, and each lowering lifted its own copy of the body. The first lowering now keeps its value (Lowerer.speculatedClosures), and the argument's own lowering in the same function and with the same type bindings takes it instead of lowering the closure again. A lowering that reported diagnostics is not kept. --- .../frontend/lowering/generic-functions.ts | 17 ++++++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 7896da578..0f9e41723 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -2082,6 +2082,10 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) ) { + // A closure lowered here before (another call site's instance key, or + // a repeated demand) keeps its value for the argument's own lowering. + const known = lowerer.speculatedClosure(arg); + if (known !== null) return known.type; const saved = lowerer.diagSink; const diagnostics: typeof lowerer.diags = []; lowerer.diagSink = diagnostics; @@ -2092,8 +2096,17 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp lowerer.diagSink = saved; } // Direct inferred helpers can receive native callbacks even when their - // result is an opaque handle that cannot cross checked storage. - if (diagnostics.length === 0 && value?.type.kind === "func") return value.type; + // result is an opaque handle that cannot cross checked storage. The + // argument's own lowering then takes this value instead of lowering + // the closure a second time. + if (diagnostics.length === 0 && value?.type.kind === "func") { + lowerer.speculatedClosures.set(arg, { + ctx: lowerer.ctx, + bindings: lowerer.typeParamBindings, + value, + }); + return value.type; + } } if (arg.kind === ts.SyntaxKind.ThisKeyword) return lowerer.resolveThis()?.type ?? null; if (ts.isAwaitExpression(arg)) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 6fd6d863a..dd07d0075 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3174,6 +3174,14 @@ export class Lowerer { * (builtins/fs-paths.ts): each lowers to the hidden local that holds its * value, which is null until the path's lowering has saved it. */ readonly stagedArguments = new Map(); + /** Closure arguments an implicit-any call already lowered to learn their + * type (storedImplicitArgumentType), by node: the argument's own lowering + * in the same function and with the same type bindings takes that value + * once instead of lowering the closure again. */ + readonly speculatedClosures = new Map< + ts.Expression, + { ctx: FnCtx; bindings: Map | null; value: IrExpr } + >(); /** What each fs path argument of a URL-path call lowers to (the plans * of builtins/fs-paths.ts). */ readonly fsPathPlans = new Map(); @@ -12938,12 +12946,30 @@ export class Lowerer { /* ── expressions ──────────────────────────────────────────────────── */ lowerExpr(expr: ts.Expression): IrExpr { + if (this.speculatedClosures.size > 0) { + const speculated = this.speculatedClosure(expr); + if (speculated !== null) { + this.speculatedClosures.delete(expr); + return speculated; + } + } const streamValue = this.valueStreamReceivers.get(expr); if (streamValue) return valueStreamOf(varRef(streamValue.localId, streamValue.type, locOf(expr))); return this.stagedArgument(expr) ?? lowerExpr(this, expr); } + /** The value storedImplicitArgumentType lowered for `expr` in the active + * function and type bindings (speculatedClosures), or null. */ + speculatedClosure(expr: ts.Expression): IrExpr | null { + const entry = this.speculatedClosures.get(expr); + return entry !== undefined && + entry.ctx === this.ctx && + entry.bindings === this.typeParamBindings + ? entry.value + : null; + } + /** The saved value of a staged fs argument (stagedArguments, keyed * without type-only wrappers), or null for every other expression. */ private stagedArgument(node: ts.Expression): IrExpr | null { From cc6b1144681da6c0d29649d44ee203703e9db25d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:23:08 -0500 Subject: [PATCH 123/395] Take the argument count before parameters bind A function that reads arguments.length and is called through a value reads the count its adapter stored, then clears the slot. That read ran after the parameter prologue, so a parameter binding that threw would leave the slot set for the next direct call. The count is now taken, and the slot cleared, before any parameter binds. --- .../src/frontend/lowering/lower-functions.ts | 7 ++++++- ...3-argument-count-destructured-parameter.ts.json | 6 ++++++ .../8223-argument-count-destructured-parameter.ts | 14 ++++++++++++++ ...3-argument-count-destructured-parameter.ts.json | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8223-argument-count-destructured-parameter.ts.json create mode 100644 tests/corpus/8223-argument-count-destructured-parameter.ts create mode 100644 tests/harness/corpus-costs/8223-argument-count-destructured-parameter.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index c1682f7c8..bd9b5f55d 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -535,12 +535,18 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I const diagsBefore = lowerer.diags.length; lowerer.fnStack.push(ctx); try { + // The count a call through a value stored is taken (and its slot + // cleared) before the parameters bind: a destructuring default that + // throws must not leave the slot set for the next direct call. + const argumentCount: IrStmt[] = []; + declareArgumentCount(lowerer, decl, argumentCount); const receiver = decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(ts.getSourceFileOfNode(decl)) ? declareCallReceiver(lowerer, decl, sig.generator !== undefined) : null; const { params, prologue } = lowerer.declareParams(decl.parameters.nodes, sig.params); if (receiver) prologue.unshift(receiver); + prologue.unshift(...argumentCount); declareFunctionArguments( lowerer, decl, @@ -552,7 +558,6 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I (sig.params[sig.params.length - 1]!.mode === "dynRest" || sig.params[sig.params.length - 1]!.mode === "arguments"), ); - declareArgumentCount(lowerer, decl, prologue); const bodyBlock = blockBodyOf(decl); if (!bodyBlock) { lowerer.unsupported( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8223-argument-count-destructured-parameter.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8223-argument-count-destructured-parameter.ts.json new file mode 100644 index 000000000..6cae202d3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8223-argument-count-destructured-parameter.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8223-argument-count-destructured-parameter.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8223-argument-count-destructured-parameter.ts b/tests/corpus/8223-argument-count-destructured-parameter.ts new file mode 100644 index 000000000..def8c5ba8 --- /dev/null +++ b/tests/corpus/8223-argument-count-destructured-parameter.ts @@ -0,0 +1,14 @@ +// A function with a destructured parameter that reads arguments.length, +// called through a value (map passes three arguments) and directly. The +// count is taken before the parameters bind, so each call sees its own. + +function probe({ v, w }: { v: number; w: string }): string { + return `${arguments.length}:${v}${w}`; +} + +console.log([{ v: 1, w: "a" }, { v: 2, w: "b" }].map(probe)); +console.log(probe({ v: 3, w: "c" })); +const call = (f: (item: { v: number; w: string }, index: number) => string): string => + f({ v: 4, w: "d" }, 0); +console.log(call(probe)); +console.log(probe({ v: 5, w: "e" })); diff --git a/tests/harness/corpus-costs/8223-argument-count-destructured-parameter.ts.json b/tests/harness/corpus-costs/8223-argument-count-destructured-parameter.ts.json new file mode 100644 index 000000000..664b49416 --- /dev/null +++ b/tests/harness/corpus-costs/8223-argument-count-destructured-parameter.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 4200 } From 7fb950deaf04b0eb48943694c213a7b652f5903b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:29:34 -0500 Subject: [PATCH 124/395] Keep rejectUnauthorized on unless false, and certificates independent of raw A server with requestCert read a runtime rejectUnauthorized through JavaScript truthiness, so null, 0 or "" let clients without a valid certificate through, and tls.connect read it the same way. Both now treat only false as off, as Node's options.rejectUnauthorized !== false does; a standalone TLSSocket keeps Node's truthiness test. An X509Certificate's methods read the record's raw Buffer, so writing into the Buffer that certificate.raw answers changed later checks, the PEM and the public key. The record now keeps a hidden copy of the DER that the methods read, and raw stays the same Buffer at every read, as in Node. --- .../src/frontend/lowering/builtins/x509.ts | 17 +++--- .../src/frontend/lowering/lower-classes.ts | 3 + packages/compiler/src/frontend/type-mapper.ts | 9 ++- ...79-tls-reject-unauthorized-values.mjs.json | 6 ++ .../tests/corpus/8181-x509-raw-writes.ts.json | 6 ++ packages/runtime/src/scr_tls.c | 14 +++-- .../8179-tls-reject-unauthorized-values.mjs | 55 +++++++++++++++++++ tests/corpus/8181-x509-raw-writes.ts | 20 +++++++ ...79-tls-reject-unauthorized-values.mjs.json | 1 + .../corpus-costs/8181-x509-raw-writes.ts.json | 1 + 10 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8179-tls-reject-unauthorized-values.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-x509-raw-writes.ts.json create mode 100644 tests/corpus/8179-tls-reject-unauthorized-values.mjs create mode 100644 tests/corpus/8181-x509-raw-writes.ts create mode 100644 tests/harness/corpus-costs/8179-tls-reject-unauthorized-values.mjs.json create mode 100644 tests/harness/corpus-costs/8181-x509-raw-writes.ts.json diff --git a/packages/compiler/src/frontend/lowering/builtins/x509.ts b/packages/compiler/src/frontend/lowering/builtins/x509.ts index bc0092369..5d81070eb 100644 --- a/packages/compiler/src/frontend/lowering/builtins/x509.ts +++ b/packages/compiler/src/frontend/lowering/builtins/x509.ts @@ -16,13 +16,14 @@ import { } from "../../../ir/ir.js"; import { everyStmtList } from "../../../ir/traverse.js"; import { unsupportedDiag } from "../../../diagnostics/diagnostic.js"; -import { X509_PUBLIC_KEY_FIELD } from "../../type-mapper.js"; +import { X509_DER_FIELD, X509_PUBLIC_KEY_FIELD } from "../../type-mapper.js"; import { keyedHelperName } from "../stable-names.js"; import { x509RecordCall } from "../lower-classes.js"; /* node:crypto's X509Certificate members beyond its fields: the methods * and the publicKey getter. A certificate is the Dirent-style record - * (type-mapper.ts), and each member reads its raw DER. checkHost, + * (type-mapper.ts), and each member reads the record's hidden copy of its + * DER, which writes into the `raw` Buffer leave alone, as in Node. checkHost, * checkEmail, checkIP, checkIssued, toLegacyObject and the PEM of * toString/toJSON run in scr_x509.c; * publicKey, verify and checkPrivateKey read keys in scr_pki.c. The first @@ -110,7 +111,7 @@ export function refuseX509DynValues(lowerer: Lowerer, functions: readonly IrFunc /** String(x509), `${x509}` and x509 + "": the certificate's toString(), * its PEM. */ export function x509ToString(record: IrExpr, loc: SrcLoc): IrExpr { - return { kind: "libCall", fn: "crypto.x509Pem", args: [rawOf(record, loc)], type: STRING, loc }; + return { kind: "libCall", fn: "crypto.x509Pem", args: [derOf(record, loc)], type: STRING, loc }; } /** Node keeps a certificate's fields as prototype getters, so own-key @@ -131,13 +132,13 @@ export function fenceX509KeyWalk( ); } -function rawOf(record: IrExpr, loc: SrcLoc): IrExpr { +function derOf(record: IrExpr, loc: SrcLoc): IrExpr { if (record.type.kind !== "record") return record; return { kind: "recordGet", obj: record, shapeId: record.type.shapeId, - field: "raw", + field: X509_DER_FIELD, type: BYTES_U8, loc, }; @@ -254,7 +255,7 @@ export function lowerX509PublicKey( value: { kind: "libCall", fn: "crypto.x509PublicKey", - args: [rawOf(c, loc)], + args: [derOf(c, loc)], type: DYN, loc, }, @@ -310,7 +311,7 @@ export function lowerX509Call(lowerer: Lowerer, call: ts.CallExpression): IrExpr `the supported form is ${method.form}`, ); } - const raw = rawOf(lowerer.lowerExpr(access.expression), loc); + const raw = derOf(lowerer.lowerExpr(access.expression), loc); if (method.fn === "crypto.x509LegacyObject") { return { kind: "libCall", fn: method.fn, args: [raw], type: DYN, loc }; } @@ -337,7 +338,7 @@ export function lowerX509Call(lowerer: Lowerer, call: ts.CallExpression): IrExpr loc, }; } - const other = rawOf(lowerer.lowerExpr(args[0]!), loc); + const other = derOf(lowerer.lowerExpr(args[0]!), loc); return { kind: "libCall", fn: method.fn, args: [raw, other], type: BOOL, loc }; } const first = lowerer.lowerExprExpecting(args[0]!, DYN); diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 9035869d9..23dac58a2 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -100,6 +100,7 @@ import { jsOpenObjectType, typeKey, withUnitArm, + X509_DER_FIELD, X509_PUBLIC_KEY_FIELD, } from "../type-mapper.js"; import { @@ -11313,6 +11314,8 @@ function x509RecordHelper( type: DYN, loc, }), + // A copy: writes into `raw` must not reach the methods. + [X509_DER_FIELD]: () => call("crypto.x509RawDer", BYTES_U8), }; /** A string | undefined field: the runtime's text, or undefined for "". */ function optionalText(local: string, fn: IrLibFn, type: IrType): IrExpr { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index f51efd5f1..9a8ae46ba 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6715,8 +6715,14 @@ function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): * filled on the first read (builtins/x509.ts). */ export const X509_PUBLIC_KEY_FIELD = "%publicKey"; +/** The X509Certificate record's hidden copy of its DER, which its methods + * read: a program can write into the Buffer `raw` answers, and in Node + * that changes neither the certificate's checks nor its PEM. */ +export const X509_DER_FIELD = "%der"; + /** The X509Certificate record's fields in @types/node's declaration order - * ("string?" is string | undefined), then the hidden publicKey slot. */ + * ("string?" is string | undefined), then the hidden publicKey and DER + * slots. */ export const X509_FIELDS: readonly (readonly [string, IrType | "string?"])[] = [ ["ca", BOOL], ["fingerprint", STRING], @@ -6735,6 +6741,7 @@ export const X509_FIELDS: readonly (readonly [string, IrType | "string?"])[] = [ ["validTo", STRING], ["validToDate", DATE_T], [X509_PUBLIC_KEY_FIELD, DYN], + [X509_DER_FIELD, { kind: "bytes", elem: "u8" }], ]; export function withUndefinedArm(t: IrType, unions: UnionRegistry): IrType | null { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8179-tls-reject-unauthorized-values.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8179-tls-reject-unauthorized-values.mjs.json new file mode 100644 index 000000000..ef98570e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8179-tls-reject-unauthorized-values.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8179-tls-reject-unauthorized-values.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-x509-raw-writes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-x509-raw-writes.ts.json new file mode 100644 index 000000000..d92d8f8fd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8181-x509-raw-writes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8181-x509-raw-writes.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index e764926b2..547fef27a 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -2232,8 +2232,8 @@ static void scr_tls_opt_fence(const char *api, const char *key, const char *hint scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); } -/* JS truthiness over the dyn kinds — Node reads requestCert / - * rejectUnauthorized through `if (options.x)`. */ +/* JS truthiness over the dyn kinds — Node reads requestCert (and a + * standalone TLSSocket's rejectUnauthorized) through `if (options.x)`. */ static bool scr_tls_dyn_truthy(const ScrDyn *v) { switch (v->kind) { case SCR_DYN_UNDEF: @@ -2245,6 +2245,12 @@ static bool scr_tls_dyn_truthy(const ScrDyn *v) { } } +/* tls.Server and tls.connect read rejectUnauthorized as + * `options.rejectUnauthorized !== false`: null, 0 and "" keep it on. */ +static bool scr_tls_dyn_not_false(const ScrDyn *v) { + return v->kind != SCR_DYN_BOOL || v->v.b; +} + /* PEM material from a runtime option value: a string, a Buffer/Uint8Array, * or an array of those. `concat` (the client `ca` bundle) joins array * entries; cert/key arrays accept exactly one entry (one identity per @@ -2630,7 +2636,7 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt continue; } if (strcmp(e->key, "rejectUnauthorized") == 0) { - if (auth != NULL) auth->reject = scr_tls_dyn_truthy(v); + if (auth != NULL) auth->reject = scr_tls_dyn_not_false(v); continue; /* without client certificates it has no effect */ } if (server_ok && (strcmp(e->key, "ALPNProtocols") == 0 || strcmp(e->key, "minVersion") == 0 || @@ -3300,7 +3306,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed, continue; } if (strcmp(e->key, "rejectUnauthorized") == 0) { - reject = scr_tls_dyn_truthy(v); + reject = scr_tls_dyn_not_false(v); continue; } if (strcmp(e->key, "ca") == 0) { diff --git a/tests/corpus/8179-tls-reject-unauthorized-values.mjs b/tests/corpus/8179-tls-reject-unauthorized-values.mjs new file mode 100644 index 000000000..d0275ab4a --- /dev/null +++ b/tests/corpus/8179-tls-reject-unauthorized-values.mjs @@ -0,0 +1,55 @@ +// rejectUnauthorized is off only when it is false, as in Node: on a server +// with requestCert, null, 0, "" and undefined keep rejecting clients that +// send no certificate, and in tls.connect they keep rejecting a server +// certificate that does not verify. +import { readFileSync } from "node:fs"; +import * as tls from "node:tls"; + +const D = "tests/fixtures/server/certs"; +const r = (f) => readFileSync(`${D}/${f}`); +const ca = r("ca.pem"), cert = r("localhost.pem"), key = r("localhost-key.pem"); +const self = r("selfsigned.pem"), selfKey = r("selfsigned-key.pem"); + +const values = [ + ["false", false], + ["true", true], + ["null", null], + ["0", 0], + ['""', ""], + ["undefined", undefined], +]; + +for (const [label, value] of values) { + const events = []; + const server = tls.createServer({ cert, key, requestCert: true, rejectUnauthorized: value }, (s) => { + events.push(`server authorized=${s.authorized} error=${s.authorizationError}`); + s.end("ok"); + }); + const port = await new Promise((res) => server.listen(0, () => res(server.address().port))); + await new Promise((done) => { + const c = tls.connect({ port, ca, servername: "localhost" }); + c.on("secureConnect", () => events.push("client secureConnect")); + c.on("data", (d) => events.push(`client data ${d}`)); + c.on("error", (e) => events.push(`client error ${e.code}`)); + c.on("close", () => setTimeout(done, 50)); + }); + console.log("rejectUnauthorized", label, "|", events.join(" ; ")); + await new Promise((res) => server.close(() => res())); +} + +{ + const server = tls.createServer({ cert: self, key: selfKey }, (s) => s.end("ok")); + const port = await new Promise((res) => server.listen(0, () => res(server.address().port))); + for (const [label, value] of values) { + const events = []; + await new Promise((done) => { + const c = tls.connect({ port, servername: "localhost", rejectUnauthorized: value }); + c.on("secureConnect", () => events.push(`secureConnect authorized=${c.authorized} error=${c.authorizationError}`)); + c.on("data", (d) => events.push(`data ${d}`)); + c.on("error", (e) => events.push(`error ${e.code}`)); + c.on("close", () => done()); + }); + console.log("tls.connect rejectUnauthorized", label, "|", events.join(" ; ")); + } + server.close(); +} diff --git a/tests/corpus/8181-x509-raw-writes.ts b/tests/corpus/8181-x509-raw-writes.ts new file mode 100644 index 000000000..17704096d --- /dev/null +++ b/tests/corpus/8181-x509-raw-writes.ts @@ -0,0 +1,20 @@ +// A certificate's raw Buffer is the same object at every read, and writing +// into it changes neither the certificate's fields, checks and PEM nor its +// public key, as in Node: the methods read the certificate itself. +import { X509Certificate } from "node:crypto"; +import { readFileSync } from "node:fs"; + +const cert = new X509Certificate(readFileSync("tests/fixtures/server/certs/localhost.pem")); +const ca = new X509Certificate(readFileSync("tests/fixtures/server/certs/ca.pem")); +const raw = cert.raw; +console.log("same Buffer", raw === cert.raw); +const report = (): string => + `${cert.fingerprint256}\n${cert.subject}\n${cert.checkHost("localhost")}\n` + + `${cert.checkIssued(ca)} ${ca.checkIssued(cert)} ${cert.verify(ca.publicKey)}\n` + + `${cert.toString()}\n${cert.publicKey.export({ format: "pem", type: "spki" })}`; +const before = report(); +raw.fill(0); +console.log("unchanged", report() === before); +console.log("raw now", cert.raw.every((b) => b === 0), cert.raw.length); +const other = new X509Certificate(readFileSync("tests/fixtures/server/certs/localhost.pem")); +console.log("fresh certificate raw", other.raw[0], other.raw.equals(cert.raw)); diff --git a/tests/harness/corpus-costs/8179-tls-reject-unauthorized-values.mjs.json b/tests/harness/corpus-costs/8179-tls-reject-unauthorized-values.mjs.json new file mode 100644 index 000000000..ef441a0df --- /dev/null +++ b/tests/harness/corpus-costs/8179-tls-reject-unauthorized-values.mjs.json @@ -0,0 +1 @@ +{ "plain": 5582, "san": 65742 } diff --git a/tests/harness/corpus-costs/8181-x509-raw-writes.ts.json b/tests/harness/corpus-costs/8181-x509-raw-writes.ts.json new file mode 100644 index 000000000..df264cf6e --- /dev/null +++ b/tests/harness/corpus-costs/8181-x509-raw-writes.ts.json @@ -0,0 +1 @@ +{ "plain": 3996, "san": 8383 } From 2edebe7e2ff7df84903a9236aa147472950b3002 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:30:32 -0500 Subject: [PATCH 125/395] Read spread tuple fields before later rest arguments run The packing path for `(T | undefined)[]` rest parameters saved plain arguments to temporaries but read tuple fields only when it built the pack, after every later argument had run. A later argument that wrote the tuple changed values already spread into the call. Each tuple field is now saved where its spread appears, in argument order. --- .../src/frontend/lowering/call-arguments.ts | 14 +++++++++----- .../corpus/8004-optional-element-conversions.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index b02d0b85c..a139bfc64 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -421,12 +421,16 @@ export function completeArgs( ) { const stmts: IrStmt[] = []; const values = restNodes.map((a): IrExpr => { + // A tuple field is read where its spread appears: a later + // argument may write the tuple. + let value: IrExpr; if (isIr(a)) { - if (a.ir.kind !== "seqExpr") return a.ir; - stmts.push(...a.ir.stmts); - return a.ir.result; - } - const value = lowerer.lowerExpr(a as ts.Expression); + value = a.ir; + if (value.kind === "seqExpr") { + stmts.push(...value.stmts); + value = value.result; + } + } else value = lowerer.lowerExpr(a as ts.Expression); const saved = lowerer.declareHiddenLocal("%callArg", value.type); stmts.push({ kind: "varDecl", localId: saved.id, init: value, loc: value.loc }); return { kind: "varRef", localId: saved.id, type: value.type, loc: value.loc }; diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 2a394c331..07178e9b0 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -33,6 +33,22 @@ function label(first: string, ...rest: (string | undefined)[]): string { } const words: [string, string | undefined, string | undefined] = ["a", undefined, "c"]; console.log(label(...words), label("b", undefined)); +// Spread tuple fields are read before later arguments run. +const changing: [boolean, boolean | undefined] = [true, undefined]; +function flip(pair: [boolean, boolean | undefined], second: boolean | undefined): boolean { + pair[0] = !pair[0]; + pair[1] = second; + return pair[0]; +} +console.log(flags(...changing, flip(changing, true))); +console.log(flags(...changing, flip(changing, undefined), ...changing)); +const swapped: [string, string | undefined] = ["x", "y"]; +function swap(pair: [string, string | undefined]): string { + pair[0] = "w"; + pair[1] = undefined; + return "v"; +} +console.log(label("z", ...swapped, swap(swapped), ...swapped)); function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From 51557931556f79b49f4b8a00fe80c6a59aeaaf05 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:33:30 -0500 Subject: [PATCH 126/395] Decode passphrases with the key's encoding, and refuse RSA-PSS keys restricted to other digests createPrivateKey decoded a string passphrase as UTF-8 whatever the options' encoding, so a correct passphrase given in that encoding failed to decrypt, and an encrypted export read its passphrase the same way. Both now decode it with the encoding, as Node does. An RSA-PSS key whose parameters name a digest the runtime does not sign with, such as sha512-224 or sha512-256, was imported without its digest, MGF1 and salt restrictions, so it allowed operations the key forbids. It now throws a coded SC2020 error that names the digest. Parameters that are malformed still leave the key unrestricted, as OpenSSL reads them. --- .../8183-key-passphrase-encoding.cjs.json | 6 ++ packages/runtime/src/scr_pki.c | 78 +++++++++++++++---- tests/corpus/8183-key-passphrase-encoding.cjs | 28 +++++++ .../8183-key-passphrase-encoding.cjs.json | 1 + tests/harness/errors.test.ts | 32 ++++++++ 5 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json create mode 100644 tests/corpus/8183-key-passphrase-encoding.cjs create mode 100644 tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json new file mode 100644 index 000000000..ee89a2add --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8183-key-passphrase-encoding.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index 908129243..9877ac6c9 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -1635,48 +1635,79 @@ static SpKey *sp_ed_from_der(const unsigned char *der, size_t len, bool private_ return k; } -/* RSASSA-PSS-params at *p (RFC 4055): true with the restrictions set on - * `k`, false when they are malformed or unsupported, which leaves the key - * without restrictions, as OpenSSL reads a negative or oversized salt. */ -static bool sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k) { +/* The name of a digest OID that RSASSA-PSS-params can hold and this + * runtime does not sign with, for its refusal. */ +static const char *sp_pss_other_digest(const mbedtls_asn1_buf *oid) { + /* 2.16.840.1.101.3.4.2.x, the NIST hash arc */ + static const unsigned char nist[] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02}; + if (oid->len == sizeof nist + 1 && memcmp(oid->p, nist, sizeof nist) == 0) { + switch (oid->p[sizeof nist]) { + case 0x05: return "sha512-224"; + case 0x06: return "sha512-256"; + case 0x07: return "sha3-224"; + case 0x08: return "sha3-256"; + case 0x09: return "sha3-384"; + case 0x0a: return "sha3-512"; + } + } + mbedtls_md_type_t md; + if (mbedtls_oid_get_md_alg(oid, &md) == 0) { + if (md == MBEDTLS_MD_MD5) return "md5"; + if (md == MBEDTLS_MD_RIPEMD160) return "ripemd160"; + } + return "this digest"; +} + +/* RSASSA-PSS-params at *p (RFC 4055): 1 with the restrictions set on `k`; + * 0 when they are malformed, which leaves the key without restrictions, as + * OpenSSL reads a negative or oversized salt; -1 with *digest named when a + * hash or MGF1 digest is one this runtime does not sign with, so that the + * key is refused rather than imported without the restrictions it carries. */ +static int sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k, const char **digest) { size_t n; - if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return 0; const unsigned char *params_end = *p + n; mbedtls_md_type_t md = MBEDTLS_MD_SHA1, mgf1 = MBEDTLS_MD_SHA1; int salt = 20; if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { mbedtls_asn1_buf oid, params; if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) != 0 || - mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || mbedtls_oid_get_md_alg(&oid, &md) != 0 || - !sp_pss_md_name(md)) - return false; + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0) + return 0; + if (mbedtls_oid_get_md_alg(&oid, &md) != 0 || !sp_pss_md_name(md)) { + *digest = sp_pss_other_digest(&oid); + return -1; + } } if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) { mbedtls_asn1_buf oid, params; if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1) != 0 || mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || oid.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1) || memcmp(oid.p, MBEDTLS_OID_MGF1, oid.len) != 0) - return false; + return 0; /* MGF1's parameters are the hash's AlgorithmIdentifier, whose * content starts with the hash's OID. */ - if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return false; + if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return 0; unsigned char *alg = params.p; mbedtls_asn1_buf hash_oid = {.tag = MBEDTLS_ASN1_OID}; - if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return false; + if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return 0; hash_oid.p = alg; - if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) return false; + if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) { + *digest = sp_pss_other_digest(&hash_oid); + return -1; + } } if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) { if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2) != 0 || mbedtls_asn1_get_int(p, *p + n, &salt) != 0 || salt < 0) - return false; + return 0; } *p = (unsigned char *)params_end; k->pss_params = true; k->pss_md = md; k->pss_mgf1 = mgf1; k->pss_salt = salt; - return true; + return 1; } /* An RSA-PSS key from PKCS#8 or SubjectPublicKeyInfo DER whose algorithm @@ -1706,7 +1737,19 @@ static SpKey *sp_pss_from_der(const unsigned char *der, size_t len, bool private p += oid_len; SpKey *k = sp_key_alloc(pkcs8); k->rsa_pss = true; - if (p < alg_end && !sp_pss_params_read(&p, alg_end, k)) k->pss_params = false; + const char *digest = NULL; + if (p < alg_end) { + int read = sp_pss_params_read(&p, alg_end, k, &digest); + if (read < 0) { + sp_key_release(k); + *failed = true; + char what[96]; + snprintf(what, sizeof what, "RSA-PSS keys restricted to %s", digest); + sp_not_supported(what); + return NULL; + } + if (read == 0) k->pss_params = false; + } p = (unsigned char *)alg_end; int r; if (pkcs8) { @@ -1967,7 +2010,8 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { } ScrBytes *pass = NULL; if (passphrase && !sp_absent(passphrase)) { - pass = sp_source_bytes(passphrase, NULL); + /* Node decodes a string passphrase with the key's encoding. */ + pass = sp_source_bytes(passphrase, encoding); if (!pass) { scr_bytes_release(bytes); return NULL; @@ -2292,7 +2336,7 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { /* OpenSSL's error, raised once the key is encoded. */ } else if (ci) { const unsigned char *start = der + SP_DER_MAX - n; - ScrBytes *pass = sp_source_bytes(passphrase, NULL); + ScrBytes *pass = sp_source_bytes(passphrase, sp_opt(options, "encoding")); const unsigned char *pass_data = pass ? pass->data : (const unsigned char *)""; size_t pass_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; if (pkcs8) { diff --git a/tests/corpus/8183-key-passphrase-encoding.cjs b/tests/corpus/8183-key-passphrase-encoding.cjs new file mode 100644 index 000000000..e52fa6c1c --- /dev/null +++ b/tests/corpus/8183-key-passphrase-encoding.cjs @@ -0,0 +1,28 @@ +// A string passphrase is decoded with the key's encoding, as in Node: +// createPrivateKey's { encoding } and the encoding option of an encrypted +// export both apply to it. +const c = require("node:crypto"); + +const { privateKey } = c.generateKeyPairSync("ec", { namedCurve: "P-256" }); +const pass = "pässwörd"; +const hex = Buffer.from(pass).toString("hex"); +const pem = privateKey.export({ format: "pem", type: "pkcs8", cipher: "aes-256-cbc", passphrase: hex, encoding: "hex" }); +const der = privateKey.export({ format: "der", type: "pkcs8", cipher: "aes-128-cbc", passphrase: pass }); + +function attempt(label, options) { + try { + const key = c.createPrivateKey(options); + console.log(label, key.asymmetricKeyType, key.equals(privateKey)); + } catch (e) { + console.log(label, "throws", e.code); + } +} + +attempt("utf8 passphrase", { key: pem, passphrase: pass }); +attempt("hex passphrase, encoding hex", { key: Buffer.from(pem), passphrase: hex, encoding: "hex" }); +attempt("base64 passphrase, encoding base64", { key: Buffer.from(pem), passphrase: Buffer.from(pass).toString("base64"), encoding: "base64" }); +attempt("latin1 passphrase, encoding latin1", { key: Buffer.from(pem), passphrase: Buffer.from(pass).toString("latin1"), encoding: "latin1" }); +attempt("hex passphrase, no encoding", { key: pem, passphrase: hex }); +attempt("Buffer passphrase, encoding hex", { key: Buffer.from(pem), passphrase: Buffer.from(pass), encoding: "hex" }); +attempt("der, hex passphrase, encoding hex", { key: der, format: "der", type: "pkcs8", passphrase: hex, encoding: "hex" }); +attempt("hex key string, encoding hex", { key: Buffer.from(pem).toString("hex"), passphrase: hex, encoding: "hex" }); diff --git a/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json b/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json new file mode 100644 index 000000000..0d507484f --- /dev/null +++ b/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json @@ -0,0 +1 @@ +{ "plain": 3665, "san": 67315 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c8849a10f..663806942 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -870,6 +870,38 @@ console.log(\`\${register(7)}\`); }); }); +// An RSA-PSS key whose parameters restrict it to a digest the runtime does +// not sign with (sha512-224 and sha512-256, which OpenSSL reads) throws a +// coded SC2020 error naming the digest, instead of importing the key +// without the restrictions it carries. +describe("RSA-PSS keys restricted to unimplemented digests", () => { + test("importing one throws SC2020 naming the digest", async () => { + const r = await compileAndRun( + "pss-restricted-digest", + `import { createPublicKey } from "node:crypto"; +const keys = [ + "-----BEGIN PUBLIC KEY-----\\nMIGQMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIFBQChHDAaBgkqhkiG9w0B\\nAQgwDQYJYIZIAWUDBAIFBQCiAwIBCANLADBIAkEArMDQESqjeWDLqFa1E5SzsMl4\\n6tyUsEqcDKSwRUvPilv1AUudf/w6qaXlPnAxBmFjZYihdQSZlk0czRLOX3o7gwID\\nAQAB\\n-----END PUBLIC KEY-----\\n", + "-----BEGIN PUBLIC KEY-----\\nMIGQMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\\nAQgwDQYJYIZIAWUDBAIGBQCiAwIBCANLADBIAkEAuOa9NkzXzMDelHCIEkf0mnKe\\nnQdalNUm9vZtIMwpDJcn5U0Vbhhd6MTfRPhSo5nW71Oh5eU7v5M3PMYOHS7cMQID\\nAQAB\\n-----END PUBLIC KEY-----\\n", +]; +for (const key of keys) { + try { + console.log(createPublicKey(key).asymmetricKeyType); + } catch (error) { + const e = error as Error & { code?: string }; + console.log(e.name, e.code, e.message); + } +} +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "Error SC2020 'RSA-PSS keys restricted to sha512-224' is part of the standard library types but has no scriptc lowering yet\n" + + "Error SC2020 'RSA-PSS keys restricted to sha512-256' is part of the standard library types but has no scriptc lowering yet\n", + ); + expect(r.stderr).toBe(""); + }); +}); + // A cipher name Node lists in crypto.getCiphers() but the runtime does not // implement throws a coded SC2020 error naming the cipher when the name is // computed; literal names are refused at compile time (node-types-fenced). From 6e4bfb3c913bf82bd9119243495f9074bced4ea0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:34:40 -0500 Subject: [PATCH 127/395] Await the final value of an untyped sync pipeline source for await over a sync iterator awaits the value of the result that ends it too, so a sync iterator that returns a rejected promise rejects the pump. The source's generator skipped that value and reported success. It now awaits a final value that is an object (a promise included) before it stops, without closing the finished iterator. --- .../src/frontend/lowering/lower-stream.ts | 80 +++++++++++-------- .../7504-pipeline-untyped-source-errors.mjs | 24 +++++- 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index b519ce378..d3883427a 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3954,6 +3954,38 @@ function lowerDynIterableSource( const sync = (): IrExpr => varRef(syncId, BOOL, loc); const step = (): IrExpr => varRef(stepId, DYN, loc); const value = (): IrExpr => varRef(valueId, DYN, loc); + // for await over a sync iterator awaits each value, the final one + // included (CreateAsyncFromSyncIterator): an object (a promise or a + // thenable) passes on its result + const awaitsValue = (): IrExpr => ({ + kind: "logical", + op: "&&", + left: sync(), + right: { + kind: "logical", + op: "||", + left: { kind: "dynTest", test: "promise", value: value(), type: BOOL, loc }, + right: { + kind: "logical", + op: "&&", + left: { + kind: "dynTest", + test: "nullish", + negated: true, + value: value(), + type: BOOL, + loc, + }, + right: { kind: "dynTest", test: "object", value: value(), type: BOOL, loc }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }); const get = (obj: IrExpr, name: string): IrExpr => ({ kind: "dynKeyGet", value: obj, @@ -4053,46 +4085,28 @@ function lowerDynIterableSource( type: BOOL, loc, }, - then: [{ kind: "break", loc }], + // the final value is awaited too: a rejection rejects the + // pump, and the finished iterator is not closed + then: [ + { kind: "assign", localId: valueId, value: get(step(), "value"), loc }, + { + kind: "if", + cond: awaitsValue(), + then: [{ kind: "exprStmt", expr: awaitDyn(value()), loc }], + else_: null, + loc, + }, + { kind: "break", loc }, + ], else_: null, loc, }, setActive(true), { kind: "assign", localId: valueId, value: get(step(), "value"), loc }, - // for await over a sync iterator awaits each value - // (CreateAsyncFromSyncIterator): a promise or a thenable - // passes on its result, and a rejection closes the iterator + // a rejection here closes the iterator { kind: "if", - cond: { - kind: "logical", - op: "&&", - left: sync(), - right: { - kind: "logical", - op: "||", - left: { kind: "dynTest", test: "promise", value: value(), type: BOOL, loc }, - right: { - kind: "logical", - op: "&&", - left: { - kind: "dynTest", - test: "nullish", - negated: true, - value: value(), - type: BOOL, - loc, - }, - right: { kind: "dynTest", test: "object", value: value(), type: BOOL, loc }, - type: BOOL, - loc, - }, - type: BOOL, - loc, - }, - type: BOOL, - loc, - }, + cond: awaitsValue(), then: [{ kind: "assign", localId: valueId, value: awaitDyn(value()), loc }], else_: null, loc, diff --git a/tests/corpus/7504-pipeline-untyped-source-errors.mjs b/tests/corpus/7504-pipeline-untyped-source-errors.mjs index 3ee984547..5d6faded1 100644 --- a/tests/corpus/7504-pipeline-untyped-source-errors.mjs +++ b/tests/corpus/7504-pipeline-untyped-source-errors.mjs @@ -1,7 +1,7 @@ // pipeline() over untyped iterable sources: a destination that fails closes // the source, backpressure waits for drain, an abort signal stops the pump, // async generator and Map sources reach object-mode destinations, and a -// sync source's promise values are awaited. +// sync source's promise values, its final value included, are awaited. import { pipeline, Writable, PassThrough } from "node:stream"; import { pipeline as pipelineP } from "node:stream/promises"; @@ -113,3 +113,25 @@ try { } catch (err) { console.log("promised rejected", err.message); } + +// a sync source's final value is awaited too: a rejected one rejects the +// pipeline, and a resolved one is not written +function* finalRejects() { + yield "f1"; + return Promise.reject(new Error("final value rejected")); +} +function* finalResolves() { + yield "r1"; + return Promise.resolve("not written"); +} +for (const [label, source] of [["finalRejects", finalRejects], ["finalResolves", finalResolves]]) { + try { + await pipelineP( + pick(source()), + new Writable({ objectMode: true, write(c, _e, cb) { console.log(label, "write", c); cb(); } }), + ); + console.log(label, "resolved"); + } catch (err) { + console.log(label, "rejected", err.message); + } +} From 43ff8a11222f131b8eaed0bcb40e1e945f49982f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:35:25 -0500 Subject: [PATCH 128/395] Inline destructured literal results and read ASCII slice ranges inline - A function whose whole body is `return [a, b]` or `return { a, b }`, with number, boolean or string fields, is now inlined where its result is only destructured (`const [head, tail] = split(text, at)`). The parameters are declared from the arguments, then each field straight into its destructured binding, in the literal's order; an unread field still evaluates into a binding of its own. No record is built, and a slice field read only through its length or char codes becomes a scalar slice that copies nothing. Arguments may own their value, but as for numeric records nothing inside an argument may hold a temporary of its own. - A scalar slice of a proven-ASCII string computes its range inline: the bounds clamp over the byte length exactly as scr_str_slice_range does, and no boundary can split a surrogate pair. Other strings keep the runtime call, which gains the same ASCII fast path. - value-records' tuples section, `[head, tail] = split(...)` read only through lengths, goes from two heap slices per step to none. --- packages/compiler/src/backend/llvm/emitter.ts | 28 +- .../src/backend/llvm/stack-objects.test.ts | 5 +- .../src/backend/llvm/string-slices.ts | 100 +++++- .../compiler/src/ir/scalar-records.test.ts | 160 ++++++++- packages/compiler/src/ir/scalar-records.ts | 321 ++++++++++++++++++ .../corpus/8241-literal-result-slices.ts.json | 6 + packages/runtime/src/scr_string.c | 11 + tests/corpus/8241-literal-result-slices.ts | 81 +++++ .../8241-literal-result-slices.ts.json | 1 + 9 files changed, 696 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8241-literal-result-slices.ts.json create mode 100644 tests/corpus/8241-literal-result-slices.ts create mode 100644 tests/harness/corpus-costs/8241-literal-result-slices.ts.json diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 0243763f1..7c4a8aed1 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -68,7 +68,7 @@ import { type StoredSplitSnapshot, type StringSplit, } from "./split-loops.js"; -import { scalarizeNumericRecords } from "../../ir/scalar-records.js"; +import { scalarizeLiteralResults, scalarizeNumericRecords } from "../../ir/scalar-records.js"; import { addRecordResultEntries, resultEntryName } from "./record-results.js"; import { specializeCallbackCalls } from "../../ir/callback-specialization.js"; import { @@ -578,27 +578,29 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { ? selectInt32Arrays( versionParameters( addRecordResultEntries( - scalarizeNumericRecords( - specializeCallbackCalls( - specializeNumericCalls( - inlineExpressionFunctions( - foldConstantGlobals( - copyStableCaptures( - resolveSelfCaptures( - hoistFinalFieldReads( - freezeInitOnlyGlobals( - holdCharLocalsAsUnits( - dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + scalarizeLiteralResults( + scalarizeNumericRecords( + specializeCallbackCalls( + specializeNumericCalls( + inlineExpressionFunctions( + foldConstantGlobals( + copyStableCaptures( + resolveSelfCaptures( + hoistFinalFieldReads( + freezeInitOnlyGlobals( + holdCharLocalsAsUnits( + dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + ), ), ), ), ), ), ), + mangleFunction, ), mangleFunction, ), - mangleFunction, ), ), ), diff --git a/packages/compiler/src/backend/llvm/stack-objects.test.ts b/packages/compiler/src/backend/llvm/stack-objects.test.ts index a74878080..c4f2bedc4 100644 --- a/packages/compiler/src/backend/llvm/stack-objects.test.ts +++ b/packages/compiler/src/backend/llvm/stack-objects.test.ts @@ -286,8 +286,9 @@ function bump(s: State): State { return { ...s, count: s.count + 1 }; } export function tokens(n: number, w: string): number { const t = { kind: "id", text: w, pos: n }; const u = make("num", w, n); - const [q, r] = divmod(n, 7); - return t.pos + width(u) + width({ kind: w, text: w, pos: 0 }) + q + r; + // Read by index: a destructured literal result is inlined instead. + const qr = divmod(n, 7); + return t.pos + width(u) + width({ kind: w, text: w, pos: 0 }) + qr[0] + qr[1]; } export function states(n: number, w: string): number { let s: State = { name: w, count: 0 }; diff --git a/packages/compiler/src/backend/llvm/string-slices.ts b/packages/compiler/src/backend/llvm/string-slices.ts index d00c681d6..35abaec14 100644 --- a/packages/compiler/src/backend/llvm/string-slices.ts +++ b/packages/compiler/src/backend/llvm/string-slices.ts @@ -8,6 +8,7 @@ import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse import { F64_INF, f64Lit, sizeToDouble } from "./common.js"; import type { CallLifetimes } from "./call-lifetimes.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; +import { emitStringAsciiFlag, emitStringByteLength } from "./string-reads.js"; type Slice = IrExpr & { kind: "strIntrinsic" }; @@ -66,6 +67,58 @@ export function findScalarStringSlices( return slices; } +/** scr_slice_unit: a slice()/substring() bound `v` over `length` units. A + * non-negative bound clamps to the length; a negative one counts from the + * end for slice (truncated toward zero) and is 0 for substring, as is NaN. */ +function emitSliceUnit( + host: LlvmEmitterContext, + v: string, + length: string, + substring: boolean, +): string { + const B = host.B; + const size = host.sizeType; + const lengthNumber = B.tmp(), + nonNegative = B.tmp(), + past = B.tmp(), + whole = B.tmp(), + clamped = B.tmp(), + result = B.tmp(); + B.line(`${lengthNumber} = ${sizeToDouble(size)} ${size} ${length} to double`); + B.line(`${nonNegative} = fcmp oge double ${v}, 0.0`); + B.line(`${past} = fcmp oge double ${v}, ${lengthNumber}`); + // Frozen: the conversion is only used where the bound is in range. + const raw = B.tmp(); + B.line(`${raw} = fptosi double ${v} to i64`); + B.line(`${whole} = freeze i64 ${raw}`); + let unit = whole; + if (size !== "i64") { + unit = B.tmp(); + B.line(`${unit} = trunc i64 ${whole} to ${size}`); + } + B.line(`${clamped} = select i1 ${past}, ${size} ${length}, ${size} ${unit}`); + if (substring) { + B.line(`${result} = select i1 ${nonNegative}, ${size} ${clamped}, ${size} 0`); + return result; + } + // Negative: (-length, -1] counts back from the end; (-1, 0) truncates to + // 0, and at or below -length, and NaN, give 0. + const negLength = B.tmp(), + inside = B.tmp(), + zero = B.tmp(), + back = B.tmp(), + counted = B.tmp(), + negative = B.tmp(); + B.line(`${negLength} = fneg double ${lengthNumber}`); + B.line(`${inside} = fcmp ogt double ${v}, ${negLength}`); + B.line(`${zero} = icmp eq ${size} ${unit}, 0`); + B.line(`${back} = add ${size} ${length}, ${unit}`); + B.line(`${counted} = select i1 ${zero}, ${size} 0, ${size} ${back}`); + B.line(`${negative} = select i1 ${inside}, ${size} ${counted}, ${size} 0`); + B.line(`${result} = select i1 ${nonNegative}, ${size} ${clamped}, ${size} ${negative}`); + return result; +} + export interface StringSliceSnapshot { source: string; range: string; @@ -87,10 +140,55 @@ export function emitStringSliceSnapshot( `${range} = alloca { ${host.sizeType}, ${host.sizeType}, i32 }`, ); B.line(`store ptr ${source.name}, ptr ${sourceSlot}`); + const start = args[0]?.name ?? f64Lit(0), + end = args[1]?.name ?? F64_INF; + const substring = slice.method === "substring"; + // A proven-ASCII receiver's units are its bytes, so the range is the + // runtime's clamped bounds over its byte length, and no boundary splits a + // surrogate pair (scr_str_slice_range); other strings ask the runtime. + const ascii = B.newLabel("slice.range.ascii"), + runtime = B.newLabel("slice.range.runtime"), + done = B.newLabel("slice.range.done"); + B.condBr(emitStringAsciiFlag(host, source.name), ascii, runtime); + B.startBlock(ascii); + const size = host.sizeType; + const length = emitStringByteLength(host, source.name); + let from = emitSliceUnit(host, start, length, substring), + to = emitSliceUnit(host, end, length, substring); + if (substring) { + const swap = B.tmp(), + low = B.tmp(), + high = B.tmp(); + B.line(`${swap} = icmp ugt ${size} ${from}, ${to}`); + B.line(`${low} = select i1 ${swap}, ${size} ${to}, ${size} ${from}`); + B.line(`${high} = select i1 ${swap}, ${size} ${from}, ${size} ${to}`); + from = low; + to = high; + } + const nonEmpty = B.tmp(), + difference = B.tmp(), + units = B.tmp(), + startPtr = B.tmp(), + lengthPtr = B.tmp(), + splitPtr = B.tmp(); + B.line(`${nonEmpty} = icmp ugt ${size} ${to}, ${from}`); + B.line(`${difference} = sub ${size} ${to}, ${from}`); + B.line(`${units} = select i1 ${nonEmpty}, ${size} ${difference}, ${size} 0`); + const rangeType = `{ ${size}, ${size}, i32 }`; + B.line(`${startPtr} = getelementptr inbounds ${rangeType}, ptr ${range}, i32 0, i32 0`); + B.line(`store ${size} ${from}, ptr ${startPtr}`); + B.line(`${lengthPtr} = getelementptr inbounds ${rangeType}, ptr ${range}, i32 0, i32 1`); + B.line(`store ${size} ${units}, ptr ${lengthPtr}`); + B.line(`${splitPtr} = getelementptr inbounds ${rangeType}, ptr ${range}, i32 0, i32 2`); + B.line(`store i32 0, ptr ${splitPtr}`); + B.br(done); + B.startBlock(runtime); host.declare("declare void @scr_str_slice_range(ptr, double, double, i1 zeroext, ptr)"); B.line( - `call void @scr_str_slice_range(ptr ${source.name}, double ${args[0]?.name ?? f64Lit(0)}, double ${args[1]?.name ?? F64_INF}, i1 ${slice.method === "substring" ? "true" : "false"}, ptr ${range})`, + `call void @scr_str_slice_range(ptr ${source.name}, double ${start}, double ${end}, i1 ${substring ? "true" : "false"}, ptr ${range})`, ); + B.br(done); + B.startBlock(done); host.toFrame(source); return { source: sourceSlot, range }; } diff --git a/packages/compiler/src/ir/scalar-records.test.ts b/packages/compiler/src/ir/scalar-records.test.ts index e3132088d..7b355ee26 100644 --- a/packages/compiler/src/ir/scalar-records.test.ts +++ b/packages/compiler/src/ir/scalar-records.test.ts @@ -10,7 +10,7 @@ import { type IrStmt, type IrType, } from "./ir.js"; -import { scalarizeNumericRecords } from "./scalar-records.js"; +import { scalarizeLiteralResults, scalarizeNumericRecords } from "./scalar-records.js"; import { validateModule } from "./validate.js"; const loc = { file: "scalar-records.ts", start: 0, end: 0 }; @@ -284,3 +284,161 @@ test("bounds expansion in callers with many eligible calls", () => { expect(remaining).toBeGreaterThan(0); expect(remaining).toBeLessThan(300); }); + +const tuple: IrType = { kind: "record", shapeId: "t0" }; +const strRef = (localId: string): IrExpr => ref(localId, STRING); +const element = (localId: string, field: string, type: IrType): IrStmt => ({ + kind: "varDecl", + localId, + init: { kind: "recordGet", obj: ref("result", tuple), shapeId: "t0", field, type, loc }, + loc, +}); + +/** `split(text, at)` returning `[text.slice(0, at), text.slice(at), at > 0]`, + * and a `main` destructuring it. */ +function literalFixture(after: IrStmt[]): IrModule { + const slice = (args: IrExpr[]): IrExpr => ({ + kind: "strIntrinsic", + method: "slice", + receiver: strRef("text"), + args, + type: STRING, + loc, + }); + return { + irVersion: 17, + sourceFile: loc.file, + entry: "main", + records: [ + { + id: "t0", + fields: [ + { name: "0", type: STRING }, + { name: "1", type: STRING }, + { name: "2", type: BOOL }, + ], + tuple: true, + }, + ], + functions: [ + { + name: "split", + params: [ + { localId: "text", name: "text", type: STRING }, + { localId: "at", name: "at", type: F64 }, + ], + returnType: tuple, + locals: [ + { id: "text", name: "text", type: STRING, mutable: false }, + { id: "at", name: "at", type: F64, mutable: false }, + ], + body: [ + { + kind: "return", + value: { + kind: "recordLit", + type: tuple, + fields: [ + { name: "0", value: slice([num(0), ref("at")]) }, + { name: "1", value: slice([ref("at")]) }, + { + name: "2", + value: { kind: "bin", op: ">", left: ref("at"), right: num(0), type: BOOL, loc }, + }, + ], + loc, + }, + loc, + }, + ], + loc, + }, + { + name: "main", + params: [{ localId: "source", name: "source", type: STRING }], + returnType: VOID, + locals: [ + { id: "source", name: "source", type: STRING, mutable: false }, + { id: "result", name: "result", type: tuple, mutable: false }, + { id: "head", name: "head", type: STRING, mutable: false }, + { id: "tail", name: "tail", type: STRING, mutable: true }, + ], + body: [ + { + kind: "varDecl", + localId: "result", + init: call("split", [strRef("source"), call("effect", [num(1)])], tuple), + loc, + }, + ...after, + ], + loc, + }, + { + name: "effect", + params: [{ localId: "x", name: "x", type: F64 }], + returnType: F64, + locals: [{ id: "x", name: "x", type: F64, mutable: true }], + body: [{ kind: "return", value: ref("x"), loc }], + loc, + }, + ], + }; +} + +function literalTransformed(mod: IrModule): IrModule { + expect(validateModule(mod)).toEqual([]); + const out = scalarizeLiteralResults(mod); + expect(validateModule(out)).toEqual([]); + return out; +} + +test("declares destructured elements from a literal producer's fields in evaluation order", () => { + const mod = literalFixture([element("head", "0", STRING), element("tail", "1", STRING)]); + const snapshot = structuredClone(mod); + const out = literalTransformed(mod); + expect(mod).toEqual(snapshot); + const caller = main(out); + expect(nodes(caller.body, "recordGet")).toEqual([]); + expect(nodes(caller.body, "call").map((c) => c["callee"])).toEqual(["effect"]); + const declared = caller.body.map((s) => (s.kind === "varDecl" ? s.localId : s.kind)); + // Parameters, then every field in the literal's order: the unread third + // field still evaluates, into a binding of its own. + expect(declared.slice(2)).toEqual(["head", "tail", declared[4]]); + expect(caller.locals.find((l) => l.id === declared[4])?.type).toEqual(BOOL); + expect(caller.locals.some((l) => l.id === "result")).toBe(false); + // Slices stay slices of the renamed parameter. + expect(nodes(caller.body, "strIntrinsic").map((n) => (n["receiver"] as IrExpr).kind)).toEqual([ + "varRef", + "varRef", + ]); +}); + +test("keeps the call when the result is read other than by adjacent element declarations", () => { + const escaped = literalFixture([ + element("head", "0", STRING), + { kind: "exprStmt", expr: ref("result", tuple), loc }, + ]); + expect(literalTransformed(escaped)).toBe(escaped); + const apart = literalFixture([ + element("head", "0", STRING), + { kind: "exprStmt", expr: num(0), loc }, + element("tail", "1", STRING), + ]); + expect(literalTransformed(apart)).toBe(apart); +}); + +test("keeps the call when an argument holds a counted temporary of its own", () => { + const mod = literalFixture([element("head", "0", STRING)]); + const decl = main(mod).body[0] as IrStmt & { kind: "varDecl" }; + const length: IrExpr = { + kind: "strIntrinsic", + method: "length", + receiver: { kind: "strLit", value: "abc", type: STRING, loc }, + args: [], + type: F64, + loc, + }; + (decl.init as IrExpr & { kind: "call" }).args[1] = length; + expect(literalTransformed(mod)).toBe(mod); +}); diff --git a/packages/compiler/src/ir/scalar-records.ts b/packages/compiler/src/ir/scalar-records.ts index 0a5b0c80f..f75e522bf 100644 --- a/packages/compiler/src/ir/scalar-records.ts +++ b/packages/compiler/src/ir/scalar-records.ts @@ -10,6 +10,7 @@ import { } from "./ir.js"; import { everyExpr, + transformExpr, everyExprChild, everyStmtChild, everyStmtList, @@ -362,3 +363,323 @@ export function scalarizeNumericRecords(mod: IrModule): IrModule { }); return changed ? { ...mod, functions } : mod; } + +/** A producer whose whole body is `return `, with number, boolean + * or string fields, inlined where its result is destructured. */ +interface LiteralProducer { + fn: IrFunction; + shape: IrRecordShape; + literal: IrExpr & { kind: "recordLit" }; + size: number; +} + +/** Whether `value` holds no counted temporary of its own: every counted + * node in it only views a binding, so evaluating it in its own statement + * shortens no lifetime. A message argument of a throw counts as nothing: it + * lives until the throw, which ends the evaluation anyway. */ +function viewsOnly(value: IrExpr): boolean { + if (!isRefCounted(value.type)) return everyExprChild(value, viewsOnly, () => false); + switch (value.kind) { + case "varRef": + return true; + case "unionNarrow": + return viewsOnly(value.value); + case "ternary": + return viewsOnly(value.cond) && viewsOnly(value.then) && viewsOnly(value.else_); + case "libCall": + return value.fn === "error.nodeThrow"; + default: + return false; + } +} + +/** The node's own value may be counted (it moves into the binding that + * takes it); everything under it must hold no temporary (viewsOnly), as + * scalarTemporaries requires for the numeric pass. */ +function ownedTemporaries(value: IrExpr): boolean { + return everyExprChild(value, viewsOnly, () => false); +} + +const LITERAL_FIELD_KINDS: ReadonlySet = new Set(["f64", "bool", "string"]); + +function literalProducer( + fn: IrFunction, + shapes: ReadonlyMap, +): LiteralProducer | null { + if ( + fn.async || + fn.generator || + fn.captures?.length || + fn.classCaptures?.length || + fn.returnType.kind !== "record" || + fn.body.length !== 1 + ) + return null; + const shape = shapes.get(fn.returnType.shapeId); + if ( + !shape || + shape.indexValue || + shape.fields.length === 0 || + shape.fields.length > MAX_FIELDS || + shape.fields.some((f) => !LITERAL_FIELD_KINDS.has(f.type.kind)) + ) + return null; + // Every local is a parameter: the body declares nothing. + const params = new Set(fn.params.map((p) => p.localId)); + if (fn.locals.some((l) => l.boxed || l.tdz || !params.has(l.id))) return null; + const ret = fn.body[0]!; + const literal = ret.kind === "return" ? ret.value : null; + if ( + literal?.kind !== "recordLit" || + literal.type.kind !== "record" || + literal.type.shapeId !== shape.id || + literal.fields.length !== shape.fields.length || + !literal.fields.every( + (f) => + !f.overflow && + shape.fields.some((sf) => sf.name === f.name && sf.type.kind === f.value.type.kind) && + ownedTemporaries(f.value), + ) + ) + return null; + let size = 0; + const small = everyExpr(literal, { + expr: (node) => + ++size <= MAX_CALLEE_NODES && + node.kind !== "closure" && + node.kind !== "selfRef" && + node.kind !== "awaitExpr" && + node.kind !== "awaitUnionExpr" && + node.kind !== "yieldExpr", + stmt: () => false, + }); + return small ? { fn, shape, literal, size } : null; +} + +/** Rewrite every statement list under `body`, innermost first. */ +function mapStatementLists(body: IrStmt[], f: (list: IrStmt[]) => IrStmt[]): IrStmt[] { + const lists = (s: IrStmt): IrStmt => { + switch (s.kind) { + case "if": + return { + ...s, + then: mapStatementLists(s.then, f), + else_: s.else_ === null ? null : mapStatementLists(s.else_, f), + }; + case "block": + case "while": + case "doWhile": + case "for": + case "forOf": + return { ...s, body: mapStatementLists(s.body, f) }; + case "switch": + return { + ...s, + cases: s.cases.map((c) => ({ + ...c, + body: mapStatementLists(c.body, f), + })), + }; + case "tryCatch": + return { + ...s, + tryBody: mapStatementLists(s.tryBody, f), + catchBody: s.catchBody === null ? null : mapStatementLists(s.catchBody, f), + finallyBody: s.finallyBody === null ? null : mapStatementLists(s.finallyBody, f), + }; + default: + return s; + } + }; + return f(body.map(lists)); +} + +/** How often each local is read, anywhere in `fn`. */ +function localReads(fn: IrFunction): Map { + const reads = new Map(); + const count = (id: string): void => { + reads.set(id, (reads.get(id) ?? 0) + 1); + }; + everyStmtList(fn.body, { + expr: (node) => { + switch (node.kind) { + case "varRef": + case "incDec": + case "assignExpr": + count(node.localId); + break; + case "closure": + case "classRef": + for (const id of node.captures ?? []) count(id); + break; + } + return true; + }, + stmt: (node) => { + if (node.kind === "assign" || node.kind === "forOf" || node.kind === "rethrow") + count(node.localId); + return true; + }, + }); + return reads; +} + +/** Inline small literal producers where the result is only destructured. + * + * `const [head, tail] = split(text, at)`, where `split` is + * `return [text.slice(0, at), text.slice(at)]`, lowers to a result binding + * and one declaration per element read from it, right after the call. Each + * such declaration now takes its field's value directly: the parameters are + * declared from the arguments in order, then the fields in the literal's + * order (unread fields go to fresh bindings), then the destructured + * bindings, so evaluation order is unchanged and no record is built. A + * slice element read only through its length then needs no copy at all + * (findScalarStringSlices). The declarations stay in the caller's own + * statement list, so every binding keeps its scope; parameters live to the + * end of it, which only lengthens the arguments' lifetimes. Every other use + * of the result binding keeps the call. */ +export function scalarizeLiteralResults(mod: IrModule): IrModule { + const shapes = new Map((mod.records ?? []).map((s) => [s.id, s])); + const producers = new Map(); + for (const fn of mod.functions) { + const p = literalProducer(fn, shapes); + if (p) producers.set(fn.name, p); + } + if (producers.size === 0) return mod; + let changed = false; + const functions = mod.functions.map((fn): IrFunction => { + if (fn.async || fn.generator) return fn; + const locals = new Map(fn.locals.map((l) => [l.id, l])); + const used = new Set([...locals.keys(), ...(mod.globals ?? []).map((g) => g.id)]); + collectLabels(fn.body, used); + let next = 0; + const fresh = (): string => { + let id: string; + do { + id = `%literal.${next++}`; + } while (used.has(id)); + used.add(id); + return id; + }; + let reads: Map | null = null; + const added: IrLocal[] = []; + const removed = new Set(); + let budget = MAX_INLINE_NODES; + const rewrite = (list: IrStmt[]): IrStmt[] => { + const out: IrStmt[] = []; + for (let i = 0; i < list.length; i++) { + const decl = list[i]!; + const call = decl.kind === "varDecl" ? decl.init : null; + const p = call?.kind === "call" ? producers.get(call.callee) : undefined; + const local = decl.kind === "varDecl" ? locals.get(decl.localId) : undefined; + if ( + decl.kind !== "varDecl" || + call?.kind !== "call" || + !p || + !local || + p.fn.name === fn.name || + p.size > budget || + local.mutable || + local.boxed || + local.tdz || + local.type.kind !== "record" || + local.type.shapeId !== p.shape.id || + call.args.length !== p.fn.params.length || + !call.args.every(ownedTemporaries) + ) { + out.push(decl); + continue; + } + // The element declarations right after the call, one per field. + const elements = new Map(); + let j = i + 1; + for (; j < list.length; j++) { + const element = list[j]!; + const get = element.kind === "varDecl" ? element.init : null; + const target = element.kind === "varDecl" ? locals.get(element.localId) : undefined; + if ( + element.kind !== "varDecl" || + get?.kind !== "recordGet" || + get.obj.kind !== "varRef" || + get.obj.localId !== local.id || + get.shapeId !== p.shape.id || + elements.has(get.field) || + !target || + target.boxed || + target.tdz || + target.type.kind !== get.type.kind + ) + break; + elements.set(get.field, element); + } + reads ??= localReads(fn); + if (elements.size === 0 || reads.get(local.id) !== elements.size) { + out.push(decl); + continue; + } + budget -= p.size; + changed = true; + removed.add(local.id); + const renamed = new Map(p.fn.params.map((param) => [param.localId, fresh()])); + for (const l of p.fn.locals) { + // Copied field by field: the native compiler does not spread a + // record whose type has a union. + const copy: IrLocal = { + id: renamed.get(l.id)!, + name: l.name, + type: l.type, + mutable: false, + }; + if (l.source !== undefined) copy.source = l.source; + added.push(copy); + } + p.fn.params.forEach((param, index) => { + out.push({ + kind: "varDecl", + localId: renamed.get(param.localId)!, + init: call.args[index]!, + loc: decl.loc, + }); + }); + const rename = (value: IrExpr): IrExpr => + transformExpr(value, { + expr: (node) => + node.kind === "varRef" && renamed.has(node.localId) + ? { ...node, localId: renamed.get(node.localId)! } + : node, + stmt: (node) => node, + }); + for (const field of p.literal.fields) { + const element = elements.get(field.name); + let id = element?.localId; + if (id === undefined) { + id = fresh(); + const type = p.shape.fields.find((f) => f.name === field.name)!.type; + added.push({ + id, + name: `${local.name}.${field.name}`, + type, + mutable: false, + }); + } + out.push({ + kind: "varDecl", + localId: id, + init: rename(field.value), + loc: element?.loc ?? decl.loc, + }); + } + i = j - 1; + } + return out; + }; + const body = mapStatementLists(fn.body, rewrite); + if (!changed || removed.size === 0) return fn; + return { + ...fn, + locals: [...fn.locals.filter((l) => !removed.has(l.id)), ...added], + body, + }; + }); + return changed ? { ...mod, functions } : mod; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8241-literal-result-slices.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8241-literal-result-slices.ts.json new file mode 100644 index 000000000..9cd832dca --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8241-literal-result-slices.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8241-literal-result-slices.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 37b92e48c..2d7f46d85 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -2210,6 +2210,17 @@ static void scr_str_slice_bounds(ScrStr *s, ScrSidx *e, double start, double end void scr_str_slice_range(ScrStr *s, double start, double end, bool substring, ScrStringSlice *out) { + if (__builtin_expect(s->ascii, 1)) { + /* Units are bytes: no index entry, and no boundary splits a pair. */ + size_t length = s->len; + size_t from = scr_slice_unit(start, length, substring); + size_t to = scr_slice_unit(end, length, substring); + if (substring && from > to) { size_t swap = from; from = to; to = swap; } + out->start = from; + out->length = to > from ? to - from : 0; + out->split = 0; + return; + } ScrSidx *e = scr_sidx_for(s); size_t from, to; scr_str_slice_bounds(s, e, start, end, substring, &from, &to); diff --git a/tests/corpus/8241-literal-result-slices.ts b/tests/corpus/8241-literal-result-slices.ts new file mode 100644 index 000000000..11de82819 --- /dev/null +++ b/tests/corpus/8241-literal-result-slices.ts @@ -0,0 +1,81 @@ +// Destructured results of small literal producers: string slices, numbers +// and booleans, with evaluation order, unread fields, throws and every +// slice() and substring() bound kind. +let trace = ""; +function mark(tag: string, value: T): T { + trace += tag; + return value; +} + +function split(text: string, at: number): [string, string] { + return [text.slice(0, at), text.slice(at)]; +} +function cut(text: string, from: number, to: number): [string, number, boolean] { + return [text.substring(from, to), text.length, from > to]; +} +function divmod(a: number, b: number): [number, number] { + return [Math.floor(a / b), a % b]; +} +function pair(text: string): { head: string; size: number } { + return { size: mark("s", text.length), head: mark("h", text.slice(1, -1)) }; +} + +const bounds = [0, 1, 2, 3, -1, -2, -3, -10, 10, 0.5, -0.5, 1.9, -1.9, -0, NaN, Infinity, -Infinity]; +const texts = ["", "a", "ab", "abcdef", "héllo", "東京ok", "a\u{1F600}b"]; +let lengths = 0; +let units = 0; +for (const text of texts) { + for (const at of bounds) { + const [head, tail] = split(text, at); + lengths = lengths * 7 + head.length * 3 + tail.length; + lengths %= 1000000007; + if (head.length > 0) units += head.charCodeAt(head.length - 1); + if (tail.length > 0) units += tail.charCodeAt(0); + for (const to of bounds) { + const [part, size, swapped] = cut(text, at, to); + lengths = (lengths * 5 + part.length + size + (swapped ? 1 : 0)) % 1000000007; + } + } +} +console.log("lengths", lengths, "units", units); + +// The pieces themselves, as strings. +const [left, right] = split("scriptc", 3); +console.log(left, right, `${left}|${right}`); + +// Arguments, then fields in the literal's order, then the bindings. +trace = ""; +const { head, size } = pair(mark("a", "[inner]")); +console.log("order", trace, head.length, size); + +// Unread fields still run. +trace = ""; +const [onlyHead] = split(mark("x", "unread"), mark("y", 2)); +const { size: onlySize } = pair("<>"); +console.log("unread", trace, onlyHead, onlySize); + +// A mutable destructured binding. +let [first, rest] = split("mutable", 1); +first += "!"; +console.log("let", first, rest.length); + +// Numbers and a throw from an argument. +let sum = 0; +for (let i = 0; i < 50; i++) { + const [q, r] = divmod(i * 31, 17); + sum += q * 3 + r; +} +console.log("divmod", sum); +const list: (string | undefined)[] = ["xy", undefined]; +for (const item of list) { + try { + const [a, b] = split(item!, 1); + console.log("ok", a.length, b.length); + } catch (error) { + console.log("threw", error instanceof TypeError); + } +} + +// The producer keeps working for every other caller. +const whole = split("kept", 2); +console.log("kept", whole[0], whole[1], whole.length); diff --git a/tests/harness/corpus-costs/8241-literal-result-slices.ts.json b/tests/harness/corpus-costs/8241-literal-result-slices.ts.json new file mode 100644 index 000000000..be69de5f7 --- /dev/null +++ b/tests/harness/corpus-costs/8241-literal-result-slices.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 2900 } From a711d74ecf3171189109b576fec9378bc5f49709 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:35:51 -0500 Subject: [PATCH 129/395] Keep copied array callbacks exact with local classes, switches and contracts - A binding that a local class captures counts as written: the class's methods may assign it while the loop runs, so the call keeps its helper. - Copied bodies rename the labels of switch and for-of statements and the binding of for-of loops. - The known-union and record-temporary cleanups rewrite switch cases and for-of bodies, where they left declarations of removed locals. - Copied locals keep their source bindings. In programs that write contract types, a parameter with a source binding keeps its own local, so an Int32 parameter is checked against the elements the loop passes. --- .../lowering/containers/array-pipelines.ts | 104 +++++++++++++++--- .../test/array-pipeline-fusion.test.ts | 59 ++++++++++ tests/diagnostics/contract-int32.ts | 4 +- .../__snapshots__/contract-int32.ts.txt | 13 ++- 4 files changed, 164 insertions(+), 16 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index c83a9c447..7c2e07b1c 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -1,3 +1,5 @@ +import * as ts from "../../ts7/adapter.js"; +import { CONTRACT_TYPES_MODULE } from "../../contract-types.js"; import { keyedHelperName } from "../stable-names.js"; import { writtenLocals } from "../../../ir/callback-specialization.js"; import { AmbientReceiverReaders } from "../../../ir/receiver-readers.js"; @@ -6,6 +8,7 @@ import { BOOL, F64, VOID, + type IrBindingSource, type IrExpr, type IrFunction, type IrGlobal, @@ -667,6 +670,10 @@ class PipelineFusion { /** Bindings each function, or a closure it creates, writes after their * declaration. */ private readonly written: Map>; + /** classCaptured's answers by function name. */ + private readonly classCaptures = new Map>(); + /** Whether a module imports or re-exports the contract types. */ + private readonly contractTypes: boolean; /** Bindings of the function being fused that inlined callbacks captured. */ private inlinedCaptures = new Set(); /** The helper calls each callback with an undefined receiver; a copied @@ -680,6 +687,15 @@ class PipelineFusion { this.effects = new CallbackEffects(functions, lowerer.globalsList); this.written = writtenLocals({ functions }); this.receivers = new AmbientReceiverReaders(new Map(functions.map((fn) => [fn.name, fn]))); + this.contractTypes = lowerer.moduleOrder.some((sf) => + sf.statements.nodes.some( + (st) => + (ts.isImportDeclaration(st) || ts.isExportDeclaration(st)) && + st.moduleSpecifier !== undefined && + ts.isStringLiteral(st.moduleSpecifier) && + st.moduleSpecifier.text === CONTRACT_TYPES_MODULE, + ), + ); } run(fn: IrFunction): void { @@ -933,19 +949,22 @@ class PipelineFusion { /** inlineTarget for a callback created in `fn` whose captured bindings * nothing assigns after their declaration: `fn`'s own locals and * parameters, written neither by `fn` nor by any closure that shares - * them, and not by the callback body. The callback must not observe the - * ambient receiver (AmbientReceiverReaders). */ + * them, and not by the callback body. A binding a local class captures + * counts as written, since its methods may assign it. The callback must + * not observe the ambient receiver (AmbientReceiverReaders). */ private stableTarget(fn: IrFunction): InlineTarget { const locals = new Map(fn.locals.map((l) => [l.id, l])); const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); const written = this.written.get(fn.name); + const classCaptured = this.classCaptured(fn); return (arg) => { const target = this.closureTarget(arg); if (!target || !written || arg.kind !== "closure") return null; if (!this.receivers.ignores(target)) return null; for (const id of arg.captures) { const local = locals.get(id); - if (!local || local.tdz || inherited.has(id) || written.has(id)) return null; + if (!local || local.tdz || inherited.has(id) || written.has(id) || classCaptured.has(id)) + return null; } const captures = new Set((target.captures ?? []).map((c) => c.localId)); const assigns = !everyStmtList(target.body, { @@ -957,6 +976,22 @@ class PipelineFusion { }; } + /** The bindings of `fn` that a local class it creates captures. */ + private classCaptured(fn: IrFunction): ReadonlySet { + const known = this.classCaptures.get(fn.name); + if (known) return known; + const ids = new Set(); + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "classRef") for (const id of e.captures ?? []) ids.add(id); + return true; + }, + stmt: () => true, + }); + this.classCaptures.set(fn.name, ids); + return ids; + } + /** The function behind a closure-literal callback the loop can inline. */ private closureTarget(arg: IrExpr): IrFunction | null { if (arg.kind !== "closure" || arg.adapts) return null; @@ -967,7 +1002,12 @@ class PipelineFusion { } private emit(plan: Plan): IrExpr | null { - const built = new FusedLoop(this.lowerer, plan.chain, plan.inline ?? this.inlineTarget).build(); + const built = new FusedLoop( + this.lowerer, + plan.chain, + plan.inline ?? this.inlineTarget, + this.contractTypes, + ).build(); if (!built) return null; for (const id of built.inlinedCaptures) this.inlinedCaptures.add(id); let name = this.helpers.get(built.key); @@ -1153,10 +1193,13 @@ class FusedLoop { private next = 0; private failed = false; + /** `contractTypes`: the program writes contract types, which the + * contract checker binds to source bindings. */ constructor( private readonly lowerer: Lowerer, private readonly chain: Chain, private readonly inlineTarget: InlineTarget, + private readonly contractTypes: boolean, ) { this.loc = chain.loc; } @@ -1165,9 +1208,9 @@ class FusedLoop { return `${name}.${this.next++}`; } - private local(name: string, type: IrType, mutable = false): string { + private local(name: string, type: IrType, mutable = false, source?: IrBindingSource): string { const id = this.fresh(name); - this.locals.push({ id, name, type, mutable }); + this.locals.push(source ? { id, name, type, mutable, source } : { id, name, type, mutable }); return id; } @@ -1234,17 +1277,27 @@ class FusedLoop { return true; }, }); + // Copies keep their source bindings, so contract types written on + // them are checked against what the loop stores. In a program that + // writes contract types, a parameter with a source binding keeps its + // own local for the same reason. + const sources = new Map(fn.locals.map((l) => [l.id, l.source])); const aliased = new Set(); fn.params.forEach((p, index) => { const arg = args[index]!; - if (arg.kind === "varRef" && !assigned.has(p.localId) && typeEquals(arg.type, p.type)) { + if ( + arg.kind === "varRef" && + !assigned.has(p.localId) && + typeEquals(arg.type, p.type) && + !(this.contractTypes && sources.get(p.localId) !== undefined) + ) { renamed.set(p.localId, arg.localId); aliased.add(p.localId); } }); for (const l of fn.locals) { if (renamed.has(l.id)) continue; - const id = this.local(l.name, l.type, true); + const id = this.local(l.name, l.type, true, l.source); renamed.set(l.id, id); } const rename = (id: string): string => renamed.get(id) ?? id; @@ -1258,11 +1311,15 @@ class FusedLoop { return e; } }; - const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => - aliased.has(p.localId) - ? [] - : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], - ); + // A bound parameter's value is an element or index with no source text + // of its own; it is located at the parameter, without a span. + const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => { + if (aliased.has(p.localId)) return []; + const source = sources.get(p.localId); + const arg = args[index]!; + const init = source ? { ...arg, loc: { ...source.loc, end: source.loc.start } } : arg; + return [{ kind: "varDecl", localId: rename(p.localId), init, loc }]; + }); const labels = new Map(); const label = (name: string): string => { let id = labels.get(name); @@ -1281,9 +1338,14 @@ class FusedLoop { case "break": case "continue": return st.label ? { ...st, label: label(st.label) } : st; + case "forOf": { + const renamedFor = { ...st, localId: rename(st.localId) }; + return st.labels ? { ...renamedFor, labels: st.labels.map(label) } : renamedFor; + } case "while": case "doWhile": case "for": + case "switch": case "block": return st.labels ? { ...st, labels: st.labels.map(label) } : st; default: @@ -1946,8 +2008,11 @@ function foldKnownUnions(fn: Omit): void { case "for": case "while": case "doWhile": + case "forOf": case "block": return { ...s, body: prune(s.body) }; + case "switch": + return { ...s, cases: s.cases.map((c) => ({ ...c, body: prune(c.body) })) }; default: return s; } @@ -2135,6 +2200,19 @@ function scalarizeRecordTemporaries(lowerer: Lowerer, fn: Omit ({ + test: c.test === null ? null : rewriteExpr(c.test), + body: rewriteList(c.body), + })), + }, + ]; case "block": return [{ ...s, body: rewriteList(s.body) }]; default: { diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index 0ff456a2c..ba814105f 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -154,3 +154,62 @@ console.log(a.length, b.length, limit); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.map"]); }); + +test("a lone call keeps its helper when a local class's method can assign a capture", async () => { + const mod = await lowerSource(`${prelude} +let k = 1; +class Bump { + go(): void { k = 10; } +} +const b = new Bump(); +const out = rows.map((r) => { if (r.id === 2) b.go(); return r.id * k; }); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.map"]); +}); + +test("a copied callback keeps its switch and for-of labels and bindings", async () => { + const mod = await lowerSource(`${prelude} +const out = rows.map((r) => { + let n = 0; + outer: switch (r.id % 3) { + case 0: + n = 10; + break outer; + case 1: + inner: for (const v of [1, 2, 3]) { + n += v; + if (v === 2) break outer; + if (v === 3) continue inner; + } + break; + default: + n = r.value; + } + return n; +}); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); +}); + +test("a copied callback's switch-local records and known unions are rewritten in its cases", async () => { + const mod = await lowerSource(`${prelude} +type Shape = { kind: "sq"; side: number } | { kind: "rect"; w: number; h: number }; +const out = rows.map((r) => { + switch (r.id % 3) { + case 0: + const p = { a: r.id, b: 2 }; + return p.a + p.b; + case 1: + const sq = { kind: "sq" as const, side: r.value }; + const s: Shape = sq; + return s.kind === "sq" ? s.side * 10 : 0; + default: + return r.value; + } +}); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); +}); diff --git a/tests/diagnostics/contract-int32.ts b/tests/diagnostics/contract-int32.ts index f6e900626..20231c535 100644 --- a/tests/diagnostics/contract-int32.ts +++ b/tests/diagnostics/contract-int32.ts @@ -29,8 +29,10 @@ function bump(): void { const sizes = [1, 2, 3]; const len: Int32 = sizes.length; +const weights = [1.5, 2]; +const shifted = weights.map((w: Int32) => w + 1); const span = new Span(1.5); span.advance(); bump(); -console.log(width(span), half(7), count, len); +console.log(width(span), half(7), count, len, shifted); diff --git a/tests/harness/__snapshots__/contract-int32.ts.txt b/tests/harness/__snapshots__/contract-int32.ts.txt index 2f3a65c1d..90f046973 100644 --- a/tests/harness/__snapshots__/contract-int32.ts.txt +++ b/tests/harness/__snapshots__/contract-int32.ts.txt @@ -48,6 +48,15 @@ contract-int32.ts:31:20 - error SC7002: the value stored into 'len' (Int32) is n 30 | const sizes = [1, 2, 3]; 31 | const len: Int32 = sizes.length; | ^~~~~~~~~~~~ - 32 | + 32 | const weights = [1.5, 2]; - hint: make the int32 conversion explicit, for example 'sizes.length | 0', so Node computes the same value \ No newline at end of file + hint: make the int32 conversion explicit, for example 'sizes.length | 0', so Node computes the same value + +contract-int32.ts:33:30 - error SC7002: the value stored into 'w' (Int32) is not provably an int32 + + 32 | const weights = [1.5, 2]; + 33 | const shifted = weights.map((w: Int32) => w + 1); + | ^ + 34 | + + hint: make the int32 conversion explicit with '| 0', so Node computes the same value \ No newline at end of file From fe6dd9a5c5fa057f799fc7072e92bc2772c96785 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:38:25 -0500 Subject: [PATCH 130/395] Skip a byte order mark at the start of package.json The run-time resolver parsed package.json as it read it, so a file that starts with a UTF-8 byte order mark failed to parse and computed require and require.resolve ended with the invalid-package-config refusal. Node's reader skips the mark; the resolver now does too. The corpus program writes the package with the marked package.json to a fresh directory at run time and resolves it by its absolute path, so no committed file contains the mark. --- packages/runtime/src/scr_require.c | 7 +++++++ .../8211-require-resolve-computed/main.ts | 6 ++++-- .../node_modules/resolve-probe/index.js | 21 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/runtime/src/scr_require.c b/packages/runtime/src/scr_require.c index 645273bce..d1ede2186 100644 --- a/packages/runtime/src/scr_require.c +++ b/packages/runtime/src/scr_require.c @@ -138,6 +138,13 @@ static ScrDyn *req_read_package(ScrRequire *r, ScrStr *pjson) { if (scr_exc_pending()) scr_exc_clear(); return NULL; } + /* Node's reader skips a leading UTF-8 byte order mark. */ + if (text->len >= 3 && (unsigned char)text->data[0] == 0xEF && + (unsigned char)text->data[1] == 0xBB && (unsigned char)text->data[2] == 0xBF) { + ScrStr *body = scr_str_new(text->data + 3, text->len - 3); + scr_str_release(text); + text = body; + } ScrDyn *pkg = scr_json_parse(text); scr_str_release(text); if (pkg == NULL) { diff --git a/tests/corpus/8211-require-resolve-computed/main.ts b/tests/corpus/8211-require-resolve-computed/main.ts index d8e441d2f..ab94d0625 100644 --- a/tests/corpus/8211-require-resolve-computed/main.ts +++ b/tests/corpus/8211-require-resolve-computed/main.ts @@ -1,7 +1,9 @@ // require.resolve with requests computed at run time, called directly and // read as a value, resolves like Node: builtins, relative files with -// Node's extension search, package "main" and "exports" entries, a -// package's own name, and MODULE_NOT_FOUND for requests that name nothing. +// Node's extension search, package "main" and "exports" entries (a +// package.json that starts with a byte order mark included, written at run +// time), a package's own name, and MODULE_NOT_FOUND for requests that name +// nothing. import { resolveAll } from "resolve-probe"; for (const line of resolveAll()) console.log(line); diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js index 870a14401..4be08c224 100644 --- a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -1,5 +1,7 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; -import { dirname, relative } from "node:path"; +import { tmpdir } from "node:os"; +import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; const importer = fileURLToPath(import.meta.url); @@ -39,6 +41,22 @@ const requests = [ 42, ]; +// A package whose package.json starts with a byte order mark, written to a +// fresh directory and resolved by its absolute path: "main" is still read. +function resolveWithByteOrderMark() { + const dir = mkdtempSync(join(tmpdir(), "scriptc-bom-")); + try { + const pkg = join(dir, "with-bom"); + mkdirSync(join(pkg, "lib"), { recursive: true }); + const manifest = JSON.stringify({ name: "with-bom", main: "lib/entry.js" }); + writeFileSync(join(pkg, "package.json"), "\uFEFF" + manifest); + writeFileSync(join(pkg, "lib", "entry.js"), 'module.exports = "with-bom";\n'); + return relative(pkg, load.resolve(pkg)); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + export function resolveAll(options = {}) { const resolve = options.resolve ?? load.resolve; const label = (request) => (typeof request === "string" ? JSON.stringify(request) : String(request)); @@ -46,5 +64,6 @@ export function resolveAll(options = {}) { ...requests.map((request) => `call ${label(request)} -> ${describe((r) => load.resolve(r), request)}`), ...requests.slice(0, 4).map((request) => `value ${label(request)} -> ${describe(resolve, request)}`), `options ${describe((r) => load.resolve(r, {}), "with-main")}`, + `byte order mark -> ${resolveWithByteOrderMark()}`, ]; } From 01f5a3455a215634771f94ab4e813803013253f9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:38:44 -0500 Subject: [PATCH 131/395] Test row-pair overlaps only for nests of two or more rows The paired copy's test read the stored and read arrays' headers whatever the row range, so a nest whose arrays the program assigns only for a nonempty range dereferenced their NULL headers on an empty range, where the loop as written never reads them. The overlap tests now run only when the nest has at least two rows, and an array that is not there answers that it is not disjoint, so the loop runs as written. --- .../compiler/src/backend/llvm/lib-dispatch.ts | 35 ++++++++++++++----- packages/compiler/src/ir/row-pairs.ts | 17 ++++++--- tests/corpus/8194-row-pair-sums.ts | 18 ++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index f4ce9e405..95cee3860 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -937,10 +937,24 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { } if (e.fn === "bytes.absent") return host.own({ name: "null", type: e.type }); if (e.fn === "bytes.disjoint") { - // [data, data + len * size) of each view, compared as addresses. + // [data, data + len * size) of each view, compared as addresses. A view + // that is not there (a binding the nest never assigned, NULL) answers + // false without reading its header, so the caller keeps its loop. const B = host.B; - const range = (arg: IrExpr): { start: string; end: string } => { - const value = host.emitReadReceiver(arg); + const values = e.args.map((arg) => ({ arg, value: host.emitReadReceiver(arg) })); + const present = values.map(({ value }) => { + const t = B.tmp(); + B.line(`${t} = icmp ne ptr ${value.name}, null`); + return t; + }); + const both = B.tmp(); + B.line(`${both} = and i1 ${present[0]}, ${present[1]}`); + const compare = B.newLabel("disjoint.compare"), + done = B.newLabel("disjoint.done"); + const from = B.currentLabel(); + B.condBr(both, compare, done); + B.startBlock(compare); + const range = (arg: IrExpr, pointer: string): { start: string; end: string } => { const size = arg.type.kind === "bytes" ? BYTES_ELEMENT_SIZE[arg.type.elem] : 1; const dataSlot = B.tmp(), data = B.tmp(), @@ -949,23 +963,28 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { bytes = B.tmp(), start = B.tmp(), end = B.tmp(); - B.line(`${dataSlot} = getelementptr inbounds %ScrBytes, ptr ${value.name}, i64 0, i32 3`); + B.line(`${dataSlot} = getelementptr inbounds %ScrBytes, ptr ${pointer}, i64 0, i32 3`); B.line(`${data} = load ptr, ptr ${dataSlot}`); - B.line(`${lenSlot} = getelementptr inbounds %ScrBytes, ptr ${value.name}, i64 0, i32 1`); + B.line(`${lenSlot} = getelementptr inbounds %ScrBytes, ptr ${pointer}, i64 0, i32 1`); B.line(`${len} = load ${host.sizeType}, ptr ${lenSlot}`); B.line(`${bytes} = mul ${host.sizeType} ${len}, ${size}`); B.line(`${start} = ptrtoint ptr ${data} to ${host.sizeType}`); B.line(`${end} = add ${host.sizeType} ${start}, ${bytes}`); return { start, end }; }; - const left = range(e.args[0]!), - right = range(e.args[1]!); + const left = range(values[0]!.arg, values[0]!.value.name), + right = range(values[1]!.arg, values[1]!.value.name); const before = B.tmp(), after = B.tmp(), + apart = B.tmp(), t = B.tmp(); B.line(`${before} = icmp ule ${host.sizeType} ${left.end}, ${right.start}`); B.line(`${after} = icmp ule ${host.sizeType} ${right.end}, ${left.start}`); - B.line(`${t} = or i1 ${before}, ${after}`); + B.line(`${apart} = or i1 ${before}, ${after}`); + const compared = B.currentLabel(); + B.br(done); + B.startBlock(done); + B.line(`${t} = phi i1 [ false, %${from} ], [ ${apart}, %${compared} ]`); return { name: t, type: e.type }; } if (e.fn === "record.isFrozen") { diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index ed71166a8..2e189f42e 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -317,8 +317,10 @@ function pairRows( loc, }; // Both bounds are int32, so `row < end - 1` is `row + 1 < end`, and the - // start is at most the end. Rows are independent when the written array - // shares no byte with an array the term reads. + // nest has at least two rows. The overlap tests run only then, so they never + // read an array the program assigns only when there are rows. Rows are + // independent when the written array shares no byte with an array the + // term reads; an array that is not there answers false. const int32 = (e: IrExpr): IrExpr => ({ kind: "bin", op: "===", @@ -330,7 +332,14 @@ function pairRows( const tests: IrExpr[] = [ int32(match.start), int32(match.end), - { kind: "bin", op: "<=", left: again(match.start), right: again(match.end), type: BOOL, loc }, + { + kind: "bin", + op: "<", + left: bin("+", again(match.start), num(1), loc), + right: again(match.end), + type: BOOL, + loc, + }, ...match.reads.map((read): IrExpr => ({ kind: "libCall", fn: "bytes.disjoint", @@ -400,7 +409,7 @@ function pairRows( * expression of `row + 1`. The term only reads numbers, the counters and * typed-array elements, so it runs no other code and cannot throw. The * nest runs paired when both row bounds are int32 (so `row < end - 1` is - * `row + 1 < end`), the start is at most the end, and the array the rows + * `row + 1 < end`), there are at least two rows, and the array the rows * store into shares no byte with any array the term reads, so no row reads * another row's result. An odd row count runs its last row as written. * Otherwise the nest runs as written. Two independent sums take turns in diff --git a/tests/corpus/8194-row-pair-sums.ts b/tests/corpus/8194-row-pair-sums.ts index 9aa22b9bd..f28242ff5 100644 --- a/tests/corpus/8194-row-pair-sums.ts +++ b/tests/corpus/8194-row-pair-sums.ts @@ -70,3 +70,21 @@ weighted(f, w, out, 0, n); show("weighted", out); weighted(f, w, out, 1, n - 1); show("weighted inner", out); + +// Arrays a program assigns only when the row range is nonempty: an empty or +// one-row range never reads them, and the paired copy does not either. +function lazyRows(n: number, start: number, end: number): number { + let u!: Float64Array; + let v!: Float64Array; + if (end > start) { + u = new Float64Array(n).fill(1); + v = new Float64Array(end); + } + for (let i = start; i < end; i++) { + let t = 0; + for (let j = 0; j < n; j++) t += u[j]! / (i + j + 1); + v[i] = t; + } + return end > start ? v[end - 1]! : -1; +} +console.log("lazy", lazyRows(4, 3, 3), lazyRows(4, 2, 3), lazyRows(4, 0, 5), lazyRows(0, 5, 5)); From e25d871a3444c28124d9581768fba2b02040d5ba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:45:59 -0500 Subject: [PATCH 132/395] Allocate stream ticks from the small-block allocator, and emit stream events by immortal names Each deferred stream step (afterWrite, emitReadable, resume and the others) queued a ScrStreamTick and a process.nextTick entry, both from libc calloc and free. They now come from the runtime's small-block allocator, as do the nextTick entries scheduled through scr_next_tick and scr_process_write_callback. The stream unit emitted each event with a newly allocated name string. The names it emits ("data", "end", "drain", "finish" and the others) are now immortal static strings, so an emit allocates no name. --- packages/runtime/src/scr_async.c | 12 +++---- packages/runtime/src/scr_stream.c | 54 +++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 0ae774df7..f9c6944c2 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -1275,7 +1275,7 @@ static ScrClosure *scr_als_bind(ScrClosure *cb /*moves*/); /* A tick runs with the AsyncLocalStorage context active when it was * queued, as Node's process.nextTick does. */ void scr_next_tick(ScrClosure *cb /*moves*/) { - ScrNtick *t = calloc(1, sizeof *t); + ScrNtick *t = scr_mem_calloc(sizeof *t); if (!t) scr_oom(); t->cb = scr_als_bind(cb); if (scr_nt_tail) scr_nt_tail->next = t; @@ -1288,7 +1288,7 @@ void scr_next_tick(ScrClosure *cb /*moves*/) { * adapter ABI also used by fs.rename: emitted adapters construct the * program's Error | null union, and NULL selects its null arm. */ void scr_process_write_callback(ScrClosure *cb /*moves*/, ScrFsRenameFn fn) { - ScrNtick *t = calloc(1, sizeof *t); + ScrNtick *t = scr_mem_calloc(sizeof *t); if (!t) scr_oom(); t->cb = cb; t->error_cb = fn; @@ -1303,7 +1303,7 @@ void scr_process_write_callback(ScrClosure *cb /*moves*/, ScrFsRenameFn fn) { * endReadableNT, ...). The hook dispatches exactly one stream tick; * teardown just drops markers (the stream queue owns its entries). */ void scr_next_tick_raw(void (*fn)(void)) { - ScrNtick *t = calloc(1, sizeof *t); + ScrNtick *t = scr_mem_calloc(sizeof *t); if (!t) scr_oom(); t->raw = fn; if (scr_nt_tail) scr_nt_tail->next = t; @@ -1314,7 +1314,7 @@ void scr_next_tick_raw(void (*fn)(void)) { /* A raw tick at the front of the queue: it runs before the ticks already * queued. */ static void scr_next_tick_raw_first(void (*fn)(void)) { - ScrNtick *t = calloc(1, sizeof *t); + ScrNtick *t = scr_mem_calloc(sizeof *t); if (!t) scr_oom(); t->raw = fn; t->next = scr_nt_head; @@ -1327,7 +1327,7 @@ void scr_nticks_teardown(void) { ScrNtick *t = scr_nt_head; scr_nt_head = t->next; if (t->cb) scr_closure_release(t->cb); - free(t); + scr_mem_free(t); } scr_nt_tail = NULL; } @@ -4435,7 +4435,7 @@ static bool scr_loop_run_ticks(void) { ScrClosure *cb = t->cb; ScrFsRenameFn error_cb = t->error_cb; void (*raw)(void) = t->raw; - free(t); + scr_mem_free(t); if (cb) { if (error_cb) error_cb(cb, NULL); else ((void (*)(ScrClosure *))cb->fn)(cb); diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 552fef146..406821961 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -557,7 +557,7 @@ static void scr_stream_dispatch_one(void); static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/, ScrClosure *cb /*moves*/) { - ScrStreamTick *t = calloc(1, sizeof *t); + ScrStreamTick *t = scr_mem_calloc(sizeof *t); if (!t) scr_stream_oom(); t->s = scr_stream_retain(s); t->op = op; @@ -580,15 +580,57 @@ static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; } /* ── emit helpers ─────────────────────────────────────────────────────── */ +/* The event names the stream unit emits, as immortal strings (the layout + * of an emitted string literal), so an emit allocates no name. */ +#define SCR_STREAM_EVENT_NAME(name) \ + static const struct { \ + size_t rc; \ + size_t len; \ + size_t cap; \ + char data[sizeof #name]; \ + } scr_stream_lit_##name = {SIZE_MAX, sizeof #name - 1, \ + SCR_STR_CAP_WORD(sizeof #name - 1, 0, 1), #name} +SCR_STREAM_EVENT_NAME(data); +SCR_STREAM_EVENT_NAME(error); +SCR_STREAM_EVENT_NAME(close); +SCR_STREAM_EVENT_NAME(drain); +SCR_STREAM_EVENT_NAME(end); +SCR_STREAM_EVENT_NAME(finish); +SCR_STREAM_EVENT_NAME(pause); +SCR_STREAM_EVENT_NAME(prefinish); +SCR_STREAM_EVENT_NAME(readable); +SCR_STREAM_EVENT_NAME(resume); +SCR_STREAM_EVENT_NAME(pipe); +SCR_STREAM_EVENT_NAME(unpipe); +#undef SCR_STREAM_EVENT_NAME + +static ScrStr *const scr_stream_event_names[] = { + (ScrStr *)&scr_stream_lit_data, (ScrStr *)&scr_stream_lit_error, + (ScrStr *)&scr_stream_lit_close, (ScrStr *)&scr_stream_lit_drain, + (ScrStr *)&scr_stream_lit_end, (ScrStr *)&scr_stream_lit_finish, + (ScrStr *)&scr_stream_lit_pause, (ScrStr *)&scr_stream_lit_prefinish, + (ScrStr *)&scr_stream_lit_readable, (ScrStr *)&scr_stream_lit_resume, + (ScrStr *)&scr_stream_lit_pipe, (ScrStr *)&scr_stream_lit_unpipe, +}; + +/* The event name as a string: an immortal one for the names above, else a + * new string (+1 either way; releasing an immortal string does nothing). */ +static ScrStr *scr_stream_event_name(const char *name) { + for (size_t i = 0; i < sizeof scr_stream_event_names / sizeof *scr_stream_event_names; i++) { + if (strcmp(scr_stream_event_names[i]->data, name) == 0) return scr_stream_event_names[i]; + } + return scr_str_new(name, strlen(name)); +} + static bool scr_stream_emit0(ScrStream *s, const char *name) { - ScrStr *n = scr_str_new(name, strlen(name)); + ScrStr *n = scr_stream_event_name(name); bool had = scr_emitter_emit((ScrEmitter *)s, n); scr_str_release(n); return had; } static void scr_stream_emit_stream(ScrStream *s, const char *name, ScrStream *payload) { - ScrStr *n = scr_str_new(name, strlen(name)); + ScrStr *n = scr_stream_event_name(name); scr_emitter_emit((ScrEmitter *)s, n, payload); scr_str_release(n); } @@ -596,7 +638,7 @@ static void scr_stream_emit_stream(ScrStream *s, const char *name, ScrStream *pa /* 'error': unhandled throws the payload (Node's crash), via the emitter's * error contract. Borrows err. */ static void scr_stream_emit_error(ScrStream *s, ScrError *err) { - ScrStr *n = scr_str_new("error", 5); + ScrStr *n = (ScrStr *)&scr_stream_lit_error; scr_emitter_emit_error((ScrEmitter *)s, n, err); scr_str_release(n); } @@ -765,7 +807,7 @@ static void scr_stream_emit_data(ScrStream *s, void *chunk, ScrEntryKind kind) { } else { b = chunk; } - ScrStr *n = scr_str_new("data", 4); + ScrStr *n = (ScrStr *)&scr_stream_lit_data; scr_emitter_emit((ScrEmitter *)s, n, b, str, value); scr_str_release(n); if (scr_exc_pending()) return; @@ -7858,7 +7900,7 @@ static void scr_stream_tick_free(ScrStreamTick *t) { if (t->err) scr_error_release(t->err); if (t->cb) scr_closure_release(t->cb); scr_als_ctx_release(t->ctx); - free(t); + scr_mem_free(t); } /* One tick-marker's dispatch: the queue's FIFO head (markers and entries From 65555dace3072de3546caeb9f525cea72dbfb221 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:47:58 -0500 Subject: [PATCH 133/395] Drop struct type names before the thin link instead of renaming promoted locals The partitions of a program build have no locals for ThinLTO to promote: symbols that other partitions refer to are already hidden. What changed the modules after importing, and with them the object cache keys, of partitions an edit did not touch was the numbering of struct types. Every partition declares the program's types under the same names, and the thin link loads the importing partition and the partitions it imports from into one context, which tells same-named types apart by appending a counter that runs across the whole link. An edit that adds a type anywhere shifted the counter. Partitions now drop the names of their struct types before their summary is written; importing matches unnamed types by their bodies. The promoted-locals option, the per-partition source file name and the partition index in the summary key are removed. --- native/llvm-codegen/README.md | 2 +- native/llvm-codegen/src/emit.cpp | 45 +++++++++----------------------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index d72aebdfd..70c118202 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -37,7 +37,7 @@ Repeating `--output` divides the program into that many partitions, which are co WebAssembly code generation differs from LLVM's default pipeline in two ways that do not change its output. The pass that gives multiple-entry loops a single entry runs only on functions whose control flow is irreducible; its reachability analysis grows with the square of a function's block count even when it has nothing to change. Object emission also leaves out the module pass that collects external symbols for assembly declarations, so each function's machine code is freed once it is printed instead of when the whole module is compiled. -`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. The locals ThinLTO promotes for importing are named after their partition rather than a hash of its bitcode, so an edit to one partition leaves the partitions that import from it unchanged. +`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. Partitions drop the names of their struct types before the thin link, which would otherwise number same-named types from different partitions in an order that any edit can shift, changing the module after importing in partitions the edit did not touch. `--import-bitcode `, given once per runtime-pack unit that ships bitcode, imports runtime functions for inlining; scriptc's program builds do not pass it. Small runtime functions reachable from the program's runtime calls are imported as `available_externally` so LLVM can inline them; the runtime objects remain the only definitions. The helper never runs a sanitizer pass, so with import it also drops the emitter's inert `sanitize_address` attributes, which would otherwise block that inlining. Without `--import-bitcode` the module is optimized exactly as emitted. diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 4e8c7a30c..c087dead8 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -170,33 +170,18 @@ void limitProfileImports() { (void)Limited; } -// ThinLTO renames the locals it promotes so that the code other partitions -// import can refer to them: switch tables, constants and the other private -// symbols optimization creates. By default the new name carries a hash of -// the defining partition's whole bitcode, so an edit anywhere in a partition -// renames them in every partition that imports from it, and those partitions -// miss the object cache although their own code is unchanged. Named after -// the partition instead (PartitionSourceName), they keep their names while -// the partition changes. The option is process-wide; the helper sets it once. -void stablePromotedNames() { - static const bool Set = [] { - StringRef Name = "use-source-filename-for-promoted-locals"; - if (cl::Option *Option = cl::getRegisteredOptions().lookup(Name)) - Option->addOccurrence(0, Name, "true"); - return true; - }(); - (void)Set; -} - -// Gives a partition's module its own source file name, which the names of -// the locals ThinLTO promotes derive from (stablePromotedNames). It runs -// after the pre-link optimization, so profile annotation still matches the -// program's local functions under their source path. -struct PartitionSourceName : PassInfoMixin { - explicit PartitionSourceName(std::string Name) : Name(std::move(Name)) {} - std::string Name; +// Drops the names of a partition's struct types before its summary is +// written. Every partition declares the program's types under the same +// names, and a thin link loads the importing partition and the partitions +// it imports from into one context, which tells same-named types apart by +// appending a counter that runs across the whole link. An edit that adds a +// type anywhere shifts the counter, so the module after importing, the +// object cache key, changed in partitions whose code had not. Unnamed types +// carry no such suffix; importing still matches them by their bodies. +struct UnnameStructTypes : PassInfoMixin { PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { - M.setSourceFileName(Name); + for (StructType *Type : M.getIdentifiedStructTypes()) + Type->setName(""); return PreservedAnalyses::all(); } static bool isRequired() { return true; } @@ -591,12 +576,8 @@ compileOptimizedPartitions(const EmitOptions &Options, size_t Count = Plan.Modules.size(); std::vector> Summaries(Count); SharedFailure Failures; - stablePromotedNames(); forEachPartition(Count, [&](size_t I) { - // The partition's index names its promoted locals, so it keys the - // summary with the partition's source. - std::string Key = - hashParts({Salt, "summary", std::to_string(I), Plan.Modules[I]}); + std::string Key = hashParts({Salt, "summary", Plan.Modules[I]}); if ((Summaries[I] = Cache.lookup(Key, "bc"))) return; std::string LookupError; @@ -629,7 +610,7 @@ compileOptimizedPartitions(const EmitOptions &Options, // link; the post-link optimization reads the annotated weights. Pipeline P(*Machine, Level, profileOptions(Options)); ModulePassManager MPM = P.PB.buildThinLTOPreLinkDefaultPipeline(Level); - MPM.addPass(PartitionSourceName("scriptc-partition-" + std::to_string(I))); + MPM.addPass(UnnameStructTypes()); MPM.addPass(BitcodeWriterPass(Stream, /*ShouldPreserveUseListOrder=*/false, /*EmitSummaryIndex=*/true, /*EmitModuleHash=*/true)); From c0a4496900d41522c1011d29999afa4cf86f9793 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:50:45 -0500 Subject: [PATCH 134/395] Check with a tsc-ts server shared by later builds (opt-in) With SCRIPTC_CHECKER_SERVER=1 on Linux and macOS, the native compiler's checker connections go to a shared `tsc-ts --api --listen` server, which keeps the files it parsed for the next build. When the variable is unset, nothing changes. checkerServerName names the server by a digest of the tsc-ts executable's identity, the working directory, the file system callbacks, the timing flag and the TSC_TS_* environment. scriptc_ts7_attach takes the server's lock file with a non-blocking flock, connects to its socket in this user's private server directory ($XDG_RUNTIME_DIR/scriptc or /tmp/scriptc-), and starts the server when none answers: in its own process group, with its standard streams on /dev/null and no other descriptor of the build, with --idle-exit 300 and TSC_TS_LISTEN_MEMORY=2048 unless the environment sets it. A build that finds the server in use, cannot start or reach it, or gets no answer to a ping checks with its own tsc-ts process, as without the variable; so does a tsc-ts without --listen, which exits as it starts. The syntax-only source parser, which reads no libraries, always uses its own process and leaves the server to the program's checker. --- docs/content/docs/cli.mdx | 3 + .../compiler/native/ts7-process-child.test.c | 52 ++++- packages/compiler/native/ts7-process.c | 190 +++++++++++++++++- packages/compiler/native/ts7-process.ffi.json | 1 + packages/compiler/native/ts7-process.h | 13 ++ packages/compiler/native/ts7-process.test.c | 88 ++++++++ packages/compiler/src/frontend/services.ts | 4 +- .../src/frontend/ts7/checker-server.test.ts | 47 +++++ .../src/frontend/ts7/checker-server.ts | 50 +++++ .../compiler/src/frontend/ts7/native-api.ts | 21 +- .../src/frontend/ts7/native-process.ts | 39 +++- .../compiler/src/frontend/ts7/program-host.ts | 3 + .../self-hosting/ts7-native-process.ts | 37 +++- .../harness/self-hosting-ts7-process.test.ts | 30 ++- 14 files changed, 569 insertions(+), 9 deletions(-) create mode 100644 packages/compiler/src/frontend/ts7/checker-server.test.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-server.ts diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 9fa5e9447..050d076e3 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -210,6 +210,9 @@ With `--sparse`, the profile leaves out the functions the runs never reached. Th
SCRIPTC_CHECKER
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code.
+ +
SCRIPTC_CHECKER_SERVER
+
Set to 1 on Linux and macOS to check with a tsc-ts server that later builds share. A build connects to the server for its working directory, starting it when none answers, and the server keeps the files it parsed, so the next build of the same program loads it faster. A build that finds the server in use by another build, or cannot start or reach it, checks with its own tsc-ts process. The server exits after 5 minutes without a build, or after a build when its resident memory is above 2048 MB (TSC_TS_LISTEN_MEMORY sets another limit in megabytes). Diagnostics and generated code are the same with and without it. It applies to the default checker, tsc-ts.
```console diff --git a/packages/compiler/native/ts7-process-child.test.c b/packages/compiler/native/ts7-process-child.test.c index 7202cab51..386a0a7c3 100644 --- a/packages/compiler/native/ts7-process-child.test.c +++ b/packages/compiler/native/ts7-process-child.test.c @@ -12,7 +12,49 @@ #include #include #else +#include +#include +#include #include + +/* `--listen PATH --idle-exit SECONDS`: a stand-in for a shared server. In + * mode "listen-exit" it exits as an executable without --listen does. In + * mode "listen" it answers each client, in order, with its client number, + * its listen arguments and TSC_TS_LISTEN_MEMORY, then echoes until the + * client closes, and exits after SECONDS (at most 5) without a client. */ +static int listen_main(const char *mode, char **argv) { + if (strcmp(mode, "listen-exit") == 0) return 2; + if (strcmp(mode, "listen") != 0) return 73; + struct sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + if (strlen(argv[0]) >= sizeof(address.sun_path)) return 77; + strcpy(address.sun_path, argv[0]); + int listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener < 0 || bind(listener, (struct sockaddr *)&address, sizeof(address)) != 0 || + listen(listener, 4) != 0) + return 78; + const char *memory = getenv("TSC_TS_LISTEN_MEMORY"); + int idle = atoi(argv[2]); + if (idle > 5) idle = 5; + for (int client = 1;; client++) { + struct pollfd entry = {listener, POLLIN, 0}; + if (poll(&entry, 1, idle * 1000) <= 0) break; + int fd = accept(listener, NULL, NULL); + if (fd < 0) return 79; + char line[512]; + int size = snprintf(line, sizeof(line), "client %d\n%s %s\n%s\n", client, argv[0], argv[2], + memory == NULL ? "unset" : memory); + if (size <= 0 || write(fd, line, (size_t)size) != size) return 80; + uint8_t bytes[17]; + ssize_t count; + while ((count = read(fd, bytes, sizeof(bytes))) > 0) + if (write(fd, bytes, (size_t)count) != count) break; + close(fd); + } + unlink(argv[0]); + return 0; +} #endif static int child_main(int argc, char **argv) { @@ -20,9 +62,15 @@ static int child_main(int argc, char **argv) { _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); #endif - if (argc < 5 || argc > 6 || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || - strncmp(argv[4], "--callbacks=", 12) != 0 || (argc == 6 && strcmp(argv[5], "--timing") != 0)) return 71; + int listening = argc >= 9 && strcmp(argv[5], "--listen") == 0 && strcmp(argv[7], "--idle-exit") == 0; + int fixed = listening ? 9 : 5; + if (argc < fixed || argc > fixed + 1 || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || + strncmp(argv[4], "--callbacks=", 12) != 0 || (argc == fixed + 1 && strcmp(argv[fixed], "--timing") != 0)) + return 71; const char *mode = argv[3]; +#ifndef _WIN32 + if (listening) return listen_main(mode, argv + 6); +#endif if (strncmp(mode, "argv:", 5) == 0) { fwrite(mode + 5, 1, strlen(mode + 5), stdout); fputc('\n', stdout); diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index ea451345d..dda3bac46 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -1,5 +1,8 @@ #ifndef _WIN32 #define _POSIX_C_SOURCE 200809L +/* flock(2), for the checker server's lock file, and syscall(2). */ +#define _DEFAULT_SOURCE +#define _DARWIN_C_SOURCE #endif #include "ts7-process.h" #include @@ -16,7 +19,13 @@ #include #include #include +#include #include +#include +#ifdef __linux__ +#include +#endif +#include #include #include #include @@ -38,6 +47,9 @@ typedef struct Ts7Process { HANDLE output; #else int socket; + /* The checker server's lock file, held while connected to the server + * (scriptc_ts7_attach); -1 for a server this process started. */ + int lock; #endif } Ts7Process; @@ -283,7 +295,7 @@ static int start_process(Ts7Process *p, char **args, size_t argc) { if (error == 0) error = posix_spawn(&pid, args[0], &actions, NULL, args, environment); posix_spawn_file_actions_destroy(&actions); if (error == 0) { - p->socket = sockets[0]; p->pid = (uint32_t)pid; + p->socket = sockets[0]; p->pid = (uint32_t)pid; p->lock = -1; } done: close(sockets[1]); @@ -293,6 +305,9 @@ done: static void stop_process(Ts7Process *p) { close(p->socket); + // A shared checker server keeps running; closing the connection ends this + // build's session, and closing the lock file lets another build connect. + if (p->lock >= 0) { close(p->lock); return; } int status; pid_t pid = (pid_t)p->pid; for (int attempt = 0; attempt < 5; attempt++) { @@ -338,6 +353,179 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, return p->id; } +#ifdef _WIN32 +uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, + const uint8_t *executable, size_t executable_size, + const uint8_t *cwd, size_t cwd_size, + const uint8_t *callbacks, size_t callbacks_size, + uint8_t timing, uint32_t idle_exit, uint32_t memory) { + (void)name; (void)name_size; (void)executable; (void)executable_size; (void)cwd; (void)cwd_size; + (void)callbacks; (void)callbacks_size; (void)timing; (void)idle_exit; (void)memory; + error_text("a shared checker server needs Unix sockets"); + return 0; +} +#else +/* This user's checker server directory: $XDG_RUNTIME_DIR/scriptc, or + * /tmp/scriptc-. It must be a directory this user owns with no access + * for others; one that exists otherwise is not used. */ +static int server_directory(char *path, size_t size) { + const char *runtime = getenv("XDG_RUNTIME_DIR"); + int count = runtime != NULL && runtime[0] == '/' + ? snprintf(path, size, "%s/scriptc", runtime) + : snprintf(path, size, "/tmp/scriptc-%lu", (unsigned long)geteuid()); + if (count < 0 || (size_t)count >= size) { error_text("checker server directory path is too long"); return 0; } + if (mkdir(path, 0700) != 0 && errno != EEXIST) { system_error(errno); return 0; } + struct stat info; + if (lstat(path, &info) != 0) { system_error(errno); return 0; } + if (!S_ISDIR(info.st_mode) || info.st_uid != geteuid() || (info.st_mode & 077) != 0) { + error_text("checker server directory is not private to this user"); + return 0; + } + return 1; +} + +/* A connection to the server listening at `path`, or -1. */ +static int connect_server(const char *path) { + struct sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + memcpy(address.sun_path, path, strlen(path) + 1); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { close(fd); return -1; } +#ifdef SO_NOSIGPIPE + int no_sigpipe = 1; + if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe)) != 0) { close(fd); return -1; } +#endif + int status; + do { status = connect(fd, (const struct sockaddr *)&address, sizeof(address)); } while (status != 0 && errno == EINTR); + if (status != 0) { close(fd); return -1; } + return fd; +} + +/* Starts the server in its own process group, so that a terminal's signals + * to the build do not reach it, with its standard streams on /dev/null and + * no other descriptor of this process, so that it holds no pipe a caller of + * the build waits on. `limit` (TSC_TS_LISTEN_MEMORY=...) is added to the + * environment unless it sets TSC_TS_LISTEN_MEMORY. */ +static pid_t start_server(char **args, char *limit) { +#ifdef __APPLE__ + char **inherited = *_NSGetEnviron(); +#else + char **inherited = environ; +#endif + size_t count = 0; + int has_limit = 0; + for (; inherited[count] != NULL; count++) + if (strncmp(inherited[count], "TSC_TS_LISTEN_MEMORY=", 21) == 0) has_limit = 1; + char **environment = malloc((count + 2) * sizeof(char *)); + if (environment == NULL) { error_text("out of memory"); return -1; } + memcpy(environment, inherited, count * sizeof(char *)); + environment[count] = has_limit ? NULL : limit; + environment[count + 1] = NULL; + long descriptors = sysconf(_SC_OPEN_MAX); + if (descriptors < 0 || descriptors > 65536) descriptors = 65536; + sigset_t signals; + sigemptyset(&signals); + pid_t pid = fork(); + if (pid == 0) { + // Only async-signal-safe calls until exec. + int null = open("/dev/null", O_RDWR); + if (setpgid(0, 0) != 0 || null < 0 || dup2(null, STDIN_FILENO) < 0 || dup2(null, STDOUT_FILENO) < 0 || + dup2(null, STDERR_FILENO) < 0) + _exit(127); +#if defined(__linux__) && defined(SYS_close_range) + if (syscall(SYS_close_range, 3U, ~0U, 0U) != 0) +#endif + for (long fd = 3; fd < descriptors; fd++) close((int)fd); + sigprocmask(SIG_SETMASK, &signals, NULL); + execve(args[0], args, environment); + _exit(127); + } + int error = errno; + free(environment); + if (pid < 0) { system_error(error); return -1; } + return pid; +} + +uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, + const uint8_t *executable, size_t executable_size, + const uint8_t *cwd, size_t cwd_size, + const uint8_t *callbacks, size_t callbacks_size, + uint8_t timing, uint32_t idle_exit, uint32_t memory) { + if (next_id == 0) { error_text("server handle IDs are exhausted"); return 0; } + char *server = copy_argument(name, name_size, ""); + if (server == NULL) return 0; + char directory[256], socket_path[sizeof(((struct sockaddr_un *)0)->sun_path)], lock_path[512]; + int named = strchr(server, '/') == NULL; + if (!named) error_text("checker server names cannot contain '/'"); + if (named) named = server_directory(directory, sizeof(directory)); + if (named) { + int count = snprintf(socket_path, sizeof(socket_path), "%s/%s.sock", directory, server); + named = count > 0 && (size_t)count < sizeof(socket_path); + if (!named) error_text("checker server socket path is too long"); + else (void)snprintf(lock_path, sizeof(lock_path), "%s.lock", socket_path); + } + free(server); + if (!named) return 0; + // One build at a time uses the server; a build that finds it in use + // starts its own server instead of waiting. + int lock = open(lock_path, O_RDWR | O_CREAT | O_CLOEXEC | O_NOFOLLOW, 0600); + if (lock < 0) { system_error(errno); return 0; } + if (flock(lock, LOCK_EX | LOCK_NB) != 0) { + if (errno == EWOULDBLOCK) error_text("the checker server is in use"); + else system_error(errno); + close(lock); + return 0; + } + int fd = connect_server(socket_path); + if (fd < 0) { + char *exe = copy_argument(executable, executable_size, ""); + char *dir = exe == NULL ? NULL : copy_argument(cwd, cwd_size, ""); + char *callback_flag = dir == NULL ? NULL : copy_argument(callbacks, callbacks_size, "--callbacks="); + char idle[16], limit[48]; + (void)snprintf(idle, sizeof(idle), "%lu", (unsigned long)idle_exit); + (void)snprintf(limit, sizeof(limit), "TSC_TS_LISTEN_MEMORY=%lu", (unsigned long)memory); + pid_t pid = -1; + if (callback_flag != NULL) { + char *args[] = {exe, "--api", "--cwd", dir, callback_flag, "--listen", socket_path, "--idle-exit", idle, + timing ? "--timing" : NULL, NULL}; + pid = start_server(args, limit); + } + free(exe); free(dir); free(callback_flag); + // The server listens as it starts. One that exits first, such as a + // tsc-ts without --listen, or one that is not listening within 2 s, + // leaves this build to start its own server. + for (int attempt = 0; pid > 0; attempt++) { + fd = connect_server(socket_path); + if (fd >= 0) break; + int status; + pid_t result; + do { result = waitpid(pid, &status, WNOHANG); } while (result < 0 && errno == EINTR); + if (result != 0) { error_text("the checker server exited as it started"); break; } + if (attempt == 1000) { error_text("the checker server did not start listening"); break; } + struct timespec delay = {0, 2000000}; + while (nanosleep(&delay, &delay) != 0 && errno == EINTR) {} + } + } + if (fd < 0) { close(lock); return 0; } + Ts7Process *p = calloc(1, sizeof(*p)); + if (p == NULL) { error_text("out of memory"); close(fd); close(lock); return 0; } + if (!exit_registered) { + if (atexit(scriptc_ts7_close_all) != 0) { + error_text("could not register server cleanup"); free(p); close(fd); close(lock); return 0; + } + exit_registered = 1; + } + p->socket = fd; + p->lock = lock; + p->id = next_id++; + p->next = processes; + processes = p; + return p->id; +} +#endif + uint32_t scriptc_ts7_pid(uint32_t handle) { Ts7Process *p = find_process(handle); return p != NULL ? p->pid : 0; diff --git a/packages/compiler/native/ts7-process.ffi.json b/packages/compiler/native/ts7-process.ffi.json index 86f3cb4d2..5e3bcbc5e 100644 --- a/packages/compiler/native/ts7-process.ffi.json +++ b/packages/compiler/native/ts7-process.ffi.json @@ -2,6 +2,7 @@ "ffi_format": 6, "functions": [ { "name": "ts7NativeOpen", "symbol": "scriptc_ts7_open", "params": ["string", "string", "string", "bool"], "returns": "u32" }, + { "name": "ts7NativeAttach", "symbol": "scriptc_ts7_attach", "params": ["string", "string", "string", "string", "bool", "u32", "u32"], "returns": "u32" }, { "name": "ts7NativeRead", "symbol": "scriptc_ts7_read", "params": ["u32", "mutable-bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativeWrite", "symbol": "scriptc_ts7_write", "params": ["u32", "bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativePid", "symbol": "scriptc_ts7_pid", "params": ["u32"], "returns": "u32" }, diff --git a/packages/compiler/native/ts7-process.h b/packages/compiler/native/ts7-process.h index 3d2a0ba05..cb5c72097 100644 --- a/packages/compiler/native/ts7-process.h +++ b/packages/compiler/native/ts7-process.h @@ -13,6 +13,19 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, uint8_t timing); +/* Connects to the shared checker server `name` (`--api --listen`) in this + * user's private server directory, starting it in its own process group + * with `--idle-exit idle_exit` and a limit of `memory` megabytes + * (TSC_TS_LISTEN_MEMORY, unless the environment sets it) when none answers. Returns 0 with the error + * text when another build is connected to it, it cannot be started or + * reached, or the platform has no Unix sockets; the caller then starts its + * own server with scriptc_ts7_open. Closing the handle ends the connection + * and leaves the server running. */ +uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, + const uint8_t *executable, size_t executable_size, + const uint8_t *cwd, size_t cwd_size, + const uint8_t *callbacks, size_t callbacks_size, + uint8_t timing, uint32_t idle_exit, uint32_t memory); int32_t scriptc_ts7_read(uint32_t handle, uint8_t *bytes, size_t size, uint32_t offset, uint32_t length); int32_t scriptc_ts7_write(uint32_t handle, const uint8_t *bytes, size_t size, diff --git a/packages/compiler/native/ts7-process.test.c b/packages/compiler/native/ts7-process.test.c index e17c367c7..dc0c67ced 100644 --- a/packages/compiler/native/ts7-process.test.c +++ b/packages/compiler/native/ts7-process.test.c @@ -11,7 +11,9 @@ #include #else #include +#include #include +#include #include #include #endif @@ -171,6 +173,89 @@ static void failure_cases(void) { scriptc_ts7_close(UINT32_MAX); } +#ifndef _WIN32 +static uint32_t attach(const char *name, const char *mode, uint32_t memory) { + return scriptc_ts7_attach((const uint8_t *)name, strlen(name), (const uint8_t *)executable, strlen(executable), + (const uint8_t *)mode, strlen(mode), (const uint8_t *)callbacks, strlen(callbacks), + 0, 1, memory); +} + +static void expect_line(uint32_t id, const char *expected) { + char line[512]; + size_t at = 0; + do { + assert(at + 1 < sizeof(line)); + read_exact(id, (uint8_t *)line + at, 1); + } while (line[at++] != '\n'); + line[at] = '\0'; + assert(strcmp(line, expected) == 0); +} + +static void attach_cases(void) { + char runtime[] = "/tmp/scriptc-ts7-attach-XXXXXX"; + assert(mkdtemp(runtime) != NULL); + assert(setenv("XDG_RUNTIME_DIR", runtime, 1) == 0); + assert(unsetenv("TSC_TS_LISTEN_MEMORY") == 0); + char directory[256], socket_path[512], expected[600]; + uint8_t bytes[4]; + snprintf(directory, sizeof(directory), "%s/scriptc", runtime); + snprintf(socket_path, sizeof(socket_path), "%s/server.sock", directory); + // The first build starts the server with its idle time and memory limit. + // The server holds none of the build's descriptors, such as an output + // pipe its caller reads to the end. + int output[2]; + assert(pipe(output) == 0); + uint32_t first = attach("server", "listen", 123); + assert(first != 0 && scriptc_ts7_pid(first) == 0); + close(output[1]); + struct pollfd ended = {output[0], POLLIN, 0}; + assert(poll(&ended, 1, 5000) == 1); + assert(read(output[0], bytes, 1) == 0); + close(output[0]); + expect_line(first, "client 1\n"); + snprintf(expected, sizeof(expected), "%s 1\n", socket_path); + expect_line(first, expected); + expect_line(first, "123\n"); + write_exact(first, (const uint8_t *)"ping", 4, 0, 4); + read_exact(first, bytes, 4); + assert(memcmp(bytes, "ping", 4) == 0); + // A build that finds the server in use gets no handle. + for (int i = 0; i < 3; i++) { assert(attach("server", "listen", 123) == 0); fail_message(); } + scriptc_ts7_close(first); + scriptc_ts7_close(first); + // A later build connects to the same server. + uint32_t second = attach("server", "listen", 123); + assert(second != 0); + expect_line(second, "client 2\n"); + scriptc_ts7_close(second); + // An executable without --listen exits as it starts. + assert(attach("old", "listen-exit", 0) == 0); + fail_message(); + // A server directory others can access is not used. + assert(chmod(directory, 0755) == 0); + assert(attach("server", "listen", 123) == 0); + fail_message(); + assert(chmod(directory, 0700) == 0); + // A name that would leave the directory, or make the path too long. + assert(attach("../server", "listen", 123) == 0); + fail_message(); + char long_name[200]; + memset(long_name, 'x', sizeof(long_name) - 1); + long_name[sizeof(long_name) - 1] = '\0'; + assert(attach(long_name, "listen", 123) == 0); + fail_message(); + // The server exits after its idle second and removes its socket. + unlink(socket_path); + snprintf(expected, sizeof(expected), "%s.lock", socket_path); + unlink(expected); + snprintf(expected, sizeof(expected), "%s/old.sock.lock", directory); + unlink(expected); + rmdir(directory); + rmdir(runtime); + assert(unsetenv("XDG_RUNTIME_DIR") == 0); +} +#endif + static int process_main(int argc, char **argv) { assert(argc >= 2); executable = argv[1]; @@ -196,6 +281,9 @@ static int process_main(int argc, char **argv) { argument_cases(); exchange_cases(); failure_cases(); +#ifndef _WIN32 + attach_cases(); +#endif assert(resource_count() == before); #ifndef _WIN32 if (saved_input >= 0) { assert(dup2(saved_input, STDIN_FILENO) >= 0); close(saved_input); } diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index a0acb5818..182611526 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -61,7 +61,9 @@ export class FrontendServices { // Called as a plain function: the instance is not its receiver. const createApi = this.createApi; return (this.parser ??= new Ts7SourceParser( - (options) => createApi({ ...options, collectTiming: false }), + // The parser reads no libraries, so it gains nothing from the shared + // checker server, and leaves it to the program's checker. + (options) => createApi({ ...options, collectTiming: false, shared: false }), this.cwd, )); } diff --git a/packages/compiler/src/frontend/ts7/checker-server.test.ts b/packages/compiler/src/frontend/ts7/checker-server.test.ts new file mode 100644 index 000000000..9a0371fe6 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-server.test.ts @@ -0,0 +1,47 @@ +import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { checkerServerName } from "./checker-server.js"; + +const dir = mkdtempSync(join(tmpdir(), "scriptc-checker-server-")); +const executable = join(dir, "tsc-ts"); +writeFileSync(executable, "server"); +const enabled = { SCRIPTC_CHECKER_SERVER: "1" }; +const posix = process.platform === "linux" || process.platform === "darwin"; + +afterAll(() => rmSync(dir, { recursive: true, force: true })); + +test("builds use the shared checker server only when they opt in, on Linux and macOS, with tsc-ts", () => { + expect(checkerServerName(executable, dir, false, {})).toBeNull(); + expect(checkerServerName(executable, dir, false, { SCRIPTC_CHECKER_SERVER: "0" })).toBeNull(); + expect( + checkerServerName(executable, dir, false, { ...enabled, SCRIPTC_CHECKER: "tsgo" }), + ).toBeNull(); + const name = checkerServerName(executable, dir, false, enabled); + if (!posix) expect(name).toBeNull(); + else expect(name).toMatch(/^[0-9a-f]{32}$/); +}); + +test.skipIf(!posix)( + "the server's name changes with what its answers depend on, and with nothing else", + () => { + const name = checkerServerName(executable, dir, false, enabled); + expect( + checkerServerName(executable, dir, false, { ...enabled, SCRIPTC_CHECKER: "tsc-ts" }), + ).toBe(name); + expect(checkerServerName(executable, dir, false, { ...enabled, PATH: "/elsewhere" })).toBe( + name, + ); + const others = [ + checkerServerName(executable, join(dir, "other"), false, enabled), + checkerServerName(executable, dir, true, enabled), + checkerServerName(executable, dir, false, { ...enabled, TSC_TS_LIB_DIR: dir }), + checkerServerName(executable, dir, false, { ...enabled, TSC_TS_LISTEN_MEMORY: "512" }), + ]; + for (const other of others) expect(other).not.toBe(name); + // An updated tsc-ts executable gets its own server. + utimesSync(executable, new Date(0), new Date(0)); + expect(checkerServerName(executable, dir, false, enabled)).not.toBe(name); + }, +); diff --git a/packages/compiler/src/frontend/ts7/checker-server.ts b/packages/compiler/src/frontend/ts7/checker-server.ts new file mode 100644 index 000000000..6d80e2599 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-server.ts @@ -0,0 +1,50 @@ +import { createHash } from "node:crypto"; +import { checkerBackend } from "./checker-backend.js"; +import { checkerExecutableIdentity } from "./lib-diagnostics.js"; +import { TS7_FILE_SYSTEM_CALLBACKS } from "./rpc-filesystem.js"; + +/** How long a shared checker server waits for the next build before it + * exits, in seconds. */ +export const CHECKER_SERVER_IDLE_EXIT = 300; + +/** The resident memory, in megabytes, above which a shared checker server + * exits after a build. tsc-ts never frees a program's syntax trees, types and + * symbols, so its memory grows with each build it serves. */ +export const CHECKER_SERVER_MEMORY = 2048; + +/** + * With `SCRIPTC_CHECKER_SERVER=1` on Linux and macOS, the native compiler + * checks with a tsc-ts server shared by the builds that follow + * (`tsc-ts --api --listen`): a build connects to it, starting it when none + * answers, and the server keeps the files it parsed for the next build. + * Returns the server's name, a digest of everything a server's answers + * depend on besides the files it reads through the build: the tsc-ts + * executable, the working directory, the file system callbacks, timing, and + * the `TSC_TS_*` environment. Returns null when the shared server is not used. + */ +export function checkerServerName( + executable: string, + cwd: string, + timing: boolean, + env: NodeJS.ProcessEnv = process.env, +): string | null { + if (env["SCRIPTC_CHECKER_SERVER"] !== "1") return null; + if (process.platform !== "linux" && process.platform !== "darwin") return null; + if (checkerBackend(env) !== "tsc-ts") return null; + const tscTsEnv = Object.keys(env) + .filter((name) => name.startsWith("TSC_TS_")) + .sort() + .map((name) => [name, env[name] ?? ""]); + return createHash("sha256") + .update( + JSON.stringify([ + checkerExecutableIdentity(executable), + cwd, + TS7_FILE_SYSTEM_CALLBACKS, + timing, + tscTsEnv, + ]), + ) + .digest("hex") + .slice(0, 32); +} diff --git a/packages/compiler/src/frontend/ts7/native-api.ts b/packages/compiler/src/frontend/ts7/native-api.ts index 259529541..f8e10efc5 100644 --- a/packages/compiler/src/frontend/ts7/native-api.ts +++ b/packages/compiler/src/frontend/ts7/native-api.ts @@ -1,7 +1,9 @@ import { Ts7Api } from "./api.js"; -import { spawnNativeTs7Wire } from "./native-process.js"; +import { checkerServerName } from "./checker-server.js"; +import { attachNativeTs7Process, spawnNativeTs7Wire } from "./native-process.js"; import { Ts7RpcClient } from "./rpc-client.js"; import type { Ts7FileSystem } from "./rpc-filesystem.js"; +import { Ts7Wire } from "./rpc-wire.js"; /** The native driver supplies its installed TypeScript executable path. * Package discovery is separate from starting and owning a server. */ @@ -10,8 +12,25 @@ export function createNativeTs7Api(options: { cwd: string; fs: Ts7FileSystem; collectTiming?: boolean; + shared?: boolean; }): Ts7Api { const timing = options.collectTiming ?? false; + const server = + options.shared === false ? null : checkerServerName(options.executable, options.cwd, timing); + const shared = + server === null + ? null + : attachNativeTs7Process(server, options.executable, options.cwd, timing); + if (shared !== null) { + const rpc = new Ts7RpcClient(new Ts7Wire(shared)); + try { + // A server that ended the connection leaves this build to start its own. + rpc.requestText("ping", "null"); + return new Ts7Api(rpc, options.fs, timing); + } catch { + rpc.close(); + } + } return new Ts7Api( new Ts7RpcClient(spawnNativeTs7Wire(options.executable, options.cwd, timing)), options.fs, diff --git a/packages/compiler/src/frontend/ts7/native-process.ts b/packages/compiler/src/frontend/ts7/native-process.ts index 7bb523d74..aa38cf4b4 100644 --- a/packages/compiler/src/frontend/ts7/native-process.ts +++ b/packages/compiler/src/frontend/ts7/native-process.ts @@ -1,5 +1,6 @@ import { Ts7Wire, type Ts7WireIo } from "./rpc-wire.js"; import { TS7_FILE_SYSTEM_CALLBACKS } from "./rpc-filesystem.js"; +import { CHECKER_SERVER_IDLE_EXIT, CHECKER_SERVER_MEMORY } from "./checker-server.js"; // Bound by native/ts7-process.ffi.json. This module is a native entry point; // the Node host continues to use rpc-process.ts's public spawn boundary. @@ -9,6 +10,15 @@ declare function ts7NativeOpen( callbacks: string, timing: boolean, ): number; +declare function ts7NativeAttach( + name: string, + executable: string, + cwd: string, + callbacks: string, + timing: boolean, + idleExit: number, + memory: number, +): number; declare function ts7NativeRead( handle: number, bytes: Uint8Array, @@ -47,8 +57,12 @@ export class NativeTs7Process implements Ts7WireIo { private handle: number; readonly pid: number; - constructor(executable: string, cwd: string, collectTiming = false) { - this.handle = ts7NativeOpen(executable, cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming); + /** With `handle`, a connection to a shared checker server (attachNativeTs7Process). */ + constructor(executable: string, cwd: string, collectTiming = false, handle = 0) { + this.handle = + handle !== 0 + ? handle + : ts7NativeOpen(executable, cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming); if (this.handle === 0) throw nativeError("startup"); this.pid = ts7NativePid(this.handle); } @@ -97,6 +111,27 @@ export class NativeTs7Process implements Ts7WireIo { }; } +/** A connection to the shared checker server `name`, started when none + * answers (`checkerServerName`), or null when this build cannot use it, such + * as while another build is connected to it. */ +export function attachNativeTs7Process( + name: string, + executable: string, + cwd: string, + collectTiming = false, +): NativeTs7Process | null { + const handle = ts7NativeAttach( + name, + executable, + cwd, + TS7_FILE_SYSTEM_CALLBACKS, + collectTiming, + CHECKER_SERVER_IDLE_EXIT, + CHECKER_SERVER_MEMORY, + ); + return handle === 0 ? null : new NativeTs7Process(executable, cwd, collectTiming, handle); +} + export function spawnNativeTs7Wire( executable: string, cwd: string, diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index ec143e401..e66d598d9 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -122,6 +122,9 @@ export interface Ts7ApiOptions { cwd: string; fs: Ts7FileSystem; collectTiming: boolean; + /** False for a connection that should not use the shared checker server + * (`SCRIPTC_CHECKER_SERVER`), such as the syntax-only source parser's. */ + shared?: boolean; } export type Ts7ApiFactory = (options: Ts7ApiOptions) => Ts7Api; diff --git a/tests/fixtures/self-hosting/ts7-native-process.ts b/tests/fixtures/self-hosting/ts7-native-process.ts index 2fdbb9e79..3b04a784c 100644 --- a/tests/fixtures/self-hosting/ts7-native-process.ts +++ b/tests/fixtures/self-hosting/ts7-native-process.ts @@ -1,6 +1,9 @@ import { writeFileSync } from "node:fs"; import { join } from "node:path"; -import { NativeTs7Process } from "../../../packages/compiler/src/frontend/ts7/native-process.js"; +import { + attachNativeTs7Process, + NativeTs7Process, +} from "../../../packages/compiler/src/frontend/ts7/native-process.js"; import { createNativeTs7Api } from "../../../packages/compiler/src/frontend/ts7/native-api.js"; import { Ts7RpcClient } from "../../../packages/compiler/src/frontend/ts7/rpc-client.js"; import { Ts7Wire } from "../../../packages/compiler/src/frontend/ts7/rpc-wire.js"; @@ -95,12 +98,44 @@ function checkTransport(executable: string): void { console.log("native transport passed"); } +function readLine(connection: NativeTs7Process): string { + const byte = new Uint8Array(1); + let line = ""; + for (;;) { + check(connection.read(byte, 0, 1) === 1, "shared server line"); + if (byte[0] === 10) return line; + line += String.fromCharCode(byte[0]!); + } +} + +// The stand-in server (ts7-process-child.test.c, mode "listen") answers each +// client with its number, its socket and idle time, and its memory limit. +function checkShared(executable: string): void { + const first = attachNativeTs7Process("server", executable, "listen"); + check(first !== null, "shared server started"); + check(readLine(first!) === "client 1", "first client"); + check(readLine(first!).endsWith("/scriptc/server.sock 300"), "listen arguments"); + check(readLine(first!) === "2048", "memory limit"); + check(attachNativeTs7Process("server", executable, "listen") === null, "shared server in use"); + first!.close(); + first!.close(); + const second = attachNativeTs7Process("server", executable, "listen"); + check(second !== null && readLine(second) === "client 2", "shared server reused"); + second!.close(); + check(attachNativeTs7Process("old", executable, "listen-exit") === null, "server without --listen"); + console.log("native shared server passed"); +} + const executable = process.argv[2]!; const directory = process.argv[3]!; const report = process.argv[4]!; const mode = process.argv[5] ?? "client"; if (mode === "transport") { checkTransport(executable); +} else if (mode === "shared") { + checkShared(executable); +} else if (mode === "api") { + checkApi(executable, directory); } else if (mode === "exit" || mode === "normal-exit") { const child = new NativeTs7Process(executable, "stall"); const byte = new Uint8Array(1); diff --git a/tests/harness/self-hosting-ts7-process.test.ts b/tests/harness/self-hosting-ts7-process.test.ts index d49a5c092..150a8860d 100644 --- a/tests/harness/self-hosting-ts7-process.test.ts +++ b/tests/harness/self-hosting-ts7-process.test.ts @@ -1,5 +1,5 @@ import { execFileSync, spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { analyze, compile } from "@scriptc/compiler"; @@ -72,6 +72,20 @@ for (const backend of ["llvm"] as const) { expect(transport.status, transport.stderr).toBe(0); expect(transport.stdout).toBe("native transport passed\n"); expect(transport.stderr).toBe(""); + if (process.platform !== "win32") { + // A shared server in a private server directory, which the stand-in + // leaves after its idle time. + const runtime = join(dir, "run"); + mkdirSync(runtime, { mode: 0o700 }); + const shared = spawnSync(built.binaryPath, [child, dir, "unused", "shared"], { + encoding: "utf8", + timeout: 15_000, + env: { ...process.env, XDG_RUNTIME_DIR: runtime, TSC_TS_LISTEN_MEMORY: undefined }, + }); + expect(shared.status, shared.stderr).toBe(0); + expect(shared.stdout).toBe("native shared server passed\n"); + expect(shared.stderr).toBe(""); + } for (const mode of ["normal-exit", "exit"]) { const pidFile = join(dir, mode + ".pid"); const run = spawnSync(built.binaryPath, [child, dir, pidFile, mode], { @@ -94,6 +108,20 @@ for (const backend of ["llvm"] as const) { dir, join(dir, "oracle.json"), ); + if (process.platform !== "win32") { + // With the shared server requested, a checker without --listen exits + // as it starts and the build checks with its own process. + const runtime = join(dir, "api-run"); + mkdirSync(runtime, { mode: 0o700 }); + const api = spawnSync(built.binaryPath, [tsgoExecutable(), dir, "unused", "api"], { + encoding: "utf8", + timeout: 45_000, + env: { ...process.env, XDG_RUNTIME_DIR: runtime, SCRIPTC_CHECKER_SERVER: "1" }, + }); + expect(api.status, api.stderr).toBe(0); + expect(api.stdout).toBe(""); + expect(api.stderr).toBe(""); + } const report = join(dir, "native.json"); // Only ordinary stdio is inherited. The native executable starts the // actual tsgo process; the test runner never creates its RPC pipes. As in From 05bc5299b0846c4c4d6c0296498cff45696fb3e3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:54:15 -0500 Subject: [PATCH 135/395] Reap the checker process within a millisecond of closing it Closing a checker process checked for its exit once and then slept 10 ms before checking again, although the process exits a fraction of a millisecond after its input closes. The checks now start 0.1 ms apart and double, so the process is reaped as soon as it exits. The total wait before the kill stays at about 50 ms (51.1 ms). --- packages/compiler/native/ts7-process.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index ea451345d..b2b7c5062 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -295,13 +295,16 @@ static void stop_process(Ts7Process *p) { close(p->socket); int status; pid_t pid = (pid_t)p->pid; - for (int attempt = 0; attempt < 5; attempt++) { + // A server exits within a fraction of a millisecond of reading EOF, so the + // checks start 0.1 ms apart and double: 51.1 ms in all before the kill. + for (int attempt = 0; attempt < 10; attempt++) { pid_t result; do { result = waitpid(pid, &status, WNOHANG); } while (result < 0 && errno == EINTR); // ECHILD also means the PID no longer belongs to us. Never signal a // potentially recycled PID after somebody else has reaped the child. if (result == pid || (result < 0 && errno == ECHILD)) return; - struct timespec delay = {0, 10000000}; + if (attempt == 9) break; + struct timespec delay = {0, 100000L << attempt}; while (nanosleep(&delay, &delay) != 0 && errno == EINTR) {} } (void)kill(pid, SIGKILL); From fcc81c034cbef0e3fbcea1835a131d43359e8692 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:59:56 -0500 Subject: [PATCH 136/395] Read the final result's value only for sync pipeline sources for await reads the value of a done result only through the async-from-sync wrapper; an async iterator's done result is not read. The source's generator read it for both, so an async iterator whose done result has a throwing value getter rejected the pipeline. It now reads and awaits the final value only when the iterator is sync. --- .../src/frontend/lowering/lower-stream.ts | 19 ++++++++--- .../7504-pipeline-untyped-source-errors.mjs | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index d3883427a..d67cf257e 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -4085,14 +4085,23 @@ function lowerDynIterableSource( type: BOOL, loc, }, - // the final value is awaited too: a rejection rejects the - // pump, and the finished iterator is not closed + // a sync iterator's final value is awaited too: a rejection + // rejects the pump, and the finished iterator is not closed. + // An async iterator's final value is not read. then: [ - { kind: "assign", localId: valueId, value: get(step(), "value"), loc }, { kind: "if", - cond: awaitsValue(), - then: [{ kind: "exprStmt", expr: awaitDyn(value()), loc }], + cond: sync(), + then: [ + { kind: "assign", localId: valueId, value: get(step(), "value"), loc }, + { + kind: "if", + cond: awaitsValue(), + then: [{ kind: "exprStmt", expr: awaitDyn(value()), loc }], + else_: null, + loc, + }, + ], else_: null, loc, }, diff --git a/tests/corpus/7504-pipeline-untyped-source-errors.mjs b/tests/corpus/7504-pipeline-untyped-source-errors.mjs index 5d6faded1..9dd671606 100644 --- a/tests/corpus/7504-pipeline-untyped-source-errors.mjs +++ b/tests/corpus/7504-pipeline-untyped-source-errors.mjs @@ -135,3 +135,37 @@ for (const [label, source] of [["finalRejects", finalRejects], ["finalResolves", console.log(label, "rejected", err.message); } } + +// the final result's value is read for a sync iterator only: an async +// iterator's done result is not read, so a throwing value getter there is +// never called +function finalResult(label, i) { + if (i === 1) return { value: `${label}1`, done: false }; + return { + done: true, + get value() { + throw new Error(`${label} final value read`); + }, + }; +} +function asyncFinal() { + let i = 0; + return { [Symbol.asyncIterator]() { return { async next() { i++; return finalResult("async", i); } }; } }; +} +function syncFinal() { + let i = 0; + return { [Symbol.iterator]() { return { next() { i++; return finalResult("sync", i); } }; } }; +} +async function runFinal(label, source) { + try { + await pipelineP( + pick(source), + new Writable({ objectMode: true, write(c, _e, cb) { console.log(label, "write", c); cb(); } }), + ); + console.log(label, "resolved"); + } catch (err) { + console.log(label, "rejected", err.message); + } +} +await runFinal("async final", asyncFinal()); +await runFinal("sync final", syncFinal()); From 8da01d3ab3778c8c1073a9624fb0aa7f2faf6240 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:02:25 -0500 Subject: [PATCH 137/395] State that SCRIPTC_CHECKER_SERVER needs a tsc-ts that serves --listen The tsc-ts 0.0.9 that scriptc includes rejects --listen, so with it every build that sets SCRIPTC_CHECKER_SERVER=1 checks with its own tsc-ts process. The variable's description now says so. --- docs/content/docs/cli.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 050d076e3..4c1b7e7c6 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -212,7 +212,7 @@ With `--sparse`, the profile leaves out the functions the runs never reached. Th
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code.
SCRIPTC_CHECKER_SERVER
-
Set to 1 on Linux and macOS to check with a tsc-ts server that later builds share. A build connects to the server for its working directory, starting it when none answers, and the server keeps the files it parsed, so the next build of the same program loads it faster. A build that finds the server in use by another build, or cannot start or reach it, checks with its own tsc-ts process. The server exits after 5 minutes without a build, or after a build when its resident memory is above 2048 MB (TSC_TS_LISTEN_MEMORY sets another limit in megabytes). Diagnostics and generated code are the same with and without it. It applies to the default checker, tsc-ts.
+
Set to 1 on Linux and macOS to check with a tsc-ts server that later builds share. It takes effect with a tsc-ts release that serves tsc-ts --api --listen. The tsc-ts 0.0.9 that this version of scriptc includes does not, so each build checks with its own tsc-ts process, as without the variable. A build connects to the server for its working directory, starting it when none answers, and the server keeps the files it parsed, so the next build of the same program loads it faster. A build that finds the server in use by another build, or cannot start or reach it, checks with its own tsc-ts process. The server exits after 5 minutes without a build, or after a build when its resident memory is above 2048 MB (TSC_TS_LISTEN_MEMORY sets another limit in megabytes). Diagnostics and generated code are the same with and without it. It applies to the default checker, tsc-ts.
```console From 5dc3a051b73383f10f0b824fa8a2e3250f3fb872 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:06:45 -0500 Subject: [PATCH 138/395] Prove typed array reads in while loops bounded by the length in bounds findBytesBounds proved element reads only inside for loops, and only for `counter + c` indexes. A while loop such as while (i < len) { c = line[right - i]!; // right = len - 1 i++; if (c === 10) { if (i === len) break; c = line[right - i]!; i++; } ... } kept a bounds check on every read. The out-of-bounds path also produced a NaN that LLVM threaded into a following switch, which then stayed an indirect jump instead of becoming a lookup table. - A while condition that compares an integer counter with a typed array's length (`i < len`, `i <= right`, `len > i`) starts a bound. The body may write the counter only by statements that add a positive literal, and may not write the receiver. Calls in the body are allowed: they cannot write either binding (both are uncaptured locals), and a typed array's length is fixed at construction. - The body is followed in order. An increment raises the bound and the counter's minimum by its step. An if condition that compares the counter with the length narrows the bound in its branches (`if (i === len) break` restores `i < len` after an increment). Branches join to the weaker bound, and a statement that advances the counter inside a nested loop or labeled block leaves the counter unbounded after it. - Indexes of the form `L + d - counter` (`len - 1 - i`, `right - i`) are proven for both for and while bounds, from the counter's upper bound and its minimum. --- .../compiler/src/backend/llvm/bytes-bounds.ts | 199 +++++++++++++++++- .../8003-while-reversed-byte-reads.ts.json | 6 + .../test/while-bytes-bounds-emission.test.ts | 136 ++++++++++++ .../corpus/8003-while-reversed-byte-reads.ts | 168 +++++++++++++++ .../8003-while-reversed-byte-reads.ts.json | 1 + 5 files changed, 508 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8003-while-reversed-byte-reads.ts.json create mode 100644 packages/compiler/test/while-bytes-bounds-emission.test.ts create mode 100644 tests/corpus/8003-while-reversed-byte-reads.ts create mode 100644 tests/harness/corpus-costs/8003-while-reversed-byte-reads.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-bounds.ts b/packages/compiler/src/backend/llvm/bytes-bounds.ts index 86a1d7ac8..b35a949ff 100644 --- a/packages/compiler/src/backend/llvm/bytes-bounds.ts +++ b/packages/compiler/src/backend/llvm/bytes-bounds.ts @@ -14,11 +14,25 @@ interface Bound extends Extent { minimum: number; } type Node = IrExpr | IrStmt; +const FLIPPED: Readonly> = { + "<": ">", + "<=": ">=", + ">": "<", + ">=": "<=", + "===": "===", + "==": "==", + "!==": "!==", + "!=": "!=", +}; /** Relational proofs for fixed typed-array lengths. Cached lengths retain * their receiver identity only when that binding is never reassigned. * Loops may use offsets in either direction, but both index integrality - * and receiver stability are established separately from the relation. */ + * and receiver stability are established separately from the relation. + * For loops hold one bound over the body. While loops start from the + * condition's bound and follow the body in order: increments raise it and + * comparisons with the length in if conditions narrow it. Indexes are + * `counter + c` or `L + d - counter`, where L is the receiver's length. */ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): ReadonlySet { const proved = new Map(); if (fn.async || fn.generator || !fn.locals.some((local) => local.type.kind === "bytes")) @@ -96,6 +110,20 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly const result = left + (e.op === "+" ? e.right.value : -e.right.value); return Number.isSafeInteger(result) ? result : null; } + /** For an index of the form `L + d - counter` (`len - 1 - i`, `right - i` + * with `right = len - 1`), where L is the receiver's length: d. */ + function reversed(e: IrExpr, bound: Bound): number | null { + if (e.kind !== "bin" || (e.op !== "+" && e.op !== "-")) return null; + if (e.op === "-" && e.right.kind === "varRef" && e.right.localId === bound.counter) { + const length = extent(e.left, false); + return length && length.receiver === bound.receiver ? length.offset : null; + } + if (e.right.kind !== "numLit" || !Number.isSafeInteger(e.right.value)) return null; + const left = reversed(e.left, bound); + if (left === null) return null; + const result = left + (e.op === "+" ? e.right.value : -e.right.value); + return Number.isSafeInteger(result) ? result : null; + } function affineRange(e: IrExpr, bound: Bound): IntegerRange | null { if (e.kind === "varRef" && e.localId === bound.counter) { const max = Number.MAX_SAFE_INTEGER + bound.offset - 1; @@ -159,7 +187,7 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly if (s.cond.left.kind !== "varRef" || s.cond.left.localId !== counter) { if (right.kind !== "varRef" || right.localId !== counter) return null; right = s.cond.left; - op = ({ "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as Record)[op] ?? ""; + op = FLIPPED[op] ?? ""; } const update = s.update; let step = 0; @@ -212,6 +240,90 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly } return result && stable(s.body, result.receiver, counter) ? result : null; } + /** A comparison of the counter with the receiver's length: the operator + * with the counter on the left, and the length's offset. */ + function lengthComparison(e: IrExpr, bound: Bound): { op: string; offset: number } | null { + if (e.kind !== "bin") return null; + let other = e.right, + op: string = e.op; + if (e.left.kind !== "varRef" || e.left.localId !== bound.counter) { + if (other.kind !== "varRef" || other.localId !== bound.counter) return null; + other = e.left; + op = FLIPPED[op] ?? op; + } + const length = extent(other, false); + return length && length.receiver === bound.receiver ? { op, offset: length.offset } : null; + } + /** The step of `counter = counter + c` with a positive literal c, or null. */ + function increment(s: IrStmt, counter: string): number | null { + return s.kind === "assign" && + s.localId === counter && + s.value.kind === "bin" && + s.value.op === "+" && + s.value.left.kind === "varRef" && + s.value.left.localId === counter && + s.value.right.kind === "numLit" && + Number.isSafeInteger(s.value.right.value) && + s.value.right.value > 0 + ? s.value.right.value + : null; + } + function writes(s: IrStmt, id: string): boolean { + return !everyStmtList([s], { + expr: (e) => (e.kind !== "assignExpr" && e.kind !== "incDec") || e.localId !== id, + stmt: (child) => + !( + ((child.kind === "assign" || child.kind === "forOf" || child.kind === "varDecl") && + child.localId === id) || + (child.kind === "tryCatch" && child.catchLocalId === id) + ), + }); + } + /** A while loop whose condition bounds an integer counter by a typed + * array's length. The body may write the counter only by statements + * that add a positive literal, and may not write the receiver. Calls + * cannot write either binding (both are uncaptured locals), and a typed + * array's length is fixed at construction, so the body may hold calls. */ + function whileBound(s: IrStmt & { kind: "while" }): Bound | null { + const cond = s.cond; + if (cond.kind !== "bin") return null; + const counterRef = [cond.left, cond.right].find( + (side, i) => + side.kind === "varRef" && + eligible(side.localId) && + extent(i === 0 ? cond.right : cond.left, false) !== null, + ); + const minimum = counterRef ? ranges.get(counterRef)?.min : undefined; + if (counterRef?.kind !== "varRef" || minimum === undefined) return null; + const counter = counterRef.localId; + const length = extent(counterRef === cond.left ? cond.right : cond.left, false)!; + const receiver = length.receiver; + const comparison = lengthComparison(cond, { receiver, counter, offset: 0, minimum }); + if (comparison?.op !== "<" && comparison?.op !== "<=") return null; + const writesOk = everyStmtList(s.body, { + expr: (e) => + (e.kind !== "assignExpr" && e.kind !== "incDec") || + (e.localId !== counter && e.localId !== receiver), + stmt: (child) => { + if (child.kind === "assign" && child.localId === counter) + return increment(child, counter) !== null; + if (child.kind === "assign" || child.kind === "forOf" || child.kind === "varDecl") + return child.localId !== counter && child.localId !== receiver; + return ( + child.kind !== "tryCatch" || + (child.catchLocalId !== counter && child.catchLocalId !== receiver) + ); + }, + }); + return writesOk + ? { + receiver, + counter, + offset: comparison.offset + (comparison.op === "<=" ? 1 : 0), + minimum, + } + : null; + } function access(node: Node, array: IrExpr, index: IrExpr, active: Bound[], width: number): void { const integer = ranges.get(index); let safe = false; @@ -228,6 +340,13 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly safe = true; break; } + // counter < L + bound.offset and counter >= bound.minimum put + // L + d - counter in [0, L - width]. + const d = integer ? reversed(index, bound) : null; + if (d !== null && bound.offset <= d + 1 && bound.minimum >= d + width) { + safe = true; + break; + } } } proved.set(node, (proved.get(node) ?? true) && safe); @@ -244,8 +363,84 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly (child) => stmt(child, active), ); } + /** The bounds where `cond` is true and where it is false, from the + * bound before it. */ + function narrow(cond: IrExpr, bound: Bound): [Bound, Bound] { + const comparison = lengthComparison(cond, bound); + if (!comparison) return [bound, bound]; + const j = comparison.offset, + offset = bound.offset; + // counter < L + j, counter <= L + j, and counter !== L + j. + const below = { ...bound, offset: Math.min(offset, j) }, + atMost = { ...bound, offset: Math.min(offset, j + 1) }, + unequal = offset === j + 1 ? { ...bound, offset: j } : bound; + switch (comparison.op) { + case "<": + return [below, bound]; + case "<=": + return [atMost, bound]; + case ">": + return [bound, atMost]; + case ">=": + return [bound, below]; + case "===": + case "==": + return [atMost, unequal]; + case "!==": + case "!=": + return [unequal, atMost]; + default: + return [bound, bound]; + } + } + /** Visits a while body in order, from the bound that holds on entry. + * Returns the bound at the end of the list. Its offset is -Infinity (and + * its minimum Infinity) when the end is unreachable, and Infinity when + * nothing bounds the counter there. Every write of the counter adds a + * positive literal (whileBound), so the minimum survives any write. */ + function flow(list: IrStmt[], active: Bound[], bound: Bound): Bound { + for (const s of list) { + const current = Number.isFinite(bound.offset) ? [...active, bound] : active; + const step = increment(s, bound.counter); + if (step !== null) { + bound = { ...bound, offset: bound.offset + step, minimum: bound.minimum + step }; + } else if (s.kind === "if") { + expr(s.cond, current); + const [then, else_] = narrow(s.cond, bound); + const left = flow(s.then, active, then), + right = s.else_ ? flow(s.else_, active, else_) : else_; + bound = { + ...bound, + offset: Math.max(left.offset, right.offset), + minimum: Math.min(left.minimum, right.minimum), + }; + } else if (s.kind === "block" && !s.labels?.length) { + bound = flow(s.body, active, bound); + } else if (writes(s, bound.counter)) { + stmt(s, active); + bound = { ...bound, offset: Infinity }; + } else { + stmt(s, current); + if ( + s.kind === "break" || + s.kind === "continue" || + s.kind === "return" || + s.kind === "throw" + ) + bound = { ...bound, offset: -Infinity, minimum: Infinity }; + } + } + return bound; + } function stmt(s: IrStmt, active: Bound[]): boolean { if (s.kind === "bytesSet") access(s, s.arr, s.index, active, 1); + if (s.kind === "while") { + expr(s.cond, active); + const bound = whileBound(s); + if (!bound) return s.body.every((child) => stmt(child, active)); + flow(s.body, active, bound); + return true; + } if (s.kind === "for") { if (s.init) stmt(s.init, active); if (s.cond) expr(s.cond, active); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8003-while-reversed-byte-reads.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8003-while-reversed-byte-reads.ts.json new file mode 100644 index 000000000..26031ac1f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8003-while-reversed-byte-reads.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8003-while-reversed-byte-reads.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/while-bytes-bounds-emission.test.ts b/packages/compiler/test/while-bytes-bounds-emission.test.ts new file mode 100644 index 000000000..2b6dc1d3c --- /dev/null +++ b/packages/compiler/test/while-bytes-bounds-emission.test.ts @@ -0,0 +1,136 @@ +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 } from "../src/index.js"; + +async function emitLlvm(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-while-bounds-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ll"); + await writeFile(entry, source); + const result = await compile(entry, { + outDir: dir, + outPath, + outputKind: "llvm", + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + return await readFile(outPath, "utf8"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +/** The number of bounds-checked element reads and writes in a function's + * statement body (the borrowed-argument body when there is one). */ +function checks(ll: string, name: string): number { + const definition = (prefix: string): number => + ll.search(new RegExp(`define [^\\n]*@${prefix}_${name}\\(`)); + const start = definition("sc_bf") >= 0 ? definition("sc_bf") : definition("sc_f"); + expect(start).toBeGreaterThanOrEqual(0); + const fn = ll.slice(start, ll.indexOf("\n}\n", start)); + return fn.split("label %bytes.index.valid").length - 1; +} + +test("reads from the end of a typed array in a while loop are proven in bounds", async () => { + const ll = await emitLlvm(` +function code(c: number): number { + switch (c) { + case 65: return 84; + case 84: return 65; + } + return 0; +} +function reversed(line: Uint8Array): number { + const len = line.length; + const right = len - 1; + let i = 0; + let total = 0; + while (i < len) { + let c = line[right - i]!; + i++; + if (c === 10) { + if (i === len) break; + c = line[right - i]!; + i++; + } + total += code(c); + } + return total; +} +function pairs(values: Uint16Array): number { + let i = 0; + let total = 0; + while (values.length > i) { + i += 2; + total += values[i - 2]!; + if (i < values.length) total += values[values.length - 1 - i]!; + } + return total; +} +console.log(reversed(new Uint8Array([65, 10, 84])), pairs(new Uint16Array([1, 2, 3]))); +`); + expect(checks(ll, "reversed")).toBe(0); + expect(checks(ll, "pairs")).toBe(0); +}); + +test("reads a while loop does not keep in bounds keep their checks", async () => { + const ll = await emitLlvm(` +function inclusive(line: Uint8Array): number { + const len = line.length; + let i = 0; + let total = 0; + while (i <= len) { + total += line[len - i]!; + i++; + } + return total; +} +function unguarded(line: Uint8Array): number { + const right = line.length - 1; + let i = 0; + let total = 0; + while (i < line.length) { + total += line[right - i]!; + i++; + total += line[right - i]!; + } + return total; +} +function ahead(values: Uint16Array): number { + let i = 0; + let total = 0; + while (i < values.length) { + i += 2; + total += values[i - 1]!; + } + return total; +} +function nested(line: Uint8Array): number { + const len = line.length; + let i = 0; + let total = 0; + while (i < len) { + const c = line[len - 1 - i]!; + i++; + while (i < len && line[len - 1 - i]! === c) i++; + total += line[len - 1 - i]!; + } + return total; +} +console.log( + inclusive(new Uint8Array(2)), + unguarded(new Uint8Array(2)), + ahead(new Uint16Array(3)), + nested(new Uint8Array(3)), +); +`); + expect(checks(ll, "inclusive")).toBe(1); + // The read before the increment is in bounds; the one after it is not. + expect(checks(ll, "unguarded")).toBe(1); + expect(checks(ll, "ahead")).toBe(1); + // The read before the nested loop is in bounds. The nested loop's + // condition is not a bound, and the read after the loop can be at -1. + expect(checks(ll, "nested")).toBe(2); +}); diff --git a/tests/corpus/8003-while-reversed-byte-reads.ts b/tests/corpus/8003-while-reversed-byte-reads.ts new file mode 100644 index 000000000..9b2efd4f0 --- /dev/null +++ b/tests/corpus/8003-while-reversed-byte-reads.ts @@ -0,0 +1,168 @@ +// While loops that read a typed array or Buffer backwards from its length +// (`line[len - 1 - i]`, `line[right - i]`), with increments, guards that +// exit at the length, calls in the body, reads at or past either end, and +// counters advanced by nested loops. + +function complement(code: number): number { + switch (code) { + case 65: + return 84; + case 67: + return 71; + case 71: + return 67; + case 84: + return 65; + } + return 63; +} + +function reverseComplement(line: Uint8Array, target: Uint8Array): string { + const len = line.length; + const right = len - 1; + let ileft = 0; + let iright = 0; + let c: number; + while (iright < len) { + c = line[right - iright]!; + iright++; + if (c === 10) { + if (iright === len) break; + c = line[right - iright]!; + iright++; + } + target[ileft] = complement(c); + ileft++; + } + return Buffer.from(target.subarray(0, ileft)).toString("latin1"); +} + +const out = new Uint8Array(32); +console.log(reverseComplement(Buffer.from("ACGT\nTTGA\n"), out)); +console.log(reverseComplement(Buffer.from("\nGATTACA"), out)); +console.log(reverseComplement(Buffer.from("\n"), out)); +console.log(reverseComplement(Buffer.from("A\n\nC"), out)); +console.log(JSON.stringify(reverseComplement(new Uint8Array(0), out))); + +// Reads one past the end: the first pass reads line[len], which is undefined. +function inclusive(line: Uint8Array): string { + const len = line.length; + let i = 0; + const seen: (number | undefined)[] = []; + while (i <= len) { + seen.push(line[len - i]); + i++; + } + return JSON.stringify(seen.map((v) => (v === undefined ? "u" : v))); +} +console.log(inclusive(new Uint8Array([1, 2, 3])), inclusive(new Uint8Array(0))); + +// A read after an increment with no guard reads line[-1] on the last pass. +function unguarded(line: Uint8Array): string { + const right = line.length - 1; + let i = 0; + let total = 0; + let missing = 0; + while (i < line.length) { + total += line[right - i]!; + i++; + const next = line[right - i]; + if (next === undefined) missing++; + else total += next * 100; + } + return `${total} ${missing}`; +} +console.log(unguarded(new Uint8Array([1, 2, 3])), unguarded(new Uint8Array([9]))); + +// Guards in other forms: >=, !==, and a guard in an else branch. +function guards(line: Uint16Array): number[] { + const len = line.length; + const result: number[] = []; + let i = 0; + while (i < len) { + i += 1; + if (i >= len) { + result.push(-1); + break; + } + result.push(line[len - 1 - i]!); + } + let j = 0; + while (len > j) { + j++; + if (j !== len) result.push(line[len - 1 - j]! * 10); + else result.push(-2); + } + let k = 0; + while (k < len) { + k++; + if (line[0] === 0) { + result.push(-3); + } else if (k === len) { + break; + } else { + result.push(line[len - k - 1]! * 100); + } + } + return result; +} +console.log(guards(new Uint16Array([5, 6, 7]))); +console.log(guards(new Uint16Array([0, 1]))); +console.log(guards(new Uint16Array(0))); + +// A nested loop advances the counter, so the read after it can fall +// outside the array. +function skipRuns(line: Uint8Array): string { + const len = line.length; + let i = 0; + const parts: string[] = []; + while (i < len) { + const c = line[len - 1 - i]!; + i++; + while (i < len && line[len - 1 - i] === c) i++; + const after = line[len - 1 - i]; + parts.push(`${c}:${after === undefined ? "end" : after}`); + } + return parts.join(" "); +} +console.log(skipRuns(Buffer.from("aabbbc")), skipRuns(Buffer.from("zz"))); + +// A condition with <= against the last index, a labeled block, and +// counters that start past zero or past the end. +function labeled(line: Int8Array, start: number): number[] { + const len = line.length; + const right = len - 1; + const result: number[] = []; + let i = start; + while (i <= right) { + found: { + if (line[len - 1 - i]! < 0) break found; + result.push(line[len - 1 - i]!); + } + i += 2; + } + result.push(i); + return result; +} +console.log(labeled(new Int8Array([1, -2, 3, -4, 5]), 0)); +console.log(labeled(new Int8Array([1, -2, 3, -4, 5]), 1)); +console.log(labeled(new Int8Array([1, -2, 3]), 5)); + +// An exception thrown from a call in the body. +function throwing(line: Uint8Array): string { + const len = line.length; + let i = 0; + let total = 0; + try { + while (i < len) { + const c = line[len - 1 - i]!; + i++; + if (c === 0) throw new Error(`zero after ${total}`); + total += c; + } + } catch (error) { + return (error as Error).message; + } + return String(total); +} +console.log(throwing(new Uint8Array([4, 0, 2, 3])), throwing(new Uint8Array([1, 2]))); diff --git a/tests/harness/corpus-costs/8003-while-reversed-byte-reads.ts.json b/tests/harness/corpus-costs/8003-while-reversed-byte-reads.ts.json new file mode 100644 index 000000000..6c521804e --- /dev/null +++ b/tests/harness/corpus-costs/8003-while-reversed-byte-reads.ts.json @@ -0,0 +1 @@ +{ "plain": 2359, "san": 4025 } From c8ee28de80b24f5b35f7f99eb811d6ba9525e1e1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:23:17 -0500 Subject: [PATCH 139/395] Write slices stored in records that are only read into stack buffers A slice whose every reader only borrows it is written into a stack buffer instead of a heap allocation. A slice stored in a record field counted as kept, so a tokenizer's `const text = source.slice(start, i); const t = token(kind, text, start); sum += weight(t);` allocated and freed a string per token even though `t` is read only for `t.text.length`. A slice local stored directly in a field of a stable record local, built by a record literal or by a factory that only returns a record literal of its parameters, is now lent when the record is only projected: its field reads are lending reads, and it is passed only to record parameters whose bodies read that field only in lending positions (a fixpoint, like the one for lent string parameters). A record that is returned, stored, captured or passed anywhere else keeps the slice on the heap. includes, indexOf and lastIndexOf on string arrays now lend their search argument. --- .../src/backend/llvm/emit-function.ts | 2 +- packages/compiler/src/backend/llvm/emitter.ts | 19 +- .../src/backend/llvm/string-slices.test.ts | 184 ++++++++++- .../src/backend/llvm/string-slices.ts | 288 +++++++++++++++++- .../corpus/8114-record-field-slices.ts.json | 6 + tests/corpus/8114-record-field-slices.ts | 122 ++++++++ 6 files changed, 597 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8114-record-field-slices.ts.json create mode 100644 tests/corpus/8114-record-field-slices.ts diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index a3ebbe0e8..ca45a6624 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -191,7 +191,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { fn, emitter.callLifetimes, emitter.scalarStringSlices, - emitter.lentStringParameters, + emitter.lendingFacts, ) : new Set(); emitter.mapReadLifetimes = findMapReadLifetimes( diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 586517356..ce698be55 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -200,8 +200,10 @@ import { StackCallbacks } from "./stack-callbacks.js"; import { findAppendedArrayLiterals } from "./appended-array-literals.js"; import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js"; import { + findLentRecordFields, findLentStringParameters, - type LentStringParameters, + findRecordFieldProducers, + type LendingFacts, type StringSliceSnapshot, } from "./string-slices.js"; import { LazyCaptures } from "./lazy-captures.js"; @@ -792,8 +794,9 @@ export class LlEmitter { readonly stringSlices = new Map(); /** Slices written into stack buffers (findScratchStringSlices). */ scratchStringSlices: ReadonlySet = new Set(); - /** String parameters a caller may lend scratch slices to. */ - lentStringParameters: LentStringParameters = new Map(); + /** String parameters, record fields and record producers a caller may + * lend scratch slices to. */ + lendingFacts: LendingFacts = { strings: new Map(), records: new Map(), producers: new Map() }; localStackUnions = new Map(); localStackObjects = new Map(); reassignedStackObjects = new Set(); @@ -1893,8 +1896,14 @@ export class LlEmitter { }, (wrap) => this.borrowableNullableWrap(wrap) === "ref", ); - this.lentStringParameters = - this.debug === null ? findLentStringParameters(this.fnByName, this.callLifetimes) : new Map(); + if (this.debug === null) { + const strings = findLentStringParameters(this.fnByName, this.callLifetimes); + this.lendingFacts = { + strings, + records: findLentRecordFields(this.fnByName, this.callLifetimes, strings), + producers: findRecordFieldProducers(this.fnByName), + }; + } this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes, this.nodeIndex); // Integer clones signal a stack overflow through their result, which // needs the single-threaded guard; worker programs also poll for diff --git a/packages/compiler/src/backend/llvm/string-slices.test.ts b/packages/compiler/src/backend/llvm/string-slices.test.ts index b2656e560..43bec955a 100644 --- a/packages/compiler/src/backend/llvm/string-slices.test.ts +++ b/packages/compiler/src/backend/llvm/string-slices.test.ts @@ -1,8 +1,19 @@ import { expect, test } from "vitest"; -import { BOOL, F64, STRING, VOID, type IrExpr, type IrFunction, type IrStmt } from "../../ir/ir.js"; +import { + BOOL, + F64, + STRING, + VOID, + type IrExpr, + type IrFunction, + type IrStmt, + type IrType, +} from "../../ir/ir.js"; import { analyzeCallLifetimes } from "./call-lifetimes.js"; import { + findLentRecordFields, findLentStringParameters, + findRecordFieldProducers, findScalarStringSlices, findScratchStringSlices, } from "./string-slices.js"; @@ -142,12 +153,12 @@ function lending(body: IrStmt[]): IrFunction { function scratch(fn: IrFunction, extra: IrFunction[] = []): Set { const functions = new Map([fn, ...extra].map((f) => [f.name, f])); const lifetimes = analyzeCallLifetimes(functions); - return findScratchStringSlices( - fn, - lifetimes, - findScalarStringSlices(fn, lifetimes), - findLentStringParameters(functions, lifetimes), - ); + const strings = findLentStringParameters(functions, lifetimes); + return findScratchStringSlices(fn, lifetimes, findScalarStringSlices(fn, lifetimes), { + strings, + records: findLentRecordFields(functions, lifetimes, strings), + producers: findRecordFieldProducers(functions), + }); } test("slices read only by lending consumers use a stack buffer", () => { const inline = slice("source", 0, 3); @@ -242,3 +253,162 @@ test("helper parameters read only by lending consumers accept stack slices", () const all = new Map([fn, ...functions].map((f) => [f.name, f])); expect([...findLentStringParameters(all, analyzeCallLifetimes(all)).keys()]).toEqual(["isGet"]); }); + +const TOKEN: IrType = { kind: "record", shapeId: "token" }; +const record = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: TOKEN, loc }); +const textOf = (obj: IrExpr): IrExpr => ({ + kind: "recordGet", + obj, + shapeId: "token", + field: "text", + type: STRING, + loc, +}); +const lengthOf = (receiver: IrExpr): IrExpr => ({ + kind: "strIntrinsic", + method: "length", + receiver, + args: [], + type: F64, + loc, +}); +const tokenLit = (text: IrExpr): IrExpr => ({ + kind: "recordLit", + fields: [ + { name: "text", value: text }, + { name: "pos", value: { kind: "numLit", value: 1, type: F64, loc } }, + ], + type: TOKEN, + loc, +}); +const keepIn = (value: IrExpr): IrStmt => ({ + kind: "exprStmt", + expr: { kind: "arrayLit", elems: [value], type: { kind: "array", elem: STRING }, loc }, + loc, +}); +/** A function of one record parameter `t`. */ +function recordHelper(name: string, body: IrStmt[]): IrFunction { + return { + name, + params: [{ localId: "t", name: "t", type: TOKEN }], + locals: [{ id: "t", name: "t", type: TOKEN, mutable: false }], + returnType: F64, + loc, + body, + }; +} +/** `make(text)` returns `{ text, pos: 1 }`; `twice` also reads its parameter elsewhere. */ +function producerHelper(name: string, extra: IrStmt[] = []): IrFunction { + return { + name, + params: [{ localId: "text", name: "text", type: STRING }], + locals: [{ id: "text", name: "text", type: STRING, mutable: false }], + returnType: TOKEN, + loc, + body: [...extra, { kind: "return", value: tokenLit(ref("text")), loc }], + }; +} +function containing(init: IrExpr, declare: IrExpr, uses: IrStmt[]): IrFunction { + const fn = lending([ + { kind: "varDecl", localId: "word", init, loc }, + { kind: "varDecl", localId: "tok", init: declare, loc }, + ...uses, + ]); + fn.locals.push({ id: "tok", name: "tok", type: TOKEN, mutable: false }); + return fn; +} +const reads = recordHelper("reads", [ + { kind: "return", value: lengthOf(textOf(record("t"))), loc }, +]); +const keepsText = recordHelper("keepsText", [ + keepIn(textOf(record("t"))), + { kind: "return", value: { kind: "numLit", value: 0, type: F64, loc }, loc }, +]); +const escapes = recordHelper("escapes", [ + { + kind: "exprStmt", + expr: { kind: "arrayLit", elems: [record("t")], type: { kind: "array", elem: TOKEN }, loc }, + loc, + }, + { kind: "return", value: { kind: "numLit", value: 0, type: F64, loc }, loc }, +]); +const passCall = (callee: string, arg: IrExpr): IrStmt => ({ + kind: "exprStmt", + expr: { kind: "call", callee, args: [arg], type: F64, loc }, + loc, +}); + +test("a slice stored in a record that is only projected uses a stack buffer", () => { + const helpers = [reads, keepsText, escapes, producerHelper("make")]; + for (const declare of [tokenLit(ref("word")), callOf("make", ref("word"))]) { + if (declare.kind === "call") declare.type = TOKEN; + const init = slice("source", 1, 5); + const fn = containing(init, declare, [ + { kind: "exprStmt", expr: equals(textOf(record("tok"))), loc }, + passCall("reads", record("tok")), + ]); + expect(scratch(fn, helpers).has(init)).toBe(true); + } + const all = new Map(helpers.map((f) => [f.name, f])); + const lifetimes = analyzeCallLifetimes(all); + const lent = findLentRecordFields(all, lifetimes, findLentStringParameters(all, lifetimes)); + expect(lent.get("reads")?.get(0)).toEqual(new Set()); + expect(lent.get("keepsText")?.get(0)).toEqual(new Set(["text"])); + expect(lent.has("escapes")).toBe(false); + expect(findRecordFieldProducers(all).get("make")).toEqual(new Map([[0, "text"]])); +}); + +test.each([ + "kept-field", + "returned", + "kept-by-callee", + "escapes-callee", + "captured", + "producer-reuse", +])("a slice stored in a record that may be kept (%s) stays owned", (reason) => { + const helpers = [ + reads, + keepsText, + escapes, + producerHelper("make"), + producerHelper("twice", [keepIn(ref("text"))]), + ]; + const init = slice("source", 1, 5); + const declare = + reason === "producer-reuse" ? callOf("twice", ref("word")) : tokenLit(ref("word")); + if (declare.kind === "call") declare.type = TOKEN; + const uses: IrStmt[] = [{ kind: "exprStmt", expr: equals(textOf(record("tok"))), loc }]; + if (reason === "kept-field") uses.push(keepIn(textOf(record("tok")))); + if (reason === "returned") uses.push({ kind: "return", value: record("tok"), loc }); + if (reason === "kept-by-callee") uses.push(passCall("keepsText", record("tok"))); + if (reason === "escapes-callee") uses.push(passCall("escapes", record("tok"))); + const fn = containing(init, declare, uses); + if (reason === "captured") fn.locals.find((l) => l.id === "tok")!.boxed = true; + expect(scratch(fn, helpers).has(init)).toBe(false); +}); + +test("searches of string arrays lend their needle", () => { + const keywords: IrExpr = { + kind: "varRef", + localId: "keywords", + type: { kind: "array", elem: STRING }, + loc, + }; + const classify = helper("classify", [ + { + kind: "return", + value: { + kind: "arrIntrinsic", + method: "includes", + receiver: keywords, + args: [ref("text")], + type: BOOL, + loc, + }, + loc, + }, + ]); + const init = slice("source", 0, 3); + const fn = lending([{ kind: "exprStmt", expr: callOf("classify", init), loc }]); + expect(scratch(fn, [classify]).has(init)).toBe(true); +}); diff --git a/packages/compiler/src/backend/llvm/string-slices.ts b/packages/compiler/src/backend/llvm/string-slices.ts index d00c681d6..81a4a5a3e 100644 --- a/packages/compiler/src/backend/llvm/string-slices.ts +++ b/packages/compiler/src/backend/llvm/string-slices.ts @@ -145,6 +145,10 @@ const LENDING_LIB_FNS: ReadonlySet = new Set([ "num.parseFloat", ]); +/** Searches of string arrays compare the needle with the elements during + * the call. */ +const LENDING_ARRAY_SEARCHES: ReadonlySet = new Set(["includes", "indexOf", "lastIndexOf"]); + /** Typed string-keyed collection operations: reads probe with the key, and * an insertion stores scr_str_scratch_keep's copy (scr_map_append). */ const LENDING_KEY_METHODS: ReadonlySet = new Set(["get", "has", "set", "add", "delete"]); @@ -176,6 +180,12 @@ function lentOperands(value: IrExpr, parameters: LentStringParameters): readonly return LENDING_KEY_METHODS.has(value.method) && stringKeyed(value) ? value.args.slice(0, 1) : []; + case "arrIntrinsic": + return LENDING_ARRAY_SEARCHES.has(value.method) && + value.receiver.type.kind === "array" && + value.receiver.type.elem.kind === "string" + ? value.args.slice(0, 1) + : []; case "call": { const lent = parameters.get(value.callee); return lent ? value.args.filter((_, index) => lent.has(index)) : []; @@ -185,41 +195,123 @@ function lentOperands(value: IrExpr, parameters: LentStringParameters): readonly } } +/** Record parameters, by function and index, that the body only projects: + * it reads their fields and passes them only to such parameters. The set + * lists the string fields some read keeps (reads outside a lending + * position); every other string field is lent (findLentRecordFields). */ +export type LentRecordFields = ReadonlyMap>>; + +/** Functions whose body only returns a record literal that stores some + * parameters, each read once, directly as fields: by function, parameter + * index to field (findRecordFieldProducers). A call stores those arguments + * in the fields of its result and keeps them nowhere else. */ +export type RecordFieldProducers = ReadonlyMap>; + +/** What lends a string: lent string parameters, lent record fields and + * record field producers. */ +export interface LendingFacts { + strings: LentStringParameters; + records: LentRecordFields; + producers: RecordFieldProducers; +} + +const NO_LENDING_FACTS: LendingFacts = { + strings: new Map(), + records: new Map(), + producers: new Map(), +}; + interface LendingScan { /** Slice operands of lending consumers, excluding any node also reached * in an owning position. */ scratch: Set; /** Candidates read anywhere other than a lending position. */ invalid: Set; - /** Direct callees, for the parameter fixpoint. */ + /** Record candidates read other than by a field read or a lent record + * argument. */ + invalidRecords: Set; + /** String fields of record candidates read outside a lending position. */ + keptFields: Map>; + /** Direct callees, for the parameter fixpoints. */ callees: Set; } /** One pass over `fn`: which candidate bindings only lending consumers read * (a slice initializer of a candidate local is not itself a read), and * which slice operands those consumers lend. A switch discriminant is only - * compared with the case tests. */ + * compared with the case tests. Record candidates (`records`) may only be + * projected: their field reads are checked as reads of the field, and they + * may be passed to lent record parameters. A record `container` + * declaration stores candidates in fields of a record candidate; those + * stores are not reads. */ function scanLending( fn: IrFunction, candidates: ReadonlySet, initializers: ReadonlyMap, - parameters: LentStringParameters, + facts: LendingFacts, + records: ReadonlySet = new Set(), + containers: ReadonlyMap = new Map(), ): LendingScan { - const scan: LendingScan = { scratch: new Set(), invalid: new Set(), callees: new Set() }; + const scan: LendingScan = { + scratch: new Set(), + invalid: new Set(), + invalidRecords: new Set(), + keptFields: new Map(), + callees: new Set(), + }; const owned = new Set(); + const keep = (record: string, field: string): void => { + let fields = scan.keptFields.get(record); + if (!fields) scan.keptFields.set(record, (fields = new Set())); + fields.add(field); + }; + const projected = (value: IrExpr): value is IrExpr & { kind: "recordGet" } => + value.kind === "recordGet" && value.obj.kind === "varRef" && records.has(value.obj.localId); function lent(value: IrExpr): boolean { if (value.kind === "varRef" && candidates.has(value.localId)) return true; + if (projected(value)) return true; if (!isSlice(value)) return expr(value); scan.scratch.add(value); return everyExprChild(value, expr, stmt); } + /** A call's arguments: lent string parameters lend, lent record + * parameters project their record, and `stored` arguments (a producer's + * fields in a container declaration) are not reads. */ + function callArgs(value: IrExpr & { kind: "call" }, stored: ReadonlySet | null): void { + scan.callees.add(value.callee); + const strings = facts.strings.get(value.callee); + const recordParams = facts.records.get(value.callee); + value.args.forEach((arg, index) => { + if (stored !== null && stored.has(index)) return; + const kept = recordParams?.get(index); + if (kept && arg.kind === "varRef" && records.has(arg.localId)) { + for (const field of kept) keep(arg.localId, field); + return; + } + if (strings?.has(index)) lent(arg); + else expr(arg); + }); + } function expr(value: IrExpr): boolean { if (isSlice(value)) owned.add(value); + if (projected(value)) { + if (value.type.kind === "string") + keep((value.obj as { localId: string }).localId, value.field); + return true; + } if (value.kind === "varRef" && candidates.has(value.localId)) scan.invalid.add(value.localId); + if (value.kind === "varRef" && records.has(value.localId)) + scan.invalidRecords.add(value.localId); if (value.kind === "closure" || value.kind === "classRef") - for (const id of value.captures ?? []) scan.invalid.add(id); - if (value.kind === "call") scan.callees.add(value.callee); - const operands = lentOperands(value, parameters); + for (const id of value.captures ?? []) { + scan.invalid.add(id); + scan.invalidRecords.add(id); + } + if (value.kind === "call") { + callArgs(value, null); + return true; + } + const operands = lentOperands(value, facts.strings); if (operands.length === 0) return everyExprChild(value, expr, stmt); return everyExprChild( value, @@ -231,6 +323,20 @@ function scanLending( // A candidate's single declaration initializes it with its slice. if (value.kind === "varDecl" && value.init !== null && initializers.has(value.localId)) return everyExprChild(value.init, expr, stmt); + const container = value.kind === "varDecl" ? containers.get(value.localId) : undefined; + if (container && value.kind === "varDecl" && value.init !== null) { + const init = value.init; + if (init.kind === "recordLit") + for (const field of init.fields) { + if (!container.stored.has(field.value)) expr(field.value); + } + else if (init.kind === "call") + callArgs( + init, + new Set(init.args.flatMap((arg, i) => (container.stored.has(arg) ? [i] : []))), + ); + return true; + } if (value.kind === "switch" && value.disc.type.kind === "string") { lent(value.disc); for (const entry of value.cases) { @@ -276,7 +382,7 @@ export function findLentStringParameters( const fn = functions.get(name); if (!indexes || !fn) continue; const ids = new Set([...indexes].map((index) => fn.params[index]!.localId)); - const scan = scanLending(fn, ids, new Map(), lent); + const scan = scanLending(fn, ids, new Map(), { ...NO_LENDING_FACTS, strings: lent }); for (const callee of scan.callees) { let set = callers.get(callee); if (!set) callers.set(callee, (set = new Set())); @@ -296,6 +402,148 @@ export function findLentStringParameters( return lent; } +/** Functions whose whole body is `return { ... }`, a record literal storing + * some parameters, each read nowhere else, directly as fields (a factory + * such as `token(kind, text, pos)`). A synchronous body only: the result + * then holds those arguments, and nothing else does. */ +export function findRecordFieldProducers( + functions: ReadonlyMap, +): Map> { + const producers = new Map>(); + for (const fn of functions.values()) { + if (fn.async || fn.generator || fn.body.length !== 1) continue; + const only = fn.body[0]!; + if (only.kind !== "return" || only.value?.kind !== "recordLit") continue; + const literal = only.value; + const reads = new Map(); + everyStmtList(fn.body, { + expr: (value) => { + if (value.kind === "varRef") reads.set(value.localId, (reads.get(value.localId) ?? 0) + 1); + return true; + }, + stmt: () => true, + }); + const fields = new Map(); + fn.params.forEach((param, index) => { + if (param.type.kind !== "string" || reads.get(param.localId) !== 1) return; + const field = literal.fields.find( + (entry) => + !entry.overflow && entry.value.kind === "varRef" && entry.value.localId === param.localId, + ); + if (field) fields.set(index, field.name); + }); + if (fields.size > 0) producers.set(fn.name, fields); + } + return producers; +} + +/** Record parameters that every body only projects (LentRecordFields), with + * the string fields some read keeps. Start from every borrowed record + * parameter of a synchronous function, keeping no field, and grow the kept + * fields and drop escaping parameters until nothing changes; a record passed + * to another lent record parameter keeps what that parameter keeps. */ +export function findLentRecordFields( + functions: ReadonlyMap, + lifetimes: CallLifetimes, + strings: LentStringParameters, +): Map>> { + const lent = new Map>>(); + for (const fn of functions.values()) { + if (fn.async || fn.generator) continue; + const stable = lifetimes.borrowed.get(fn.name); + const indexes = new Map>(); + fn.params.forEach((param, index) => { + if (param.type.kind === "record" && stable?.has(index)) indexes.set(index, new Set()); + }); + if (indexes.size > 0) lent.set(fn.name, indexes); + } + const callers = new Map>(); + const pending = new Set(lent.keys()); + while (pending.size > 0) { + const name = pending.values().next().value as string; + pending.delete(name); + const indexes = lent.get(name); + const fn = functions.get(name); + if (!indexes || !fn) continue; + const ids = new Set([...indexes.keys()].map((index) => fn.params[index]!.localId)); + const scan = scanLending( + fn, + new Set(), + new Map(), + { strings, records: lent, producers: new Map() }, + ids, + ); + for (const callee of scan.callees) { + let set = callers.get(callee); + if (!set) callers.set(callee, (set = new Set())); + set.add(name); + } + let changed = false; + for (const [index, kept] of [...indexes]) { + const id = fn.params[index]!.localId; + if (scan.invalidRecords.has(id)) { + indexes.delete(index); + changed = true; + continue; + } + const fields = scan.keptFields.get(id); + if (fields) + for (const field of fields) { + if (kept.has(field)) continue; + kept.add(field); + changed = true; + } + } + if (!changed) continue; + if (indexes.size === 0) lent.delete(name); + const affected = callers.get(name); + if (affected) for (const caller of affected) if (lent.has(caller)) pending.add(caller); + } + return lent; +} + +/** A record local whose declaration stores slice locals directly in some of + * its fields: a record literal, or a call to a record field producer. */ +interface RecordContainer { + /** The stored candidate reads of the declaration, by field. */ + stored: Map; +} + +/** Record locals declared with slice locals in their fields. */ +function findRecordContainers( + fn: IrFunction, + bindings: ReadonlySet | undefined, + slices: ReadonlySet, + producers: RecordFieldProducers, +): Map { + const containers = new Map(); + everyStmtList(fn.body, { + expr: () => true, + stmt: (stmt) => { + if (stmt.kind !== "varDecl" || stmt.init === null || !bindings?.has(stmt.localId)) + return true; + if (stmt.init.type.kind !== "record") return true; + const stored = new Map(); + const init = stmt.init; + if (init.kind === "recordLit") { + for (const field of init.fields) + if (!field.overflow && field.value.kind === "varRef" && slices.has(field.value.localId)) + stored.set(field.value, { field: field.name, slice: field.value.localId }); + } else if (init.kind === "call") { + const fields = producers.get(init.callee); + init.args.forEach((arg, index) => { + const field = fields?.get(index); + if (field !== undefined && arg.kind === "varRef" && slices.has(arg.localId)) + stored.set(arg, { field, slice: arg.localId }); + }); + } + if (stored.size > 0) containers.set(stmt.localId, { stored }); + return true; + }, + }); + return containers; +} + /** Slices whose every reader borrows them are written into a stack buffer * instead of a heap allocation (scr_str_slice_scratch): a slice operand of * a lending consumer, and the initializer of a stable local read only by @@ -304,12 +552,18 @@ export function findLentStringParameters( * non-ASCII results keep the ordinary owned string. Each site owns its * buffer: a site runs again only after its previous value is dead, since * nothing can keep it. Coroutine frames do not keep stack buffers across a - * suspension, so they are left alone. */ + * suspension, so they are left alone. + * + * A slice local may also be stored in a field of a stable record local + * declared with it (a record literal, or a record field producer's result): + * the record is then only projected, and every read of that field lends. + * The record is declared after the slice in its scope and is never kept, so + * it is dead before the slice's site runs again. */ export function findScratchStringSlices( fn: IrFunction, lifetimes: CallLifetimes, scalar: ReadonlyMap, - parameters: LentStringParameters = new Map(), + facts: LendingFacts = NO_LENDING_FACTS, ): Set { if (fn.async || fn.generator) return new Set(); const bindings = lifetimes.bindings.get(fn.name); @@ -328,7 +582,19 @@ export function findScratchStringSlices( return true; }, }); - const scan = scanLending(fn, new Set(locals.keys()), locals, parameters); + const containers = findRecordContainers(fn, bindings, new Set(locals.keys()), facts.producers); + const scan = scanLending( + fn, + new Set(locals.keys()), + locals, + facts, + new Set(containers.keys()), + containers, + ); + for (const [record, container] of containers) + for (const { field, slice } of container.stored.values()) + if (scan.invalidRecords.has(record) || scan.keptFields.get(record)?.has(field)) + scan.invalid.add(slice); for (const [id, init] of locals) if (!scan.invalid.has(id)) scan.scratch.add(init); return scan.scratch; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8114-record-field-slices.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8114-record-field-slices.ts.json new file mode 100644 index 000000000..58ce71d85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8114-record-field-slices.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8114-record-field-slices.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8114-record-field-slices.ts b/tests/corpus/8114-record-field-slices.ts new file mode 100644 index 000000000..69d41a1c8 --- /dev/null +++ b/tests/corpus/8114-record-field-slices.ts @@ -0,0 +1,122 @@ +// Slices stored in record fields: records built from a literal or a factory +// whose fields are only read for their length, comparisons and keyword +// searches, next to records that are kept after the iteration that built +// them (pushed into an array, held in an outer binding, captured by a +// closure, read into a kept array, returned from a helper). Slices of +// non-ASCII text and slices longer than a short token are included. +interface Token { + kind: string; + text: string; + pos: number; +} + +const KEYWORDS = ["let", "const", "if", "else", "return"]; + +function token(kind: string, text: string, pos: number): Token { + return { kind, text, pos }; +} + +function classify(text: string): string { + return KEYWORDS.includes(text) ? "keyword" : "ident"; +} + +function weight(t: Token): number { + return t.kind === "keyword" ? t.text.length * 3 : t.text.length + (t.pos & 7); +} + +function isReturn(t: Token): boolean { + return t.text === "return"; +} + +function textOf(t: Token): string { + return t.text; +} + +/** Calls `visit` with the start and end of every run of word characters. */ +function words(source: string, visit: (start: number, end: number) => void): void { + let start = -1; + for (let i = 0; i <= source.length; i++) { + const c = i < source.length ? source.charCodeAt(i) : 32; + const word = c > 32 && c !== 44 && c !== 59; + if (word) { + if (start < 0) start = i; + } else if (start >= 0) { + visit(start, i); + start = -1; + } + } +} + +function lentTokens(source: string): string { + let sum = 0; + let returns = 0; + let longest = 0; + for (let i = 0, start = -1; i <= source.length; i++) { + const c = i < source.length ? source.charCodeAt(i) : 32; + if (c > 32 && c !== 44 && c !== 59) { + if (start < 0) start = i; + continue; + } + if (start < 0) continue; + const text = source.slice(start, i); + const t = token(classify(text), text, start); + sum += weight(t); + if (isReturn(t)) returns++; + const literal = { text, pos: start }; + if (literal.text.length > longest) longest = literal.text.length; + start = -1; + } + return `lent ${sum} ${returns} ${longest}`; +} + +function keptTokens(source: string): string { + const kept: Token[] = []; + const texts: string[] = []; + const later: (() => string)[] = []; + const returned: string[] = []; + let last: Token | null = null; + for (let i = 0, start = -1; i <= source.length; i++) { + const c = i < source.length ? source.charCodeAt(i) : 32; + if (c > 32 && c !== 44 && c !== 59) { + if (start < 0) start = i; + continue; + } + if (start < 0) continue; + const text = source.slice(start, i); + const pushed = token(classify(text), text, start); + kept.push(pushed); + const held = { kind: "held", text, pos: start }; + last = held; + const read = token("read", source.slice(start, i), start); + texts.push(read.text); + const captured = { kind: "captured", text: source.slice(start, i), pos: start }; + later.push(() => captured.text); + const viaHelper = token("helper", source.slice(start, i), start); + returned.push(textOf(viaHelper)); + start = -1; + } + return [ + `kept ${kept.map((t) => `${t.kind}:${t.text}`).join(" ")}`, + `last ${last === null ? "none" : last.text}`, + `texts ${texts.join("|")}`, + `later ${later.map((f) => f()).join("|")}`, + `returned ${returned.join("|")}`, + ].join("\n"); +} + +const ascii = "let total = count; if (total) return total; else return 0;"; +const mixed = "lét naïve = café; return 東京, x"; +const long = `const ${"identifier".repeat(12)} = ${"v".repeat(150)};`; + +for (const source of [ascii, mixed, long]) { + console.log(lentTokens(source)); + console.log(keptTokens(source)); +} + +let count = 0; +words(ascii, (start, end) => { + const text = ascii.slice(start, end); + const t = token(classify(text), text, start); + count += weight(t); +}); +console.log("callback", count); From 61c32fbc4803a91cf3769c42603fdbfca200c47b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:24:13 -0500 Subject: [PATCH 140/395] Keep Map.get results owned when a copy or a temporary map can outlive the entry A borrowed Map.get local holds the entry's own reference, so every use must run before anything that can release the entry. Two cases escaped that proof: - A local declared from the borrowed local (`const copy = held`) is a stable alias of it, with no reference of its own. Its reads after a write to the map were not counted as uses. Reference locals declared from the local, at any depth of the statement that declares them, now count as its uses. - A read from a map that no local holds (`fresh().get(k)`) borrowed an entry of a map released at the end of the declaration. The map must now be a local or a field chain rooted at one, whose changes the proof sees as edge changes. Both keep the owned read. Corpus 8181 covers the two cases, and the emission test checks that their reads retain the value. --- .../src/backend/llvm/map-read-lifetimes.ts | 57 +++++++++++++++---- .../test/map-get-borrow-emission.test.ts | 26 ++++++++- tests/corpus/8181-map-get-borrows.ts | 27 ++++++++- 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index 0f22f9e05..3371a7f70 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -166,6 +166,17 @@ export function findMapReadLifetimes( return result; } +/** Whether a map read's receiver keeps the map past its own statement: a + * local or a field of one. Changing either is an edge change the borrow + * proof sees. A temporary map (a call's result, `new Map()`) is released at + * the end of the declaration, and its entries with it. */ +function heldReceiver(expr: IrExpr): boolean { + if (expr.kind === "varRef") return true; + if (expr.kind === "fieldGet" || expr.kind === "recordGet") return heldReceiver(expr.obj); + if (expr.kind === "unionNarrow" || expr.kind === "downcast") return heldReceiver(expr.value); + return false; +} + /** The locals, declared by statements `candidate` accepts, that can hold a * map entry's own reference instead of a +1 copy. The statements after the * declaration run in order, and every one that names the local must @@ -174,7 +185,9 @@ export function findMapReadLifetimes( * while the local is read. An `if` passes its condition's state to both * branches and joins them; any other statement that names the local is * judged whole. A later pass of an enclosing loop declares the local again, - * so only the rest of its own statement list matters. An update such as + * so only the rest of its own statement list matters. The map must be held + * by a local or a field (heldReceiver), and locals declared from the local + * count as its uses. An update such as * `const c = m.get(k); if (c === undefined) m.set(k, v); else c.n++` keeps * the borrow: the write runs only on the branch that does not read `c`. */ export function findMapReadBorrows( @@ -184,12 +197,14 @@ export function findMapReadBorrows( ): Set { const result = new Set(); if (fn.async || fn.generator) return result; - const names = (node: IrStmt | IrExpr, id: string, isStmt: boolean): boolean => { + // `ids` holds the local and the locals declared from it: a stable alias + // (`const d = c`) reads the same entry without a reference of its own. + const names = (node: IrStmt | IrExpr, ids: ReadonlySet, isStmt: boolean): boolean => { let found = false; const visitor = { stmt: () => !found, expr: (expr: IrExpr) => { - if (expr.kind === "varRef" && expr.localId === id) found = true; + if (expr.kind === "varRef" && ids.has(expr.localId)) found = true; return !found; }, }; @@ -197,28 +212,48 @@ export function findMapReadBorrows( else everyExpr(node as IrExpr, visitor); return found; }; + // Adds the reference locals that `stmt` declares from one in `ids`, at any + // depth: a `var` inside a loop or a try stays in scope after it. + const alias = (stmt: IrStmt, ids: Set): void => { + everyStmt(stmt, { + stmt: (node) => { + if ( + node.kind === "varDecl" && + node.init !== null && + isRefCounted(node.init.type) && + names(node.init, ids, false) + ) + ids.add(node.localId); + return true; + }, + expr: () => true, + }); + }; // Whether an edge may have changed after `list`, or null when a statement // names the local after such a change (or changes edges itself). - const walk = (list: readonly IrStmt[], id: string, changed: boolean): boolean | null => { + const walk = (list: readonly IrStmt[], ids: Set, changed: boolean): boolean | null => { for (const stmt of list) { if (stmt.kind === "if") { const preserving = effects.preserves(stmt.cond); - if (names(stmt.cond, id, false) && (changed || !preserving)) return null; + if (names(stmt.cond, ids, false) && (changed || !preserving)) return null; const condition = changed || !preserving; - const then = walk(stmt.then, id, condition); - const otherwise = stmt.else_ === null ? condition : walk(stmt.else_, id, condition); + const then = walk(stmt.then, ids, condition); + const otherwise = stmt.else_ === null ? condition : walk(stmt.else_, ids, condition); if (then === null || otherwise === null) return null; changed = then || otherwise; continue; } if (stmt.kind === "block") { - const inner = walk(stmt.body, id, changed); + const inner = walk(stmt.body, ids, changed); if (inner === null) return null; changed = inner; continue; } const preserving = effects.preservesScope([stmt]); - if (names(stmt, id, true) && (changed || !preserving)) return null; + if (names(stmt, ids, true)) { + if (changed || !preserving) return null; + alias(stmt, ids); + } changed ||= !preserving; } return changed; @@ -227,8 +262,10 @@ export function findMapReadBorrows( list.forEach((stmt, index) => { if ( stmt.kind === "varDecl" && + stmt.init?.kind === "mapIntrinsic" && + heldReceiver(stmt.init.receiver) && candidate(stmt) && - walk(list.slice(index + 1), stmt.localId, false) !== null + walk(list.slice(index + 1), new Set([stmt.localId]), false) !== null ) result.add(stmt.localId); for (const nested of nestedStmtLists(stmt)) visit(nested); diff --git a/packages/compiler/test/map-get-borrow-emission.test.ts b/packages/compiler/test/map-get-borrow-emission.test.ts index 950fe35c2..eba430453 100644 --- a/packages/compiler/test/map-get-borrow-emission.test.ts +++ b/packages/compiler/test/map-get-borrow-emission.test.ts @@ -32,9 +32,26 @@ function replaced(m: Map): number { m.set(1, new Counter()); return old === undefined ? 0 : old.num; } +function aliased(m: Map): number { + const held = m.get(1); + const copy = held; + m.set(1, new Counter()); + return copy === undefined ? 0 : copy.num; +} +function fresh(): Map { + return new Map([[1, new Counter()]]); +} +function temporary(keys: number[]): number { + let total = 0; + for (const key of keys) { + const held = fresh().get(key); + if (held !== undefined) total += held.num; + } + return total; +} const numbers = new Map(); count(numbers, [1, 2, 1]); -console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers)); +console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers), aliased(numbers), temporary([1, 2])); `, ); const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); @@ -50,8 +67,11 @@ console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers)); expect(fn(name)).not.toContain("@sc_retain_Counter"); expect(fn(name)).not.toContain("@sc_release_Counter"); } - // A write between the read and its use keeps the read's own reference. - expect(fn("replaced")).toContain("@sc_retain_Counter"); + // A write between the read and its use keeps the read's own reference, + // also when the use goes through a local declared from the read, and so + // does a read from a map that is released after the declaration. + for (const name of ["replaced", "aliased", "temporary"]) + expect(fn(name)).toContain("@sc_retain_Counter"); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/tests/corpus/8181-map-get-borrows.ts b/tests/corpus/8181-map-get-borrows.ts index be546cf73..5df7ad226 100644 --- a/tests/corpus/8181-map-get-borrows.ts +++ b/tests/corpus/8181-map-get-borrows.ts @@ -1,7 +1,8 @@ // Map.get results held in locals across the operations that can and cannot // release the entry's value: an update on the absent branch, a write, delete // or clear before a later read, a call that may change the map, loops, -// string keys, and values that are stored, returned or compared. +// string keys, values that are stored, returned or compared, a local declared +// from the read, and a read from a temporary map. class Counter { num: number; label: string; @@ -128,3 +129,27 @@ function loopWrites(m: Map): number { return total; } console.log("loop", loopWrites(new Map([[0, new Counter(1, "a")], [1, new Counter(2, "b")]]))); + +// A local declared from the read is read after a write that replaces the +// entry; it reads the value the map held when the local was declared. +function aliasThenWrite(m: Map): string { + const held = m.get(1); + const copy = held; + m.set(1, new Counter(10, "ten")); + m.delete(1); + const filler = [new Counter(11, "filler"), new Counter(12, "filler")]; + return copy === undefined ? "none" : `${copy.num}:${copy.label}:${filler.length}`; +} +console.log("alias", aliasThenWrite(new Map([[1, new Counter(1, "one")]]))); + +// A read from a map that no local holds: the map is released after the +// declaration, and the value stays readable. +function freshMap(): Map { + return new Map([[1, new Counter(5, "five")]]); +} +function fromTemporary(): string { + const held = freshMap().get(1); + const filler = [new Counter(6, "filler"), new Counter(7, "filler")]; + return held === undefined ? "none" : `${held.num}:${held.label}:${filler.length}`; +} +console.log("temporary", fromTemporary()); From ecd103d1fb904cdb5f8fe298261aacda03aee265 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:32:42 -0500 Subject: [PATCH 141/395] Keep the while-loop bound off counters written inside expressions flow follows a while body's statements and applies each increment it meets as a statement. An increment inside an expression, such as an if condition's comma expression `if ((i++, i > 0))`, lowers to a statement nested in the expression; whileBound accepted it as an increment, but flow never applied it, so a read after it kept the pre-increment bound and lost its bounds check. whileBound now gives no bound when any expression in the body holds a write of the counter. --- .../compiler/src/backend/llvm/bytes-bounds.ts | 12 ++++++++++-- .../test/while-bytes-bounds-emission.test.ts | 14 ++++++++++++++ tests/corpus/8003-while-reversed-byte-reads.ts | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/backend/llvm/bytes-bounds.ts b/packages/compiler/src/backend/llvm/bytes-bounds.ts index b35a949ff..29b1c8e15 100644 --- a/packages/compiler/src/backend/llvm/bytes-bounds.ts +++ b/packages/compiler/src/backend/llvm/bytes-bounds.ts @@ -300,10 +300,18 @@ export function findBytesBounds(fn: IrFunction, ranges: IntegerRanges): Readonly const receiver = length.receiver; const comparison = lengthComparison(cond, { receiver, counter, offset: 0, minimum }); if (comparison?.op !== "<" && comparison?.op !== "<=") return null; + // An increment is a statement of the body: one inside an expression + // (a comma expression's statements) would run where flow does not + // follow it. const writesOk = everyStmtList(s.body, { expr: (e) => - (e.kind !== "assignExpr" && e.kind !== "incDec") || - (e.localId !== counter && e.localId !== receiver), + ((e.kind !== "assignExpr" && e.kind !== "incDec") || + (e.localId !== counter && e.localId !== receiver)) && + everyExprChild( + e, + () => true, + (child) => !writes(child, counter), + ), stmt: (child) => { if (child.kind === "assign" && child.localId === counter) return increment(child, counter) !== null; diff --git a/packages/compiler/test/while-bytes-bounds-emission.test.ts b/packages/compiler/test/while-bytes-bounds-emission.test.ts index 2b6dc1d3c..a2604eed1 100644 --- a/packages/compiler/test/while-bytes-bounds-emission.test.ts +++ b/packages/compiler/test/while-bytes-bounds-emission.test.ts @@ -119,13 +119,27 @@ function nested(line: Uint8Array): number { } return total; } +function comma(line: Uint8Array): number { + const len = line.length; + let i = 0; + let total = 0; + while (i < len) { + if ((i++, i > 0)) total += line[len - 1 - i]!; + total += line[len - 1 - i]!; + } + return total; +} console.log( + comma(new Uint8Array(2)), inclusive(new Uint8Array(2)), unguarded(new Uint8Array(2)), ahead(new Uint16Array(3)), nested(new Uint8Array(3)), ); `); + // The increment in the comma expression is not a statement of the + // body, so the loop has no bound and both reads keep their checks. + expect(checks(ll, "comma")).toBe(2); expect(checks(ll, "inclusive")).toBe(1); // The read before the increment is in bounds; the one after it is not. expect(checks(ll, "unguarded")).toBe(1); diff --git a/tests/corpus/8003-while-reversed-byte-reads.ts b/tests/corpus/8003-while-reversed-byte-reads.ts index 9b2efd4f0..1407b9324 100644 --- a/tests/corpus/8003-while-reversed-byte-reads.ts +++ b/tests/corpus/8003-while-reversed-byte-reads.ts @@ -166,3 +166,18 @@ function throwing(line: Uint8Array): string { return String(total); } console.log(throwing(new Uint8Array([4, 0, 2, 3])), throwing(new Uint8Array([1, 2]))); + +// An increment inside an if condition's comma expression: both reads are +// at index -1 on the last pass. +function commaIncrement(line: Uint8Array): string { + const len = line.length; + let i = 0; + const out: (number | undefined)[] = []; + while (i < len) { + if ((i++, i > 0)) out.push(line[len - 1 - i]); + const v = line[len - 1 - i]!; + out.push(v); + } + return JSON.stringify(out); +} +console.log(commaIncrement(new Uint8Array([1, 2, 3])), commaIncrement(new Uint8Array(0))); From 383b69cf2df230e4d9f8c529337612163a569bd0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:39:02 -0500 Subject: [PATCH 142/395] Represent record-only unions used as Map keys and Set elements as record pointers --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/union-node-sets.json | 6 + .../runtime/workloads/union-node-sets.ts | 141 +++++++++++++++++ packages/compiler/native/cli.profdata.gz | Bin 1751358 -> 1806722 bytes .../compiler/src/backend/llvm/emit-module.ts | 6 +- .../src/backend/llvm/expr-containers.ts | 4 +- .../compiler/src/backend/llvm/expr-control.ts | 86 ++++++++++ .../src/backend/llvm/nullable-unions.test.ts | 32 +++- .../src/backend/llvm/nullable-unions.ts | 31 +++- .../backend/llvm/record-union-keys.test.ts | 148 ++++++++++++++++++ packages/compiler/src/backend/llvm/shapes.ts | 98 ++++++++---- .../src/backend/llvm/test-boxed-unions.ts | 33 ++-- 12 files changed, 540 insertions(+), 46 deletions(-) create mode 100644 benchmarks/runtime/workloads/union-node-sets.json create mode 100644 benchmarks/runtime/workloads/union-node-sets.ts create mode 100644 packages/compiler/src/backend/llvm/record-union-keys.test.ts diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index c69ec43d9..83cfcb831 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -36,6 +36,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `typed-array-kernels` | cpu | element-wise loops that run to one typed array's length and index others at the same position: `Float64Array` axpy, `Float32Array` blend, `Uint8Array` mask, wrapping `Int32Array` add, `Uint8ClampedArray` brighten, RGBA to gray, a row-major `Float32Array` matrix-vector product, and `reduce`/`some`/`map` callbacks | | `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage | | `unicode-lexer` | cpu | a hand-written lexer over a 2.7M-unit bundled module whose comments and strings are in several languages: `charCodeAt` scans, line starts | +| `union-node-sets` | cpu | syntax-tree passes over a discriminated union of object literals: a `Set` of visited nodes, a `Map` from node to value, conversions to a sub-union of leaves | | `validate-errors` | cpu | throw/catch on a fraction of inputs, small call chains | | `value-objects` | cpu | small immutable records and class instances: vector helpers, method chains, particle updates | | `value-records` | cpu | records that hold strings, spread state updates and destructured tuples: token helpers, an immutable reducer, tuple returns | diff --git a/benchmarks/runtime/workloads/union-node-sets.json b/benchmarks/runtime/workloads/union-node-sets.json new file mode 100644 index 000000000..98e9fe2e5 --- /dev/null +++ b/benchmarks/runtime/workloads/union-node-sets.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/union-node-sets.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/union-node-sets.ts b/benchmarks/runtime/workloads/union-node-sets.ts new file mode 100644 index 000000000..16d6f4dfe --- /dev/null +++ b/benchmarks/runtime/workloads/union-node-sets.ts @@ -0,0 +1,141 @@ +// Syntax-tree passes over a discriminated union of object literals, the way +// compilers walk their IR: a DAG of shared subexpressions, a `Set` of +// visited nodes, a `Map` from node to its value, and conversions between +// the whole union and the sub-unions passes narrow it to. +type Num = { kind: "num"; value: number }; +type Var = { kind: "var"; slot: number }; +type Neg = { kind: "neg"; operand: Node }; +type Add = { kind: "add"; left: Node; right: Node }; +type Mul = { kind: "mul"; left: Node; right: Node }; +type Pick = { kind: "pick"; cond: Node; then: Node; else_: Node }; +type Leaf = Num | Var; +type Node = Leaf | Neg | Add | Mul | Pick; + +function children(node: Node, visit: (child: Node) => void): void { + switch (node.kind) { + case "num": + case "var": + return; + case "neg": + visit(node.operand); + return; + case "add": + case "mul": + visit(node.left); + visit(node.right); + return; + case "pick": + visit(node.cond); + visit(node.then); + visit(node.else_); + return; + } +} + +function leafValue(leaf: Leaf, env: number[]): number { + return leaf.kind === "num" ? leaf.value : env[leaf.slot]!; +} + +function evaluate(node: Node, env: number[], memo: Map): number { + const cached = memo.get(node); + if (cached !== undefined) return cached; + let result: number; + switch (node.kind) { + case "num": + case "var": + result = leafValue(node, env); + break; + case "neg": + result = -evaluate(node.operand, env, memo); + break; + case "add": + result = (evaluate(node.left, env, memo) + evaluate(node.right, env, memo)) % 1000003; + break; + case "mul": + result = (evaluate(node.left, env, memo) * evaluate(node.right, env, memo)) % 1000003; + break; + case "pick": + result = + evaluate(node.cond, env, memo) % 2 === 0 + ? evaluate(node.then, env, memo) + : evaluate(node.else_, env, memo); + break; + } + memo.set(node, result); + return result; +} + +function reachable(root: Node): number { + const seen = new Set(); + const stack: Node[] = [root]; + while (stack.length > 0) { + const node = stack.pop()!; + if (seen.has(node)) continue; + seen.add(node); + children(node, (child) => { + if (!seen.has(child)) stack.push(child); + }); + } + return seen.size; +} + +function leaves(root: Node): Leaf[] { + const out: Leaf[] = []; + const seen = new Set(); + const walk = (node: Node): void => { + if (seen.has(node)) return; + seen.add(node); + if (node.kind === "num" || node.kind === "var") out.push(node); + else children(node, walk); + }; + walk(root); + return out; +} + +let seed = 2463534242; +function next(): number { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + return seed >>> 0; +} + +function build(size: number): Node { + const pool: Node[] = []; + for (let i = 0; i < 64; i++) + pool.push(i % 2 === 0 ? { kind: "num", value: i * 7 + 1 } : { kind: "var", slot: i % 16 }); + // Operands come from the most recent nodes, so most of the DAG is + // reachable from the root and many nodes are shared. + const recent = (): Node => pool[pool.length - 1 - (next() % 48)]!; + for (let i = 0; i < size; i++) { + const a = recent(); + const b = recent(); + const r = next() % 10; + let node: Node; + if (r < 1) node = { kind: "neg", operand: a }; + else if (r < 5) node = { kind: "add", left: a, right: b }; + else if (r < 8) node = { kind: "mul", left: a, right: b }; + else node = { kind: "pick", cond: a, then: b, else_: recent() }; + pool.push(node); + } + return pool[pool.length - 1]!; +} + +const scale = Number(process.argv[2] ?? "1"); +const rounds = Math.max(1, Math.round(40 * scale)); +let checksum = 0; +let nodes = 0; +let leafCount = 0; +for (let round = 0; round < rounds; round++) { + const root = build(20_000); + nodes += reachable(root); + const found = leaves(root); + leafCount += found.length; + for (let pass = 0; pass < 4; pass++) { + const env: number[] = []; + for (let i = 0; i < 16; i++) env.push((round * 31 + pass * 7 + i) % 97); + checksum = (checksum + evaluate(root, env, new Map())) % 1000003; + for (const leaf of found) checksum = (checksum + leafValue(leaf, env)) % 1000003; + } +} +console.log("rounds", rounds, "nodes", nodes, "leaves", leafCount, "checksum", checksum); diff --git a/packages/compiler/native/cli.profdata.gz b/packages/compiler/native/cli.profdata.gz index 31423fa043e0a12aba496a4ae1604d79da4773ef..aa9398b5febacdfdc156d2c385c07312e12909f3 100644 GIT binary patch literal 1806722 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≠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
  • w_bYHksWL?#OZ@ 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Ξ!-T!N7k_2P+D{C3lJ-ubx;yT_iq9Z>?6P|8SLovMfs8;6i@SbA>E#VTi z2-lAGG9QcdN1GK6=8vt+LoY}?YYliI`jyQtJ^Ab0;ApkMI%3(n5mr5+kH)K}j=m?EISURix< z^f4Ny_xfoM?SJ1_^@4b+;2VokFv36g(@gG1t&2hrCYK;k)!%T+__H(2nMH=mCS#dG zbrgKpTrm)YzZbsblCPB@Rso$n6EO}^`!7YAIAI_1`jG{wdLcP3mq%jl7pVR0Lj#}? ziz*z9Q6KkNz23U9xzTRBQM};&I0xmz5XD3pmMW?ms6mUGOK2Xsr0q^L`}NK^u=!;@ zB0x6E#^3l<4!j+5UMv1cO|;b1^#M_wdFBRU;K{hlp)8*yix;R1X{biFySiOCdi*|{ z@Zyd1a}@|-FgfSTozlOXw$=OEsrblDmcOQJmV&}%T)nu-3L466c&b#(8C{>q9?
    4K(A&w;e%wG)z4g4TMzVj*UnuFqL^s^48R!%vM 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]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%0ATB!i@_r|K@81IwN@I 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%0D(sOE}2aB<<*K2Q_Vx-C9 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%0DZN zV3K>rRhYZ(aTV#=BxgxkxhxqD3M(oxEY&73oxuy?d*sofy=cbmE^fiP0;ru3MZRh9 zM%zM;R{>ZRJkv1w6?aJeF@H7*fJh#}o6JxSB~-!zswcktlkcexpeL&G>A|8Cv+*ao zN3q^LiBNq1a((-0POV=e2J6a^S3wn>=l={RF4ZaoomzyPIZpSQq?6rS(??Bd+^u$R z+1$E-L>Uu;+o~*3=nvvB)Un73*FzM)=Fl)%9z$ z8|^U2>>BecC84FY4+{yBJWU!&=FM#ONL_;Q!+JRm3N2QO517~D$bo}%zmNKo4&XWv zH1;xEsAZoC9gNoqLt3xi%Q%3d>aM^lZ>@^i^Ud(bsN~}8MTcwujEX$rRRN%>Pz$K& zAj*-pLl*f-tMOh|o5{zQ^LK2M_F8g)|-)c7=v*>gdD>donMO5Ip(is zy+ukG;X|>opok$=epEg(`ue)GZVxQ-lkGgcPk!!oIhMAXZT;~Af2A+G&F2QjyYk39 zq8hON;Ts`6;d%&}f9}+vujblpBi|wZ_kF3w!U>LC7GtZb84(J6dVqu zFEyuD3O31_-vJs2r$tEOG3Bm74F6O<+wmgbUaT#vP2&t;Og!w@hvp;C$rppXA4=E7?9zSI4U;kM*MUs9`C$#` zNSwAseDhBZY&mg~kswe8SnCyfm2t<`3>NuIJ?|-p-w*~X{?e*J za`uo@6ntyoEjlTnu=GFf>afpm@1TRw{I^UlWI;8KGx$9*qaiWV|Vym5NV5FFEdIOMi4Nap|)#$ z_sVw9SB9iuOr>QOAw$t}cFDU_XI0a|2ZoS}+viVuf|hc_~D|qtpady^R#14?m(9`cdM)*R(81!P_e%+i>zw} zQ|@UN!5Ul|>6u&32&?YDn^cQq*7!)9Ubkx`N8A^)whn3Q?U+C{-&_p%a#Vtg-@`K; z`H6km%x8}+zn_CwR*n`=aNWkkg$aY6asA77l03rTlg+d156^cLo;oyd^yV>&g#H9+ z4m#3|8OO{|=KCGAqfds+VeQ?Q8JEJda|) zJFy>pf3dmMIrpWqz%cvH%MM^#Y^@3{WiFv~X|@JbqcGzaT>^2i1xSCVE{xjM3$-5I zW~fj7ihp-jZ!(DhCIp3$E5-`uY7IOYoyFkLjq#Bn;*`SO+TTu+*72kdDAzAV$)D(q z&9@mxJKq;O?&Sr0S2527jc#T+PS3m&iFNhd5j95_Mf+2Ota6G9Ld;@7>ghM>k_&5= zJBb_QH_ZGnH7srRU|E>5^yHpPV*AyC0sstrgM`SMSNBiU+MhmkNLKG;VSU%m zj-bf_+sWw}eu7NJc(p&!V0~)mL5*HHSbHZr+NUnO)L>yM|LC(iHAW{3Iz#9W7~2MZ zf#yh@mgh`NwN+)&yGd%f&DVY%+`9l~yS7Dcp&6J$tG$YR`FOGsCL3HDJjuOODL|Gz zUZ3o}SmlT48$1qEY8`+OgUortp?DWDN%qbz@bkf}DYg97u5xal#rQFFcmtp1+~}X! z)%H$yNU4kufpoC% zoq~|Brwp8HxI>@Yl@L^+6Z`-cvmB^g7j0!bTnGv6OOCF3XuEZ)ILEO0hHsi)XFcfLy5h1cgULdqQ=KI?m9zBkKRkqf6=r4uX$ND zP~$ZcWNTfD@XxGf0BrZ?gjMB7N87EyY@-2E!?K%xv*Qkx!L>K7t9o2wU7_%Fn74!f z7wFm_rrSg&v(iPBbe<^PS39qo7v9edJDI@5I7_P!SV!$+ZAy|$+oF&hqPz0FC=(iec~bwqj^&m zS!85IH~VucCY!GBS+wbHq{hi#0E^+RpK>)z)~#&8j4u?jN-AaGb&=4q{T3DSeYb@a z@$72G=*7B8gd#TBdPuJ&jk9;{Ew(9W5v%u}%PGhE${~UBGeud5eL$3oxV?ZvV62ZA#o;SM1*>?lnKh?qd z-LWxq*A_2dnt9bKS;tpRlr_e~o*DZ9zN*Q~yq=)9CE|G4a>WykTGNC-=DD0W83HRdA(n+=M(oLx&-n(k$o#Se<+iSo1crzpw@ zM^R=1pE(xTzx{O|I5SXKK;W;m-em?YmR#^T>o&gvrD*$!lD4dqZ!9gB4yL+(`uE>u zASX{O{(PZTvSali{zxC@v&PdXy8+e^AeQUW(Og<(vK`a2i5F_DNM_eDA@e_|k$1IG ztNuwj4xlD&xZ;jacS(}!Thmvuq($%7?jFu2VF1UOqc$&!#&A2_l9X^Eo>US0hRz2UW*Fe8-J{Pb-T#%a=d9Ic)CTC!!L zXMCsybryyTkv7;=DK_F?8I=i+lKl*a_!*WdF`)*PGiqN3-RHJyI~|G9{deBdIe*U> zumZq1lrcMBl7fw!!UnKiL~Nq9(DlsjH&8nzlYgnx%YrI=9h!8nPmD}yA|Nj1K1jZ) zc^CvFOOxhGh2BF=cc9ha$W_T#tq-RTp#6t?=>_^^16Wy7SEh_z(3d00P}TmzBxuw} zgDyS$Tv)J1#-R8hqN+4K`ICK!x#3~BU!nEB>%x|gY9k%bCB83Y@++78l<)61`;@sN z)JfD>9g*c4FDEs94?3G%O1rb(g{G+!YgBR=>{466GC_DnkoVX+fVv(4&6ua+xHBCQ z>GQ+-(1lV5GmZ^6vCxoAHPBnncFAz6*+zb=3uv|}=MQf%>ctgE{EoB^WYK*;3c*>I z>cv?F(#_g8wq$W@5}Z*mI#+XQBVFC!UIAu1Xtf zna`>WXDCT-uXL7`3%mPkvwTM<*YRHXt4V_BoAiOh##OqI)h20kZwz(dbw@%7-dg!@1Z*9Qe}2=%f;T8tHJJWS5jxKHqRxt#GXf8$y+q+-;MjDj`V&q zLOWG7+tVSKRX;wUQT!(xh^l{uJ4LcH+-&tFCP;O^sbVzPa~f8aJv0?KE@k6D?BK;k zo1_Mn5VAo~uQ8a^5z-hFu>%4!-kHc!3AcK`e>Dr=11Y(yDZaM9y8UT6VfF@7>gfl( zvs%R-7?MY{`vJ-7BK@dpS_!!n0y;V8Z2b2y$0jr^fUM&;sOlhBac0Ssk?iTJm|TN+ zZRE!K!Zd=keCj6!yEGx}E4=WLyRaKlkab)(5NOt- zkx(Q(L7*;cyXLxbJ7A!H!V4X_u*?Q~lwcJzqXJ!v0oZ!}ceF2g`+#y4G}5kZF$e6< zAxEj)ky~WV4+f`e3G5a{=9FRZenJnm>z7)d zBbp`7zj+SK%CX?HR!CP1-RtAvaZiVMh(=p?+Z5m`Vtxw!*+VzV5pzX#Cy>3;m&M}z{28vo90*4g7eW;~SDHYC# zdrQuOyuZEHQ@srxPX^W7iS|DQNCg#Q>kg?NQ|Pd9wPN|}Tp)h?BBxn#7Kv<7N2RQq zZg~heZcu^WG0}TkET?vxT|Ev>J+4l=f5YW518O8!yat4=5ww3`O`Ty1M@m-^Q}kSL zD+Mb8>iGjN)$v1q6C-AEF0$akyg4a-q?A%|=FU2^ki0!KL$41^G)Zv(uUXk2lHFGa|{g;Q&&BS<&uV#0Rzxwa0)dhhMNXXP`CZwv9 z@)`Sez`E}|r>T)v4rM!}(5m&moGu!1A=ysH>s6w*;PdoRAWI7*_c+;QT7ARJIpr7A z$qn9CxEOtcukK;iMKtV6gYeZWgC?7QP6!3k<&%*MWA8~$n5BaLgOW^&HSTxC!jrm9 zbC6`1a9jv-c#Evt1Oi=qLy&Vedhiil7StRk67_cRU=qG}djwE}ryeSNiEa-6Q~EcC zoUi1Yq;vxM{X1tn?ua3;?H>6(_kiLiX;WSFjRJY3)$dk z^!j7`7d@5jXMx+_6>yc|jU<6}f6WH9EbFo)DR;57NUs1ZNu_i>zl~wE5gfZ2%Csp$ zr$}dh++^xqHK5d!?!RI0d)Ij@|CCsg4)Lv`t>TDL{b*W$WSzfvCwIGW){9wyTTww~oE&^ZBI0*3drPb3#NZ2#TY#`7i*oD-TGEb?)C!;58A3#HMaC0gDPy zn*Zy?qr;Kti+nZ6?=az~ATrlcM-BDsW?@nN#ZmldtBkgHnVxdH+A*t)5U~^0Qlpcc zBRJ1!0DOsA-w!_S=XS?%G9p{}_$n59dw&47@jNuq2ePScsP>QWeVcs| z*|xldpm^lV(m~{rq9c^DmjvjE8~WmK8#Rx=J&DR?GRC}g=CIttB*pV?D3;y3X{pTi zIjgK@&@KP&1rBivcA<;U6NU|EEL#Y#32OxEBN{z#9m~SN3Z(OvRMPMzJi=38SX9%G zDSdr`mn%Li?WOtuwebLLGg@jwEV>rf?|h% zN1As9R2ZRV2F#f?4*w|K((Ef=9cy4f)-6Tbgy+pU!P>=GoutwSDW&V&<&8aLM$9*1 z&}vn61U9s~fb>#gh*JaC=gnp`0_+TH{&KktD%Y=@U8u7$dDVxd>tvVopgLKRz+4k< z9sGd)%7OeX3eB*V0$e<>0LzKo!DQ2^dp_fcV{ zZFv>w(^a(B__p7u%=))MQx~S!M;l~YGIeugl3JULriwL?F>M}+Qrp}Kx!KPm6m}bl zXFrYDUr90%-#fib4}0XcY&Ch1iUy;O3-qZeX7D;X!c26J@*tuCzKkeK`kqDp4Dthd zf76RishFsTkMkE`JmY$~PufW6}0js+Fx=ln+K9dDV}I^aHR#xTJ8%az1H(?SYBJSEukGx&%esaQbA3GfFif?dXx#XV!EiV3 zr(;QG5&nOLA-kFxZ5gx1&m8YmG=Ubso6eO28V)O6wRtUeUEZZ$;ynCf9Sm>Q<+$9= zKX$i7d;0Slo>lq6j%C&9StW;Xzoe!icNTRd1^!-bj_?9#jBmUq+>b2~A0O*8$i1Dq zayj|SQM;$vI3MAO4n~nP7c?CK$|{1)l9$2GzYVWY`<|6FPdoTjLFF^6p-+ND{PDm^ zf!T#@tfR>idw(T(D1(8SaKiSHWWO<*^6P|YXI(dC(TuE@o)e)l+XW!Z{NnUt zYMA)LBhf?xn{+R6{IG()YH5q6%5QRM_X~z99J_q}qZF>g8rJG9y*&~CqzVa0{I#Z< z7H~@ZWwIK<$+Vg-A|8o0skxv-R93<+cP)cYEFq%K!-EQ=UfJz`$|zc=kIY(mxc3B7 z@lT6~IR+>^+M8T`A2PZtKIn5}19ZQya>oK`?rc|L*V6~3*6C{(KKVmfBm~v_uea1M zTliPw%c^=7pdJzJm%ZP4rKIXL`Y-E7%&^mPAdO!ZVV)GcUKSi!?v!dhheuT6T6Dgc z+V4_PTxipQ%mWHZNsKSZ_ZyBLP7R;~fKDazD*lgf+;$k(Vj$sZix5dWsLFJRh2{ro9AKBlcE}>&c2AlrAdqvF zu;fsz-$KKng;wpaG*0PQeFAp3d_JAoZv@2>B9HDa(dE4hU{>w@f29 ztu-0q$GP|+&Kq|c#H*^Oac`444>KvL0G}O=8+Xi@of2Q2m`w8>iP)$_UNgvnk$&h$ zu)@6E!QnraL9d8cR|o>0zLEVrv79XLi3Xl2_c76yDGtRe@+|tbRXy9R!Ekk@E76J< zHFScb9XtOM&9LOd<`3*X!fYjda<#6J0%5f?6X zENPS63fcxUt!KwNxS{>UB{-3KuyswRZL{NmlFHw9!RWRgI^YT?A)Lr5i3i*(;q>2JZ7KHvvK zc(9)VvS7mN7NdsU+|mqGTI@tEsj)zo6DlJJRX<^XC=-h|A_6ubU*q>YAmh8 z1Dn5GFs^623>(Eh98vpqSNkrvu8=Rbnt9oi%8+iAD_U;Y#;xS-@(}YCIBosrd_z=Lf&^HGiP z;Lz7~Jgt8xtn1l+4f?x#!lt$#V$dFz4Bu;#T$}aBX}U390EID+2b=Wcv%NlQ6Q{{4m7f2e&q z!s~7=pKMO?3u?E|zU^qq^e<7rAc2cJw*I9J=8q^tZTmCiR*t&4ODp(7ah$!t<_Ow& z+ZEd}DkHdaSFz(058v zsixeqe9|eA*1YWMmc^PWp45z|y7wV$7ilwjoy&IR!Ge8)5xWNdEC0hv^mNI^-@;@6 zVE7+YtS6~Ij-2RtL*cZT>No_{gQU2L4>IFg-@D8aP|p2u##`C_`^Ik9gbA+dh@~*{ z`lBtY$=}lt>;ssTet>Uqoy1TdM)WCD>3pYt$cZmpug(L(4YVQU@jv_*B~WU4(&ok9 zp-?KEx^;3?W5z7Eh`sy_$SR_8hh;`G+=EbdQ?>S2?z*EhtkmOcV8knlvo84&EVCCZ z;Zn64GFdaeF400E3trmdum3!ba;v&qhC80C0?iTbMlo$`Q%_a>gL+_=cQ$Iq+)@+$ zL`z-|14w_)j7mm^dz0_Nmt}*dWj?dVvsC+-nZW%MBX;j(p~r5_Bf^VSC~&iP?yyjDRfaEocdsRUj&9Zm z%2eBW;Q7?SEOn&Yx({v6^#XN!p;pgLAe{3-9k^d-iJZ{~Hu4aE=#r_-$C{`sTV3)~ znywNEQs||<$^^@ard{cizuc~|j-0iV7tAv#YKXJD1UdWi;2fP`Ri4uFrl}{2Kz(cc z4gQvXBnGlsqBR*A6DNMqiMVRrt*Y48`L7k>9*4^LdwS{nS8;=my^p}`%=gO!MO@UQ zVx2>pH;1xMSN^01U1Vm}XeI%@$qQHJ=w7#~i~8TXp8VFuu805La#sH0z8wvKe6u~I z_V`@se?iqO$1yO;-H!*A{GscFDFR23%(QL#CS(eBqfXP*-rsup!mTP(7Ud9GvH?A5 zBL=d1xM5w?`8<)hQMD&J+w!{RIfFz}D*X0>;+*^aEa?3ii`p@BrOTZ2{=%~4U;ix! z9<%Ezy`agq0v|mwYYo^^tG*P}#;L(=UYf6|LS3~%$v>}Et)@K_ ze>g8E3kB!x(x@SA~C*9^&coW`MkMB@5@j;`KigAX~v=Mk?~41{1(v|5FH@w zgr9l3Zo6{bo!jd|G7zCa$O@>Vu$TT)LxvC5{_1nQIN|=3Ajitb=ORbi!#RYq_523{ z0+N?isVK#&+t^KYlAqBoD?Y9zGEE=e7fD=+|LIk2mZ+CJAAm$n{x>U)`03J4eJ;z3 zW|lxjz@nYVJYg;*kJn^2h|mBn(!?3WJEp{lHTe@>3QDr38%d4P%V?!sjbGbkBKN?* z`@2Kb^7znY#9Ad6VXs1$-n?AEt_Ey8m&wSvk}>ackwu-Mtw4F&kQWQv0<$(SxaD0! zSUEO0ALEj$D3!>H>-2}gDU#`j=yw^BwpE)*uox&W?#dtf^A~gY#@e0ui!udwTG-EG zm9G~a)44m`C?i7GkzN6KYDf8|eGEr6(L47}U^^gnhECa@D>SVO#FkXWLb1oc(V4#J znq3;R!|E{~t_x#|)td@U1Qo-N=Et{BeVZRjjMMH$Y!x9IGy6zSU6VGW>KgZpB1(qv zd+N$C9$OEVlr$8+qT@0Dg`a`%5^ag-WMk)lQ0$hO!an|a-e~qTzC#FKkVeZYmFK^8 z$%gp&%B&LYLt$=RKn%6ix_QmgkZxYH*&k~5FZ;4ln34*M) z27aQ8JLB^c2JN<^$Q};R-ufIz#GgYvT6!OU zvqHo-orWCJE6BQ$bA+`A^E$c5DV`gk0uuA1rHC`#EX9!63rS^kdvF)lgu~Y+=>{tS79V z^(IKm1?TK>fPune%wJjr;06qxxuW$Vk5*X(S_s}ooWQaar@oSmRil1W@qNehAnEbB zq$a`x`ps4>gQOX9+pqAseQj=dwx zJIR$N9l&V9_iN;8zqUH_HBjstgF}Dtj%UcMGdVDDgGHil1he5?1AJEeQE(gFPp}-N zT=33X3j5~?AJ*y!hyYG`u23&M@;jiD`%^VPYd0^Pa&C1biis8c`f;T?SHJ)4?@L!2an7ZCm&~j%k?k4vt`}m{t3^d`kS^sO{ zuVLjBq8pU6rWiwuv%x`-Z@L8j8ECi$UrtWeAQQkh%%iC+P!OLQTS&0J|BRiFYIo zhYt0*XQ?F8sKyH^nd2o|sakNVgqa!7OZQ-`ghzt68+7l_YT*DM#T5C7Pp*O~k0d@& zXZC2Sx@)*bXn8UDc zA7O~}iiU{x%r#r`OAFw?<-d4&X2CDR-$nW<^nb6=*H5=jQC6qYZI+@77vVD#{u%hKkSfg2b z6Wnmtphwqt79LdV^gGePxQNTRSJJ$_$NP%95VGYS*CbKOR!Jel=kK6KhWJjvy|f$# zmbN9?+XESM#~ulg+(AUv&28&!(9!@$?%+1PPP2xYJ&_hLa1?*^q2#uED9lMesM z3Ae%Ge(vnhR#Ad7gXc;gjE2h1>>WF`Z_~Fb=$9XXD8?ciup-bLL`0d%q5L5{=t*VF zmfw`iHsS1BM+TFW7G>=_N-v#)9=Arb%+Ca!l$X|sXmLR)B)8Q|Lt|*z8tKt)Lex&4 zPr4g>5K%+(1A+=mnm?^wvM*Be*^xPPnji7Zk@1bQDu4Q!%b-)mq3SGen<3#$hlMkk zyUz_5c^-AgJGw*#em}$C#mg;{i5qd1U<(sbadT%7BcM1S8AAb4n8Km!_fR%DC0mvQ zN0B!PYkom~rxY*4k;s##0`T@M*<&gSz|4d=82ff`Z!LO6W9&(7u||(?)LpHzSd!jX z<+A;dQweu43pcZUE(VnMLC*!0P=I#7IQG3EY;B9Aw8dP;GhTtt;O+F5a8J=-?}TsIoQ=`;85^DSGyZ z8`ic2TG!5b^qx9k?O2u+X~IlNQjKCFwTKs?+9uyrAWJL~0rA5#LGe;;h=L4VO##cHcA%BHW4#YMi{f{G|zS!wHlI2itmXH zXJn)CFtOsjl!-&8i#B2^Ry+Ie9Q*n*7lQ;4dcCT_FL)_fk+e#&K?dcD@Du+L%?i4N zT~HqPvOcInXetwPE$~^nY}9iYN~+pWg*{n#9Xq6$a&093XSNgru2S->T}b6>OPfJz z0@TlD;%-ebXdUp)%7jVKdCcXTRx(?6nRL?|{p5(theu+)uFtcNUN5dW!gAZhhaVS) zNz>L(*Jz_hL${HH(rrXi_}*c=&A%}LhgQo#-zQ3ZMsFh(H8bblj)G&t=bV164MpT1 z?+6esp6D>nP+z@SPZYkI*-6Q}3#^V9i4imWBLhJl6?!1)y{#6I)}{vFu~J-X-FD2r@=j(r;`nEIt#Y4sHG} z;tRf4fk4F=@zuKFwS`J#E|8n}bQboE;WdSgNZcwju$%zG1ZO-{tZ6(; zKvI${Us7GY#DAf=`A$A~;9^AjCgVY=RsJbK!TI>QS&Q!PYa)WQ2q`xbR8l^~rQdXR zO&_ggS0lyccOnCjd^b%6e=3;=wfKLOUiw3Qn-34tHwp4Z8;)YIfEK9QJ# z{fYAH!5{fi!6^#C=SvKtV|LBr3yjd0sj|Y8sC$MI4@mX-+ZmBQ^GEvD_9M?S1eU={ zpJmAg(x9ZI`W27+q$6>S#MOeK1A{8Wm*kh~p_C0erz`tWI7dIyN3!DO%6ybahWRi$GBg4O;^kM0mBJ|f# z>3g^PQ!n&1N8>j3`+Fq}jgf~l)&xXk?DUV{snUV9O`)Dnu{cFF%T9Kjumfj4vp;WD zLYBD^?r_{?U$-Bdxxy6+$rZyPBK9aK%sk!%YuQ_jwc(u(sLsOd;0C@s^H$7R&W&nP zLos(?VU^YsK23|PzdqW^-J!jk96)CHsncX)%;NH$(JhM^$vaWT$)}cOvh3ga!7J!U z?f!-^F}tAM7j@f}x1#l<3m`TA(L9V9T)5w7MQOo_xct|l2%0@BVKwjaz7V;eEODgw z3%Az+Hsg9*u6v&Jh<@n8qTFi|yI=f#FfO-c&x__A1yM!Eq15*OSX1ze9XvyMW zIKyH8l4S?j7W_%=YA}1-0PdrC*Q7aVXjOORe&*M@MT*UvEiPswC9@ex>4GMqsxyK3 z?D0`*~KtQM~_$OW;4D9rMF0L~S8P zEk5%X?OBq>2=;ZC_vdD~UyhqZg5l*6p5v!kT6Mgqs^ejvi#*+e)4mIxv<~W2Jgu@mn?d4Q{@On7Tn{^Tlm2*Q_Zj z@>QVHdXkIMfb9vz=A3HOjV8w+82k3zgl3&%QV*5oWH-2yj42hjD4ht~2QC&AV}>&G+!1Y%PXS&2@L(h+CSm-mvaCIFAOW z>B;~t0E2A`PS8h$shJj^@9j0fKk-!-2Axduzx09i(L(a&q)f4Yn(=b=VE>CL!&{T{ z5YGBSgV+kyMztB+Y}rpu_y}zX_icG#%(DnSkL7fgVp!(!Kz-;$kns9T@eI*{5o^mk zqd>!DLx`>^)!F>h%WC{v>Nlv1hwDe{bhpFeUtMzBTQH?n_cqY(-r_NKTN2}{?VeMA zjBA-h&nJQx!$+YlI5j(5-YgBL92Uarm5>cyG>A$2C^bEyn)@VzwRzWB;IVHe@xBU; z#E}+sR^~YOsUaEz>5;LAbItJIM?JglPErp1vxRehAQBO?o3dfQ%b>7Qb+e_Xvo02{ z|1y5dNe>9$1AcNFaY`IDKQaF2!2Zq@aaVdC2r!^rJ^eygOi9!!r6qU!vl`@& z!R{#EGA4poGE3D-k;+3@C}nxmv9-LXJL27Z!)#=vYi*zD?EN(>kn8@H&7uQp8d#kJ zA#7??N}Vsfwi|yls`kRH8@#jlm(?z1K#>Ho3*b|{voW(YBbIh`|E8%JpKvMzS0Rbg zW}0sWWVvV@gucQf)^XUU#JrhM={#EtpJKXL$1VIV+u&1(!?cy6rfdeH#x(E^ZVsfd zgSWpsq5WCu0-NRQte97$@VZMrC7N^em`B0;(G1+N6s}S0fbLmT^NDE8!jmEVsqN>5 z?lC6%36{l@AagY2@dTEQE0uVZ@Af%dw^r|vO(8?LFLQp&kJQmUs^V?A;RLXK61Kp% zo%SEe;0_%{D$bSW&fYfl3m7=BBgH3Wb_qve?B$E1N?_~ywGrpZ<)YG=^ z?022prvE&wk=(^qeUJmbqi&C-Z>lP>lRKyJ5DzKEXDu6Y%@Sg!?>8iN?Q1Bmf#OutUyvhXq2K`HgRt3e#Mn@brD7sCF=4LYWtaw`t78s zIh7|0?mF zf@}YZADF=v%|~H^3W6-T)A+aXuxE){N0;83Ul`pt`1Cc*3;AaeGwbS!jOX<9v&_t2 zD%$ksgsuZ~qYi!fb(-%tXo*zAPtZdRjPdqHQY?ELEUe_xXrxA$NjC4LZzxb);Ic)p zvLzMhfAGbuwgutJR-R{i>OX)%TW|658D^hwd(G6m{whpB>Y~YKD?6~|kt*nLnI_cx^NcKV0z3N=KEh5x zzHwt7Gf8AqXZO5-&FQOv*22-doVxqCgKv`8E}lG&_J1m37cOgpV24{liI@zN-B`z< z=Mcx03Ckgee{B~Pd%wZ2o*3EX3;gkrL>#$RFUR}jFR4flgQ+9@)>H=gP!Sils0j*- z`N^R+i1#v3DVL~*Cn;8FAk0*7JVh1^Uh3NLhwe5}Zw)Y~Ex={vJouMqQ|AsYunz7_ zRUQyCTv_Fqo0cK+&z3W8n=$G{o%(-~$wq=-7YvLUv7Y1~+=ic=i z{XbB-Sx+=|umnc(NPKX&Q@)chsnYxxW|E&DYR*9g3+gG4HH zvNXTiP6Vwn5}BP4 zcdA191{#OTizXR82|*rT+Z^FvbwWaa@h*AQ;_W!{;&p)Cd9CQn52hNb&zT15iT)I# zxof4ga{Q%LcAW9|C;0}?Ec@R{+-;aeoB7g~LbS4aRo8>rV|(H_(4s$hpN_Nx8t)m; zCED4LTV27Bvs>yBxjp$f#zbSb&%rY2F_Yc6`4frfVav`oaJ1>)R8khO<*Oq{Bcav+ z#2C(&TzJ}A2mKrpa}$i@DR!H6MDaJS zRVgu;JG&MKs28%ij>WTv-a}4sP`$t{ghLB0R5q?4p=u`PT`ssQeifqNiv8wgs72@g z_c`V>^#KO18h2W?6wbP@cd{NOhdcB$PKhSGb6L@$SbnnZbZ_GxSj+du9{jZlXk-4I z>?SAMk#r+1maX?Vj5_jS`uIYqJWV;ZP5~J;jDH>%3mfOAYCI>kdk#r^T(pvw>}T_R zrnn4`fBwWLBs&h^A@G_?V7;QrPfGxa;L#d7TN(B~gLqwR<>=x85_UXb)^nR3#v-1v z)G&k(?DLLoO`!DG;fX=wCw&hS&tB?*9%_T`zwZ(^Ni1c2QtB^LPQ$L z`svd&+J>1Ecq-N9KkX8wF48TFU60jNpn9uN;TO3tN78${E@?9Ev@$R+P6n;)6gHm^ zlfr9W2OHi#7+NJk`_1y63vQfsi$sf!c|puaN; z*<22bLUJ1h*ttzvx=U9%#efi6Jes}6AE)gwPv-a>Z%K+EcdNiKl{C#*mE3W1eEr0p zyM{%iUK1=h6~XKav78pYA>gWqG9;H6jViAvAb`0O5lb4#8nfmIFI2Z>7a%$F&{yLR&&^6y zgTS*Wg@X8s>zY-v#T8TQsI$JAGpQ46`!%{{MyH=1(c?3mceh1gJ;YW9J`i3#z!dh+ z6%TLj=Dxow)ru0+{I@MQ-Unc|P%!X�xu_KTjY>W6e}@uB*}uaE`7)Wg}KideN+X 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 literal 1751358 zcmV*5Ky<$!iwFP!000021MFRQd=$kO-@WwEdqQtgEU>*O(gH~D2vR)u?)Jz*8h00| z0yYqASV2$$8%p^pR#a3#MFkbC6crG#BMO3|2>d?D?U3yEZnH^9s5k#izMI>f**9m%qalP@9g#3C=(@PzBwad7^wq;U&eZzxY@;mT?aeegv zlJo0VkKdeM4<;QILp?~`e?@*>Gsfuu8zIX6IITi{-7xf6>W^ua^Xm_1IYRFjA``{b ztCC+o*DZuygG2SO_Mz(sjprsF4%Odu3SB4tcB~C`Na`HAo@`uy?y*oi9gOP^<9f@! zF{cLpj$hvz_vb`HvH8YN0&b_v>vT@!M5i~)$#~to#JWVU$Z^y#r~kR#T1-Fc^hyjB zEf)fvNdIEbQu~Qy4p1>vqmGA~KDn6ivO-1TWtmZ2qQ|2NUN0a2>4;lXmM^SA-l1bL z+lrwE)*m8|+FNu>lBfx)qVtUAm9eF^Mm@o%C7Oz*R;aH(yn9iqafK;pD-iVS_K(!Kl)p9BA3JIm7C?u&T?jHet-4^r{GoDyjPKRPJH^kVH2nP zwEC2+C#8AoGs9PL)Ghx_?y;j;D)30a?JO8-m&e7ilBn>SD%EIlz7x3MHhr{)nMoko$ z#uokPYjM4WQ4fQTTmwR}W*9R?F2il;LzfX#w*`gGs-#+6-v+pqQP(kQ-4VI}4x907 zUdHcPUd3^NDGB98Z~8o=Q+-1av@!59W1wrmxW}KN-^apYL;8Jx8U5b@<5b-K5xG&T znAol#ztNFvd=9xz?*GwPP@b<~UT<23>^kZ%L-2l6&D@U!=vOm3a*fI?xvz?`Qck8K z*Ujk2N^z~E$n|8P3%Nebz)u*2?`q)h=(72LQz~*z{@)d@C1<9V(@US!$?etyfuYY--}@;2a^f(Hr`H60Bj6c{#k|KF~EjnB~gl>MxzL9N?!2)gKzG_M=2r6O=y7Wa0>lQM3r!R1g9t=(UY=0NsouT=)pf5XX zO!~YPX@y!#ncz0n;nsL<>Z1-d#4T@JW?ad!q$wvl2kKtkuV=aSp?+Ub4>(=EV4$D3 z&QCure6ag}iDX$!u4}F-i|hFR;xa;Q>XMI^-Syh>M#O(ihjm%=`}1U@lT*DOURPw< z%_y51Z1ucb<tRzYb)#`ZL=FZHdaPA*F!O3&tZS`Z`n9=Sioorzu)1`b_+EU2Xqv)dg6P(s<5ggs2ndd7=g!fp?aT16Ar35rFHmU)9+(QfLYZD(YKt;IrU6maHtmWb<550 z`Z6+V4EnFpzJsGm?TfZsQ~QC=OX@#LsMC_?95reZ+e89CN#xNIxbKo;w$m7Fm&Hci zq?rAqFA!dBPOTfZzw5xX%fS~A7Fx;kytB8@8`M4B{92~&bZ2J;g8p20Fx!uzY^^Ps zwk7q@nof(mebUglQt0HM->+xrT0nRDb+;fh9$oV)9@ZJi$e!@?b#r#qS-!9e;iWX2 z8ryA2rTsF(G3-V1ZV^IqGkMX7R?7=eb7}5zo%~~*ZWq(W$8&g6)lp2^I;d9EURTyX zQ|@UNE(gq~JpFo58{-r`f+T9JOZD;!M#+8 z;1wiJ*QK`gcf9b##Oo^BMYOgy@&PnTDf<7dQ%W|}$)$81;eA%aKvP#z)B0?>{@E$y zn0I43f=;4}POopG?irk$lc5jF@#~tWN0vAH#AN=PnzOEsw_3C@XGcw&UU#&q@i{eZ zo?o+=?UtXi(4M-5&3gWk?J40JpFdC4@?|;woLg}7Zb@WBx3K5L;G4wccPbOzs;47z zjV;Ac9jLxf-^M=G(>fQ4Dyo7a<(YfDz%wFRdYg>8k;Th&pdR-wpR$uq4Oh?$XCsom zUe3cPoFJk${!V{~#e=u4zpZ0#F40XVM$*MOM$#Cb7rg@Gc6#(c(4RfY>6tUH)!O7n zhmwf2I!c@Czv-e^#G~+!RxnNQzSvX$OQYyZy4G$ovvN4Ovy6ca=t0z-N9_q^&**zN z*@9uZQMrlBeRpjtlFFRqRs>N|d5Lo&m0QqOyx5D{-l6j6-``0VgZ>d01JqE=6W^PVl<)Hof$N zGq%%}td@@!m5RP@^?$pfRyn=jTQBy*s7u>*8J^V9QtMK9)}y)b(_~!zYFO0Rl9NjM=F%<;HxtRWIAW$eRnbX_#bZNU&G_G{F@`s9&>g=88YtsEr zcZL=S^vKM~&@=U{pzi7K&kkn0von;boz~19aXf|uLX8<=))Ky?i`y-odTHc5b@rdB z;Zr)p<98~oGgAxdexH`%o2)z2wZLryZja9-nLTKPr#TcO7;dMQ!6UkIW!P6bd}^%ZdWeE3Bxfg1`w3i>7TY3&BO2@KY@|{MhnXd+P=`Ne~!S6-7mn z1tl!*@0&&;>eq_3qE?V8Rgp9Sb6= zoGNpY#B-v;s{$_0GmI~~?6PcPIyHOd7jxYoTJ9uzS&!~=xjc-fdT}Rlzp-0;_}1d8 zX0xe&Yddrpvd&tc5m-f*IGN=XhG+RObz|+fp~g4NjkRdvPOA3id%LSolbz&3=NLS0 zr!0z{#&7blH8K;+D|?QWV*cMbD!1o&05y-T+GU#|ka;D|& zEq0ldrDgiuU3`HYE$B|qH}5)YW4ddB6W^{CTeiD4z9I+&QYxONl0cqU*^*NCQJudC z>My-UMk7)rBgcsO2a)<{IeqmBJJtuc-Z^1JGKpWDbef#k7HX?t&(gL-?i*glGiJCY zO&_Eh#oqT`gRB(ml|&Hp77{k^b5xv!VmCz&)x#`#Z~^tf)DQ3Z^Y)^QZjlkaUe+bL z6-`H@`^(jl>+qHqQhTed$=JNN2%}r#7?Bk?k<0sbmx?b_$AXm$?D|Kjo_DD4Y^_=+ zd_wtye$A~r{Vq4_QDsJwdD$~2ecs4w75_XX4Xol-hk@~c5jpWTIMxC`LN#i$;Nj)3 z5Q;Re6T!mfScVrkUE(E9yY!2b*RLMXH|8s&$Fas&E?+U8xX19#cwsV=V^(h)7k)Hq zvPFHu=0w> zgM;#uq+trNNj9n~X>Spgyspxl$3G*KD74>ScpFkywyjjINGX&f&2IM<8=|SjRBG~l z-aB6^GB=x>af>e5<92!6k#n=*F+8lp3HmtoN@A<&>*tf;kCez?%GGbs{ga$tT~#MM|b)H`H5!o{%&*Rg-3@kd;ioht*viP<{$TS>5`fku-lW=nkSCc ztI&?*b7QP}$%(vk;Kcr#Kah9QHru1){@_rDxejk@DK#?fj^t@EMY1;UQWc)zRMq9t zBeHg7n>xJRWz-1IWbwz^5l-y}jCR}P>K;iJygKhubq}7}wacVa(A07&Q02$}x{fdU z1H_uB$-3mx6pry=?9@Zmt)BfnRcrNo%^J)jGqiss^0_3Jq^Ta!%LzOQ&#F6N-9m2V z@|ak#+gsS!^H!M@9MsFyN6!Zii@%ZA>>lwoy|N^EG}Wtl6j{tZG^g+x?5d^Y*q5o= zGdKM*J(sa2uraGd*4ZO7GdHNYGW2dfJ;M_yDp}@H3&9vey-r>Kc9nfI29bB&ofzp| z-J;@U1%?;AUMV+l)$Unw_f$-Yo|`S#5~Wj=o>BTo>2tpPK3byXd@e*MdYwAj^3!8$ zuOPV+J4QAYGqoHiFUqo;l~s{v+&fntAKtv17~2#ctDU6}S7W;bTV1y+9ZD$=IutWo z>FboE-c*P1GcjX62y;T@rTKEHZjtvgl1mfDKmB~drgt_ZSRENH?TM?H)4boRSf5OJ zgPN&z{okHF#Z2HXFsj6>I;ZM_hB4=DY;CwVsXzYf=)HPt(P@KqUeN?b^-7H5{`%{; z#owPAkVxEBapgvcx2ei6jrwJYzc`tEIhkcVtXtCDyi(juzIH=f+Mz#PRub&z1 z<$sUj`6#fYCE7GU$T&4Nq~@;!BR_}S zH|-wtMBZU6NwLP+o0Sto{p$HWZJJMl5v))6sp4z#CcYv3j{R?>Pb(#x`H}E* zRYM?<`L*NTNU$>l)FSe zPruAq7O%YABu9x{=gB-X`0F>LuKu8T97!SLiR%t2gdYgM5Pq08%}}n4btc&u*O`Br z$CL0O;TOVhzY;a(r_hCuT84g*xh^z*S8$>6*uWe7v?Mf+$Bg$U+iP8n@mswh^!x99 zq5d=X#hB=X>l>jv881RtGCoLGGG1g{$heYmCwxH0i;Oqn12Vp^8Sk^4H;==Aoy~O0 zHt6)gywH0eV$JQ6aUuQrWMrrvlJ2?wAEEvXG5Ryy&}rKmAz`D9Vd`w$SI@ZK*CIr< zhS6Tnvt+tOOSD8wv_#9rRfztFdh!>>I(Z#q)*FdlPV662h+a(eaiV9F@|~d{zR@+t zqz4eYg6P-8ej<7~(Z7j4@%?DCzUG1VA@&KeTTq`vJDKRKR(d$mQ;7bJcG>f{7r0IG zx|Whsw{A*mYHDgqe#!fTN=d0#FEzDp-IRLu@~+j*yJGyHp7FbsN+~IMH>b2tO-aeW zR-H2bLs50>rJO3H-M_n;`|(i+Gj0w?T0R4pWg6hw5G2tZa?QN};RS5~pMYSI-ys>_ zdw_2Md;|``e*t(n;Guw10B1qQ{^5Xo0-g?f*)YI^0IzOq#+Bgx55e<)0PF`m8lFD{ zj@ygmZ@e+U@APbUJ70ek=wv1|w22FBA5u<| zW^OQTm3hM5)7Xr^?rQe0?oBge9k2knT_-dCxs@5e132S(Sihb&WA~FVK7ikS!ig{l~m%#s}U2{&?ApKLETH z_Ft}h!HnhQX8hYSGj5n+##6jz+{J0eJm6iUpnbp(k2K?30s8@u1l)6k8E?AEjMr5* z&yVpCN%-y`=J62ro5!R3F4#|a!;G)p3I6PN&G>pqBeWdouLExMtQjwQ#*AlWn(@0fA_ z-9V35%$S4uaT4Z7PZ*E1m1g|SQZt^G4Znw@q0hO@xYzY&+!g5mJJ5R((03u=D**2T z`hNxZFwBonZN}qQr&<0tn-2OavqnSie65{onvhk7Lx1K_U!Zvwm?@T-7l0-gYP3}6lL5Ws@~_W)eUCTaW$`1diuUju#*@B+ZO zDF1-pW&^zk09FB?fd1|W{663p0Y3rwCcr_!;{f*p+#N6v_zJ+S0q?X~Up4{$5b%RG z>ou+)4W^nU(p+G;<=JLDf35lc_#88SwlCPDfbZ>N#tDF{^?>`koAI1hU_S$H4R~Wq zSf2s+1^hQScyzzb_e)^^eGC4e+X2UeKzRWa z)O9cchR!nM)F8+wzj-`r!GfLD!8{%t{x*-tjkC=HY|iuM@i+E3>3d@K0pdN(xV zR7iRD1Q-B&Dw=U~z^}k~z}R&Y*n8I( zJs#$#3{pVZ2lWg?Gs{yCl*tfp`{tocBfVTnO0(b-3KY-r^ zyxPW|z8&xc8~+3HC%6+S3>WVL_{wT#1MN{zNH-ib|9%O`-ys3;4?~CU0Q?d7qhrAz z{na3l?|}D!KX4r22f#nr1n`dx=tF=v1AYSVV!*QjX9KPU^7dWu->o$GmF&k6_wKX9 zZfUcue&;Iidei_IFBgp055L2Ff${o!HrNlqFdYH^4CD1O>=#@=800C8R|OIL<2YV` zb-=&E_^kl^3DA2b;2D6IL3~C(Jf#NXm6ULL<25wKERgEM1ilye|5I2HUTp~S9~@Af zVST;{{Kv~-|9k;p5f+RDz<0oUJ_Od=c);(#f|CsR>nbolfH%W}^eEtWFkT4adyZ8w zk5?9~2TNmNKN%d36JR`Eg#Coyz`#j_1$i-y&vF=_TY>&p0+xZ^^+Ep`4f@YUpnnG7 zc|iXF;A24lIY95*04D){6~^N*jK_|O=JB{3#^W2nBH*@w$5Q6!jkfUz_qWl{df4b| zxW6I+hV3h=wT*t)s%UXTHMhZdq`RSw|2nmpn8-8CARQf_(;J@#lF8wz{fPxd&4S2K7e&+uG zFSm&|x)CsJ_fbOu_q5sX{2u7v6X@R^=E#^q&oQI?%f-;5&hSnSgP>_!qz* z*u)>a2KXhwF95y+@NB>{01pS;+lKy6pM>=l;zehHf2Z9Q;IBph1>gZ-Uq1`${}&+8 zo&&4{9u4>((6?s;-U0UXmuO!DW^5ezuYvu21vu0cA*|0*P9e-|7g8`=TCz*nm^OA1HjGulH);EX`V_`qM z*LC23y%zNAA>a>(4UQ)v9+U@M5pXq7DF1~G=SqMF!+!Thu#aTGbpThs4E)_7ANIip z$+w^{9tM886XtJ67{5f2=MzDGy8)+y{CyDQ3}-}t`GPm*f-w*UTYIyUls6f zu!kN7d=KDRHu3ZA04so-LOk*kH~`Ze^!dMGzF!IUj6IjxIf@^F#qoc{`ejEV>jScfO7%&0o)ic`p?_lY95fr5HBM_Jb7){!0ZkB`>$X> z{R8^??|^dv-w5mZ6u^^Vz1al%y#v;pim-lM3iDk6eo}y+j)6S53Gi>Q-pmIq0^X8n z)`$9o|AOp4ko^V-rwz+rl3;V50nfkLomaRM@GXErJ)___HwDM-!@e}@w!s)52+LDg zJZo1QjPZn^&QMp|_(zEQj*J&9H&i`XUw4CjdldMS2mZSoYEf~*-`2rf-V2o$6iwCTO z`lwC*y+=SkbpqTH@GrohTL3=_xB}o6a4`^&I>Ld~<;KSn$q@MN3$*1_id zd>zb30^?EA_zn6T!+4$@j=NHj-i%sgW53J+oNcpS4Y#pB@VwFh8~M<~Mm|9L$FTiY zJpO_4P52ofJYQGC23NP4@AXUAeodS9s@Pz=_^R_QKB}a6KXmvz_`w3Sw_pOT1$@2D zdBlHg^l8+;I@{=99Zpliq-7(nT7y<^y?Vupd0PF<34fY4FhX9suA>QOwz{f$r{Zpf8|Kx{&9|wGo4ZaKT z9e^hQ-U|XgP&u-FPyAai+t_c!|3&Pw>ng^M5$Ft>turJMTR1R{OSjhTtkg zyZ`<9^e0b+S4_*we5PNMFHVKyCnX%}IDN&A9VLx7jy=cMx033Eiy&b0JMj124*N$P zV8OZ)90uEA!FUYj|4T6cKLUel63EjnAkV6TzwsrAXPOHN(=Je$7{KdbKJSP5JOS_{ z;D5yZvQdC92Yd|j&E5j?ITj{l8_bUY19%QxQI9^!(E)M8F*oOX?Pir*#&jEJ>4C%5@ec|`idH@*G;ZT1= z|1n?1=Qi|4e2)z-Z~9*X^W`-VxOm>l0eH2|_X5ygLIZvR#(O5~KP-c1b2CgsgJoNuxYh$0-`Pb3@#r&G7HvX}iHuiS4 zGRBq9FrEksQyTF50E`C&{+j_<1Kh(V-!JAP{L$t-$y0zIx8Z-pHv@J9cG=k9-E8s! zr2zgI=ndu&^*GQ!6X^ew#d$|kZUy?L+l8#AQw2@}_r5>5uyrc>WpBKVrO&-S-YKK4*Z9JnL#B zztBFg+us*$eGdj`piO+pbvFK(CN}om0l**GV9c+L z{%G6}hIGXg9ES*t??C^}12+Dg*=X+ocG}37H3Q5$^lx=C55VCM%o@)h?aTu(5;p99 z{t@g4*pKq;1O5X1064f>eFpRUW-}fSFT4aCBuBtO@+Ab+c>w=l#O+E1!?Kygq)v>kk`#7-pk?cCnG4SK0WF1RH;c zU4HgvHu!YyWwejcA6>=9UMi`*m|fNU#LkDz`qcIZ%?!La-pp`gz(c}A08|1bSdaO} zj0b^(@SDxBz5xCU6o@qtfVc)0{0tEItpUdZehv;Kj05~11T>ETTm!Hh4q)8`4vzsa zV7Q-D$;RFrVv}E~mkn-27wxY_`{6AcegAfw^#RYnO$MA(LX7eQj*n4;Z1k_`HtSU@ z8-G+;`zI34F3>eqzda=UK2pKl!ok)3dkIYuX4eLSPUo(GvPpk1b901XDVO`a9f*v{P$r#2f(8MHw9c5a4o{|!xTpx&vlNJxK(1YnYrwE?%&@7FVQEucI7 z9+~GoE{=D5TrOuIBYVQn*Ui~cXZgY^#7f9YD!RsXHkR5*-SSIZrQYACf&~_EI|D(# zKHez^D#xm-z$hZi%Dm#Ja@pW6!@oP_S()W9*Vx2H$~WUPb?yl=Gy4}b;PN`16FJeT z`TfobK2I>+$ugS6NU|b$UGkJq=Lf(4Igm_NK-`<6EOZAeonj;PMeX6a*Y_t=cuPS? zJZ>k;a!yyaXHw%?edvBm7 zcckgSJ4PRS?Nk;8K8~dj{$}CqL`FL|%a@(y^y@)wj8heLo!2>D zk|cRd`n(lsg&6B@BNtbvCRUAoMs3>Fb$st5mb}Zes-mc>#LEmXDw2x4OFkEn922<- z2lW*-I{DWv!+Tm!4~ADcNf0H0XIYh%v5CPaLd2Vw7`chBs75zm_u0{{#1pZ$QW(K9 zIaP6HWKYojU9_N{re$P2wQlKcv*nm7Bt0(y8Fd3bwEJEw$sZmkzW$Oxys^*A8nV+cnmyX9fK}J>V31S#*0@$t|#Q z>$W4VT(h)p49S#mk}e&sPta#(Oi$7mCoj__q(gbp>0+aQap`VHmlGE$-q2qm2*;nyKYDIaT2pnRCe=k>9!M`0(c4 z#8^Ce9e-A*>b-$CNJr$iwz__{iv%?(wN6v1vU^>8eqk;seDB}X>~&M#k6S=up9@6+dorD@B<9CH zuTS;dky0p{oP1rxst%vzN2$NYk9>RJGlUc77s&}cr|`O>c_cy7dW|?V;@`fTt6X6r zW=krT&-SuV+}|Y@^Ev;Nv3mYbF*zrOI!P@`ulm&D4dmMxg|@0sMJV$M@6>P5vx0rK zOue7?M`_m3)jvO(L^i8$DkTw5QeB=HRi$cT+wf(NG#zT66**`BTvvuKkgj_MPs-5) zPIq=zAn4C^2ebXS941*d*S78ZYviknu|#=giBmm-?B=|j)e^PyED0-Ymtd>wcIEq0 zz6aiCwM026n(nskMDONT*Rj-~B!&|hhGAr03D=*v#L(j^TB&IC*k|aE#y@O%2ErqG zSYB|k5-ag7HeWj}FVKPrLBWD)to6ZIx|*Z4(%~)g79>e&7}tuD>g=5t*RT7XxmkXF zj4u$>{kkVDLkk4@c|VECP3Uv!gxCqjbI8YT72`zAR%1qzZP&|!s^z;aG{jkyRTqB!K17kWrX=HC99|0N-htbIUk$Q#A z5L^y5d2dp0trz*eZ;k(Ba&OWesR{i_jqQ9J6Zv_h#*Y=hWg+{?^|y+-mQ1Z?uI-A| zbMgMZj>y>RdYdXytFS0K6qbMSCKWx7Zu@GJyMA?&aH;+#E6h0jCutsDS2><@yL6{V z4+Q<$lboKq6V@%{RxXbryjAyD@j7X3FC@_XfW<$GD@d-r=OeB6oRBA()6zA6An!oZ zp{3WhiQTm*zB&lYq+;r^6)bQCo8Oh*Xs=Aa#Sh$i`*&@^4PR>^?at233HmbiYqgAA zeQF|11^#y0rety6%-aT6Ul1SwZ1hMPyZl zkwpf(uNkJ&k{GKd>eIJ={Av14K5`;9J<_D`h>Ffjtmst*cm9dkwY_(>+TUnuC9;;R z8Bx5Nq9SAKdD4vTKd0aPY>ni&TpVeh2+kbMJx&{=4@&oCcG2A#nqLe0va<&36N3*u z@y27{W_C}+j~xEC^jz_5HK(OdfB$O1mn1D`)o_J4Q{Y8sKo55Ecn0c$+>BrsU%;*T zJ-X+Wox^WV((kQ+-{5=~>f)An&(r0gfSfJqH4pn^c2^@}er`mL_Q)=`%uB4Bl?ufC z*A2f+dZF(}u_VFNLWjAY;4llIX@HwsbZ#L?=2pP&w1q>$LA9bk7#CE2nQVPBHt#qr zC$N&LsEQ(oNr1IRZYnxHGQC>S+Go$LNdKycqqnRma)QdJ62tOLn1qa35IRdc8NT9M zFd!VAvYxH9z5(IyE#ds_&2lP|tO*{E$IE*>`PV!?zwQoBXD)kl#vWZF=bkXKbe{)jY$(g*(mPTifI!{n7ufmPZ~V z#{`B^k>-Y~@-Epc^CF{3_ibcOwoP3hmtfNeAG`~6itlal|`B1IZ5(xH&42EkL%6K zBxmXh|22#Io;7Z~!KgPH|7a9rW(RV;DzHggxK2_A z-3jfGT&eZocT?bZ_2B;3%4jNEYnm!Ob@BLa_ieUjHd#?vl~Y+!R0M$ubC7*!EVcJp z>ho98>05@(j2S}MjeN<`%x(fBa*V($iYUpG9l4c%&V4HBsi?t!UY0ZEta-quz4O&+ zWVIg?J$oE2Ma=`MkUPV);Rf1JU-*4H3$>gSt4~YOR6Ba%rsb=D+8p7mX@S`(OQPb| zy__Pj;_XkY`0-$&v!*1AY!{n4TutggA0B@2``(|Bw203nMevb`yz_=^pT{>@_vHB! zU7CBGntdoWXF*0hTTVfVXZqmy*%k_qSzLekJfBoiVH+)Hvn)7ApC4DkdGnZ!JczS+ zZtPi>tEWqsIGgKdYMjqiK6P9m^E8?$rfdt*@H)^vzOLC&_?W1ck@mnikq_nk?2#4B zp1=*C9cj;NMtd`Uwu}_N8%y{?G}V!Qf7s&RJ5D4!COHvaNm86%pFa@nGAT>T^tt=x z=%HP`$yKJz3v~BaB)c?!8BWuoapoG!qCur2KTUO_TYs}IZvBU~tz1K_z$gmG3JS-{ z68eQ&B}KMn3l#Y7^vEwhtljeniFIjQHH;d@D9eJgcShGtUobDSQ_u7jYy=MOzbI|^ z7cW*I(!^0rVPmJ<#g?-$Bfqa+Od)cizL!jx384XFKtlS`Fda$`#rUcVl!UEsqg*q>x%mT|jzR&^^H35&(E53e*e z*Ax{Q+XcB^cItmU=pI)OK6uM4BAT8_h~VGi1*bdRm*MF;(HG2*JIffHo|`qUTeg39 zgYN#{#(Y)b;2G8|=~DimE@eB#t`(@7^)cKUVA6X=}l%Lw|iuF1%DX&HU=OqcEt+&=li zFDE=WDW-lgn~p!t|DSs#96;~ABIP&lMsj*0!d_tEPyZ<%Ro2`Jqbj2M)~Z?kyzOp? zs|psxx$|-Z>G9K7Xx~3q^hXmk&h2%3R7uo55`HwHrfnO8>9<$Zs@a$&r;(V5JjgnI zfv$dkw!d564rKmcc?mCC-u>sztM6V?0h3T9lya&koTjKqfa?k8h)5|Q_Ym5<|JLQV zq!pF=ij!DY)I1U|xjmTr>hPh2h;?{N*V36k|1`V%Sj!AdtSU0H$g!Nrh%77MHtm=? zA*$7@WA!dW>CCo`ANB4dCRCw=Yy~Ei$|Sso!V4`=S=V7wB>Z4|{?|pjW&8ViYj=?6zbHHxe~E?jp+gDhTTjA~ ziADea!c!?U`tEsgjTDvLzyxe5r1@+PL!oi-g^v~X7u={Nko-{z=Q6KV9C1_ z?j!q1Ei9go$sY-K>@w;`@Eof`hP(!}-@?W^y3FReLrOS)Qw##vE@QrEZZR%oKaMBk z1aUhREa>O35OkXdmQFIyGwMpvugX-kM9YPvqRyu09Bi5_bh1o~=yHp)=F&u7U|pCN zafgvv=0Q-&thJnb=;`-V+Wz!d>r?EEn3uNY_e?PUb-sX%Ng@+ z5ekeda55(|3X_+>Gc4Efej_0IC(9-tqKEE({rbT_Gzr)0$-3;<-OdR$OVP#FYd34;;)t}v(ekw~%1a(_rC8sYiP#>S`^Xq*yUsg9? zhMuKm>M!)Hc;Mz1?M?~t7JH56cb}h3>!9Y-zg7Oe(hXaRPE6vJJOanb9=9laUd>(m z%J)A9l8NU2tFf@W9~XH+DsErq)3>cp>;L++BFz$pmA#^zQ zw^Ksny@Y;i)J8dLBWJxN8X2+#-q|-dBSUj#=mT|cH=my2F{MiXaQ+>8_Z3L?J^|N9W}ww7^fcWzZE5z}8MVSS zy^!we_Ofn8@p4|bM~S<=&!vU6Jo|5?FQs>!n9;fWUqs6r7HKO^Q1dC5o2du#6T76w)vIx@h?*<&itcpI0F~bIz?6Ts)EMoA{X`%p0<|H{vSPNd&~8EbBP|g zJ2{-&Se0=Gf|hfdTz8t%ek%Xuc8w^OPTRaACfq5ba>v8OpX!r z6O9ZC`g>#r{l2V#&wcs*AO2MB<~S<$JYNFKOB1UVWinG4f04g`dF^S}yvTgL1yCJ9 z(=MC@2oAv=65QS0HMqMw9D+Lp2=49@Jh;2t!QJiP4hQ!Gm-oBh_t$^xR^6_xovNv= zot>H9ot^HdpFWPbO2-)0bsEaveE$fiOkl!GYat!>43m+R_n9K)=BSwboe0A4-zkuk zCruxr0^)z!kj<2XO>9VPtCH7;J)z$fAs6sRe7msajkUMpHZHMKxW zjMk}fo1qC>2^~b->7{eY)7~vt6O}Peh%f!o$^NF)Q^BmA|B5-$CZc~1JyoNRa+t+C zF@%Y{iVnUSa8&cC2NjdB^bUOS|VgeY z7u2JT_IEy2cZ7t276DqIHx}g1PdAfk`5GXqAE_zd_(R)M~yMX1VfYU3mbLQC-U04 zlg_QT9nO$IQszxg!JHSHm*hs34`PEry!WbVyQNF-TRuau?UUX(#rCSy_Tc7W`tlUm z#Tzpu5li70jgCP*!2vQrr~(MUe=7JqUsMzsv?@F$w^?v=|k`OCb!%XhIM+(~vnE;vGZ4We|5 z84y4c!hRV7nBQaF=HKz?9NqDK&}zU?71cJjY*B3Z_4_Y~XCq(8^|u0fM=W6du7fe0PWBWc-+Zlp z{0fwW&`Hk+)O^=N`NBV#PZZCXA!xo2Po85}uDUP&ZL#@M+z*y0-N7a9jL?sWp!FTg z9@?EN(h;(8G`%nsw8;TDgX-=^z#d-u3(}7gvcR4C@%N2P1kr0ssgd`V`DQnQn~m5d zTstn|^u?2na?s(ms>^ZwouYsw6<3!_GsnDF{X4$kUn&w!8~)DtJ(9E|I4%_N9CxpJ zh(3q=vr;!xX($qO$=aar+>aoC*UG7x{`~ABPO#^f7(Vs77=rbq-X z22Im+P;R!DTr-M936t6eYk^GL8l4kDPMl0rBcvO&Jp^W>-#Vfjb+i~Ok z^j1i$-5b@_>Zz^v@i)xYXtb$)<(+vqk^W}W{YWxeI`#($ATZ=qt?ba(R(^0RZ6Gr2 zav8cR8k12=#Ue?*FAUhFZ#Fb=an9QJ`+&P=#jaj4K38FdPx)WRm|sw>oMMwN_On*( z@XWew5f#LCy2<*NSv;DwU`rsbeKEH{-6(jtb!Xz+wFUHv69o<1CbS?cAR=)k7VA@ z0F;a0wRvBpY78V|oWHVH=djSQ--&Nh$Oh8ZMqIeGYn@M25u`>xw;;6nl>@Q;-*66k zp>A*Q^iix&PebH-kYYh2wNd{^4DxS-QwZk*+t=Ldu%By6O(CO6CIBfnkKri8)(M5x zcaa;QNzALO5ZXlWwY$fM5xmE=Erz3Ep#Wue%%BL+AxXxEF1t7Q(~*XkEG$r0HS4p$ zvVYC=Kjq-hECVcay^s9cKu{=t+kqyX>GQXZ^M76MWF=GTinEopG7l&?0WO02zzxYEK`(4I=S23SE?5-&3b0s+gb0fuEIGq+*EpK4~lMWYY?v}T{Ox1GI)kGfzzUW2w#RkfOqUy z+c+B`IQrdXpfVVW;|xV3QIk&;RI5WcI|w4r$`5wC(kuJk0DGXEMG zwCrm3+nvwXndgHn^bpeZ4u{96}le#97~c1ef)Yjq}@8K$rf|2meHkh@SbGVz41ENbsW2L@h7%$;-h z+g#*IDRuM7QQSqCZCWJ8(&gQq3Z_fnN3Be96ft8j{L1-9E99gEQGdXNFDnr z>G&eOBY3_fyOO$^`UA}A&&l} z3ms!_YLcMkXjh8j&07O&(-?26(woi(=!v~rYOGa%j~f1CS^2+@(F%Tx?dczWBs(R2 zu>&+mH=)R%f6C}zuF=L&axqn$`=*+WE~Dps;d*cXn!nU@r2IcY-TFk2bz zdtcCS$bXj}q|IJVXZAZsC~#7Gc;Ya5uQRIOGalXkk{q7`(j{8PRTQA=&TCi;UwVn; z+VEwpyR4<&NU~FtM??wk?;l>8!xXLR zJ$3HQN`z>ke~>48`OTlCBS*|VYY4i&9IZMm{+dab98@Q^XOFwro31FYN?dxc*nU(o zu55tdRC0;k6G5_l{yNI=|Da>J(R@A~aj#I0T1bX#=l$Dds2ZzAuBIR<{YkR}l}t<4 zhMR1z-!a_1=q$T7fe2Ro&Wa%vFp_(q*H)ah6E6Fr*D;krkij=^*==>>D8c#EPOhf6=$AC*XZuRx$8|GJGn(m;g9@2y(nLrJ`IU&$i( z*zqN?Y-fI%D-ssMzGpvp@+YRIV`xcDkXQcU`rXDR z9Nz>lRhNq|rTfuvJpR@qGk z*L8Cv@r9uak)Ss>up=V(CxkiJ3~F6c$|`l&Q{9YQKDo1`?z4si>((KYhCzaGF;Hn$ z?_-zg)@l?#{}@e`)zoOme8Qvt$+Qk6%Zb~lKdzpsCTf>j&autz=)+PQv_mBCM3Sv< z8c`HL=}1`ei74{0{S$PnYOA`56)6y6k{ex56JU}zKP6)5)OiGf4;a^OQ?MsKq(wgKgagho%-9}|AUbeBBa!R4? z+u4FK1aV1I%PAlWp&A#<9%sJotfmGn|5>jgOVP_bNoY#p1xxOUe;rSrvDkNu`T#tM z{XUpdBUFn6*ZrR`Nx6ok1}%U3j>O$`zM;;i0|5!Yj^UyjdOEr?xkZWcSe!wvHp({c zrh~K6w~_d^ZFx*5#dh+cb7Qq|oKAqx{Zh&{$r3kZSG}vciCz3!0qv=}8WWR;>rC`o zLHX7U)0&5lN$EvQMq+Gn3?!X>ML;FdKm=`_SY`^bqYV;{B}A-eQ2u3Q`3dMzssFXg zHlX(SBBqN&adP=eF&j>7e{y(obWPJlBF18;jAiox`ci--n! zb$)F^UemJAc|)I}rUO;zktWcp$Oei}N?izeG%~sSZvmd`6)m1XD;|eKBSk$VnFO5a zc|+;GhFj+=+9f5mGegTa-l&geL}isWIMKo9bu6(G)cilK! zR%^`8D%efIJlUnAq0=PwfHcpc*n>CCdI#tVZ!2le;h;VC#~jfwBui%hel63eLBCq! zopq<01PerajPU7rk7$P&Lo4j+B~)DKx%znA08Hrp%v-iMEJN>?8GALB&PT)fd}r{n@j!|G_M%f`2si8;T{@N(Ow74+>cyaR5bwYy>^`3VbZ z5f0VOG|aU`5)8adK)uTIVMlh1sWgV06bJMVH$FG5nB&Q4F;|Vb z5ic5$#L%^6%*8`S*0jy>V1oNWDy46e*QO%{M7>P9kjAZe(n=QPEeV{laNr`K3fYexUd%uTHI} z`%(`9D!CJF099ksl#D<>BsRr)saiVIC4?nr8RlWmm86WI3llR&Mx8?B!3rR08jR0? z!j0IT;>WP<#WytL@2LBh%9o+dmvS8h{;xf8TjGMY`iz9UDo3mFoP0H5+Lahm$WQlm zJkQ=Y zXOVE8WE{_KR4s+B*GAr^72n7UF-|7Vu4@5I*y5qw4ev!qll(Xoatj@k*yV29BP_JE z(H6;V?-uf|xIp2BJU{9Qd>eo7rkBppOxEVAhG%LrbbTpd@I%H(E73Km`~_GBpwNyv zYGyY2v-{^A$kYVKUl=|x<=OkCTy`sySY*TNQ?)rSjDuuW#4{9E9UV~_ z>xXk>Jn)qf);YNh55KWuXw19EUS28|F)TX@7V-}Yx_r8hq6O7W$kTCP^i>=Sn4U(J zI!T=7n45B-y8G4NQ~u4@OmZz)k+2I`N*QELe z?S8fhx%tt%Dpw_qFm*{v1y5g2Z9JL4I|SGfM6?`1MzMBvH$Dqf^_x$Vz8>7g!ctJ> zuQ<>U!uHp&EXt@mpE=3$jdAM{O=d7TF%VhRG&mve*5m7q4=3$yzIQc9s!1XdJkksl#`% z0#WYI$A%Tza*0W1PaXBTMwMS$Uy91*+wnjO2l#)pp+{Y{F-#0W?lSOhL(KBfw&>6FDLw zDIr5oH4>G&?~w|?!nzX;T%c2MtypJ%%ZY8*(Iw65k*>?anI)r6h(a81)3Jzz)=fKk zWYn-<^~KdTLMLNluIo7ptQtiin-X~1?-#>AQ=~1AZrgcgMxRs`(Rv`n`l)e|O8)3i z2v^sp)}TdGe2~P)#p`A81^^4;H?dS{S@N6SBrLh4C+*+<96d>(`Z7D~`p|OWB^h&J z$?M>KQb1CoZVj`mw8~VcM~%34oYKd_m`v6Ml8<>~A$o7)nBhIg8?(l*X2}@qH(IUV z@%oIYF_9jF;^6nTzFIvZ=%C>7Q=F3NTEWmrX}a1rU9673jmyFFgqH>A59Dard`P6) z@yel8w+K8nYq}DAiJpveAxNgbsK&&C!*QW8T3RETP(*rFA^=#gIUy7b*Y(wO(6dGL z6`agOA}=h^Lw&w*DsfQDpXJ9PK;geumlD_XMaEK=z}Ozd{ewG3 zDo@J~?zL~PLf%V6hvHX*iFQs9Zm-16ayuqC>Myf=U%sGo>26)(w@%ZC<^LwaQWpQl z7YI}H@CAW3Vixo4t!7TV;`ghdrP~E#;!ihKCif4JduHawSrQi;xYLvEMlz>3I?ng0 zzLBLq!sjV?RjX21)#qtYV$I)<_iU_B1n)P8!x$pc=N`_-?(^`AOFAN+`jUIEYMIAI zX=OE-1WiosE2Tuq<2}}K3<+tpOC@R~^aifR`-_=K- zYKIg(X5s8&f%)#Sz(KmygHL_uUOdbVKXUqiWPS?%=Txw9ip#$hWC_eg#++*Hb6})?RO~whXHCYS7t3&TUTfi_{FE)rGWoXeli14&+ZbC*=1aHOot7Q z@0yCPCG^C;8I$|<9=+5uHMk33@O2jJAv7aAqV)+9BY$Yf_qWD-xnGV{QKdpEky=B2 zyI&6KEa2}1Hm^baz7DWe{5?hDe5O5$+n|spVb?G1NY&V1{8% z(9Z4qD?}O}s7#!I#*@C?mIaaV(%DtU`;J^wu}rOyw7KO0DUXib_ywJ0r8JDrf=(09 z%TSXc9_`oqa9Ia|>7kd+!m=lDaA`8^i1FG-}2$& zxmb6FJP7ey)`bQc^LU^F9Sdd`AAa$lFTY+4cL*Jj;dGm+EKqA>$Y^V94KfYFrRq*z z-~m;|y54`yNf|fOCLop9uoq3^%k}G$cWH;pK6{3~zMN%J`ERm&c&ND%_S1HRF7kI2 z(p^IEk0}S;mkk_$J~iBcr~s2lj9ofM#lv}YiXq>9W;%A_4Ee7PUxiM;x}W~0EIytT z5VUrwnB`QixnsHx25J&DJ76~pczjuA#(~BttwUD*fu*C#&knqqC38^B@_veC`rf?W zZMXLY0e%sxijyWwMM1m1uX{hut#;bwifv!PK+KhJwEw7K^j{J^i#3U2S5w`}0w7%> zlgNbYrHryVcw@2fvSZkrUE~KA#a>e}iV|EwkTp*ew+@Jf;lVKGK&AMzJM1Y()hx9GSUFW~3M<8w{-g~%XE@uJ zvlmjF&im0>OvSpu?^oz_bt1Oa?SPxmAT)IU%!1x|hC`u&0(FEGONsac+>kx-l*BEh zwr;CmWK?B1-KQ-od}A*wZQl@cl4BdaUBc&SPzV1Z0O?mWJqZj#_Tyi2OnsX@mpXK* zqMFg;_o)N^(j-s}ZjB*#8oItR@jqJ-iDQHLnjE@LPTQJ;yN$!m#AC~>zf9EEY%U?7 zznsrbc=fsgbH3iyyZPc7z?it^Ea1u-!9)`^U$yz;WPiKvdCYZS7ozSr$KJnl1HRGF zb+@`uADvV5@_2I;Etj`-4%Yi*=vsKlEK)02P`8aS9J`J#o+w4$TxpI7xx6ldtMvyl zJ}|xXg?KZSHeP=5oE=)(>Uq(3BZBAMn&$2V3G@vA@c;Eie73Q*lh`ETyI(iF^qAo} z%AuN08fFWWp3CY{WfHZO+#+SVM8kr}3V z8*z?|*RqW7m;;KiA~Z4MXCIx{CfC=VZd(XzsJy%rVyRl9Ua{A{;K}+Qxf`JdtSC<| zevNgAG?K<4%Q+2kju-l?ZK?58F+=i&$|r9smNO@oYsUe%vTi*LWc5e??`CZc@zL$y z^NJ@)4``J*7&5{%V(#CYL8Gp8`D;)YMnYp5+3=g#%Nu{*-f_1xCE;Fm1EXmZqiT#p zAD5?CY9QkvhX?M(n2zJvY{dc?$`O_dad@PDxqNb%e@%wC-bHVr<2x9k;9rm{R1&B! z&J52-^kZ@e6L4i!!#VjD&HM|$*2!Xs!*~hhW6xR_9zIN2r~}GVl{;ow1l5%<63bu@ z)6m7;^IK*hqeKR|wMLL=~5jOU+fmQ z4DW4Zg+)c$u{amLtB&5h*5&q7fZ?^l{PPigSovf?|Bv_-vk(kw`S~Y>!w&bWAvvb0 zpic7#39Y!TL*K+b%qu2ulUb42i+3L46BfURjH2JAOn5S=Q|MqT zc-P=xgWA7|KlgcbEyb~hmJj!v!K^(^*7|8xAE)x;<`f+#6iySP4$4(P*RvyA+dlU? zih<&BUfqy9lwRkt>r&s`Gr8o;JE#+hL*^f!*q-(J&ok2&KCuMI+^UP*g-X}GtE@K)$L$-J;Ik6Gl9NOs{J&h^8uE>M#7ljWjQziw+ zb0aA5p{C!9Na^&C)pzqF4*@eScY_LWld<^;B_eNa32Xsv^2dIBQGNQ&`(hD}iDf=kyPie(Yk%lHRT!0^BJcXYS`9WE6`(Kv3SJ=AX9O ztXVFFzTSDRCE6((ixL^wt_J%&$?6jtixZO&p!b+-hsC`H2SfY0Auzc*hiWetcDLku zMr+ruLUUdw*pxlO#0u4iS>r%7v^Uyibq129Pa^1ui?okSX@f@N+|cKoAf-yuY55#E z4$HGoM^KeSf(3oGRdvf#&S;BaFgJaJ=##M|f7QS^O89Ri$!|A&gso^zaoN~>x03p0 zNY}9mo1rE&rXZ*PC`7vXuT0kly}LElw*J64uB+#Ar8x0ufmMerYyN2Py6kN-8S|gd zIec-xJrcLs#g#mDLKR^Ca7?_IeHeK)=p=B5nEV&9Oa_qo%l26v6|5QJYz;dzqJUjHFH#>| z{?th_$|_O=5I@a0I_%&zI?Xj96H6d&13^)_NR#EPsAas=*%TgU{et1|Xa@N1xQfz@ zlNob^9SURH#FCt|Uk^t*Tm-p(T3j3}xK6QZQ56H@bSf!>E)jn{5TOwRh{QjsZiZ2h z`NQ^AOn<%dPAXX^{FCG+EbG{D#+bKhY1)ZJcm95tyZVR0^m=@)J#^)_fg3Q5T&1C^ zk6`*e$^*lNXy@o?+p^n*`Rt8YJ((Xt;<%9Zv=k!1Scu#x$l|@t;A_E;Ik^(`)3z-? zF6nGL$jyr5PoECu3A`+%RI%c1u>JM1@A4%LPvergZ4{msOXW!`5eK*L(-de#IKJbv zBWUC__jXnoWm8`{M%Gf+OdnIbXxzV3un@AH4)HdKOGorI!p_)6i0l?Sb&d`*ca4}b zY#={Nu}hhy5jmum#O7&PIwDf)EQVpRn_?Qn=O;So(ugR6PB4)UK*KFBPhtp*8f4?* z_fyn@oeZRxMpvwhki4dzs5Xuoa9-9yE9z@Xaov;xTVQ(x7}KOAa0ih6%R`iX2%-Fmv|i&wpcp}M6M_R1 zHN*Y$dOl@2`_yy?BQ0#zhPUaGPiXG^IM z#!K(`<;P~>jpyjIes~H-Wac{rNon~YB*D=9E562c4L-$lrGx|0OW9m+isoLim@04m zbVyhxd?SM5i$GZ9`cmh{B+d{Ac|Qw1-IdykfdZp+p2H{-`KO<8#Nul?7q(5~U!hEN z;WYmdGPFbrunl3wK7q+cOTV;QU57KVuG+gE8l)AXfCPGvm6dR9c|vrfP^*h?<*>wc zs}aJ5F}%j8-gte~gv+CcS}AMC^^deF8|EFr>~45E~;Tn!`yZ)_g%jLi27F(kDv6Dy^G>2$}_ zim{?+WG%5ChvakBU5;S}4VL@g)l=x`(QHvtDcHRjYh9b4*riED|^LiU{;#G7SUJ8x!`_p8I?{f%QjnF5a(@ za3+7;9dgqm-*3dhn*3j?uX(%e-qT1>-<-3F*mpZ8rM)M<@5CIytRC6o)mtF7m2SUk zKf!dplD_}#RC4Fo)4h-j3a85Pza8i>AFiTimG&4o0!f@|#E)JF+n zL$6ff?_`Uvs$?dzWvBIcG21En_i}(df^Zrprh==DE+H{*hT=Bh_-|Y0ghq__F$(gJ z=?7QB&mTGM?{D|I2A{G|$q4#bb+p|2Q;TW%`&r9GiJ8)_dJ7*If1Hrfx{l{lAl>o( zqUx)qX7DTJ!5w|_P zEROYEtfEFDw# z+Y`0V;#_JzguUBW7rIucnG57}@@RLn+QD8HUF#wxg6F+*47ELE?4~>mgL}jIH`7P6&1rE$OW|BLDHnm>z@qx=4@ywDq4auv=hea;2nu0sBSr~< zKrTL`OgbV|b0M1!R_KlWMQv3Y+SBNFOn^?V_7{#9o)}?@4Md1RP-#j?T93=`6FnT- zDf9vme+advaz^7tT)NR$AC#Te1>c&GU6i%@uLAeDfAB4uISG5}sQAk^gDF#}kSy9a z^45y113(-hU1CI8#6MsUMbVKmS^wTs>eg^cTe85g)H zv7!cMibsk}k7$4CMOL;*jc~BUc#-U$aRxzb8goSj z(a$<@I$B8-oZUDe`u~*6;~piuL>P+~(X~J5ib$|@4zuL)7cx7}HI^tYghM)17SF*p zOB&`6z)W`z7pr{p05`sEKX@xejNXvTk8~(-j4Y=>`V>*k11Gzm1BAnCZQJ|vEqo{( z)3|wytqe6A25W21yn5?ipdpA~F|K{*#(aYB?i4cBOR=70jbW|q06N0B^sR>&WJ3?v zgnJ_IX;xCFLkY!4yal%T71Eu#T^B2nPB9iu)$?*_5K3%*$4yq)^$OOE-ILsL}O zVEwE0R4sV=dC@a26XjSm zt1<4Bn@xF3hHiPZ-R5w+)**ks&;CW=+3C+cZ?X*wgtD)uotOw}=zIr&=$ze-8IycR zZw*Y(rTqQ=`aNA(8XdhN%#51$Ku_Ry++_uH3q?H=Cj%q`swpnC<||H_)+&go5*t|k zcOydY!z{d;>;7vIHlvWXuaziuTejyyE2lgaAa+3CBw?=}LtvO8mjB2{q^Fa1Us5#C z;7np$d!xtPHoSE}W2QaQ9l{}Ub9*yMcZG$zJJbgX?En&nE^g=~mW^0zR&*RTDY3_{%82ZnN7yax*J*8!i$U_V^Sr3Evm{GJn zL*cc){-*;c+7A4i%>OMmi#~`@P*4SJ1&oxRKUzQ*p`jFs4na)(^ilg?Qt%ZDO!_~q z{Xfdmi&DTLfNv*S7Z-QI!pAV!1I6%UX=5kb^HSa8R36|3J<^Axh9Wf$jOx5?(gM>+ zE+aSGtLY04>72h@s@KDAyH zl$Rwf*o~~OgiOK&mp-URPu?$Ynm)4hTE`xD;$29q2ncu2pT#G`rgg638+U;9qmpug zWAtW2OR{-hR41E2wQ?>yrjb88>cJixu$~%!rfhjvb$vF8(K3p>iRXK#>NVxtHPxR# zrtCk$ff|EJC#9YYnYj*uW1EoY z#V#_CDV|o1SO+zqAGB3Kp1lhUys zbeMTWF($tW^r|W-w%4uGyt$oX_tH@JUEHuae^|i=rY_v>z)%d%_I~J-G!`@V)72{6 z(|wOFi~WaE+HfwK2x^iYIi$p_yu2`^n1Uv1JGInL}b1kpmnos+? zLDtuP5hE{6l;CbdlMkFk=4;jRo^RL43shu(_xr#egah8s(CGS&H1R=ReiLkn`34b9 ze5l8G^=lY;35cw?BkSFB?-=o(+=CP>h4{bcjl8H+f*m>+bp$5-Qb~4S!V%?A`1X%7;!v%Cpq z*Ywzq%7pOl_t6PXO0aGe{JY;Hwe5bxb2}sU6Vch!jxvAs;D5h|((pzP+W%XF{*Uv> z*UJ)(F*qhxo>)ZCjm_O#U|F62=0$@B7}3_FbNi(CYTxmiBA$5TpS^>K5mfmG@0UJx9e+ zqJbmN)Cw#@O9Dkf);^WN^O=o{R`$bpRcV%suA(lq!Loy#;-__IycK;??|spU5>L6* zkF;rGV2-tg>BqKqv;*P&&e6-D)>==aCjgYw67{m}5CnLLAHzm~2hvATcQNO)6y`op zTwWC@$YLA|D+po)+^dWg4<&|Aes4RH03^?|XRDee&+VlEPyMd9Yrr?yWUIwgw?0A2 zOQF%s{iUX4yO6c}>0|c5*_pgYI5M@U+eyyZnYl6xJ4KFb0gC~qn42OEfmzWx3Y`Xd zL?S5xDM*!lM&m6DbKgxq*1%Y7oqL(vR;8^HOzgc^;}?0xg>?h<66hFoijNACl)PqwuKG>x~^3baPIEw=gdrF%4*X4;B9DjL+Jeu4i3V43l|6)8n{ zVCcL${>7K>&OKBI-$jzLp~yhqfU7Mx9jUDPi>CIJoc^GSdNlkm3^fP!T?xK9w$;a^CkG`u7F>X9q3oa5B~lbBxOUEl^)`;7z>J zO4inQz)tdy2wNnj1S&@N*=WX{=PkKLFUgAo7k;XdOvX`~-17O$^xtcbGT zc*0Y(0`@%uFoWv2AI$w*P+8iSrSrYQPzGHV>VnEaos;i<)8(l;hEsvuddAFg&iOJN=|M#)OGITUH?y9b)O?_ks zAKy(i&uVlYaYrA?x92^YCH9@yJ@R4lEBOc%elX`G-{6b${(hB1V(S^=&MZ|b4^2#D zx6%u9N}EBPHt)JJ7LzjKW%vudCJO2fFFj7p7uuKUlZ~K1~eJ z+B~en%W|ohvQH<4)D7+_ydZCX91k;FD8r*6(5FCRL69mlzHQeW{8@bLJh)Q z@C+Kb7NRg^*-_K?0-T#vc{qSCg%>8t-9&gaB zfQVFW9Rci*2d{HfO%3!Kt{FF)hW4I6<1Z~=ut@Vx6XRz5W6=#?M+xQSs+c2(CK`RA zoGN8Myc%W)%8@X%D`v2?vJ*|4Sac5GQnuR@)Ox>|PS}90(DJ8}ENO0{^5*h~5HL>} z8K`q^hH9#LiR?Tj7`L>G+-(2jRA&?o!XloRxS#Hr0j!Daneo5t^`Z(h`eb8Aq_UW& zc!?a~smxNH6YW2p`G?vFe8CNN{;o$K3*<^i)3g)EL{EMqK;3jvhmswCud@4zxusxMj;0H-e!UCv z65Mg_Obk8uw}D?UyavBdL-)H@f&Jew1HN+`omqOrj%x$1Lk$IXO2l&o-X0GnTYHFW zbd*{%L6Bud_;u4LNE)Vo@tI^OI^0#pjSy6xU3AP zt0wZCemj2ps9tK%_hU3%&1`Y84S}L+ej1L=oq?7ZKMrQ!FAzFj zJCB-r9)!epeNfHR2oVQE#jY@jpmq&m=dtl4p^cJ{dwEX_PQ)0)mtBO0 zxiYGTkwyYcAy;g({-7Nu5$K7K9XDzNEt27ZSh&L~Nr95m^j=nd2A6vBwTEdg3^1V! zD-h5H?cwdpnWN8fz08PDK^;=xOIPNZE$HhNK}+_KG&{CwSanR*ft>p#RoX5 z!Ci_{h_<|C-gAGk5ad+@c!2rO(7Fs0HrMQ5!lwY~nsJQ;?DL-YaW`3_tpn%rm<9_{GC6r$#}rtnYw}1Wbwk|O7xWenuG1eA@27w8vkOugWUw9$-x~-!(f@*u6%RpQ zr>O5(RkGjw-Ab|f+So2Y)}}B@_(i>nj&yV8kG+mB@i{mjA|QDZ7Tbd%VEB!vlku(I zu+^xOukcP;16MoAEa1@NB8aHjk;~0WyQ=C{FWB|Rmy>JcE3L`m#U)PLh$gqx1qm#1 z5j3>`V}Y7M?+Hmh(G#NYI$dkF$HaK0m58F+7A}+hbYJ#=sLQ&S>-?M2<)5k)q;{F` zzWSvjiLGX(W`*@C2izL3rX{ordzv5!wka z{X^zph-`k~po(vZdArD@h=)TuC(;O*c&<#Ic6ueMr0e-a^<0!6OlsLeE%-jD6Fo)* zDm-Ei7CJ5h>n^*~WztcKlT!|GaF7{iBZYG2&GaMBW}rVehG?@8Hi5!s2$8NqODS2N?UB$5}Ust|KGY%`?u}oAXNp$8G2-5YK}0 z<=(GXAF|0*eX^DsHqtFewRyage)bu^WK(xMnO90^JZ_89k<*m1($%q}qT;}ko`b@R zC}JEe`9Gz=(pFLtz#ro5zpO{~xLjPx6qqJsC&Yf_mTr(54SM?#!f6k(0fCLPF_h}a zYYL^VncJW>i&4S0l=a6*v!R8Sx z^e*e;YN$hG1iMwCX^9?yLfKqb)wY1zqg-6f(RB zOK$n1)N_)X-CqBydMJ^(VQgz5cWxpBi#VAmY7jCopCIF~S7lQE>~ZPUHW%l|mYUJi67qBJkvKV!YzB?A+~wZp5KS)tO6 zwG~d|cfjAK7Qe0L?B9lx;PHLh2Q>Zm_+J3aKs3KF-RnE;^B(Rq`H#$C-Cb4Ol*GWg zdK$6`n>xjvqnD1jw|q}D5t5EH$xx)qvy?7&UA^|^R&|>?-PL>|xDQW=lR{q*z7K_V zC=TO&L_F0tQ&fzlmd-a8EO>P2$Ogf~vOx8^1=?LG`!sL9T#$d61`p9kTA-3`zS;=o89O;>%DZWwOVqdXuUWwLEE+}aA%k+;v-?`TIFo8QIM)FX1 zp+8Ku1I|@F3>?)|(AZ7NYd=HU5wp?M=!dv`7RHMGd_QkgG*Q4~d1 zB;hSJ^Pw;}iw@3OGd*+ex--n7Hvsi4XjSY}lbaH<9@=skmb_KpF zLYzMFYKnVKzUrIqmPt-kS(cL|nOnAh%%{_{>gU4O_?Q!}i}SYcdLC=~9dM_yAUI3^ zd5|L6>n)z+W@T067?BYqUU}s0iE}Ui@daD7?$>W1Vr};B&XS*7)O-FLu=C)Jtf11D z`3)2#g_Q+fRa9#C51l`-|2Wq+#*kSz8?^q-NOYRv&7#Oiku%z0_epCB0?U5wK!4cNa9DJ{ac}C^#(wI5jH6b-uh1pu1-5CqPcUc?^p3WH z?|T|P!|pfkZ|(QGf5*{H+x&5_K?m5iSWT-}ZOGaDw??_IH|qJR@%xpCD?I>K0Xjg> zfjb3wKi4-<{K0&GxYUS)^8}YczpHNLh5OC79YU82lpFMi45%NhG8y}zo#Fhihx+T* z4uSo%(;|C7|FI$H0S+3%{S4IqqdK9#e>^iVUy$F1jsExs;}VWfO?2g24U&^S=wDzQ zgLJ^S1u#n&^p8kU+oZonit9~e$*UIAG4n(L_#5D3fG+_)RL_=J^n%8z&*b-z+bLV4 zjL>ue;aY=^AK&T^=eD-?9#uXRjGeT$p~ju+8`t?0F7EH|pnJ~uhqF612*}U%uz%nB zYNJ4_p@yBn_y@`c<1!;KUR%FI1cZCP@`uK~v9=*b!{@K?^C7TjxR=rI`LsZK{%eG* zjrSKB4CGeBuON_nQw+v&FCF+D_&xj{`xAD%4PtBe8!ZN_-Yu73-Q-F6i$vw(jJk2W?_AQbN z=9T7`7Ij!d+%$Hm9r(vA%8e`p*z2(NyEVBLMb9#hTQ&|lbQwI*;dn!nc>NgTbz_Xz zuI6z}e@uT&e@uT&|1*>RW|zUGo5a&%jJpyXccyq8_kYZPF#R$8lO4C)`Z@h_-EsEo|5uok&tw zIm@4GxWWmWJfqzZ5wCmfQLbllBFrydZrPm{&5dK7BmVW>dCh&G`KO0$ex9UQk0w#9 zs)oF-Y=Av{Xa=+~bfU9W;YF`KF$Q##CFpL#^PHxtjHau+)U46-H~&3iM_yNiNRJeC zV|wybi(=Vz=qeU2{6$@b#&%Vv?sN zffpE6p(Qe;nEbGVGi(@4%p&K&&R_n}v!SuZ9cr_n;ZKw1D*S0q@aFrpQn$Cb*f;To z8%{p4eQOpNu}zKMalI=rp6>|^iy>MSo9y<0-&a&&rgPU-Z|uMDH1L4iIE)8eM)GKk z>|rQDR!2X!=Zmf9T$J71qM0R8qSOqI1f_%KsXph>Hk&?_1hh9hQIsf_k-&km3q7Fa_5CZm(RbUSJO%ZH<-_I zwC>?Wk=I0x|Lu#Wv&y{b0b}56MtEDq^QJ4EKM(z*-Zg)OcZ$nOtmI)NnWRMtc8c4; zRH?tpx#yc(CqL5}@We_t$W>m`C+H(MJo`W}gG3Mjag1#1LRGizHQ=MD-l=p3z+yhjQ$m|OKoU7^1(bbcnxT7G`Vuofnkrg=tZWYR+m zuqmTAQf*)CJioa3m+W~!ZC@HZH7H~_!JS{)KVR4LwLw}zL7`kUZeEEta@OX#%m+E; zKHq`k&s&V!a9_FIA3kgBM&8ypFpL*KWfnXCI5P9YdoK?`r80lKB3IcTuY0;&3RB6~ zAJqUiJ70LQ8M*1H@J2F9=P6QCXjzlE{%<_+aidCi2DXMmbYm}f7V)E-OxtMXIvtr+ z1Vs>JRb)7`>vTXLZDsO4QlP$T6{qHHW!aLx%IQ6J_1lji0taEUQcc7kQZ z)`8Xl-5Xdnz}_aASi>GSGQ7q}-|?V71#l1J0qFO&vI7lZ;IK|lBfh~1YePmq_$mxw z3)l!f2kCTxcvsM#itNz!f%lrsH(1&vLykF&RyM9Ruw7xugR7eOALNx{PacUtHYPzx z!2ZX(8~t^wL9l#dTz_X+LE8=XhmRV+XAnl~YmD`U-o^mzZ}6F$dIa(tv+>cyuJXfZv^iNFVSGz|ZPK^zI4$9?%=m1<*Yc zQSP1)ond|oa37>U=Rs<9R$BG$zh?d`fV=>5?s76vu7PI1#iU=?uBHs+NpP6e2#**Y zYA3Wt0pxu@Naen!Y53Iu6rz=4PAH$+2sw=rh#8m!am8U*=y4P;IAa_qusnzqus-}y zlI4Ii$=|^M+dL0NNx(?;dZ3pi>N~UdsRjpTF*OpX{u`~Hj)4&mfO!rGrx^m^E#p1d z$G6B4dVbW~c>d+h{_ymW>NS5FoFMr<0;cmyM*x2xY?1#rhxOUC@bVsfe@gR*U`62D ztU$g3pIrq`27!84$XKgySBPCDTCP?%d7fxJA=&-NOkO3cUu`oI1ZN<>HwwHRO6PT> z;twP8S>+0}OD$0S35`ulOg_~enFb#-8;5mF`pf6WM(xf}2PEyG;mcLR<12*Y0L z!s-%64aD}d{NwDr$w8bV1(%ybadjjq-hD zVu71-3Ie(yh_Gj?G$swhDM!*bRz< zVr_pQHTa6vdL#-!Z`xqAx62^VEMvf~hSuveXuXbt()%f^YUyS-8Q&nxgvKv$iZ7U? zPr**d)2y{4kYmT8dAtKrPLR!u7V8afdzu#^-Q2d&a^RM<@Z-p8d!YD#s=};YuZG$Y zbbA)Te;kPR?g+&nG6;4UL7-+9%V$RSM8601gyq6d7Rw(k44=`qwZ^)7YiM2F)3j>( z(-0^Q^y%D8YjOxh$9O%jZg!~*h)%I0+U=+CFkNG8_?Qu)T!Qy++90`Ph8G->@vlhb zCk(6dYk8FPFUC`Jj2ZxxUUjp@@!7~?K3NvW>)ADgu0wFw!l(koOHatKD0l5CKJI{H zuQt&0;ZU436oWU6TV%kwmPqxKc+o%A_>`IcKNaHRX^8n|XM{cm&o|>e|B6?;q}u+p zv6)tgpPBxET898Q z%Ow5oai(K+v4_=Eq{ldp5;cFR?si2t?pkq?lNi`uQWNbZC@eI>`=E;7VGnO-X@$EO zMil^@XTJh^J`Gxk0LS;iIe6Hg3-C7s_B{aqIAEUx@M8n~W2`ftcc@|XjMqN2>SiB3 zQ}v-_BVPL@;%$dcH9Z5PdZHvn?GX1s9siZI#LRQMC3&8ks)+YIA=Y_oO|u7}?8&Lx zekj0p=49DFM@E=$ywTF08uRJ~Rosu`eM}+Qa>QnmA3(hX>rVi&+LKeYA0%5&r>cIP zXt=BS_{T^t>?ZR$c)tJ`A6=pGln(LbHc|3t8)95L5&Sz+eu(YAc>H6;uIER}Pf}ff zu_Z}=NVa}E)nb0J!mM38*3j{ArrDhuBxoL6-Sy;BgaNz>5#CM!gT$f+1H)7<+f4W_ z?4O!VaJd{N^Ou?`2O~vQ+ilPE`7cKG0Lo6`JVWVmrrL8;6{W7~*?;gn7y~1Draxf&cW9vGa!HDnGg02Y+7>p{OBqc&?x-`bzd<&o)^Sv zJjYuPNY?!>seaBXQaW5*&G=5LV04@aWmT__*S$57@B@fyGF&ai%y?0cyG!b3IW zxNZ7be8+L3F}Rx0x2pRaieE)K?!djr8P&9(C&~`>cfpy-H_YVo%098l=~(50RXw7* z`=?BU=7W@R!0Ia8PtOPdJ_hd7>kY-@W!G5QC%N@%W>JoP4>^bsLHZsw^mwXi$VSZ5VVG$tIS?HiUmr+E zjKuOqGr5;&xo8zbO!PGjKS;!|c{0Xyg(J~@mngr6O`=S<7Mu@cIIP_=^mnj*u|;-Z z{E8UoL#etyG!3)i8VQts1>|reNDi;y6H^XgC&m06qblUu{XbyZCIX4_%LvPFCnPq< zHL)hYAI=CbzhV2wxGM&9Vo~ z2Lbj?32eDU$sw!q(TUJ&_ZYxGRuv{<{RrnyLg*#dZ*syn3`#=-+j(rt&IL`rjwt z1;*b3^Lry)}(t;2hQo`4<7>cgVK2Q`)?V6{IZa-R==(gKXIB&Kw~w34m85vMo0YG7=WL|=|Uyc z`s~TZf${dx`z}f9Piq@hEnf$6CEoUkNA8TwPB6)$-j!_r9PjZ8=v`GX^NYRMsHd17 znty@vZ!;K6L*xDTMzyv>>Ah|Uq=ymtt+J@lb%OZo=_Hd8p2q<4Of$k?fj(>x&vynj z#``9$P-{huARAV4O6Or(}eDHWT7wBk{rUgkXZfSUxunQg?<9biwN_a61fy+NouP zj4E2ca8~=nh4w)C_oXfL{Qd8o&~gZ%AN1lT1YiCEF<;L`)ECUJv!Qvm1D50Vz;Tfm zCEb91!FONP_PZew@&nMXP+_!wTn1H9{e@~WKkQ=syI zV}nUZTxqYGdC23f6EhD1^MrfS+I9iZ{NQRda~N`5oNv?nys0`FPNL1o1pkK-pb}LVN<& zUrt4s53tiTafTl6sPR-M&id0VBOkCoxGsXg?-?ET0Yo~J9HH$L-AwqY)%kF;<~L{R zxWmARr~aC4GCz9{$Z>;jsK!CWi<crkJwLr5Lj9y+oG18J=M(X= zXPk)B0X5WmKsG`iRM&g1I-9&l`JuHsoym@IUI_Fda2|OIq%VMVF{^q{mPvmB>%4VL z^pq^9pEOL6VeFNx!2Xpg`yI`yyTVu~fc*?yloh&v0KV;l628lbA2s6h4Y|+^(l5Ik z?f97S9OVxkv@eh&wQU&5!1or%W2#|0Zn`rPc)EuX4dP{o%7oUZfq#}gP#CS=-aSfv zB|Aa&jW-%tEWcHJ9HG7j!afnwxuMB>vw6l0llP@AYx-NNLad>DW1Ky7{gxtr56r@_ zU)l)>y9PW?!L)}$yhqKqoKeo>Qf+<`D_=+HtybyMi>R4&s_JsZx~@>&5pO$0yybgl zPGakCRleCx7R>LO>-V(jP1mIr3flcf)0c^x0bYdx?@g8Nlf>wyDPtlh4(r#9b675mF3 zY#&G{P}i*|q8{fP0Es{AZf&WEGwnS-Ebtonbd!0g=i zplW_@yV&CV)9O6bY+f4e{It5v58-(o0~Pd5W1}_fr#IR9iPiD`0M* zA8$Ee^_~Hgz5w*j-cjnAv95Qdx*loPAI#>-U^vzxk~^Pl($v~~GTQ!>XpFO{80W;O zS64T?x7j@Spv4BSgZ-Cbq!5~>Xt@SLb_6!b`QMS?jjG8q>-REwA20vb)kTk)pCq~u zHwwNX!UL64i1>_KGyBk45WRat&r$l<^5y<8Qa=Kzf@YAu6{{YD5gpDp!q=hkyB9IP zej+on`E|1AgBY>V7q*Lo{t&5MJW{@kk{d3Ia_C5e`iz-fAX+&J%g^d=4=BovtR(HL z$+$4CMY0_|kr)YMCfCnGJO{rY0*#|oMT1|Hn;*=7V82La`^S>4Pb`HLzSoU|c!~0> zi}j`C>i1^$QQ)VxLxkVKNVA8QQ-E(Af%q8UBO~KPuRVx)#(A*ZvZprRy6UIYDo-{Z ziWnh)GJ73`;@3kCy;}B(OwY6`FPg-uUZ`>%w)lOj>E&lZd=|7DHyiiiR05DkvD$l2 z`6BD*t*Z%LPA^GrzP%#+y;c5GUHx2`-9KPek3;PbLVXYW+ilt_GCPDFq3_xddT*?F z_J_o_L#oc7V)g&~d)KNh|HM_PJ^rlnb%0|9k^LT}cl3@?Z-U2ZEd+o67vk?1K<%-z zPipeNc#X^Bqpkh!azoHqb;o0M9zQcHI=K{YJ^qe;_JDaO6{Dlsqi3i-*|bk|d?QkT zahhdOf3mvYt){F`R29DO9{zp%n*#aT*2dA%`E|r|U_PFPXpczGV@KPb@OSV&I@EeF z+)vwNg|1(s^f&OkPKfz}8Cz^QJ-j@%Dkm;UkmIQqQGN%`S(*9$g6p3uc+cuQu~GHr zKVbh)w0cpj^J3tCss8Z9)bC{D#H5Srw}UO}Utk^NkP!#_)4{rk+5U8}UIO-~gLbH@ z{hurNKz^GRr9LpVo;Cd*^jF|V4*2P@>X#>4zjBG1*IVU-wG!NppnmXud|e?y2BN@! z)KsGb{?;(E{32by)Ihr%f*p3FI@obe(d>Lc; zo-D!0`DYBT!!j>rssyq2&kA&+SG6HzCI1MB@X|(BHv!&$pp;QTJi?w1>8j z#Jm2H0^TS317tw&lod!%K*!A{^gL|~O;12aK&L!gAU)4i#A`oyO-H=+zh^G%C385X49Vde)HncyX*qfBqc z8QM?cdu>>fGsQi}h~>^kLE5Y$8z3!0SL@Kgapi+-`g^w6IF%28OZX6^nX;BgXe;0Q+5QoBXaW;`!fE=G&r* z+qXxGT=Zl%OrC)T7|^G{bNIdG`~DCMxL|yP*PE({E@0j10S@zCjOv3wt0>>JD$4h_ zIHhAYuZwlxbgJ$jMDc6XdJbVxElrzbaH>oDq(j(NnV62`{z3Sh`qMw1h zDjqk@lO*3$MGr>N;f3ab<2hCI&+5MJ1oWnOo4tq{-cPF=92Wb*xmDZ`F~VaLCczm6 z=NZQL+8YA5HKg~9PLc9qdsKX3=AH@P)0Eo%^X-iI>cR0Y|8%S{Vk8mO#SZVNqVb4< zfq{X6fq{WhGcesAS%}grVSoLN5o@!nowwcf$>(`FU5xl#BP>CldEkS` zo_e99@uarByzCy`Uu?a|P1CA}VKhefNTgdSC@$61Va1AEP`b=nr+%B<=FVou=bN8$ z1calEt1@zoJ>3242Nvud<0{9#--UkPB-_6jbYx-RqR`&C*1|-gGC`dI1ijz6=A6Yt zk6aHnPjs?{BB-PbBB=;6uh81*)2EN@${L;p(o2KP1R3RNKj?h$U}4(Ly}`lHQNd{! zQatO{3iEwqr^{YVJ%650D;-->kneMPhR%O&aFGk_<%rg|eMM>3Ivwp6efWAk;0EHn z;I1Qkz3$Rtug|Sex}tkz#Urzx1J~}ov3>2MdF^T#+xa!Y=~qzxF(>oY4Ue4s6C6Mz zEBZmH&#TRJv$VuWG$)ZF&q}N)I`X=X>p$t6EHKknGXgtja2D&Ftw#UvvDpuSZhUuo zsBSDuIztPrEGQH|WahRFT;V&}VE0Vz2&)ImR;Pw^hnjLbo*AP+oz6k6qJrnH2c?pjwktl}jgg9|tdbG|D(QOX@!Szy9~H>* zB_VZuj^s&A5IGN{YEzhFCq8-T-ZY?e%TT&9B7`AFtLoST)$0ssEu$Iqd-#M2EQ@kX z;^`(l0iGekp1<=|5sf=E^LvRpxmje0UvVPo`k>{28vmfu}(|2E=E$LO%!LKL&j-HA2P~7zcRQ$f$n8 z=Wee!XJrq)^y^1Jvkj`$OMtb=tE!@)(SpvXtgz(4$A9>BeS>UJ)06gSvwqbn?;Chy zn>&H6r^Fd-K43)VKjtxVw&wNbtJ>J7T&eT{xa!t++YiDHj5SZ1b=T& zPk_NeZ#n=mYg?KNk?Yb$nrH5_T@qIj02M4BTRQeYTb zfbCq17W=RGesh{5WEQVEKOA8D^~(iKgEOeC4(OUBdw5osWPaKHF=C%}nGTTNe$z_4 z?%aCb!0estk(HoXNf0TTW=WCbBs$1g_d!>Gc#9)+7Oy*VZ|QN_tIO*KXJFJJzbN15 zW=LV$^t&GKS1IMch|nUXLS$@l5(ln6^}qdLl?;-URaN6jM&LcLlF0y@ zueAqWNIwm2KGyRK$`o3XrfEgwD4EqfzrQ&4-Rp*5k_Fsu9E$OR5pW+v|0G{jvfASN z)lsJRPlOyPQTtora#U8&9RU`WZO&W7b7oC>Agppk@sy}Dq9l+411m?h8wF;iI;|;3 z+nkvrZ7s5vwFsfRmuD;sj3THKr+5^`U0P5)=ckKq*xzXV$~=(CV3RO-5WMT0lX>En z^TP#E|K4-sXFnG{FIg9OUG!+GNP4nr_wH0VuMg4!LsVYd zozj(E{x|RtV5J*u2u4|?x<#526fa*11^JqpVTj7Olkb+*fffBbt%dZ!ez#G~>@#iP9^E#BuE35yN& zp>r+!Rl6D6kcJL{k|>#F1(BEa-`kA2@sblSW;-LCzE+ItkDZNjhs^ux>9!DkDo?j0dk6hWqu#V(!b$;@j>ZA5{7k z6#LZq{x1vvYPS#Qi+=_0Zz!Zh-aV@*-*;iD=G}H^|264a`iTT}*hC#shZiW2In#Wrwg%{lF+s+I$GI^gWibKN`rJtE?J<@Rq7M431 z4C-P>;5Y*PvZe9di})U>)@`7624S-p>zQt`{tnxji3&wwpOd(BLi+4}Xd5(3(E=q< z9LWeGE6}j{w(ZmY@K`P?i!Yo%ZGZE~Cqvqyt4k{&Utes{lt0#$kM;< zfuYS7I)1l3P!DKqBAanMIdQw~fOEi#I&be7)--r>RLO?r(R2?fu_UFFY`qSL-Icd3 z40S|0p1yOg+p)@h%{ViwBPT^_b+p@la(B^wpB^8%Q*j)MZIzcuJ(lqZHvnIRRC zq9sKnHD&lBA|v;Odz@y>+-C88MM-X6wd-=n?O_*vbY2kz)#IUQiGr*6dj_h7esP{y zw(ql-E(^ozW)@)XU` zq9_O=E0GLM!CAc5(x1nT*`c#I|`(BvOz>hUNs0hg*8wDMK-G zpt|m#&QT?su6cSiFhfpH3n8Ti(Oq2P%P%gH3&!OaYTiL|ky@brZ>aRtcmED@MS6i+ zZWU?q(a!p+Y6nD3-FcCnV)V#8L)!&*Od08ptXxDqH^O$eBhrbL&e$(e&6uq^Y$u#w zymsSH+RyEcCnX`3NLiIjd~RLhIE`m%kLr=#`Gq9~^*S8x@zAOC@(EsJi`k2iSaz5g z<3Rc+oF$JQ7))HyE}Wj}FRR8W5~ayH&3MHB%4&U|(J3eK>s8~Pc?wJlKDC9^(X`o` zcb=Q2Jsc&Il*o8U-(?qE$L*V#)c{I*lu`bTj=*@VXyc6U_h@bDwDY22hUIyY7J~HR6-IhpkyN+2W^U{D((YG6%##XKubZL-ccJXly!moL{$-kbx?CF6 zqM4|)U9NhEzAs9f6?T`9z_N@;@iMFNEWAtTTVwrdzfr0TgL0eF0^`BsE6sJq;6u+G zpNU-hmUWd^6k0R zv-1_L6zwI}QEtm!mQ>a<94v;;3K&oG*KT6*9JnU;>~ zd^xU%2mID|{#z|88P*W9O@X61iW7O3WVM!u7H_(5s%-BbAzN>3i=B3{eAhU}bgN3M z2{&u4EE;oMZx(j`_V8t(IhUZDQ>H0ekp)eqC231PYSZ5%cH{xxJ0(sBQq9tdHXraE z>U-4lC#z`f0nQiIe0E!|YxR?NFZ;e1&^zWL&! zTrjP_X{tXQ6ZOfSTCTOC?bHX)m>5+#DT*!Nd9KwBTXz2_7b)^oRwF&MCi5z#!N=OV zKo+Eard6r!YI*6Th5L7fA*wpB$gLE}rKRo?Z?UgfDK2nRtn-giE6O(0q?M1Z(4lpr z)^{E1arp5oFZwgcCc_HC9T zY&_E4u$Z0I1WEYeQ)-i&E$Ec^@w&>>VnE&Rcd>B z73Gs|F~q>YNFG{MvH*rUsBE{jb~QL}E?*Q@#D{35rPCmGSBYMMav z?6?1pyPC_|Tnp@eXqO&3gmdcZs-5+g=lDzEeM|9-%m^wi@e=7NUNCGyW#3XwERTS0 zF5lawYkIc_yO92>6_i)Seha0zuw<4`Q{7zY!CFh3>rdG~i*d=|PwX`l0|TR)0`o5n z3=9km3=E8#iHZsH*{-`v=3jT=v*0}G+ED(JD(?~pr`6!y;?wg2!@1C{RMR1M^wXhp z1n@rk3!q(BlOWa90mTnP-+v6nUx(sPLGg7^d<7K07K+2xyV+2D1QZ_##rr~W*rFsr zaT1Ed?xEX2@h0W8rCe)Q=-nsxM;>`m7+LmkJWr|~6+Y$pe1tv9VPjkwUmQED^$>8* zGlUa(zf+tt8`CtmCUT^tYMQJtT*(FbIe%w=RoF_yfU%> z`+VP`%C$wEA3~2G&h^SYzkU48A7H23*CE_Fa~$cORitWqevzh*(Mo3(`2IWNaDLC# z3-9^-wRCVW<6OfKeJ~?11ZFUBlmxyF8aQGZVdOo`2!R{X&oiurHe2X;vakb@?>olx z%@)fA=+wg?oC2{D3Tj0gB90NObP-%{6hE+MepjmwvLp}BF|?*ryduGO(=2Rm#(8?W z_<@d&qCK2JTP7)q;Utlwbrm*ffBbFe5YDNm>yK3{8&5j{E`%6m3n4Qpm5~&hQ3RIs z{C6Ql{R3^8%18P{S6~POJa6JcQMb42qE4e;zWG$R`szBXFlCov3LFR57jQc`45sXP zt~uN5zclM3X<-(^^+v+%&iwSH4P9 z@z4rMlPV=LwQiU;>j&3LCyJPz&-aI0EDB%r`Snq*PBVI(TX(bB?Pr|I@roodoB&Ih zVWtEg?OOc6(1H~r@Og3x5!`(#fpp8NI!059y@s|opvdRV*Gh$lXFc*;5tpMw=`TaD zokHXJyF_3Juobb@>N*n|P8bP=_P`KeOOzcly^6lOF3tmMXN1q=g<4R(T0{N8DyAF1 zAC}^)I+5-eL?1LC>$*RSJoVXqK!P0&(jCff5oSCdnqoUmu|tzYsG|><=kZsxeH5t|2^PjMb|abBX|VSt#}yu)dBCV zyZD1VSoJs_b;FH!efsr{Stkp_sj>_!h#W_1JjF|}Dr56rGK~Rt)V1Q7i$+2ZNIk?=#y9>=i5f#M(g3>Igpa_DfzXA%-(>c7r@WbY^WK{`?*X1ACZ8E}xA@cx@CCQxAARq`dd?ke zQ(9Q>{dZlV79nzY-QJ)NE+v93VCD0bV7@=(>0Y-U?4d0jGhpq=r$;BEa;-g1T63|V z)fb`kF5A=f`we)*))?uUp;JR3Nd&`75-G6!BXjnzowfBtM+T;fXAc#WwDZB}zn2tn z-%a|mdD;!imm>Hob)!I;N5N#A`wz%Fapa_yvU%W_6oi3 z8ki~+TeZf|9(Yg0t0QQh5_pcFS(f5OJ|figXkCRg1xw4bZFHv}TYWzh-v^c@2!Y}l zj$sL!rqL>{Ob$Qfh^!*lc5J~!f>5qPl=rBSd7h*Um$E^h(RrYg+XIFvlm469cSWHL zuoQ%+&y@Vcv)m=R7$eNtf5)=m%pTv z;c-B5?{r}vb7`7(wa<~<-cBL6aOaLv_W#>wYWCo5_C!R1fV(OgO9h@#_}d8E*(KNQ ze7BG%pJW&ogFCl7NV%Q-zT6I@y~oX3qQOvz`AxCia3Glr^ zAP_PX2!c_;-*;z(MGQaQG9fIDzu#kQ?)Q*IKRi%X+gb*7W~&VQot+1xvY??$P*k-^D5ro|8+=eMe?!Wx;Xjr1k1g) z&TXfCHg-;wQvjXW;XgXn;f}87QS|QK*sIP3(_g-82 z)Y|X0931ubvO=;Ppa4e!8lbnAy$|8WXsqq$jICW;{vNexMr26}kP0J75n&Ebwob>W zK_0fPd?K-NT2=;L%m{^0WK^D^X_X`aIC?$PyoRTb6_qOwbq&uCxS?Xm#KSiG#1|<~ zC#;?b5DKG_vdmMwjOvO0Eip}c#P;Zc&xXu-r!ekEq=jU0dr+Q}2|%feKm+!xWx~9L z+mBRwoOcs0O=w>38}1V}b#DIPsXL*U`yA}<$8xTIZhtUe^4#VN*~kT4z#H_Be)Y$e zLw{OcE5buX0EUOv7v9J+H}y$dev1vSd0IrbS`-CYB54881Y>BmSlVzVMeUek<1OF( z-EHlA)rz0%i7+=O73T(#e~XT zo6~IX4x2mwQx>v9Cn9%?bCw}Ke@98laRE+}BuSQ8g60}F`Q+W><91!=arL{`g z*m%EdT*~qr$0?#%Ei-I)IUQe|%8nklTctIP=Qv5CIFx5UI2Sh#vu(jX97QaL{J>Jo zIJmuP7q1FNxk#EK7*60wo~LU*a{7k!n=|ayke(H0RFW5gz@O`;ecPak#U1dZ#_d$-dE1ExtBtw+X?Wb(HcA*UnFf?XsI;PiJ#U^J z*sK$qgjzm*d!E+px0w95rEFh%Dcg}A=W==8*1YBW30Gd-0JH{29e0VUEbA=8>Ha{`4b zpla(J+9_R1Y>*V5kF0W$?Mcs!t#ig>#aU6JX-QK^Sz`s%Yu5VOk+_WB!^O6LI@CPA zv5^5;3WWj{xmb}v$EDzke#4e|!_se7&4h!v#$XUR$^`BydC>#D|k3W%?GnXSRB#GU_M0SV-CV*)A4JWX-5qOjlt&eQekZ{J9%j_D*E zV;d%|Xuxj<@7cQgN3GoSR*aKfMN)u5GLoRN|NZx0k4F|}*&xoIPYO>XC-VEY$GX_w zdYY~kg&Kv9uNBGT>G^(}Zp9U`wi2SKXzN*T%XSRw{Ps?~?I%SG0x$9u!*DD|MqFRo zt?N(MV`$BK+nEmAmwqs#VKm}KsD#X@lq5?ue@=ovGT_mjbr#JjG?qRbBaKAohqh<- zw7lcfN${wAsX;UimSps38lgBE-9RwMr-q_w>>b#8e*3=PSZbJuSf&tVf(a&=V1l_1 zm~cq~dKtdF=dt4=Stn-H1rtm#!GwzgiqtqC+ibh%Bz9>66;%BrLp_I#wKz)PH9^%> zR{Mi^s>d@DnK=I<$APJ$G4XFg`9V?%pI@$kI~e)cmfUmPCo?SYI%8eqv)psn8C8l< zR7PVonpIYGz2~OS=S*`z3orDf++N1^R;wU9q?FTl_=BbmwrAcy(~|uGT0s=M^j3Hm zn4?@Heg5IV-_7mqlU-o;N~J;1#@&iJ`Y?So^tx(P#PWn{>d0}X;v5G=WM<3 zav_}1twA2t&oY?fOlhGY=u{hR=H={cX+Z?LfwHw_e^<9p9}}joV3K_BKv8 zMM|%0TptRG&Xsxe>C(4hoFt~4eXGL$pzVo=#1%*3CR;l1obZ4H-kdNv4b{)rIMJ0E zI$sO9?C}A=;+8$2pXA90=*8EYt4HT>t(Gme4KE~VB%VS9ryQloK83?rpq+2pJK))qdL5!s4j zJ)|XuC1k)bvLaaeej*jWuTomOh9XStu+7-G|8Vb5U>immwm}nB2~Z@>v7&5wPI&yy z?w=;1=H5HYiTA|c`1#g0ZPfCki67L(O*)aLc_w64!O%2MMR@D*eOF*u!e$y{<6B#u zUw&EDtU2^s9_KwUKe#&|1Pa`YF%%&Q3P~_LDWhfq|CZ7fc!Fi8ZU4~5pPbwSm-t3( z!)VTxl`nhTft))f&v4)mxcorN4@jQh)T8U?Ed)-SIaIt#*ECDTLadONJ8eDl7q0$2 z4_etRH}bwJ3a(tAGQ5vF7x)9cfCAhjfIo3fr|C0#3CVEVyzYVcTJ(6$_}&{|(_%se zs8g(#-L_Om)#axf#^uh1mRU`b6;4(_Oy63G24vaj_mV<+mlvxXys6Y4+uir7qd7%2wD*~QZS)i!vLwqsno- z-L)8R$;VTiNC|>KiXu<55nddJ>$8P(;Hvn+HoeBw+3$Ue=fO!v6gX0(2$3XcGNOv< zR;!^zR#e478+8kKbL(HxEiGA2;28*lg-y*a21`D36BPgEdXpZJY zj;15Hx@Iw5ejm0i{&r(W*Qj`uKtN(yBILmyFcPW+`V?WFrqn3gwEwh5#q69P*C?Ow z@JgT~wyf?p>)%^r-53WlBXI&R@mx}$uDe3sovI*O7Cr8@h+R3*jc^3%`GOFP=0W^&ZlY@dHZXdQ@2#}`#N(yo9(Z_`kGfzz0J$1-SL)FQT1J} z+N7hlXP-E7-Sw|G#dEI3UHK3SLutGy5iHHT@==tXMN}PKw5UHkGM>U%c~qn$02+!>5x426m31uD%nxIDMdhjVrQ68g!t}LOThw6#AMxym0CeCu z)Q9c$DS@HEx=AoAjt(+?g@MsA=B)2jbYoBnRj0&Hqjbmq=KS$v+)X^;g#Kxb0;Gk& z$sgd1HmKqq=xIh7byX@gFIBbtiYLuKJ6zQ7r%~He_<|9n)DJ+v3OkcWua3>&7t3gI z=P=pJgwI4T@>NafsKOpX8dzrh-ue%c!*=jPI~`)5-M zqBs>`9^Z>byN7w5**d^%>02Pm%EF1O?r~#Zl&XZdBpz&#Bi(uH0jLEa6<-lCOi=$ztaDw74_~1y`Tt_i8zkzA~LO2 zQ7dGkO6%)K3lUrIUhT6_Q@%&n%i?x4X+R;yHzZ!|RtchAXc8JQzxTY)>bC~4+Wo}~ zC4ue{x?l(<4GnrM_ziJJZ=1Tt`U5b!#lCN22pBT&@%2ge`b=z(e|Qxgd9gcYHdh;adL_b> zd3025F8x_g{rXm|W|>q==Jkg}=m$WKnue@w&AYltUh@~H;|6RbzHukR(OJgrLTXk) z;ez7w0*P1oI#B0`BIL@a&M2bm7jM6VZ#u$oCo*Gn^pdLHjk-*m>ZBsw=Ct?*MmA%a zd(FVfuG*|I^DyfHA348%30W@Q{4@=GREn=tl$%}|*)P&xg%6#DCCFs^JeaIorlxex z8Sf2^5Bmn_M&1$nas(Xn;&U6HgxK8ljrxVjanni33Yfpq+;}@q&ur`=o+7cpg~QOL zgD%}*@TK$4FCUm|=(sKvDIZD&tg3`(PfQXe3#miqrPRp_S&8u5Tb|4o=*Xk}(c~IH zG6oMr^ZZP2eM%6|fj@9k*6|?y?1`iwGb5iqhW}ZUi7qSG=AO|}6hPGBCL5L6eeSjc zkBfkP@?1UpMqj5;Jstjv*mtIbq-{?6JVuF`$Pqf@lklX<9l9>hVUaY7E%EYww}t)9F3y7S81 z#~3zN?1bzW+nmoVu14D;l6>(IjxC(Wv`;h0=qpk!xRZ{`Y!goTq_dDa8Z|w>+e>7G zbs|@oE5|D1!ddWkp?r*2izd{YSI2h|A*98%)xPF7E0@cLJD!e#J5~S+?)W|Gz}TIk zXKeMwrcds5-}m`PCAOSIbagfhuHLauI#S@BlJ zdtlamxB!|bQ8}d8ffT%#J`cjx{LfIv65s>6*RQ{qUc`h%;SF6YMq-}#bTD0bcj4Jc zqS*tiWQ-!DxitAW642L65W^RLpgpgTYpuDXRoe}|-Mgq#lIT$w^&fr{XYuv*zFg(~ ztd;BKSvJB6oTCaqkOqiCOm04@^!cbfSS3BQ)(uybwpX0L#!_@W984$$$A!k`!a!yx zv&E1}%jjF;Ec{*{U-_rYN%S$O4%)5pW~Lj8 zK%YXvpMh`ubD8}qP-cTzXCPDE(DOvy<53S2UpF_aCzOOPl1ml+3$oZea$hmKo3;RM zgWYTR`aJMMU<`Lle^GWCezb31a`Z|+-Upi8A08n2E{7`h5rX}EW@;;GDWvRiE%@4z zvEw^z%nm8-b$F9c;mkDDmz~G@jXdy26vF!T${eI%WB2KqN|Cb{gX)k z=v&2)ph>D9)nb%Chu1QwUR7lc-c2tG%yIXN4uwn}o`-Ugo7NZ}cX&Qsps%gE_Md1rWCtsADzq4q=r>mkp^?_MRq zzNV@RNyh3zS$pSjqMU=F&nzNy__$kt?kw8ui_NF_C0Nm}A6Wku##EP1FLP@f>r)Y3z6BFhhEfh&eB%$6ccK&~Y#)jK(Ck5oik{odw(=&ha! z!IZg3E>-Cmrw+`&MW=Q&>i37xBr5GFk3blQC4|0@T8Mb zMUYeJ9C)OYC|%l4u|h>FL+W9){3|jm_^@)#3uKZ?Xdbw2+NxRJipG6P7RqJ(I{WzFi>H7PHB`;b~_U<)9H zf}cH%!?xyMT@*B%v$vbR370 zZ2e=~qFG-bGrRH#B4H;z92G$<;XTf$Zbn=f(%_@wxlepj+?ZzIS>q*K?d9N!%6Uic zQ_7c`Bb|w7L)(q$|B;~MR__j%{iA#r3wvsTRNSF))QN5?{s(-NnJXqCnq+&z=G{K0 z>y>s~!1kGFz#p;X-9MwsiKMp9ARaBV=X3JJa)gHDmBtejMZs1A_ju93fy0-^Umi7ygdkN!|xilKKADAMf7h54Tu-?cufd zoe;68mYD0}o@b|Hq8aoH7Fj|j`gk%Ox}cp-oKJI4ezye0&j+mM%-&8JUB zuWnZAOX2-B2=k~M>=ah?b_W$(%TJ4J85yE)fn6hzR6x#*{%I@wm~Js3WX~PPaLiqo z>W%_|JUIW&`-Ns8@n0}1c(wNh?#IE|$wjrFgprFY;WOIMk!cy`u$ch=u$Mi@jc@)! zMr-Tv(Tx+?FRFO(BDvaD5@bw^(kgRZk?d&74ozIEYB=pcfylQ&nd=Y?Y5P}E0y^m# zu=f885SH|Ht~9P36O5Sj`g6^4_Hgp$|4tTW%!xfKzdw7p(o{AFBST{#NAx6mJsxsL zT=fQ9W8+0MfnkpFh`Dm5y(EsJnKgyH_Vxj+!9(7?Z`I`3rg|P%xXyk@WvSg_2}z*vI2n0i1FH@?1dxbVaf%SC_}?UtakqI{sb)y?m~HVCZqUWoIz z=>tqW)q>F{l1=zNTGXmBxT%EFD8+5<+`02NF&)@5LQ5jOBH349Pf!I-NvWNGbN=%; zhi7g=ur%nv%6g~hVg6WK!;Ot^Hp4}jav!*2jZG$7inR#MHApOq0tWP6lV}p=Rsot? z8Umxw+MrYCQ*}9A6X<`2l1`=+Krgrc=Q-aXYZ&7EK&vcd-nXn z)%||g+1aZ+47!2y9`bv-7!mYh$Eqnr+ohN8@4y0nKmBNeKC<7+!8+tQ^aV#phdX%K6rz<~?* z*t6;M0mry_;%p47-sjCOsan@Cv8y=%D_q}|gpRma_K)+FA%MIKyC{>B@R|>~&swuY z-0PAL42wJ0x45Ls_x3E+|ofxSPz9{ig!j;;QC&pYkN0_S_V@WMZKarV-P z$b7Msb5%*_zuQtBC&g`6$0zK*b4;|~URyvEAbvgs9T}YCBWIlHA}K;e#@ug$*EGbl zqBbuJ`5_h~>WX6OcNUrwHBYGm$~IkuA8%ks?{6}30#Tf)KCggQaC)o6$GGCM46FO9A-n|d!1hQ2@g7q|MbnyT<;O?Wjf^jrJ_L)x zNjlx}urY$tlP8ZjO;~Vqqt6J^fd1D>+UI@2u{;5RoCYyHNbw zY1W9KUs{`;+bt_|djblXeP8WP5|q5mzmEuTZ;GY?gRv*^_4mqb5eD@7oY=ux*&$Rb z2{k7{8Mtr?Iw1IJ3j^8voYrgaeTvS^8WFn-%Y-#}g(DlSYNqk=fj6|6A{j*>^!eW- z{PeDqVk#sGXREK}skbX2HwElRpCv38LbDzb6Eqrh zm#5l75U33Uepe=kKUxfPzPKCLc+D}DqEr^Eu*fe&w7b5*n>ZHKt;a0}j$x^DkA0k7 z8joz@{Fq`#r*(`XW+|+c;ycAHzl;LEk3Keuo@*O>G`h?F zH8Wpi1hM(s4D2rM0>cmVjvw@C;*_R6hU(;Ck35E?$8&18`EUN|Tqv*rXm$vevv||4 z;x1C0Is%MhzS88iS3UzgGMI)0EKj`)sK5;*DlnxPoV43}tJe_0_qV$Irs1cE6#i;P zLhwj;63q?K$+P)=Wciug=ptAZq)mUWJ|tgC!&$%^wsY%+rdKM~+uXnS1)Yaa$M$vD zmj3*1z?_zjZZM-VS$Hu!`}9m8z5C!2Ng|MHyAX`{kg@lG3~Su@yS%F}BnG0D`8y|1 zL@KO047V3Cj(qa3(kk~dZ_qN&+U?(9h0@|rdS$2}M0ueCU7q4&DM23=AVRT~`$QvG-Mz_xJl*I_)s;&;*UH`Pcz6J73eUNB! zFeL-aGS!S4??)eB|0-BLLODihcAvFw$gihAwEQIi#1oylV}M~;%8dJTzWd1t5q}9n zeZ*OFeJ{Nw`Ief~geGQZ#l-|gVN59*6728;vHOF&o&=~wi<_;$#uPuj9(fSm!WmrV z@(#|c1;N_V({sZy;W4Z{w>w(;5&GV>=B5En^{iNzDQ_Ka#OkAONjY%Qnn;nDEM!S5 zz!WCi+*9r94H6)+e4pui-rAM3#r$KLZKbyck_?r>wUHD3bu;Bk{Z%^K=h}4PX8kX8 zh)%f5bk5IIzDv(4?Cp_P?<%e{`iob2rsvxbiPT4ostK$Mi|tdVvlG)=D2W*EBEFTd zSQzVs*?}(IYlk?Mn2A*HT#2_dGREh3oYjW2qSm6 zl%wdHmK!O|P8ayrH~AtZ-}71jN0awJOHR3{0Nwk$KS6s?>Q?st$fpW2$#al9puejC z-t(nAX6Yg~AM9yP1de-IL}ZEh z6@QcFxkstv%g_-K+qVRX{2jy&3vmhhd_&f^BNZm$F5uokt5lL2J6sq*B%)Nd?38)+ zZ_#7ym4BgdBu8yC)OS1tdDvwA-8DAE1M<3rg-KTgPN$; z3eD;5q({##I6Hgss{NuzL_-R;mYbVtDgzDb5G)VEQWhB_o54)wiSupWq^vmEor?{e zWDD4IbIxO|_$5=dI84{6-Tt`!7^3umhv;2VW>1Fts7MzJsT)DGUnJ%C@>;!h$>>d4mzexbNc{_=mbLNVZwQr2H3fl$6#MM4U5 zu+Cj(fW_-(vFX~rz(dtWXu01D-Mxqko0AT7^u;CF03HI78L9GTukRbC1I1e0soq|KT6 zy`Ihy#8jjYMsz9xShRtkyTC_5 z7Qa-7Li0u1_)hZrKPgu7m~8hlzdGxNBZ~VtULp2BtdiQ={0?k`*d_G*MCen?2|r%+ zUR*L9{DW4=hth)#|Iz!7EO8Z^i@9;pS@Kqu(NTkEC$-#nvNt^mOUb}?J!;XfUrY$R zogDJQcU{&nXFh>3fq}&Xs13Tx8Ry#e^+nOB3+4X#S6f2Pe2Y>lJ2|;{aiuNA_{3?8 zX`-kCSQzPecu6i@k0c@1e%cqOZ1=;(sy#Gy85ShGzO9IO=cr1+e}$$?sEHeZRf1jk zDy?{DRPIv49p4>>Z6bSxaEw{=v7o4!93+@hE#otDNZli;D(m*t`E$L8{@~0Gln`Z& zR{D+}La9b<6)1zbSqiwad$C?wja@aeSR=FQq#Q;xu^gk5YLmDl-uQRQ`zrfDxb8jW za{N-}Pu>-9lgF)TKa>K!=1DPg{D*hPsiGIp#l~(2IfzuC)O&dHz0^O&R|7h5Y>*2t zno_CboO4oPjd^#KDoJ`T|)6cgVjvB~>x@GZ3 z8QDw($oK{9OxJk!>G8_sE;qAXv@?#&NSd~|#PMtVsrdBqT)+hNFM+Q*isO>=uI|aW zuuoT5TY>;$QZD`#62BM+Yy)kdG@dUUX{&1zn`wm4$@2_rbc<|TQa~E|#v{L&YW>x+ zgPA_2cCm>g1QM!izt^j&^pqPC+nk4RZFl@zi`2D=3pxKovi0zm<(RAro6HU{q6k+-5M44dZ^<`lO(6y;~}FB$ZCgE8^V*{AN7$1HV%dHH@9gQRj49Y-)5PsClMn8q|tzt1S^Vsj^WpH&dXXvAZlW5S+VbQH2&LFIH-uQnyuN<8e0X3- zPfc2ROzAyE1K{}W8QdnyPoL(FjC~s9W>2&2JM~rc%%rLh-VuvbQ>+V@7Fqo4B?Nke zR%EGryqOf*FfR>@lCT&TpOaOFuP%poW;CWBekR|(M&M%ULdD8z*87Z)iCqJma6x3t zZ^O+EiSKRee2U$LyE>EY?7RJe7hz;fhEFhT z5^V9-NKj+|L&ksSB*8+xqlb*Qz_A=Xh1d7z!PgtQQ`I=HL}Jib6VY6JBv>m* zqcIFT{GNV=_r^z+>(o=aJqz|DOjH{7OKuVPY2Z_oare=A48Z{MD>6OW4*rhSp3PPl zcMWe zhqV$(eFz>1J)r{xV-DO&p6R9hSOK5dV>6Ps3-NF7QUZStQb<9^N;y@n+o1IX+~>=~ zHT^2!>ls|)o&gn(1RV_Z7VojUcf*$zAwUo3N%YIdojK-TH(hX+iJU=uQ6G{HEbG=P zgT=z)f*+fYiH;T6&wwZx!G_cs1GW1aq!2+C*{0-M(#!xlE2%`Lktsjd)fgL?YGsO> ze9{w*xQ%MKpKczZbN+NkS5sT7#SeHSnOSO(vSWhn_I!T49!@0MQP)D@S>s{(_8O42 zNUY=kEC+cfCwZg+EaSnz9{hR)_A8uS@#iyEtl9F~h8TAHv9Ywri?N=&R=?y0*EW6ab(wa_?;Gq%?=*9g!`9rtuI^@MI4c#qW&BxKS$?J*fI6It_^=o!Du0fV3p<>u zj}ZEu1(N_U>_Q~>@8_Kxqqa(9UisgUK)#)aA3oWQ^Ojv6q3e?8PASb7>;AuM7x~J! zhnTAMsA1mh#`Xyz^te^P^sPbN!5OqY|3C`Xy6evB4WmOEqc^qW`{UmYcHC*1g7$v* z?&EoSNOvHXsDLo5J3wf%GNOJ>img4n;1$cQr8N?)7*Sv!a}4rDr6!QHbhE_L?Q1|VGmkJ~ zbtfTZt-3C68-m}@eIk^4Ht#iG_g88!%vHhm_zPJxUe!r*3l)%n)2Mw~u zWLHkGy*5pe-p2RjgVN{25CVABe5VM6iEv29(my{uU0!uk!ZWDHVz$>}e0_n_rQ#DT zwBebf%#G!F;uX*fObz+vrmK=foLeiE+P~(4ZDJ2vo#TVy)xNbvBL&LsOd%cRHZ2ZJ zc9q9StSDe}ne8sc`%qR6P$D~9`5D?t+Rt8n|Atjoi#o6nAq3cKvF~i}=cn5Fb8rfY znv3dcZy3~K9rJ20^=aNqiFl0x{9@8>qwm3+u$2CILtd}9Af{`%&t={Zat)2=pIY-I zS1NpR?|YpG9(jt)O9fy`5og)X+uY8w(!ycgH&GN5xe_3Q9-`j54DSgf;;54`DFELg zH_dhNR;JpBG{aLcIR+2#hCLYF*3Wo9447ZLFcRu+Xsja8q=k3HBFrrna_2mfsJ*~8 z8~)MY@(QGO*nY?_WbA&aotgJCem(Yuk>7AAUnR%86 zZxko=?UC>du~wJ?Cfa}{ z$!7_L4oK%3`aApOiHxBk78ANWb{TvZA3e9bFJmkQfMi(ZVoHPFSm}7ckE%78Rjb$nMH5K{9$d(<=!DKRufwtGc{ zU$`bo6*nNqQ6Lr%%(r9+ytntgS#mz9BcUsfh>0PgHxzt*fOsIi(M0gg2m2#s)0r!g zYp>ReOX=9T$$ZrF!C0wCjlP72s8xbucno@d)AlnYyoQzpfdjm3f!6{xo$t>5MsgUM zLpvXgEfe@fNwT^pRx9v@MDhrY#3WBL{@38;5c4^T^cHX0@sDa8@12`-M}uSXXcrIp zqxj@NxTxtQ-)to{50_}2I7H)#{rn}5XgAl)A9!1y?Ig*2N_2hK_>8xZqO5EfePV_= zs9lWu1n+=L-&zvrlcoi0u^LfZI!PwTyzxO%B2IhFBK46V+6TrE<6@95+9{rZ{}r;g z7Tj)DvFkV+PpcaO?e492u4CugIWNAv-8Ev?;*OGELDnXm+fXp0| zW${5%o-EuR_(?p+eF3Y!aHJKID^Yp!H-gQSh^G>{5p>t1chz1+Q;~hkB8HFR>hMr!f-ChEeYvzFPTf}p zHuso+-k$%A*)6i|eO6ISQ{w3yl9Bh(oaSQI2oMv9QB1~ya#NIYkv}G$EZdId$9^cn!i#?kt#_m0d>BW97;K>Et3`zpKSLH;#FK=dXZFYabegD2qH z;xTX^kk0~MBZW&}0CxsyJJ&^!5xbe#+M~IcA0RQ#0^yNtqSknPXp@zi|H{il&WV4z z5hMt42$_zGEsVmgg7OdML7v}6nBu(_+6{uh)Y<>l`!Q&_&Kz$dyn1q0;oBP&uLN{U z;>810pdH1mfaI-L05pwf3u0wU?bGVHPpC1d{wzi?6T*<7DU+JDgcqS04`El`)M|bXtiTEkyKk_9eSSFnIfTHdUmY#P+ zF}PB%M6`zyu^?Wh{_UHe7NP_b>$-&h?rGG(5ZIOY=MyIgjxSaJ!$yeXD4?7&mZ zGVIoT*nBiyuD^oS{dD5Mz8!hqx>w;b7tZLY18}JNL!}tb`{`3ja{u02BrvqS=+Xkp z&zo@|wU7jwdA@G9{+sAgB3b1tXFx;2c;kgr>Pst+UGMVpqOFfH_9L&K@jpiRB-q>z zAwJRe_JEtf2}~1o=svdf-hHh*(iY!kZVK{I7w_?J1N-=jUt7fO2CY!eh}@V}*F|?9 z#RqN{@}-mmNQFxa2USlm6)pfcM;`k7+8$+ zw!>S3u?#gByDX&4AvQtt{v8MAl{Z94QO1API2_8pt_t7Wv&6#zJ?W1n%`3znJ z0~UN9zxKi3x02(vYwV!Odu?p8$i-CxXz?ILojj9?O6|nq0tt2O-J3Pxj$Zqk9qGE- zBUj8v)Q~B|=`YP+@S~T>J8wkwE6aG~Bcq66$ol~JlJTV+=2-$k?xr@Q&i7k4N4vVW z1=Iv)@kxIWVB!qp4{uHE*HnHpV9M!1_7V-kH zzx?zp+m4@f;%Ifl@WK;mEFuJL@7V>31@? zhT?BVkc9rGS7~H6CzI%HQrLUv;I=oDmwj82>fsfm_X;@MUfw!M5*f9AeKKG37de5H>?8Ixi73x9`w6u;Kw<#$t*(?NBs z*?^l(mWiQAf3b&;!Om?0>evnVj%PUL+yb;DON`Iv{y$#R^VXt@eB6Gx@-jw=DFf;bq4Iw2HCWS!J zmxd7emYW#Gt&i7ZEPv5>6T1WRRN47Xn?l~yh4pXj-p);DqYbWL%QIO=eS{=V1*>eB%_C*!=#bJzN}jte%=q&7L3(KEn-`VUqQw4WN$pqhgU6?OESQBLD<#s z9;1exXX4bfFg73O*$4U8;@$rmnXhTFt>f4AC>gPf!UvFgL=<{xzk8^>(|=~n&EE*j zKY^--<({a!#Lga|P9m2EG*9V#{moIMdyf1y~Bj-TvTejvCU?Oe1xIL;xyelExZ zlLu$_-U1zcQS6`Vz><;qcbop+8Q9OJ;|_1<^~AQf{W*SfED`8&Q3#yNQM{;_DM5rn zb1k}r#%k^;xRi1WR*UsnyZt#(Yr$%d=#Tt(M%$Go`{4Alh;Sun3;kxt z+pp5=*YCC{rZxBMm98S=oR@fq{t5etmu11l;a6V}1DcSqpfp+ah|lGcHnWm0z(|bV zF05c9jLwM8Io0yIuaK}X-{&v8E|h}BAFBnQQvOkY>ZPyf2xddawRy9G9Ib+BBOl@D zA~7!6LJ9N6AdNz$je?%MgFUjeQu4O>e&@n97lmr_7Tth5 z)Q-t%CiDPSy~K6lSdYHKqE}H1X<)|y8VOrsJex=2ezj&rQy+Sw8C2(7wA%R$?@g;0 zz;gpgZ-okeA=Z-99Aw7S>6r5xq_0q$Jp+yzc&Sj8_-3wg2SzTCDEe4N>bkZ6o(X<3 zk~ytn^+1xrCl3Coq+?!FT^L-6d@ZLfv#H>Y0lsGXUO(#~77!gt)%S-rQi085Tt7(= z;#?n3@0lNP-=XCf$KUUWkHjh(bVzx^$?ix2AmI=_f{UdSd$VZjbZ4Jq+6ZgRR-CWT zO-)50O(I`;&Dn&>DP)Y*g-`CY9i}w40Hs%z$q5UfZ2M2B(*MlMjI@=7utAgY5ffP%Kw;{C^BUo)uJ7#ym$~W{rI<=g<(O?VKo&M49<#}$ zKp?}I>-TKFot`;))prZMo6CydSVdT%B24I zfCoo*#97z@$@{p_ZxsI+P#i40y*>VR$B_673p$<6>*8~#vrIPTvi+xuhQZ5u>o~vt zZ&Q!F{a00?*B<@0SNQGSP?A6w>Rch@0jO(eq^(yn4`)}3hD?n?RYGYWZJT` z9momj7y{RtoEAHQD7-{03V|FjE)7UPf9amcM5^7o!V!0M+vM=LM&IFX45gq$E3e|) zQHtv|y0ywLrk%YKCHy!pjRRPK>5^wj(<#0AVBFGk_) z$Fck*$eX$JQ-tvfyrg(; zdp|xa>Lh=c~j=rt8Vh)IqH{pG5%o58pM07dHSDv3Q8{IP0P&ZFgKf{ zlKBRoiR99W+7jlL;^H;_`H%IVTBhpH@dG?B{;XRmJ9#Dr=09BS^b)_E^+p%Igvd%? zC{v4jHj&h61-#c|s*CGc`TqUO95Ir7< zaD93KZj}De9gl&jQ}g~w4ZV?9ao_J6z57n$-TD6`Ck6)wV zyk(Glw&oQdYnac?IQ5^ZeK7JL;5C6ijV1W2OKM!Vsziy=<4n9?k|XlwHRNuC=wCPQ zePjN;B$w|@74s4DprmPGR^3q~yM$-!c9ml?p3RmnW38*fnyY&msHGDDeV-m^g7Y<& zvqetesefan>Zjj$Fhly?`qxJJS||e2f~2QuuFj`#p5yFD!a1vk>W?vuauH;(q&`qE z%P2FS{kO|6L-&lPHT~HrtFi!lNIIjxyIzExhpPQ=%J(J#3?1Z= z+njV1GI8VW{axyym3uyGD%iWU7eQ)@M$s`bI6k$nq=SK%S@iy-q=OT9i?fco-M8E7 zm+pbtKBnPyzupvhJr*)$T;WJm3M+q)kl#41o|dH3OMaO@Nb0VZ7>&7Gul9%jF5QNk zPN#M^xiyBLnuP6fiK~dosDW7=n^0*)FmO~Hs*XHOy$f_V|7^4~v_omi*cDOkz!+QD zB7*|&5+dG4JKy_arfa|raCy`xy>ptr%jsxS*OJW*2Edwa&wim>F*>wnUbFnO(QDmO z_J-*zL(X(nW#tlZ;5}Cnxc*;Aql~qn+$u#EvkL7N*8aUy)OVaTT?LIC%N`DstqK4N z(IrW)%yGwrRn*F{@Q>3Ik5_Q36_D3Q74~7T4IZnkC&s8>K=rpbhR!8NbGsVOGSTlX zvk^lcx!B;>kR;NL5$zoU=%&YLbcWm^qZUqS%ntQ2_1M=f{cUk@+JKcR7jOfpOm8`` z7yx^`l9|NUIpyKkJ}+h!mz{gQf#+w6TAk&~_&15Se|G8LVMoCK5;~{}51wDDFu+_R z3;MW8j{obXUW&)%A`b?GXm?r265=Rj%YTYCHR_nUaKDTQB}q~PpjyQkQwK*4#a)Ii znB7CCf}E--XrV#AZ?eWE`^}q_HeH+8Tghw@?|pEA6DiQ0n?F*kyObzTRZT-5>N=mpGwFfqal9aeZ`X1w$uqB7*d>SGZ5frQBa#`K~$4zNPyVcm}1WCKv0Zm1lLL{9E3$4*Nic|kx?IY+86my zquy`lIeyB&3o-=e1QPyM3 zr1o;gB}WC$krP0m=W=92mkslNHsOBPJr{6-Z^5Jtpr#c{AgeQZ0ciLhGIwqmCAhq5 zirtvu`0(%|+{}vt>6e?$yLraSgAA!h0|WWqJ;#m<;;Z*{bPh!r=vlexeFpisf2@35KVi^zQRO*x?O-eLCvC{ZW}^Pj{UXg@5V*B>65V_u}6`t z2XH);{s22hbfU1It#Uzp>IC}z;07RzlahNN<_1pU*o=joT1YOa6D+f>!|Hm;qlXye zi;J5F6e~wHvix&hGoD@Z1D_!87B<;dGW1#EWJ*BK9@H;-njefaDKV1675SyF_AyTm6b5GQDOaFrVV(V zRIH0AhQC}aKYF(bHU(@SEJ3OcOp0uDmG#6gOdEtSn(kxkTP0t8^ti>|VSOULha153 zn9@n3;NhN>a%-{i97<5F#tK+OPi*$dVnp731tFQ?)MN^k{oeLqPq;tY=g$xMw~Qyw zoBxj+W>cS+mTjKNvkyc*QX&DU+R0er0cbb?quIEi8D038v`{0xEG>F}uHp-+(l&Dy ziC-8)=Xm4_@$53hQrafPSLYNP^-SE~FvC0emDNUaVmKJzK|KCY-FTh7vk21l_+$Wl z+1$+?SDVMnR2oz|e{M{9@6z{yfwR5x9qb4cE#*SUKvEpcs=kp^Ry@wD);3<{THVfDBAm^(BvtY*eQFe!m;rNJ49`7Bz2;ulg*KR34DPRb{Uy(-vQL~5k!=)~&_OD%)WhFUK5E2^44 z|2o2}OqhI0!wicw`&U?Si(HBiJdCJsh3TrBU&^o-)fHDd)>;*DiS`wg^bF&}3xhgU z4OcRjY$rAvgPeoGuZu3dx6VLH;SZuc*Ah$Lo51=G=Amfc7igyXLq z0q#(ez;8`ig1mxQ9)ANK0h+EADkumQ9Eb7#f>p_x#s1BCjz8w;f<^qt?qdl*DV|P21?zhEYe;TIXL>}Vurp)pyB8`AVc|z|f){;+{qdx)VKT{lm(0CM zh=HYu=h@$&tZFzN)Y)7N9Pi*2*#)x#wv|M>=g`H!v^H`rdEd*?H;nsan&d`Qn(yQO z$Zg}#@1uq#O_>`TMr{3W+$xo{6IXcO6OEG5Ct+SaU<=V$ds_8%NA-e*#0x7&tC#6sj_dB)MD(0%*ygu?nwODOklpxbMP^#glEf% zGN#&_Dsb6XxyV@(;kr0OYB*aHHgD@8X`v}oF_;x+C0C?Hh=UAqYIDBhd;I;k*`4P# z4C2-`pQPAz%axo zC-&ej?hS=p5M>Nm=+-yhN0B^sOY9=#po8>zJ>Ex>d>d)3C0ZC+cPKa3HtOvmcp=dF zaqUXpM?1!eI|V2fu`8a8BE@{`bh!<}ma8*4%W`(>e1&l&#wnlY-EV}ZqiA1y?F`5j zG=x3vBNhTf?6|I0Xm{5si0q8dx5on_dfPbY6aJllry@HsNQA{L?W}Y}^;Kv$OlSk8 z=hU=wP6bR(YEj$$+?V2LPaxN_bA8NYc1#CyV4wXqCm##RBhmK-_etJP`Eq`Xo|bKO z`}BbGwDA)&?K)ghx&htXM5>Pc$W4Yv?xN@1ppLSKklVY)(^Sd}b{sZNH#q(OJyzsI zmy0G_rjbY~qmna-gz6FTPXEsOZH~;-W>1jl6^eV=n?9Cdz8{IfGj>N~G96>dtZ3PrfVk)cSh(~OI6w(89e>mBq`_{= zb6zIM5HPS6EgOoK!j*j)>+9TxyVFm=?%78ttTK_(2Yx}sYC95>xKfQSj?0$*4t0k! z$2_XidEA&_+GrT@MU=j}Z|QIFspwS|k*uFX(}Ewp;`*cHO57@W*wJd(E+iJF@E@nP zy3Nv|v}7H*-g;qSF5zDKPqbYf#<B11(Y$t}Rk=g(q0X91F!z9?s~z z-&{a53wf-eQ7lN75;*=NT{+4M51NmGRVLxF1z&O&kBzHA)4y9$i32t|j$O0fTEO5f z0v?WcF^-$)$)S(iMmk{`WgQ*r6e^#%Q=!n0#9oPZV^2B~zUWGmTIY)}l1I@iwunkn z5o>fX>Di!s)F{$|?4QdNAz!*0qANqXxIK3snZu9A4`nFU{^I5J z*^|~Bk+?H}5R=Xq6M~NrDsW-bDCG~!x3$Q5#X3LX?IiTW1kN+K=ejGw1;z~_@c61O z3@rQ~0J}g$zewGI+j;9hufBCpe@Li@;t91Ps|2aA98Car*n;QxI}YXA5HZuTGNE$k zFIpeWyU;SG+qRQ=j%#A5 z<0>1H?pw%qpj-$$D0y4aLWFFok~zDA7+P-*eZo|d;?{mU&dKPdax#JXoeJd9%V64G%i?JPuh%E`rXdly^q{=Ujw|^8x16h1r$rDGHUkL zUe{3eh*JY+wFI31R2|Ul(WXuDXF|n2+-W4QX^N;yio`A&u_U>;=eev>ZrnwTUC^0% zOWQqnjIU#$Cf1p2f@cYuphZDcMU=Vzpq9-rjU_n-on7`@27Wjdi*i{8DkBq;B&q;4 zIqhLJKHbElJm1-T#GQX7Qcxd%7^lFdDZs1YVgTMSx+@cbNt77C)A=Fvw{)}}_{?zLn^kGIZE+}$C2eiTn8qHFo zKy3JW?6-rb4Rb(Oy7yxC@avI$b8G1|IVIKf;59SjoWs}TciET`T{FX}G);yqUp0b4 zH8ZQRc%0yzxH)xZm(DdJSFc{Q+v^Ux7?#_ySh%5XF(b`Cbn@_zHQq6s z2JqC>K)9s{*5w5w+xt8opoBs>@0t06ecF?>9j7lpln5}F99S_D!MXpy2LHo?BSvh;SW!SCbFKexSbh(A`!?zV zzv*cOiz;#plx?B?`ISf2{!JVP7W3`jph!AGC#MzI6jx z=a>7|mJ6S^h1W!!=V0sW(KP)vv4AHmu-;$3?UAZgfgIKcwT(^S0XeMGc#m^buGiCF z%2Kk0{&_h$UY}o)6TMXz)hbsdv2&EWP(T%U5bO*>{{wzdr-b^tq~%&zRQnKWC$PZ* zdH`pi22lTw(o)EUBcCo+p!b1eP8TRe0sLM^ME!3RbVPE;h1UCiyzoq?xqwTI(~-?s z#+j=G#IoLb{+ztv38kseo~@qo*}^oy94}qtRT|WIp>?u-<=oDLbT-Wc2hLb74@Pq&#;YXGVIsWOLm|>|OfOrrFWsAYxj9(9YbAyg z9mmP5XQaC;#tqqz*cAI8H09)_z}`=vJRHfj|7RWlI-m&8%!%^@5-TCH*8q6FP`bkGJ(-zmWdWuFgy z@3k_PkoOk04dNeHBlzC!Ej2ssUPy)57EFP25unG9me3S_|8Z!}w1@r&-@Tp?O8@Q} z9rrjw`vcH@Yz0e5f7H@^EC*@jSA9{)3=2oQuC=J+-^__ir8mGVaI_ z)}=XUs;Ve4D#r*sivz!8*em&)l+e^<($5?DVgereign<^i{Fhqoxjy%heayY<0NI0 z!AMo)NmhP`p2%`r&y;F!j!v2^Y3a12B$Zno68>ZDyEF0??`))lE_@)ZzCjJ|M0{ri^^5sF z%C5P@+Oxy)ceeHelEvdX#s-Szo$kO%fPjCgOX}eEiX8>FyTR%yTbG;1BuxE}4YF&c z2;Y-YvR1N>4gR019X;82oqTnzasCvC5@PO_5Ehm>x$M`O`R+Qp(WMxM!vq53DW2nZ z&RN8d8kU;^R){kDgL^tdv^})d>qGPK05+SJT3>(E{N~BSkxy9?I89(aO9ERXCOM6;YqQ3S0BqzsSGO31!^&)Q-0mSNU> zC$4tr{bo#H6k6hBlH$YzD=!bN*O^X)_Yy5Ph?$@d^%SjsMP0hhQOap%)BDz+>mOX( z;f;D|#t?L-@d!SjcdSzkoCRS8K~`}A9zf;nMpI&g^_k~Bx^T}hAV)5Vz=k-MP)J(D z8IojmUE`}6P-h&}XxPm06qLUo%G(S$0jS3t-Gz(N`HEKxuB_f@{kzJ5WkWU^s0G20 zDV)klv?xj(&b-z0qops683V)W@HALQ(+r=-}>rt(E!}?sPoVo53jH$)1g&7r%+_F#-KjaC`j~N-7U)C)&zqdmf(d$xwsJ9|pys@aQr zuC@$}Qe=--#E&&CY1N{*?TEvzXP*k458qnTFTDKhnvaq81(l(3S|lk|z*zV&+)8aT z-so(50iWD$9o3|9Pv0h>yPb%jyAg~?$Q;J;97A2z-RA7;m&FWEP6F$4W9U>DoL>jE z8rCd(Ldsc7h`m>U**%npPC=U*8LaED?wEwo4HHazt$n4RS{`48K8QhJ1kI5Q%is(~ z(;U3+A6pkx|CVjF==Q{3tJwBf?OsWAxI|pw$VoYK1YfQKq5|BR>-AjyhgB7l6(yRK zFtWLeB>KMJx@X{IC}__hwrETKUhDbHy62kQA5jA2WLD)Qj!|Tmhu3EjVRT9`9k5P* zXU+?4_eH*ofyEV!pd^K3RqoiayZ&mC@lg^OQ$V%^a!x;}5sHLWN@5 zAdGAnC1uMn=EH}qk3GqczPt#q(icLAN?_F07)>wrs`l46pHE7Jo&O%c&g;pONCtn{ zdScJPLo+Xe<03Q4gfkWrXM>xzy>kKwCI$D^NQeD%e2O3k4eX<*PyOQ96Y~;EMJbS! zy}|2iQDcuMnc@(KIK&|iafm}4{u@xz@|yiR$yIUtSRCRIhdBHtyh$LWC}V!6T$El z$FU5~Q7l1*?afaz2dz;bAT~HHreoHpwmrZ4^@EW&V>tomWl^DJ5#!;_*xCloIA+~d zq3pgcpGL+&u#`$O0?DbQ0As*Lm<E`h(nTpRTiCD#-4_*3j>BY2+BFa_QYk#z; zP}r_pSoSoVPFi2QaQVSkW&y{5K4`}Pk|R`_lX!|zAj}X{KR?)g!dWbOA1hU zii^Lb6o9f3IwI7-e%iXe_q`XpEspGXEKnjN3o6B{5(#f(c#%`%oV9bex!*rDtuC5% zg#?qMD2kFrN>Bs^=ETi@OEZ{|P2mGk%Rg3&{RyLU1s zU?R_xBCX`TQRnx&x*ak@K@v>(NOQ5cg~fEiIxLajwKf@$J4ct?Jk64#%1}H@5zm|d znt5tng?x_okQV#rp^8C@R80s?H$(oFcb!U&sg4({DT`Kmm+YyH9uNf~2N^{aWQyWd zkqGk&%0_n8MeF&FM;Z+&3l@)BcAc&wX{Qjl_~~Ay<9Nxf%{&_@K zY^hzeDFc5u3W~i|x>9W4!vKE|-fxB2_`JKc{_{aKiq<}WKTA6d*Q#!G?E>(cB{JQk zrOU$&qARCiF>ly;t%!>s5KHGa2u)i@xj_xrdu6hIeE?mWrR$e7w?Sk)7ohTfyT$># zO06-6v?EsfE{87AFs}*CgYf{DA1j`vj%z!vU`Q)rIvdUJ3W)gy#*5LO_oyguz;bK# z7-n#i|B34Q&xciy?t4&g(2kLH^~DMlJe#DC_gMpD=S9TxM`E0ZM)MZ5zh{-`%Kf>B zH=@K^Por@L|Nl|t*y_=$?#*GVSbl?)p!4RNtDGARu?7S=s>vk}M^I&~A*xK0?W8Oa z6?cPyw=OZFy1~#KudoUw$^`dT*E4(5f8JuBt$jN7Ut*{;m(cqgq7@QKsNP`CCPy$V z6t)>wM7#&D0U6Mu4cc43gzEj0xoGqTvSqEC#BdXbn;QyrX@|{&w_5FeI$|gVPGB*f zV>pUdIXIL8kh@?#4bB&!OtYy~y0vZA5xlU|tb-C0Xi`>jiB~Wc6`b6 zApH@_!;%NAnZvi4z>*Lw(N;llk!bUwjqeF@U` zLUuQg>g}ygzEWxAkv99Sa75^Yz2=ZqC?R4_umr13Z%qg(H~DT;NPhIop?HTtw?Lll z6-=L*8u}hcOKUz;*1_=sd^1fViz&l)l1qI0TOJJ7bEdGtdNmfh*LQ~?WjR$5XEB-~ z1X*Be)Y-A-?8M^5yvL0rNJPUcyj zkSQ59i2tIQe?i#2@2Z97!+WQSt?mBSjqZOGka%l&?_}dV<5b4^6_3BSde?jJPCwFo zeIl@$PW(B@d&)z;B;YxKyPpfD^S%k4*E>j4#JqQO@x~$jxqi#5*xGJe`Sf}bw0oQm zbLVkf=6I2$Nd|+N``2wl)3U8Y@qp(5IH=2EVY&mNhs>txwzQ8wzHr7GDR8nt5*&x) zl7I^g95}fFA`Qn>%l5!!tlHV!Dv;zIYN8ftnN>Iz7bOf6Hr#P1zqW@RjK8;|?3Jxh zcs5NrI0|EFjujY&l`vG4xQaKJ zZC8vkU$Kbwu2jPHh08-bi`TI&togNj#DfueD@m~&O=Aj!GhuPtlfuL>{jpInpX%A# zH=lTT=PS7OWSt{yY-oYV*}y%!cVxT#e#PfhvR(f6vNy-?@?`c?{CPf)Tt}`kXw2-? z+*{04l4^=WoNhNC*r7C9%UDvB-^JnIF+Sq$y*}k$Z+aVvdq(^>){7D27b(W+5%0Gf z4~f?aPk{>1!1lo8&%ga`KXBvYD(IM3NCF%e?B?;4M1Qx}pDS*9tzpk!l1oj{RL!%R zETUceZn`bTwmhVz-83EK&rRl-SR~Mx@6}S^E!_jLE10RJUA6yTM2sC!Zo7m~%^CnW zlKy~J-A~g(MYFz|OtwwCIb=H#`aL+s+(%3Q($*RuB;tLoA?~epP`82lBH$p%RJc(~ z89Cv>fnW55y6t@3jKpD>uM*-y6s-@OOWf*Z<$B9;kXn3!vSZ2sU&oN>c|w`xi8k zuGosEO82qpKYtIwzgj{L#)qB?O@EqXuvF9x<$s{Q_umuwyW@zEGe@1lv=vmJk=i@~ z@l*~yrN1;jtMz*;$mF`RmVpTWr2g~K?qK?3dg#1}7G8f!pUyC(JrJ6IR%39FY5mD) z>t6SV4ccJ|r5}KUPldL#QkFiQPynt+J(M2Z`*%cn@9R*+{S{Wk+1~H9an`z+A`aY- zCWE)(*3xscp{+{k{4ir=cUO2?`Ac$ zToi!sn@0KGdMyOT1%rWd2|>3@rs%!{_}3nj>8%Nn zoSX>h5dcO&?;o2J`af34231$9W&ikx}q`@RX1BzOuZ zDURnboQLDpbvB2ZieS;8iEZ9$_VBK}e3Om7%gfcfdYwFrk)*^DGC>Jbp8N5``8z*O zK}GSY4yCC7V+GWAC1uWr%RxDzG_ehtvAB1qp^`JFN5Esri_7;Ps!E6pXA{y!TP`u$Y?LcJGs`QEkvsZdsD!NQ~rgj>9k-hsWxV z+PqN_QEr>tNe7|wNyAmrCjEv(7A#%*)FTL^8 zugfYrfRDlhn$PFL$r|I)M(MCpp?Mg!#dN#v33Wp+cYDB6wnmOcBMXcu5ezMm0zPWx zrPSKag{G(D3&{-uTbu+@N&Rt%LnQutyUka7!Q%eoB3@gmvKWoiydYu%4F`}-Hles{ zE8Cp|H?)!FRE7*FA!m1-Lax7qH_s#I{x)a$A^P1bHh5+?=&$IeMQw2_+x(vn)_r(Z zWRrk<3XS@?QNqH4nO(kv-&revp77&Dj^Bm!S-7D`*WT@896RC@C(zq9tTQj)@wDn9AT&`n3u52S+K0$Ljg?`Z9UH_E9L@4L&Qh?1JLPz=D(Cv5x}=M((&d`f-?v8Y zZjO;;Ql@cH5W+6my%$maF1DxBny39UuX=dp-LlV#)0{I~@GCx-;C9`sI3;g(w#%QH zHTR8aK~H@1dA~Z5$C&DJ z2hM^}gv1M+A}A_@v6e-j@v|NyNU5zmcuY4|(zFdJ<9 zaN4#F51oi$8pCM)gjN94un6ZS(Dk;kp z&0^%Z2baG1Wu+mBo#Js&;_x5PeJ%g-dcvyRmg3G)d;`3r6;JN5 zA?)JB4!C3tZk=;XjU;zzoNACsm8%nOY)zW+)Pkxwgo%Q4@}R$3^YM+CNLb%ND8O2 zGATbk`~2m#riMu)iio?YK?rY_;KtWV=``P9u4vRxmKf=2zDe6#a&yORn>Yghd)X#n zZco?Y^dwI>-QKZ^?~XA6qpRS{RQ$b^vHezLoGm@4RT^M0d$nztg9tBuqyGVr=GfkU z_$DK&#gt>~&~wS`+g<<+sjL3g40yiBST!;U}SW83Qu-%6{0gX#`Q+ziUc`u&Zj zeU5E#m7{guF9Wv8$`nxk>fQxI7v8(5-FJ?kA8Sbiw#J!STBn$7K3b@A(~Vre?M`?7 zrmp4rcX#V9rI;vCGDQdyEs~2mS$}=hESQEP${spSpGm$W7VYY#S=&oy5uMXPKm6ZI z|AcNEG)4^V{#0-qqU=Qd|Np9;6jOZ`EQ@@$JL48JP*pPhyMcpZT${BVm_d!TKj})7 z#vI+b)3aG~iSXOnpk4NpUl6VxmtF@pMp2!KNJX;4_mEpD5wtVB#ErMxs#6I3EYD zQJj#oCG=f76g{&*2N`F3@vr6O6Y?!VS-O@W660}-<4BGr`A?R-TQ1<)j2fS1N=%@* z*EXl_tkQ4n1|#fHc-^vbmJ0*|9xM3d0p6}DZN9vFT8q-@MMQ0mF@L>ftb1+Kxao`V zyTSNhQ9zIampfeMNtq{ELgHA|U34mxnh^PM(AK(WX0FXC1iTMxqg?Fdtf2==(>|H+medN z+Y4&&nC;j36kuPmso%^eV_f%oA zpCUP4QaPSdcvvF&#T=T#I3Bl^Jy=EEksR@4gT%|SNXZ#qr561BBJUN(N(a8F;hUkhJg@3NiKB!{(XKR6B zdtT?30D|G1QkLLr?;q%K`TLJ``K7FT{%pKr`l<<~hu!3QMRD=n>>GQIo74mgmYuYJ zoe6ZlZRVh^mB)+*wzs=s4T|gA}8Rq)0Lg$KDy@+^=%=a#7E5fDGJbT zYYT0&K6z>09u6>)EWJx>RhH%$k;W8>k)642@7ND|9Bg!^=KX2FxbbR~sR@x!hiWiYN9fcf! zo=?ejNF+hu)X_Wk!pIOEB4G)XWR!4U*Y zhY5cf_RthY@s@3H?Zz9YbHL6tQm=Is>-2bKrM=(3FXhp+bqcORkk+W!p?W;7`&A=!q z1YZXzR@nO1Z+nzm6G^ASI8~H*hEZ8OOsD#(S}0EtIx720U9@eb@KGrh=Xq6DRZ3*&FJ8-d``E#4SyAPYKce2HH;_z3D3oD$Ot)r7Em)Rr{%Mv@RKtUx7`? zC>|boj*5r(j;+k6A08d}eq^f^hshX&V-h2TS*_j!tXxamAky&VV~uU}@FwokccWKA zf}nAd3>;kI8IBBF3HNJ3uq!9&qgZ3>+VGp~GI#6PPV?nwo^-l$d$>GG2cII0hHa-C zt*FYsZ|gC5=cB{lL0Wz(4992|4+#D&jdO6_9h%c*S%k&%ecQ0_9=hk7Wf|eRS3ndY z*$_!2Q}NgR##|v_lm%MWR!z=-Cn+?IeVy58>w53%jQ83BGCreUUzQ!_LTi0W{B&Dn)VvrLypNu8Ju8V_TPDyPNbH z80C?4jKw6G#TlIAB=|@=fTK;WkKL?*JfGV3j60lgx@IK77biqjA$X2fF&Y+pfe9Q= zVN9ReM((|R;I1tZ{fIe1B?O9P1(~MZ$G%!#DgCwZ(8UizDFpCEyO+$7-$UgKNVLT^ zbHblXs*API@^pTrE?gocUL<7>XZaS@_N{tp+>lfwmzN(j%-hV7|NkMzvGHmV$I`1& z9--Z0tJ!z<9;!n{9Z}k&jCJ}5fmSe5QAv?d{;bn)>d1@lI)IS8UXyLbSqV{crERtu zANVG=O9tL?eIlNfrUP42gh(=?D!AYJh5D%T`=)vb9Dm&v`8n$Un-wmZOXfTD!T9aA zet9$M_Rd6a;gUSbQ8>$DI8Cr&k=iuNiM5VGCY>TH`=Q&yp+92#dilIJ;`qiC85 zlN6fTjTqn8wpHWHR&DtV5(P=(7|r7>NpS>6aTtuEo_4}xA_B$NwpH&weCD5-ktG0} zmIDenf#I}LTnV6BjL*@;*=HlFo?G_OpONd%usFr25<&7Zjat!+_Kiw9s8r9_%!E!)U>s?w?s%kI3qNy_nv@L;M8tp5aL(5Av-I`Fq z`VZK?y=TF^8Bc-mQH=`ic_~f;yDHa9S&R2qeqzs_&PlaPxFQ{nCxxU&@4--NhQ2ex z7ATDWXRqty8`CN&Dm>8>^sue{BlS)^kJS!WlnMoulz55aDODnQ0eBn1u~t%`6mDpW zDY_-aZ2HmmM62Avsb3?d2_BOfoR?IA6$n_GXrc*&dF7*}z$shK_zh=wkBzJrP$DbS zDk~}+DZ^^P3?%nFZIg#e!*k&5)BSNyzck1Tq!`TDJhXt0OKGlj0KYI;UyOC`=b7DHl49u z`F84vzCYx5PLf{dkSb1yn2f7}stBv^Z&`j(`P3JSX?cX92V{jpO*|a&+{mp2_OzU9 zz_Z(${+(dHMd%-GJ5c=42-^Nj`?aX_fFZ%4e^CFo(kgN%lm|NvcOrHk5_PQ5rU>4f zH><2wu<)I=HU8wK%iDedymhO-VN#)0Q6gnhB$$m$AMtlOxG4#&%rk8IDd6YNLeEiv zy47%o?OL_{obB`o|FrhYqqW%atSHkAsZt8gE9-tK>mFb>(r@ZRYd(M~CDw>vZEHGz zxnutCpv1lUl8efTGN#Zh$+2(jS@-(sX=AKFUYMzk-sYCjJV?)(L-Qz*5L|nKML+*X ziFfE9wkzL^7*p}N%HdL6;0%#G-s9w1=a_(L+9`U+!6KXmS@55>O_^!OiIzY>KCg5B znc^|0Pm#Pnxw|sHpOTyB_V*tB;N_>jt+#fbg^zLpLIF_v8W!>U+YK01tPD5W-G|!| z-zON<^LB&pZx%q?#4g%?IEVHA+>H+IN--?WPz;R`6p7q6dt_99b1WF- zI^l_RqwR{`ab0}gyc~9a)o(X;^CW>a_}5mYL()##BHbw^+Ua-6mb3TC11-9v&2$XQ z;4H~91kdn16Q=2GuNtIb*Hjb=lYQ;%-Cyp{zlk%%yC~0zW3VeeW>{;BUAQvixvjkb ztKMls7?M45H;=50bCQZIVN5{z6uFUe_&pQ9GpC`h8Y=^#ix$clRJ*7=(pd3MwQpL| zeTMB4SQKA~yeQHtr?9xhDx!dIT=`gUwbhs;Q2Dgfn`4p|^mS$X>K2tJ_FJzLRy?RF zYdhK;;q2)31RnW2W+~EW#YdBmpFFVkqx_AoYKT;iz@g{xs@jF!+e>$QwqCepqrfGV zr=2-@qT7|5)!FCu_!W=bQIN8f@(s>9e_K{D-_Y@oIaUf(vu7`;{^Y5jk;Vv?#95Za zSQ^I}77GhuJ725rZ}m_Zs@tFVzM?$3A_%C&>9r4Movz%@!P9(-JRmQ}t@O_cxELqz z8a6q7*>j~645Y$vGfNHoiRyjRr8bd`)QS+;xMUbyWrSD_?3O6C$;24PWevOI)F(S@ z_KjR~QBq|QS0$0a`QmO3BouRbFX-)>_Jhmv`AC<2dr0H+52qed=oItDWnmeYwb@q$q zX$!8Ak3i0KQeLwS{=ersjS-{8s<=KXfLqK0+W?R)q+$`vgS)A9O7`xL;MoHIGy7VhxmMp&-*z4`uDf{S~(lqP5*6( z-79As^dLA&*E?^jyFpY@s-r2ITd1jAG}w(gy>|x&UBCSi8x*2 z5C|3exv43Tt|$<_2|cVf5ssW z|6On(QX{w>@w_^MuQVzm---6H6smlS0pA1VCqwNA-z6c+LE$%ACmTWd%m_RdM7=U% zq$7$h@J;F&5MC4=qLpuwq3_`GGNPobsfc*YC4?tdIoN{eS4W)H#NifzT7|~0O?Tck zvu~$%%|7;62E<`Pt_o5xoaIRh<4Hk8c2_;;WqgRnOi(c}jt~HcG?LA<8y$(0T#p@SF|YCx2oMlfZuirebX_ zcSw5<{xCCR5*PUlGD-2Y%+abya_|}CnHr`i0kc5lVM>`zn7#W8S8_l68=&5+ z*9oM`%ACN+l1wx6mmFyL*6pX0Vm%#i6zunU?UA74_YwB;i$~sZay>e*CqXj=gE1`6 za2Usjh0uFQE7*agHfDzDTGy>Ne>fTB!lM*H41S8VCaP?ZllS7`^S9#fUr4ylX zk4nSwGWK?dJ50*h2&PyWN7#G_%r&0J2!Rj?T2-7L!RPaiRpfqxCsQfgV{7k`$E%r! zUn4E=x9g-mHZ;J~;U;086>g9=eY5m~5j?Mb|Bsfp7g}_pfp|a1`}tPvXF!iw>F{p_ zYYCO}U)gj09Lgsmj1GoMswS-sigH|kU-M zmnDFyx6~Y(noRonMv%=79^pV0{n|1 zvbN49WD7}3;0cyzDF(wDd^u=B;lS$P0x1y6zN|qDEv7d1ee2syc;Ym0<>_P5)17dd zRVa?cIhNv9?|=Bvq_)*<9icJ@MLE5|dk_Wk%>n?bBYNp5^#2Bf_oI#G-^L&x?f1lg zoU4>IqfKRcuJCQ_^G_W0<+2sS4P}9VcobhaA|7lcn^p=nkJ{Rg_#Tfo zvz;W54O~2Y@m<{xnQaK}V8kV&zk0ia-S^h0c@M2dUoe7kG>hXTgJV2R5n&gM+@=xm z`(%9-9qcQ9YFcSkr*efXb)jZ1m&fII;yn9EzvLx_FFrq?5V^4hjp%6K*soQkdu9O- z4xNjuvk4w^x^nv|lHwYp^i*a>8*qBmA?g+=)zsp(Vjc6RQ08=N3<@B7%P&5z>yM7!4s#` zVmo5ubh7V!%GD%mCVI4z7|G%!#$XtS;XDbCR)GnocbQRh^A3B%7LMN9UMECzdfGEu{*15v5{RWadg2&KkgOz$s>;$5N-V7ar6xq4 zgQlOwK(;8L`(QgmezIZcxl%e+Rh)!&*YhhmP1Yo(_pzcvYm93f#5lR7IVA6IZwbi* z_#?p_l7{!uaQj;oYyCB{7uY+Rp&yksaPJ%|aJ;ONBFiaK*NLrO_I0wR!a+2E(Qm)g)jrOML;r5^3E?;lh@Y>-kKIlfDB00ypWPg^ElmtRlD2AgrWzN2S zd$a5n(h_tEjm$|#+83-@*sRP5V3j*>4d)~@g9oF^w)cCpT~cmG!R?+_cEMj&zB-ni z0il{!M4mbA2J_m-ZZTg#qf2IQc6e=asP*P4b7*Qb9#H?o)9M?4`z zUU=NBL*PL{V~9n-_?|@6tEl;seje+zf!hT?k2PP?&*A?q2JL;yTs*qN^)MH)y{4OF zd=wXoO20V7ArgO$b-bkOTo4~DN3w6&Agc|A0Nrb1N|?l?*-dcD*~8swyeDwib9ZHY z7scOIapx$$POH|=?}6hMVDkh!t#H}}s#6!p9vp|rfFtPFvYQMB;+w2w9z{W*RxS=JR;3=n==W_d9o}OMg&#iRydUE}}Rc2)l zeKh?*DzNT>!$1{ExU~f~no;((+~pQO-3NAa$Aw$>RF9JuC57Q+f{+&OYo zw-r@tvgsSr8r!aKk5{_J@0WaEjkZ^6mfo=m2{!EtdGQzCw4+)2k-epXlpS>5DF!8tVD_Aft8ns*6U0s)`1I7zClD!*3YA!8^QS^ z5+42zW!*KKy!L5NG++Jh_Nv;GLpr;yG!`)O<|_UHf-h6?r;I+`8#~wD#z9CKXp!X98cZJ3%FdNsbVVm_(y-ftK<&P};d-s!D?)QOB zzgLDkyYx7>BR4VsTfcqy<8|u{^QCJ~!nX%mj;C0P;OVQUqTt=Z8W2Ik36y7Fzj^A> zc837FT8Cy=43DXzNHZ+OsynLhkv}fI%ZkTG+D%yx^r5CP_P<|gmN=_S{qP}`&n8Ky zE4Po}Q#}4GCD(PYA`cj!qxAC1N@mvFH#!y?Uwff6e0+_wKkVP5ROkcT5MBs(mdo?F zydI}d@e7$wQY3kiWkdzT@(BpxF_GhTU^n*pem>NhFjJogMIu$?l}b>Eg~ZjfU zAu;A5`jma|=qH}vkpLRIBn@tg>TyySrz(uhFgQcLo78Q|*~Pz?S{dI2iU`;0WQ09V z+7L*+!T8gQ`F}}4w9_%=Uqbhr1=8arxGsz*dxBK_5^(EFvM>`&DDM&HHxS{zDp{uQWp)9kI^O4>8D0x;L~(Nlye zS!Q_xS1F3bfgNz56o5KK0eMHMdQzg|<^%Jv1Xo0i%T1*Wc~+2aFQQyM|3p zU-n#SV32^L9e>%>jA;HW`^2s7PEW29F?3SEHm-=cBvTx(8#<|)K^l(tS-Yq9=kgob zV5e?HLU>CligJoxd3;Y{d}qP!9+2hp=4EDOO}l6Kmbu$Y`?N1BYbm_l?uYVCG5w*? z9Wg}86;q|?3FRxtSbnVUlg0esUrhY|81aGrDCu?rn5*roI5j-L&9X664hhx2F_b5v z`Zh+?!wmI%_bBVx6H!k0D95Q8QBL<5@s%_b%=Ja~;}vS0dwD@b9z#&9gyFP85Dcx$ zW6mW+PQ&rOVBhlT+?UR723%-HvJN*5FmzY0B=}@S?&Hm=^LFzmU#&XR{u$JoG6<^K zAORA@*9Dd8tEIncD^UaOL*Sb0gchpK2+KpVL9dO5X6-)BXX90`_AHe`3H6dP6JwQSJS& zVjK_Eil`_>3nhKX4Aie1#APa$Q2Ah-f`B)5w4LQ_jZl;}F{)~skVkv(UM;PmHFyZ% zFA4hn+0+u+k6`|!YR^|ggoERIXF+-3wDC-g^L8XuuMtrS@F>M`B!W-gDuC7rUbL@% zM!dNE0`M(a5y2HsBt;q{2}Y90!;igidf1h;5gl)Ppi-1%gs{zvLULpnrEqG^?*RfeQRf5d%_T6sL-Wz>6%8!vU%GYXV6xXoZLK1>)PA_W5Mz4Ed}#RnH&F$q=p278rFXZ#ELBZHnvtcVe$D&VrJ z!eQM?MJclupxb1xK>c{3-S-j6Bq=zla1<{pL>QUMA&cH>Pki*)AzSO(;X~Q)6C?%r zmvDr$TYyp>y`I2xe{O@r53l>ZELSR4TMjCSpV{=e{g>WXy3D%=%&NP>dn^znNaZDl zRtTC`g+_M`Z5IfMm4C#%%&4pB&@%GXbY+>M&8|e#m-a6BtFM+7(4k>Cg2QN@Bp8n4 zSb~O`NK<=c6kpoco7Tc9OYs<&_4&Yy-*2y#RKx|*R3^&*V=W&g zf_!PWy)o^zu5HSNlQ1O67f!*BjeltK4i&lQOZzI*jw!EFpyV9=k|~;c^yuCkEaRgj zyFtk%XSCR9-*s0D>(P`9NHGqXsu2%I1>!;K#j$6yt9MbD~;}u=W@%64~_u2bXYJwZ;otMDxmN;_WK;i zKFdC#v-k$$@8LLAH~^nBQ}OS;Ejj1Wi)?B5QW;>XJD{bbqVDDI?YC8$Ibl$T%Ha&z zC%L^I#p#m-Rpu~`Bxw$Z+3}|ws^A0mwtOt(K=Op_Vc5Cf2V);&D;&f4~8B;GDZ=+tO}w?v;LfhQ(pgQ zuo;eEKK44@Zv^83dDL?wI8a1KKKMKI_&93cu%dah`<+!FfgW1F6-kx_iQ*KJdCf9o zOn?g|L8drs%$@v`ebLgH#EXrhU=lJdh!`P@I7=HciB<;rk^&T-;^MDSLiMW=wT*^IQoduUW&j*5yIwcXiQttO|opziqUDQ*yjDZDZ zV3h9}K}M-)$#TZN?d)UKfBXS(lS3xm@u|8zvQy$Dj8!QTCj@NbO#jBEduJoPSQe0x z^QiY1#CW87a>e4kkD`pl^s{~8noIYcoKq%TY0vfhoOyl~XPj=u18>YvofkB8e!B7f zt1?(^S9M+9@Uwl!8r#w)(_4j?4@Gd{oC9*%{z`7H%j@am^Je;l?0~4$U&$6cewWnA zC1iTMxqg?F3!hquHUeLQ=~w%}#s}HLX zq^xkFN;0sJ`^D@uM8Rdv4`zOL))p>VjT-ADXnys>V`t|qSf4LFZGtRQpT0{bEulR4u5FZiVF0Ql>Qfz&zty0i zlP}tzc$t3dXk_~xgYybaP#nk65F9zJgHvmSYr_RYmZv02qy&nQDEX?B zM1`Yu$r-~gCjgCDFo&ivfafx@9u`NFRo{N_Si>se)}F$VJOrAeaU2&Fo}06;AJ=B7 z%>stS0PROhq}4USF>Ux~7p#iE-gK7IX_r zw;#{2>b5?`u|H=FpKOV2n`UuYqy$A0a8W#;(#&yu(;71v&A-QJjzx>tHizZJ2Ti}Y z4T#r`^wqyyZ?@uOWr7lEmc%h$xm>o#K<{^Nn1R}O34!^NLCr2j`JNFRxz7CfNYs|i zG{?xFx227Ey<9j`4GDEaaHo1CQ-w{7MGbXRFrP~~TGm}#>5U`#Ap(+g_N@rksf^3< zbb40I0*G$lnu_YsL5b7a`~mJi)3pG7RkW+c&U_V| zXb=7`m@ltQ^&x=y^be*$!aSnY+oJgeU($%yTJzrm-LnmLYthR65E3!xYH2s^{U@}1 zeQW3z-al=EFM#Wzs%q&XXc_po_CqE#j2CIEL(3__^ySQ8eOK?$E`({C5Yj)Zp>+~$ z13Pe|Nk!+1+lLD@-T zxQr86Mgoyox(XiIt-z90t-FH1K5G}6h6}P~9TR_jvthddwId1b0xhYsB=Z7KtEhWH z$`)Z0FVrGc&awX6Sv#I|>v%Hc`i0Eqd5X-il&X+2ugIBMbMK#J&$gk|-9`qhTR8{U zyy|vhNksjcl~_R)Nr~n~8rH8*YsSn!wfX+PMxps|QkQp}YMd&MUx)Sv;#i6yNt&W4 zif5UyC{8pTOlKkeV=6eNmwF`Q^-&S;i&rI9RTP33SyF-TizmMe7S>Y>x!&|jkdK{x z#T1ecM_tL`KXiZUjJ6RRkpg0=%!#}#2(&~fLppX@_rbf5rhyV4F1B);?zr3X(aY@f zpbj7C#&D!KM!-~x78x3EQnu0gcYYsfrA(n*8-`o`6nwJj-vtGIp5Yku_6rw&tKJYy zWRzu>ph;)HaeV||uHth_S%R;lTgc6=Mm;*VeY^S&)J{|tNWrUu_`eq7`L$|zJt$x8 zhIK$SN1Z|0<*jw2(E&wXW++LaC4m;#`|q6ncjN78KnMI45gGfKLBwqE-LKauvbJ31 z`jm^-0c$ySb$ZK`dJnL7e1O=*^7`%&q%5Z@3JQgBv?%fXmm~KrY25OU6xi$T_9FTc zNBiA|82?9`7-~B{>o@3+BVX1-Tl0dJHqX^fO{c2jtjyCaM+;%5l?Tm6gSn35^&T5v z{9;0l@Ew7IZB&eLsxG%*@dd)+4N%7UM|^kJ@xo3Pu)A(M#Pi}$zWR<-=Izh_tPB?X z7xc`z0p~83WEcsfBze@*duC53lVO_=5H}klQW$m3Yi31k;5a@-C}%qirpF;&K|*2) zMU@prktN>r(ilLn6UJKc>tECdvJT$dsgA>oQD zkuoLAe-GKy^U7ysY-tEf>Ip4<9wFDHmDm7n=*YQyq~qyt>xQc}dWLcq;{+C$Ie}Af zc(*qbq1lEHp{9;@r~c@CTUrY=QcWgc1Gk6Vk?r#P6`xZXr%0V$Ze>7Lo@aDtuMe(( zCCXMebu^rD>2#yX5%*YlToeRF7IB^pbISX$6^fkA9P^KTJM!q8b0k6wHFnV;%Sk_S&D?+OOp_Lo#?sK!qKVm*xQ%3%a<{A)foxH8G%(r zRwkrJr(9U|>4a_xwGjKAW&>kH&nWjxqlGpQo_;3%Jt@m|8Y5;C;TSpL{e`1*)5CX9 ze3_E!QxvC9VJT51Sxy!ip*^ub zBjkB<3WwmGIPp^bbthl8feO_dtl6i;)EG(8((&rAzqGf$)+l^57xuvZA^? zid^rpW3L8$aoQAdBox#0?56#`R0+LT%5-Z07(}fd8_uq*HTX5)nbB2ekBY!)r`zRM ze1f}Ume(aIrB8qJTAimR+FvOma@3akcrERvjbm`scQy3h@!?=CABEp6|~H(dYp<#DvZQo6vbfW8=Q3( zRs-wBh_kZ;FF$9;_wVaO_tVq(IYq@7k`ox2Wzs*c(&?pTALU=o1mB@#i@Et-9UFxo zCvN<%LAdr*aJLf4It5uC;Jum^Spp+)g`-&!|9bvItpi73^Up|YDcFHeLGZKw2KDOd z*mtnKux&Y7EoXU^VnszzaST2JpMqE;(46Q_#{#Nmr!M6q`ClBN<9ia7Qk-aL=LzhCex*^0*>ILM^P zun&aMCF-CGgB-%CG3lQStsdT;R}b-$SjZt>$(x<+^26uEcTEaS!$~~YQD55Aphoj5 z;YPv2qlUy4f?+8|5=7W4XhAd}(KN*I?Z88)2HY1Z=q5p6PD#jB`b*v%MegC11a~fc zw&$mW=myLX#}A7hTYh2#a8cfh>@$jXUfpKx!`^&-0?xhyWZMAA8k?PNrJ_2j$s*` z;IXhBw~Lw!#y^Zcis6nId%d)^RDUolxFp^6RAD)hB?KH3rP^OqoX7k#IpL)e>cBE3 z{N8^BZZ*2a`!BMMqE?je#p!u#L4gD^!f{8YXUmZBQQSz&%OXzjDoqorh`Nz>b>fX_ z5R4b(QO}JayNE`w?5J;~9qE`@djA98?*LOu=WKrdA#?!?_%(2O-^4|2r zuBxTqD`H12)Y8-_b=7FWUQC{jsz+*!cJ()-v|(XU8a845cZ5L&DkE%tMlc#$E0;rt zjlB?F*v|VTRE`nE%5g^*ae|}zwN43-w>ZS%-vn8OcwMNi+iWg;YIxAP??0=0c6)!2 z@Ad2Dr_86Od~TF4aN|)ON7CqL-rwES$Z7_8xMjQi9me~W+)jdDfZf|>L}~pon=&2V zi$|wTDpf18B$?~=1#VO4MN(!6oWqEtlXp+8U*=rtdjGP1-+Ya&Q3aHDJ3JrV|Jx^@ zM|K6JF&1NZMHP64`NR6<<_4_>C4;rct~o_Efdrf!tq&Jpvv`#EIK&@DF6_P-$Z$ER zF^iL&^UmCEQh!&#%P807R}u!a`2F2=n-jnSvPN5>$7m8uJBNO11n_$Wu1S7}wel0E zKd?qN*Af)Laf+y51jfTwwds}g%l12J^*sD2buda7PC}u0LB@EJ#o5{i%FlSN#Xol7 zy!IP;YhKO^*Sml;cfKx`AF>y@>%nu9LhuYjsx%AldMt~1K;}7`R+_bvry>W=Q(0N2 zC7i`LSuEe+?3HB>Ya*EMgAH&cAaGkuV;y;qT$r=tlUflK6+sqq{7yEolZcUwg!34z zt~jz1LQ)vhSjQhr2E5bw^QPe>g(e*o9W`*~(&xYWWZ5RV{6CI^`J49^cPOqVBaA40 zucLbNNvAfi&In(wa<%=X=6Mp!%OWk%k|Iiv^h;h+cr%B92-*UPYJ#Kw$sxnsKZEIY zSLEpx&;lum5)_`q8YR3rF%TIYh{t5*l3{%o(}RxMC;yPEZb2XYBw3unu|SAcmcmIq z))>wYIv!d6`u$^?L_U2h7vX_rjZ)76?@e?hbxkO}vpgVcp925U0))7L2{cF3j4CoMEnB`kwfF6W z3ea%cqVc0Q%%KY428h3c14LJ}V=66=LV4IhD?`U(Qy3~5 zuYR83-ZpLG`e@WRoQ3l(H0Qv?5>Km~Ee%8*u6N zdRi}QHhy*kcd3;zI*%P|bYos!HQHzUu-g8-kH#*nEqY7l)!jarM^>ZNNVk<@>*B9sG=jSG>-A z2$rw!LdXOCQ}p$t3Mu+2l0J~0gi_mYL+}$L;2`;NFx1YwVl1y~qJDZHF_cbGnjcK_ z5h)Dyix>l8K_mSXfE$um!T+FLAYWYh8y-L4BASLJC9mEh`~>dD&WQ9{L+4wB&VXL? zqntk`gL!nT0({jIY;qPzeYNtvO2I{6j}0!2ajqDHN3ig=y~iPFGD zX7t^m?*Y?xw;1uxgzDW7x_H`175=!Me)~%!L_#1+VbF8n)(;=5LES(FYpS_gde%UN z?PpL<9O9riugs>Y4oA(hfB#$^FgI&LMCL|ds=_fe9XNnhHO2Dw?lzy;fRAD`#71nY zf&OJe*fYK`cs~wtxH%@#G)JeU@~R&t^x;d4XL*w02!_G*!sC9gEx4D=C=}Bj=Mvq$ zSC@?#iUJd5LRLvpVL1+Vns11#{Vd0)rzSN$lnoZ^^WpMMHbwElXr)(O+qw$5y~hS_ z6uug$s-xg`ANX>h)VA-w1lV?T&L1Bqak&04&tI~FkR>i){igwag~)6NeIm8Ow9{Z0 zZiy+Jd(u4b^n1N-DNAs9?(mFp`MjR1;k8_zOvRVub9wxeFaEFp+Jm2(!QsZkeL_=k zzcuQf>wikvs(hjsYPopEF|XTu!q+Fkwqj0rXlW1}7S>U;#bN=>fynnwtw?{BqdM`zhbNl6a{H|;Xtp6P+Iaj%OXdYzprfA`ex+gH6npC6d_QALg3x^bs5m_WRtWM1Q$&za$R0@ zl-V`Z_v<^LE-&cTMZy(DrU_CKMDlp8l%E3Ub%Egx+@7^c%foa;as1?KM&FklZf->T znP{DmBzTr)X`H5cjKMG#)(OuDp=mgZmmSYvyjZqt{*m={7Co|2qCk@jMT-JU*RJ3E zRN#(>{w37hXj$O34rpb*$;v6IVFcf7W4_7idHqZ*uR7YAcKKT{XcG&A6C}%0EJ<)U zkB7y#`k`HD3ZqCcz2@)^DtE`o7t&EE7@Firg2Hi*<8Xq3F*MXJkv(kD$MBk?{i~yf zZ@3!^H{pO)Nn0vd!r4*C%gpj$4dmL%m762@r7UniFl^arYC_>75ByNETHP9vmO)IS zBuo$qj*%H>uG>5IjoX>O?y)bX6{=iku>el*ALL zi;vTnho&Zzex8G72CSU#f%3obZ{9ybPOW`7iaf&RHEvoHX_0qT@sg(?7 z)Ddkf>&vvr{~w8Q%kP0A&hQ7Kq|8c3|5^h)cYIsDP@kK0F7YNA=YU+ckI$Rw6SCpJ z)t8(5Vf zz+8Pn=Yk^=XPh~1!80(h2i7mJ1-7aiy{m?4Z^E<^>f0^X_NnV| zQ4V6hC3SmB6;m9df&Pin^$g7-`9QbJFza`(+D^q$(;>%*dZi9D znF$(mLdW<=lcd(s19KH?ebb~akDW@&K+KHe+GU+X7VDop;M)hEm+2V#|ENR3!PCvD zt;E}cb8^phwiXnS=CEV;zG?QSpQsitMHhG$u!Pf<+buiC>+|HR;Hl zOX6jT=S7wgXtMR1KGO{<0rMr-_g*$)Enb*7UvlOCusGVPCss*)Sy+q zK?np2%Z~;R;rE~jeoyH>0)CPzz;sH*%HbV2KrDVgz?Y> z32G(;uZ8n94f3$%u&R!MtT4a^zX}L_4wWx!kT(i2hzdApFZ~k&jwSv#y1uBGc@{Z(iry*zLktQMM^zAA~{l zg4v6l)7KIaEe(CnHTP>Sc+}s+^kVI;At12t; z6eo+a$X$P8%$wge+-WzH|5jBlkYU=04Et?ddW(Xo4PqM+2n8f1e2c(wq(EvsD{v}H z&poj7<@IHv&H29>V+f-WWf2?3eM3dlR!gRRy1UP#!snDYFSdrNB*b zZrMNO*JhJn^F&a9SLg*iroZ0ge6!JZ+HK;#7nKJJnjA<*m=L4|?~jeXoM}UWNfLGlD<@kp>`r%UGtxRSPrC zG(}PqykN0f>exF@Y&fm=Z)Hxq4bN;UY~LpH0!LHomv{B3HEvhK47hLIr{m=?2V`0{ zL(T~F%*nx=K^9A5^oiN`7L(^SWohETlv{omk!yeM;D~2-A5KJELCKmzF*2v|EQN|I z@(_%clxwf`>CpS{oNJ&O6~Xr?nkKO{$x5n%3dfpNcxpGYKQr{uL#@t_Yq>xQyrS?b zBeFn2S*<$QtQ*^hwQe}}w;$Wa<(Nx*4bZH{bBe6dq~`V%7I_!$7?D|Y?7XtBdkM4J z@e8K8-TS{Q^WWPF#eUm!4a6HP>7MKf6`Z`u&kF^BR5$`Cz%wZvYgZ=5PraoFeg50& zcPACfYXrI_yC69}$$9agg^XkuBq0e&_>TjhtWOe>aK_WQ|8;sLxi6ev$UIBO<vv7BVE`4)`YrM*vCf zQPSI6dS^diRmz$RIuA%6m|)#R|aF0{9NW8it+@}cfm#FeU74Y)~0 zbQek?5b#J|&s2ckr{-d<3A@<0`lqe!dpo>^Hqb%{tF2B1B|$ewX^KFwqDs=Nb^em= zkr9tSuNRF`IFe$#k(bwmu-|;F+meCx8pO76vR)C6Alx1m_(Gmgj7f9}_C%oUpZ+yq z^J}x9adLstsZ0S`qZxrvD5A_bxlYUB2R?tQZ3fg481h?s6h;C4zddpgqZ{8{Fbaz0 zGP`T_v=wtiyiyOr5fsgEWY`FU=3*4*&YM|^-%7{zLT~%5?QPZ#pV}$5d!yC9MbX_S z;P-`qPt5~?Ngf3Rac1cMU*a1h}Rq>2_khgcCClmq!cd>7BApFZ0CJT=5 znsG4LFJOl{Z`Y?rE#v-pbXGDDCeFH7`ZCd(y$X8h%`3fmcS z7!R>O{dVIMYudyg4T0iCz>AtjiYz)BGit!2r)pJg)@_v>>kJG=B~LI~TNVc6$L!Z5lMc`9S4KV=ZSbP1 z$|Oh1yr8IXKXa?|7PViO-Tb$#3`7{iCn%gjpD#wb1#MyurpaYw3X;w;Stg05t(Kwo z{g#=dH_kBeGNE~VsvF28OOT|@Ga5bX&d?iAY+qohpWwo^COC;Iy5Ox^mVHQfH& zj9-pjx&^DWPVxjT5Hv>%ltA+$diJL4UGpJ!iyvVRE}MO2UT1i`kB;H1HJ_W~c~0S3 zN@i)|w(o9E-S_58R&-}iG%g;#v6tH$ZGUgc|JK1hWxUmh7@|puku-|bfQa+dG6lgF zOu9W{tYKz+;4n#NVm23CI^86lc8b$De;784V!!UtbHOEFzyqSd_<-OgQsxwe=T$J^ z>8mqFU9vbO(XenimQw>cr>WfP6m3S1n_|DCRjd0`J2j5oAtKGpD2*XWM&o3aTexF{ zaOo3SWd;V#JDQP!s(s-t7k7AIPh6c@UQ|gOiP~R+UEpR1Fum@MT`;;t?D{$q2!V5e{3W97baBXOUW6ParA5&86 z=Y=EV7(<96K~oGtks?hAF-_}~Z8k1m`;HgqHN0Ep;qo@LER-$4sT2jD%Y}tk*{?AQ3LjscJ zRG^6@$$Z-8yg@r1ztqC%&!<-MgbLVq?yFO@s6XuP#kRQJO%V!EL?B8kN3Rglw4FC@ zOdVRyyMkCvHUdI2+VFo0%H_L3`<`tdYV%seXDWG?1rn=pfTt;RbD!1CIPakSP}9^4 zbDGBWdn-zkAV?HPs0@SJ?dKw7Q#OW-D&G)8_K&}sv5wi=5-(jTnR!mt||5e zKu;+c3i54AwyX&IJIO*gfbKf(hF}XOZ7x!oWe-@#-pFl^dzNZEp^~g7$uu2f>VI!~ zDrP9JvoBb8?U=`A#M_Y>QDqsPR1}7kV|L^R?S>dF={mbZ8#v&LmGCOAD5eP~ui{vi znf?9tBBIgyG&pj2z_pV zwN6}|GQ*vy9@9#&e$7!PcSc}dOh;sla3Bip{GT&nlQ$9 z+b>(ZZsSUpj^(ECg%+1OW>)+Gb(BX974!q%A`qi4U`ue?-fgdY+t`CowM@Ww8_xF!@xSR9gMFX-a|UNz_roE-j>36yu=j&<#{*xq2ab87lTSkf8;$puR?ZcnhM-|GcR z*yrKUK<_}nU#gX|+p})j-fwekb&)ovAo$EX@b0xAcr)<(D@U=u7FkJSfGX1hNl>V- zMF@%O;NE9nyf1(D3m?UmN(n$9DV7#^m84OrRHCwr@*gVOsZ5jNJ_}TK0G8SpG??_*fkn`(*c79w$BFJD$>&k>Bn^~qJT$}7 zlBiOGq=a3EtbAtgH7%QsPeru?-mfMJHGZ)!vk%?mx=MTpf6>Bn1kKYTNecwaiZNcN z-q4-y?ZB?ihwX2^vwZ)dPg)=}l~EWBUSKtbR5^*^gja@cYjfY`cQSC^#(U_vtI6|< z0`9v;)wFsWB|JE*?3*)X}f1d zV-#@zmyh5x_I7(aebsLkyu0ZaQI9AULy8jIc_B%e7>*+>OUlIH+!as$aClX{vg@QA z{W$J`^uiviStXvg-}OS_u#Su2K{pypM^mJ`X8}+q0(FQK2%7`A6~8YS3KT0Lf1pp- zhL^Wnn^~q9)OSoYZh=(fNWz=rEA}GwyW18oh+8z~>so zy_AqiO66!lRT(DcQo@FvQt#MT+_LtIpPs@ySEm??<9LQ;2$tdam}_u&%V5VA@t%EY z+b-7~DQJwR)C);o2uDuw_y+kE$!kWCi@^)Jv@q>$b3Qjza8>E`aIQS{-NWWge08J`=~7r zD;gbi{Udv4C$WFYS~R>uEce9{?vVjdM!ES;vS9b{*FP5aL=IU^$eUh-EX=A)#J1Sy z5dCJX--nO<%5jWHuoOpe6wA>l#XLe+f7+LgE8#Qyh>V%nWz23FD{YKW%_3A*5m`y0 zNd;$i_-2P_i~{=q6hu&fYP>&$yzexj4pK}`yYjNWuoL5(FU3Re&N(DAPIf zqiChMsVy^1irj9$rb*zhExwkqEMa+(w7>}As0kbkWSk-9bC2Wr+OO^XnzkGDcS?M| zmJ>Mu1OW&PA)@}Jq4^nVh6c;{+P?VL(D+69u!OmWC9o1p(4;EMoDBBv9sO6AM(fiY z)kL0VS8^AyS-!C^zWC;zkF>$xs*!}iQM^cUBt?)6gKh)+T1J0}7V(Y!^EP#+jeRZt zl_<%|q$ny3;3W0UVOJ=TSE5#ZrZ-D?BJZ>}d+0~@>()?^y4qqWNIhxkMTsKI(xj#c zvNY}PjdyOocxp;*6Mgr=QBLd>iPZEY>1RLa z@l&K%oI_dB!meYcveDi4+uyrwXUSeD8=Y%4=r59U2SC0j7y^NzlR&`hmsFsJ1-0Sp zx9(n#6kNCRNy~d9e#!1^LJmJYt`y&;}ar+^WVdF+cBY6t#y^MrM>R-Ek&cC+7?yag z=R5V+9C*0w4h}u=D$o5Lw@+^J`k;Az;n`nQzHh3En_^EYjrDl$`BqPDyCI|F8Gbxu zRPnt;|Hq{>s#s3>u-F_&`t~7u)@q4g!~vkVCx^AZ+%m(f8l#DXObZKljM!d~o$G+J zaal&9Ga=p4&~ zyt#i{r?#Wxk`|gmGm;|HA}>%VX))}AQabMT%+iVbaH}3s>DVH39IIbi)al?bc;5eN zEOihV9*~^C^8zvE?=2Y~b*J`t`-{7sCyqc8UReO*3wfPX$xLPR9ECP6{Q0Q@7W*&)MP2 zgu=?rdLNC{+x`;LXD|&ZLbu)~=+mc%o*s>~)vJ%`O~_OnVC>H>=KIm<@&6yB_XP|J zcwd5377C=t*`k-iIHa$K>(6tM_fdfV!|&g<8b3D!_6@Bk) zgNQJHj^EyG=@N?JW?f{sy@RlREGvp4$&169_r37on%uJG?rRgBXFYo~&a84Bn>%jb zbm8C+u7h>lWiyoRk{m|^MPgZ&7q0r_`OYcnsiP}dHg4FT#|YBUBkg~->M6IUiNyU( zx%d9Qir+8C^h0&Guf2YzU8L{pFktxs9Y2PQCmhF~l~m-(sG)Uhv$SxGI8dwW9V@YH z0MF6_L5U(kQY=9cC>yZxl2SYr{w;MJ@ zl@r001+(v~8uAML9;aj1%JKK(FFw-pVXR6pM^YS5Xe33;0ClkSTt-EwXj{kJ>wD63 zZ-O(3h}HWqS3P5ST9YVB6*yXW^X5&8VoveY8p4)JKDS`fHN6|Ob=19N z{exg%>v&65CIpq?IbKj0raZJ5x>*0VMq`V`IBm9Aar#JBOFKum%#HhAq2gGxks>GX ztVl2vLs4YKqoJJdxO(`%3kEs(h{{?Law60xIMRQ)&ogDDrw{}~QejcpqzLs9V^2wJ zSge)~jyKn3bxeP|b!=OWOieY4ky%ovBu$DYY;mVQL|*i z0VPj53k*A9jdUo5>J3k5Y+PC=$FS+|b-rO7bkpALmpfXf@0v{Q7<6Z>s>e@uKIQ5vC$1khNDjIr>z ze>`pucXix2qQ7U=-MSw>%YGZCS+u~pPtM?8B|fRpqxA9wi=>cJ5H=V+8C-btzQeOt zcbk=Q6lO=jqdsAbb1@zr{}CHj3%e;+DaVj>w=dteQ-{2dj1 z-*TGjeXel=T^+|ePrh;aO$i<`f<~x{Oj9Zo>tF4L=4KS#rH<9(AA?RMX==Jy=I39H)21Vh*!L4k$$rPS2u#-ir5 zXxc11<>~s~YU$zlcj4o&_bVxj6dF4t)e@gu2_F^iBHc4&IF#V)^I!Nl96B4PVe6>k zy0lq(I*wkyx$B_D@#AT!fMGQ{95I{I(0PN4D%#UQUwqldzAfUqQWaF0AZbdH2$nE( zr8)+|DBzw)L~D0+S^rvUpKD+XKQJ`xCPhjC8bwO7AhfyZ#>0CaTU?7q@?43jL%T|H zR&6lpHWmENlHhme3baX3l3=wAa9saVp(<6eelQVv^m{?cLz${=2Lh218Lbo@KaWZ`br6 z=1}p5kzi$wV+leNMG+mwvaC=XQ_={>q^##|*t!F{LD_69qzgNI=^r*da*MJoC@Lu_ zG_8zZC~lkerPYy(X;685;);QpkGzV4E#dW(=lE?+-&@{qNSkr%EKR#hT|aw#`FSBp z_IN#^l6Xuz-qpcq$H&Dtbo%Y`*7!*o4j}=;u9QL0O%o)b6oQu}p7H!N@TW*u)1I7Y zjDy#7oR6{UPqB<~-1xxsWxdbC3WOM%;>B>l5RT+UlA%z#^&;H@;ZsZzV;rB4|Dlib zPDtLa!3WP^SyEvDLklv;Zfv?ueaE#WlSEon&twkNJ4JhSy$Zfpuaf!Xo*`#bZk)9h zx$ed|#?8#T`>}TLHlSe~7yXI?R7&Q;vrAq(U|N??mMqAC#OJNZGJ3j-^}n@-INMg% z>iG`WpEk=_$E8F=&wVeMQL;c)G*ML;g+#ZcS1=sVSjVGvzFN@Xg18<%NQoB+f}?p_ zlTnWzW(pDIZxCA9dp6%eNe~GCRn#;k?*X?|4*D_6SE;AT`KSUfa8vd>o&i(A->BEQdR*c zi;|-8xWM1JCbU3A;BUvo=Wd>}^(J@=Hs656rFqWn2@Wj^ht86`VIK!xj}P=L4g~#y zaH`edg*kP+Bdze>zVpx4s8i8aMJ}P@E56v#>G2Qxk9)i=UdpQrUimOLInq<;@#VWU z5K;<&iqgxt8?y4Va_e1EGPSq#`Tqd`{t%O@0GkQ*Z*}g9%#QJc${dLn#%@2lsuVoour#blg3**BD|H zVcgHf|6kOKtL)9D~Z05xA7Y7#Sz(^kQIvFsAiks$^!iULgfDoP5w`G zW}XeghseQP=os-FwM7x(P##G&7)q8ZRr}2g1%Olt)s3kd8>ZaP`YY@=L?C4}zHtzeP121{au= z*P)vF`vQ~lD+}oN=}B@l36+3WSep1A$HXoNT?f8ti5D|QSnQCzMr#bKFdT)38O}B( zzwdQ;+YWi=-Y4SR`ja9{@@&|CjUr-WwL?*8Q>+wosbhM{*sqDV?nScaK6Y`>cF?Stv)A<+og)Xebd zV;omFHs5$lXvxwzmX|asFeJ&4oInUDP?6_~yUteFb+aLg7g!-()a{}O1 z5|9z*ax3@?Tx)+$FYEC%6PzfdCCmq|b+7}bPFPT;`)d%ru#luVx2Les9|%d_5lRog zT5`VgM_03vH)UrdBpSqf5Eg8Y3BwGZAC6P+=$M|>A;FcqR|G+%2#LW>!O;lD$xzMDj*lE0?%s6!y@%r)G6_HsITi>UAEWNRmB+RAQ%CRX z-nZY@275D1^Bl#|1kds`Pq8sHQTryPWlXT+O8CriqW4?MFYn{YF8-upEzJ@F#R`I0 zQM+-Q<#WgFcl90q%tdeuYo8W>3u8!u=VX~v0daKggwdBxX^^=*$zCQQ31=*{OPUv- zR{HdF$L7PguDm8xFIF}YK6M2l^3Y}JxnD-QC|H6J>Ca;i-!B~e)O)Rp0)<2d8nU{I z4NJR6Oz8`J?hpuut^i?snx8v6QnqdDlZIYmzNzCv9gjes=b7}uo0aT4DM9&mWyd7N zvemI^kjFJ)Ok+IluKC;=ODY^s5wf81b(-~P@?oPv)^robHzmP3Y;`pKruFt#L|F~e zjA|eV3QedaqiKM@W@DFwlcpbaJ&NRbE5`Z><_s_BN)Y|)lKL9;&>W@vIkE&(86!>Ha_i*`BXor}noc_8dazmvo7Rp6_ry&eE0NM?e29JNi8c z@PAky%paEby2Ch)z7L^E700JZ4UK#G4!q;&Ou73LjS(R+5}R7(cp1n%15|bMU42HL z=szSKnq9WmFUWJMU8eP=Beebj-;k@}7BHo}Zfc=hY@j`SaaazHhD;R(Mq?bH&&|^1 z75mDuv{tRg*Y?EfUjt1dMO76fQKe9idiS)8WzysK!%oM-RfSXTS=S);oQ5`Rj7IXJ zrpc6~3aGn~AD^?v#YPf@XoHU3j#bZviuN_fwwDxSmgOaxr6>YzufaYdco zTX!_aUpExeZcng(VUa%&$}0kjNArLHDy(_1YyBu3ThLy|KP$fZ;h}DEg-{G7h++Fc zk|yYC5<=~Byhz_`@swFh?KcX3%9DgfN}?!;3Sk)hbdxK7iN`(NWR4pg)?p{9rvrT zt~T~NY$q}=d;Do4R%V%yQ-a9>PY4A2`-)cjw-b%lr@;w&xjq9j^xfoMTjfpyo8^Gx zFQwJWfFCObBuSnoDS;sgF>E73qf)?33|RyJKj8SVRhMhqO9|Rm(lX5{tRVBM$Pp)8 z8?$B=WZ>M%rYHEn*%fz1ufQ*_;`?}k@v$KaBB`J36j~nATH6dBRi5kai1ka#OO+pxq zmUPJRY1elzOe-ieEj1*W6pa!!f>KFJlwMl&;{`)%%`Rh;ot13kA;+2)Ye&v~2ojq{ zx4;ZVyL~6uC{TM(@q`9>f+64ofnibr(m*Z0Kq?HT^ZPH?x=u!a}p zy28P&hz%d=+xb?EjodN% zC_s|9u1<|Z51Jkf+lGFJ8}Cr#ij8k|&zGtKj@Yy%*uL`l`itY9t6P6=r{k}7fl^gY zrg=@_)vMN6*M$u(jkG%|cI^G?xcG@7KVO#t3E?8V3mZVtnkeuD;3)nH%hbVJ{w&I9 zZ!+ko8m1(eo>IwrSGTMrH^CF}SI6L??_ObR1Mm3T$8kNQ@}fxdoIr3Q^IB-Yg8y~+ z&;f^c0cM8WW-@c&cMBTU%(&$=J~jE`)rbx|M&5DJ(t=GLV+mr!c$afSM{2_Y{s}+{ zxq~Hzvfo?S;J6gYmF}9GQlZ6Ywfy0bGYgJAwjg^Kp}`fBw-^irC2w_ncJ&oZOB>D-6uk{cg(u%+ zC-xog`_EFV_I({o^?u(-@BaPS(P;=fQ;q+J`o&yI{O2_V<+I_qqe1=5u0OAZi)EJK z9f`(iia-EL6-4^dmyU!s5Z7g+cO+&iKxXQTVLH#x05638bIp<6Bex;X;J7 zGvFSU;B&a%X6Xe=h^g9r`XySEl<4eScF3IjUYFqjosom~8MQI_+^@ecH8AD_?TEY$ViJQX1ox>VLkWkg zsy>5Yy(${-APL4$CqXi=rr>OgVBVmb`>ULsOm#YXz54P~UqNzmwati}Bt?m4WFS!_ zNi}fZzoT|CVdw)((6j=Wc(=X0yx z9}V=HP1m2FZ;NIRhGRB))FH(cNc|S-`FyFTcXZKdppgC*d zn!`)yB$e8ea0-F1hYKvm>!O_gtL@y}=J4@P@?ku;SvCU#oM216u?-IUHtFv5 z_6kTIUoW6|yZt+`S2&jIpc<haqzxBzcDFOm$+lO#cqB*DknwI9=O_(x+)XygoDci&Zy z{uNidosbopRai}?71B_#d<+3^ zuF%Bkdb&gB)gQ%i)g+37K(YiyksQr1Xx|RduOaqe+Gw*hb6)Y&vYj6U%3P)ysAw3e zzy^t7TW%ja;pPm~t_KcICxlr>|F%)~iO z;`-c>Xi`xaAajhwq0Nx6S)cE$o3j4M=gT_cg`1)9jB|T@inmw=K{rb&q|6Z<1t{@C z4^5$EIosnb13Ei*-_mv9CHKZ{1}8I;NXVi&Tqea<%l3cBHX}LWtoA1Oc8`4N~FwF z;>-z`UeA6xIvaHL{T5sh%Ct(Jhh{-7_h2qcD_dT5!w_T$Qj=S*;rOOu+5JnM^{BQN zXWkrN=|iCyUKTZ)6=GxHnJIl->iod@$Pd{MU{xasmKQ~i6lDX|h(ukHmp7<~GuZK; zKjv?QD|T+&;OL|ZL`4*Nx=DkVZ~J@5CoU-RU0TJSUs}cIxc8SR4-KNY_c!UE9;3gTN)KmEQW&g|y8v}Jb%?m6wL53u?iusx`0%$q|$)~a# zWBEzn)0vZ!dg1vW7>TP2-J}o`pJ_JM^J#Bq@x^c4aBybaD7vIV2&~451j#UH6kVLu zDg|5nKF-&F`l;`Yi{ch339`%!nk;FYfEL;In$lTXsNW5*=@y-iEwZmO2%+4=2@g&`=T-A|WHbvvB6OMYrl)zO&|s;^tW&Pvudqmu78NtmMS@BH%_SC8M-=DRZPmV-#@D%O}|YCsj}W;o{fJf{DeNenff` z7710#p1NPuHAnTI)LCY&9DqcLOL}<1iT5>X4Jc!F}B%e+ajy zB=%l<6&FRK@<7Not}v<4M-?mwmt9-rIE3Y{NGcru$K+8%oWy-{t4R~%5(Ge&Iho=p zk)>j2kYm=07CF=@ZTpcUt}ZioHndLxB*h6L$%O-)Ya70Pdfubgrw@Rv)W}i{o%MTl zDtQF5y!0^dmKXFtX4|5-ul@S=o2lcymgxMZ>+dr&qucVcGdf=s=$6qK0*pxj@hiLI z^NzdpeW90r7yPttXPKr8s8;Lt-vqSl)t^7nHaeeUE28UrqF;-<4?Swzwv8ByD4p7W+QzK`o%o`?ewl4ePn<2jMwc+>$1JEWP^=e%kFC1pW?~V z9wAiW?{m18le=u6EA*=jn@_F}SOjq91V8$__Ji>Ptf&OdDI87Gf+(N?R-kTnEd<9lrNFt^`NO6^ z_F1ljO;-H!|BmAgCR#Dv=Un^m zc|Xjepb<>Nl$fzN>4eJT(?B2q)S*S;csi1I1W^0|H5{{0JR$YG@$-MIZaoJW9P(+s zpkumj4D^bSr~eN_LcZ8Cu|B=f8a-PepsFBu=n8@C=&Fq&RCL5C=XV($jnP?d23MGr z7t*5)xGs*L5A4q&nEt^5d=F=!Yvv#^1F5$KldfpKXFHUuIu$whzw*_VrF-J)2UD~L z1eTN~MIuoBU>h>G)2(Jj2AuwS3kz;4iyyh#!1EGAk)_&I!vcN<1cPu%mqyqKn-hOH ztY^Q4%dbs^@g^>8WR1cQ%tFV&cGNX_o`kASwV;z(KJ~e`?2tN$2vi@)SF`{C?tmzX z6fF`IOK?P;mctKx{#4sCC)2w!qA@zMyO3QFr@#rJcGQ7L_td$m;L}}kpe?bqu#(;VE#`+ z63&F>Z?jBt(zy$Bp19sXOWLaj+$1B0O`bu(b26S%Sfd$L*!{Jkao&@hOPct-b!Rp+ z*jUQWFqxobiIo^dk=c`mgX;!;+x}4V=?=JSKl6!IX~5)OB|fRpqm+3{D+&it4wV#v!JtqEfgw^M2xbp|W6Fx9%gT0&W*BTi z($L7}5~Tj{IBsUG>;^y0S(iR|V)i5P*guvhSy2{9PLV{E{o7X87{|_~8=MccXt!wF zaX6QXVtZ8cxoH8Y3Sb3>^tJr!pjpFJessv=s*-VJH^7w9ZoP*!lxAdcTJI9Vs^qQo2#y!ZZI}~y&1wfhD z*Xx%h??`oO(S(72tXn(V0f(%OEgI)C73;PX%RJ{*%iG^pJ0rd^y+kTNVnvprL>~2U z*)zei<~hGs=JPMlG%;EMBqsxoA_2>w+i}ZuT+XX@?7r#5#;*7_=sY_ox(9`cd=PMJ zz^8z`kQDMLBY>nzvKQoq0zfJ(-SM`JY4k+1TEkOdE88m81dpBDogMS0%)I4jTn0xY zIEAG|KucPT0VK7&6yH0{7^6kr?kpPD_^St+n9eLxaQ6&PClKf(DUu4@g;HSRV96)t z19kM9L)*RC-_VEL-HKEc zDh_~PUP$%$zaOzX^R0+wixyw2!YAKi=b+a+UpVR_covT?uSVF%EhC-`_q1W0{pav@ zfl6z#>y}dxm7}eeI@?YAt9GX&kbxQ9(SW5ExKft-a-Szu>eJH`^!xT6;QM6uZI#ux zhFleNttK%x3h316S<~^HlaPc2pf6@ZSGP4_<4TG!Yg+9m>lF3#k^1qUzj2D{Vdk+h zw${=44nUsoL^jN|Ror*s;d5RECpLQc{jPrx592-A(e$X9-1bbdvaM`AP~u~T zEqWZsW+f)aPIO*9qT^BeKo;2&`z{?bj!nk=9B>cBwQd03T3DpRYJiX@(Ayv}QAlT& z$srS@Pde-OLD`u3zPDBM|9k5j)3+5}A1Hq}HmiEq;irhaqPpqvMC)y~4 z<^IR|KiO}|b(0+bBqZV71gICZ)X6{i=I8fuaozK?f=0cE!LT;_!dvbG=U?-wZGQ#((wpF3SkdoJHINCuhwkNP>s9%g)?VQZ z&+lvp+s;y4gsjV=YF>|UUZt4Kw-jWYlH-+xbD7q#S{`=JoA%uN4Nc-&d5I(mWI+O? zq{PI%{qC+(`H>Y<~OT%1N1D^FSff%2T>H=eFA=82>8@I5SZjqK+vst{XyUw z-!pim*0*It`HwXkCq9Lra^4$g?R#khbTwpP$`H{P}XG#2??)3UTq$I@+4IJPQJJI8Lfes^$m>)39Ai>b6*ID&K!kwTtHAZ$?P z@q)nI6;J+fcvZbxus?p%$>hWOAJC-lNJ?}%wi3@em!Fq0ZRI@|#!~sZF}ZF<%82e> zpw#ozV82@I1^s=VP^lSXAc)$SU(n7N$Cm%Rv(}_}R~0XcZy?LE8bcC-Dlme8axneI z;_`mUxo=;K_d$D@=XL{Tg64U*T zsMJdaN`PGQAg-8KonKs+GifLRiOuol#e@Qqf~W8D3l@#bvHxDiB|tjvNJ^}TsB)fJ z-f(tq(`@GM{;+BWp_mJ4kt!3k77Ppci$EY$@{9ZGXa2Zsy1k5m{yI6aj7NW+^U6yO zj*~V+(sr&PX{#_2ph#AvG?{<+(5nO1c#o#TV9bq>yc<+5gd)z^V-MzpJpu!N&ayhNSDu$pQbYL9-&S^Ah)sw!yiApL55b|7#R0Z7DB`p@6C>3_}PakY4Zo;S*PV zNYy3d|K=Km7if(KSfg%&4?^RvJ4|oG2zyED5=A?@?+;)%_lEzH?38y zShRd%l49vunpfoYga&yEJ)!P_{OkI(|1&JdL7SVBa1Vd#d}iaqj&t%EyeQJp!}$j2d$-} zbsM&bt3lm*e??SodGFD)Bra$-D*>)csfe3U9n$Ifsv1_2rgUOsovPY<`oB zTLp?=1$m%Q@`XIgPTx<^f){yBq|* z&`1yr_4iSKo~lUc{6LeDw_x`wnxRNRq)19&MVhB#inv$zJNE%rnEr!v_woC0*fBT`rOAsF zLo%emQ?y7EXc0eMtMC8VA`Un=f5J9V+IFtkh=D>+C@fWF02K($y7OpDwS#FXeI zgFAo9tMQ|=_8VL4KDnZ;C3=HG19ub$f$Ekth6D;DE22bR=%K0o8=h@>WU`#uBtbSO z(R5YWrRuW9O2y<65jOINwb`jV5{L}m4qZD zGT+YCa+8pRq5C?ki)#$NTR`)4A|{MSs83*Qa;AwMFo`3Qg8Wx8H0W zM(H@ef_(BQByBd%u-~a_YA4JOGk^aE92)l7ha7j zSf8q%H@KTt$hJ1aJnyKt>-VpWzdPI5URx#mwW9MPN#C3v$hIWmOqegWvt%CG{$uOI zQo-}6y2S8i{dOKEecoBNubuZFUGGUqLK2dYgmVL+Dr+X@yf2Tt`ImFRM=zEXZN-Nz z}Y0CSedc100>qfC{5Os(s;!OuAH}{ z!Kx)?3W%LeaBQh-etLP?t-IouDpG(KS&`6Wz!;YLw>3USOSM{#JA*HF+CB9hs6TTg zqFfmEouDY1l~^E1oQfJp>*_OQcqUTPwMcpma=#v{oL{U*L+cCQeiao zG$XJwsZo?50k;Z*p@6>xH7NPYVO)gGlJ5H9`NHPSR%5lKIFh0Tfh9R!plL2fg{fDd z6i>&@qjcBoc^i)&c{#3uKS5I@$MP&k5*%va50xsQdPqGa9ouVJt`hm(x375)O5wH} zXmzu+=$;(#guqbk;jd@4xNO-|E~u63Pl?75U=NK=iN+R-aT@o(2HB?)qYKsM)5sq5dXDr7Y+#1a3*vSV52ppfKbs zL$|fLZ}U4DxKNEl^?X`c<3Fz`;J&M_P)}$+c#S59>$7@47tP-d&zxaKt5%goMWslF zJ9lMknf^!!Ifa+1C z5E$w2;rDxi_pE(*1;+xO5_cu3u!Q<42}wvo5|WUF>V#zpoVR?@*X6q5;Pfw6XF?*l$Pz>28rk2RbO$#KfQhYJ9=k5B;jm07pRi>_V+d4Q zb=5f6m6BHQzVax{I#>!>f`kC?e2+g8K66sEdM799vQ&*ZkZ%=X-vGyq|%? zgGje{91B!Z1rkS!?DZ$cy!lg}kWX9}=${o<(MZ(n`-Av-eK5vr{1s zH*AaIhMTz7joWzlAGvXi@K~1TM1~hhf@NZDk_YL+tY@%Gs;TQE@a@(n|He0=*LZ@Z zXi}gQQA^)4_QpsP`kVxpug%iVHE+&Acg9OlEYdKJScGO3krXtQrBq_=fN5PmS+XDl z3SLoxXjLQk)s*&3b~Sy6n;`96Q`BZ(zPJ#Y*3K#)J5`(LX1JrX=U+uVwJHUQG|><` zKVbgvhsbA80VAr7h}!nr%D(OFdL(|1<3hU+-&ON>XJU| zGLYX}|7Ud?=J;Yc(LE?^0SCg)Fhd2srxXQ1Fzgv|=p+ybcvKKP`68`yG(jpnPb+-C z!_K7_&TCcc$t1y$gi{C^Cg~(3;Z#7~(=-pwyuhLU65+dDTu*HKd0jVKf^MueRU>#_ z031oO`2~xf>={8bjebPOD{vXnLlC1Hb|3=H>Al3isw~ABzRS!pZfd4>;=CpOf#c|E--nHznGz14~~2J`YS(( zdyuOtP4c{;Dhf%V2YD+j3Cg;TSSD?`iHFTyY1mvz5_n4D6kZnC*Pc80N%~S}1|&Uu zi_!QZF}f^N&A-_x7aUEIZchjV zq>w*wh2$*;vqv|+q|2AL+EB$?yGb|O6MXMX13Zu1)79bXgV)Tt272UgQP*hqplM!_ zWQk!FLU#M4u)8pz_VD;rk1zk`w}wrh)B0;?ADi*ng0h)V|A9hwHuS9eQJ)KK<>)Q* zF61!X)Wn#3GE+A0rdRL*y#m9(jusH1XZ8}E4TR!22jV{^M^86H8lICNTWgKr9^*lq z3d(z`YaEu5XT~_3t&rNKV(%C0xJ2)tPjort8a*Of%eK*i&sLamKWIHW=kNLF7*7C? ztlv%Ey9y%>swuG<)Wa%jetPC*Md8XS?tdAlDPnwz<=lq(O{(^NGXt8XB9L{K^;dTt zU!yPZ$8~&P4IOx)2JxR))hIi)lKT}d9|)f%t;Q+@Ei2f+6$L9oN1iKC(e+r-;|ubP z9V+>~8RQ`6OjXCHs@u?KsqxzxY!F@F&$FWG&QUC@?pQVPmMqSSRM1o z^TtR!EhhEE!RzUX&6BJI=`UCxKXM-V8=TtpZZ(;=X2(tnGLMr`HP{|8CaEXsk?4i; zGBS^AW8@k*dIlZu1BmAmeqnIC626dN6e)b& zAyS-x`rof(!c$r;eO=>!Z2Q&AL3sMtPKha_km5Ku@Pe>||FGk85D2^ce|qv4H+i1> zwbs=MlGAXz>t=}-2SI?50`heQuAzDxbSa)?05TlGjOer#!w#U!5z2Ytduw{?$E-D) z%z?vl1v37STe%sTKKEg|*lgQ?^=oHR?`~hVgKWnsCgfoX zGJa3%4I7C04wlzWAIPgSquUEF6cP=K0?UW>-l~_6GvN6|$2FyHR(?O(-xVA<_^A9~ zJT*yGVK|DX1X2P#swSC=!DvbSUER8_UD&uyWBiiUd~Sh~36d8{iUU&LFYDi6v{n;^ z`qk7|SrzQlio&Vl_)<=DKUS60xBW*%OZa1NgzNf|QwQxJ$}Yb%jMk7QsRRpvs%V-l zr;PhP`)a%6aFXImLK2dYgfkMdPcbc!gfs1I&vQ-QK!y%~9M=Vjpb08NsDRNJfVv>r z`)DbCN{`D@WHT#rr0eY=AMSgq9aQxD6{i$Wk+gegQOM)>N#5>(fK(D@sDt6?mzT9z zx%#>b-gdr%6w=T{43&n=cT}8_p}%-ppJ?&Nv&ef#vJ<1J8zTHF6jLfNc1@OX{UERz z(Y!zt{l&USyS_CM%b1C&pD=lTN4hazj+1(xYvW+W@HC9>Xvd}NgTALrotEef{|)+k zV=>>)I7`Pl(f+7tKR`Ltr+NWT>n|KmP3$=eOWe8`F$bxa{(tL6)~M(C`ZY-7^k{t5 z{^+!>g<<(!FZXr59{1Ryg^xsn`!V`Gnm+`1{ope>u+egH@AGHLIc2kqa(y8U-|@|( z_|T^uOEWA_3p~Y&A`uhoudcpTJcKD?lxu3eL)T4kBx3j{h@?PDqN-7nh#LO+ndtoA z?Oe4kK=Z3Ec&$nGe23S1z0#!}xNYSj5g!Ve;yF;UcW{@EF(8 z5yN=7KcrgP8^x&Rn3E=TBZ_??Pa%K=#SmS=wp4sUi;~6yASf#DR%D{xRm%?g#{K4m z+FFb36~k49FZIbfyYHE?4)KFL-nH${Up{tr#rl|3RZapF#S#k5qCO_A72XCU*QU=W zweY5!dDLr&r^cHv9b!#5$cE8_$IhA{#9Oj zWl9VDwpVsm$s$ScGEcC)D7+RLu;70kK6ErUA>Yj)wTj;-ntx5_i`&*FxRyNR8S~|X z&{A)4YE1t|TI#6^&9S1!a3VtwdTjWWUmW@>f#ahkpLBM(0hc~8BKmSgX z>y+0p|M$oIKT*d3QoI$ccMgU#K_SswDFsRQML5ukLW>076H&^)lKqi{b4}9ccg-0x z_sU`K;wd$tY80^kZgP}?>cvei=zXk38^Q`R1J7<6#^ z$|Rq%bU&90y_KKOmF9ova^oHxlxK~?mF)wPv(V%UdSt~EK2<__uzndpA8^C#9TktdiJQoSBIucT1+d%eBC`{PKh zZ_DBt-0|I|rCC39Nokr*%#Hsn*MY1@FLf+|3Ufj%CldgQd$LCj6}VY}qXa?{IEi7I zn}^f3eTdK@E0{I9YUG#50I%2YF8)Y~Pe4QHZE~oYoyY>hyV*>>FBjch=la;< z*v*e@j6cD{*7$(tMN-sQg_nezNV)#fzccC~%nStR7~(n|uht7&fiOL=L{{${0I#2H z(agn5$onrL*bH96Qf1e>+FbJ41Dp7GYL}!c3oOG^swxSn+T}kOjFxo0EAOMelP~Io zJrNm_qzO?IC5lzh6Y+(=O7VO$(Gziw>-A^5yf;~CBoQNdxA&w-%sUX2;l#S&k%NQfdS5v2!&#pDq@^TY@OLkKJ((u_#NL=}2W_a^xf#x>U+uEKeZhAf%}Nu&Ak zrI)0ru;J2Vz{{iVxcJXOOta(G^#ceIU7_O*)cFQ2DHGec9Y zi4i$XG7@_a=G&y2`wwj=JjVN&PdN;mJ40mn`}DTtjzcG8be~8 zkVv3tj0Omq>JS|3ZMf+HM`c5PB^ocgy5eY;w{?+g#>xjrrauwi&oxI#Kw$_G08T~8 z^G7}_{k8g&Qv7f&Tv>}2#LJK(7;jP2!k36FBe19_srH{)m?G|VeRD{e zn6V$;2#+=*$_ddO0KsB!XarFF0d=UBGpk=?qS5*^l#E_jzim*LFswqb1$qB%%=b8e zl1{_L=xYGLWnNPb-?hUq9QGAwpC>|?j;Rh>u%DBG8#}Y@MJR%0w z7cwEv4#tpjX1sJu^pbVPh%RIEo5UU_EJvy;%VLZm@U`3g<_sMsKbR9u^JVMNXZ{%S z;JH{wPy(T-l7!)c$SCk0x3?yoymL-CjZA5+^{vC(A8d6eknTlM76+r!+0`HDUmz>N z%Z1Kyq~H{Mc|}1RKK@}ZZhAP>Z}Y%Ui|KD)w-2N7SHK4A=#DS``;8kzZ@sl_G0q{1 zNALz*(h#pu=+7?*IHdxwKj15p0tG&JC;lJun#7yd>350qZk`fbuplu4!C@4uh&%KH)TQoH!Fbl z&7;xg8U}YtLSdlDr}%q#C3k_}<<0Bq6Ff?vmlrh_mNZF+XWk@;nh@cNELUjf-D16Y z&g$*CgPX*X)Zh|N$drm6#J?X(VP^1$`%^RYE61(}glfszG<<>8Uu7ZG=o zX}5LLmToJC(P*_koS|@%;YpsrIEsu=ntfv6D&R;mBR}UhCvX^7}pT zNSDaOA!{LAF!3S9UNZ;!3i5n{C#bbrP~?-8o-Vi2T~UQ1cc8CJ^a;N4Q(xfr2KjoB z!*MBf!)Uy1yx02H)_dAD-xSk;ilE?(ppddC2{LRzCFzZD%`-S0*&bh5Z_1tC_*jaATiUy=M8yllL})cH!Y^iYmzjz3@sDDPk|ZDcT?F zYdtPCjlsU4pS5K|2#|d|I3Sb1esn)HnGq@y(LBi5tM@-}|jD~1^&9wSK{ z!wC*!X)3}D(NJv@`wKFO6V}{`TmD>^Q!g@!^TngM(Bi?Kfmi8ra;L1_9FsnLcr=)o z5QdJ_0$&e7%6BR<&M`R06D%P<_U-UP&K=jM!B-#VA#7#C&*zQ2Fnz~*eZ_o-I`KJW z{d)KK#oTUdgC)(#Qzg`QLN!s%tqcST=A zNsd!OecIZj`@%mTKO5UyHOZ&~BXNYxDf049zqEei>6H$)o(nW-!h<@rs$0rpI%A#s z?5@$R*2j>j@H|0~EQ#R^#c&b6mEY0!_dg(#IAd*nMH6yUznE&GB%|`OByucGP-r#L z6Oal~r>CWLeYn@zbd2CqE-PcQR3< zEoT@1e;u+g8MeDV`3Jl5vL=yScxh{jyg<_;jR~SkMLkMtS35pxG9kh`iYtvv{;Oph zW}P@=3KdhF#HtvDNh-mri~x(1>VF)5ng@p?M^v`$RJwcDh|gQ1`%yp zWC@82UV(x;)!6H!Cz5Sb=Cl3<|HfqK30x)=PQn>N!r+K+K#J9KY*S889MX1PlnN`g zGa}&_Er^&XNut_f<1H)duU=AW|1CMXkoOkb@C5MwM zDBRVJ^%=cz)}1BVk+vOkFCY8;w?MC9H}X`5rJQ};Jv^>JK=C;hk1LQ{;4A3ub-TPu z*Rg^tFsa)gpClc4JhdLQn@6>9b-j_AUQ#osZ(powIO`QwU0> z1c{g6QuIf}-N>fSnzekuS`hQ2(PIv3buc`G2{gST-}-0M>&RMQngTxiEC?B+MMw5% zu~bZBn@yM7PNh86veV@)Be(w1q$#2z(1e7EGRgD1?%21o{WX83!N=!988a2{cf%6= zAH54{YLoVSI6uSE0^LPCI8SK9-cu+@fqp{a5O+ZU?kZ*^f7jghW%E~S9qQa7WQB-U z!7g`jf)R0opmA9wNs{Q@e8z1H&#le|YP9=w8Xf+B-vu_^SyrFi;5*d!@bfbXN`T|t z#GWz7Cp~L!ljmK1oBUVI&4DU0q=ZYDL~{|ljvwwwirRUa+rD_V!|(EPFjszA+I4(+ zlIl}}%o|Ja3M=rc%nFX?gO2Q3(>e`aM4MirZKbXV4+9GDs5yL~z)M#iw4)de{qKTz z%TRbkC5LZKFnmjb;o&;rn{~qQ#02ZX4@QK|rk1uJ8t?q+6H9CbXhI_{F^!81uk zT}8dzWZ+`PwWJ-%4>$S?yxQKC!3Yr-M46HYy?I@~(DUYcPn4C3yUI5D^KN@C`#t6gL`f`%6C}^b zcm%)x)d344YQlEYPj{;aQ{vI@#wmiMIfcXsM$psm9tDM4>J)Yq;yXRuP|o+!0u9pk zKuh1|RwKb8I!-^;1InT=Mb`ZJL$D&hB0E)q#zYL4DNH1OU-j5^@9!*1 zhE=2nw22)C(6bElPn^0edhdavUT6E_$CrCk>l&d~fS{fM!Kwty@&d;*Z=HVX?cKKf zYYn_W6H6UqZmpKM(Ctz>KU{B{oc4G#=U#B9I>fq;FW?g-#pxq>ULi0>VJS?=%U^h# zJ?nUhh9v<}Q^-so>ta9?M1`15J#0r`-17T%TfrMqmGW@`XIM^QS%KuwU+JmyLrXH$ zj$_lza0&xF?~9x`d)kKHow<_&1OxCqXezu|OXG;cZ#J1urGQGf0ctd}9 z9fFVuK6nQY7kQeTapaqITS_#k&b3L2N=+fv80Ody;UhNDu-P=wHowKBM>czqi!@E( z1jf@C$FVrcMflY`pfTgMk(QE%*;d~5*CVSl(B7kPiX3uuH6uuW5RGrRs@VuXeq);$U@B_)Y7rxG%~H5ZKuB5ckGP@U_;X^ayj!* zoMy?kzY^#w%Ra^L&lTM6;a#p+gLUqc4LT+0?O4O&0&dm9H$GlWPnp7LGW0xNoHCY< zYK_v)HQe^z)Tt|8-3B^xe$7Fb<)Ey5FK}zsn-~2# zpa55lI?(4BR#F_ z51=b9)1dzYP#CHw(@jtq=G6N@VORsMXM*AGI$_vrd>1GT+q&qi6XtZnuu&8m3d0gA ztfXh(Zd>xtdnd&=z$ls#)nBQs@`Au9ii+`Ub8myT_ve0=;ep}=O$Zwx2JH@3%WE{6 z0lmj-^bhTW4Pd{f(PL2jqY%Ru_OAz)?MBe^B~ZSATJ2elsRUyk9&2xDv@29l@ZJ?# z{(%?<42`2uJh1(vpLJ#<6QW#thAtjQ%&6e;TCE&r;)}u=cNnU&Wj-OyueeEL@V4kFaLXMtDban3nEyEh;D5^T1 z?he~GUH@6x?_so!W0GVsjOJL9WeA!i;Qh}UOZb3@OhT|Nc=~7m zv5V3;&gY4SOY<+<{+vE-@D&-&kdjVzwc14VyDfa(0T+q<5UnU*nC0(* ziY>X(Z6cXM;Juz0R76LVKO> z%ZsXt7wr>#tZkcpVY9~{kG&hjDOw>_S`ipp*!xwdx}k?unQ`~u$+r6@7S8_Wfo74t zs$}pAh0f8+c)ybo1zO}WMNwtZJa5UDA2#f3PK7uyXj(pXYuAb9t|5 z{aYnPC1~gn;}ik5`CV4~pHpn}JRKjX)iAbTfL0V9!&OwBQ*SmQ~5P$XnR|=S2ipF$$CN#?AQ*m+=B{ebK3#V(duHjlbfrQN$?h0+}TQ*%j zmtBh&*I*x8Z>No52D<)94hbWsvBe^saz0@+de$M#vZ9dB< zI~h4Qk*p!iJSA}L;geLf1IjN7kTI7BP1ecxyN`7n>_Sm`fxkmVvdLgmiR`sFoy}jy ztA5%_j>jfGm|yVG;GH5O!ZEz^nMhhkPc`}gHuC!D%bi3?vB1{#3nWiG2#vQP)3{Rs z<)k7~lBJxjziW3^eIyHMuNz3k5@3mopBbOVFZe$Pi%!&& z88d!?i#SwuKJ3h zSS-ejnI@~KLL%^Bvsk+6E8OusVTLC~c}))S zbv?K286w@@Q8u9JRDHta*nhnop(Ub_Dr9E;1J>=ya6mlKa&%iuF$u(~=Rg8k5#pb`pK1p!3v( zgCwO3`1Hj(9KEq=w~nJ7y>&9mPq_|Z|yM@X)MSmb-+Hc#3QpvGc%gQrB5%Z>W`KJC6ARA+bH|- znZD7CA-enl%`dotk-(FB&=28q!bSyZn|1kSJH|JDB7JvCE$M{l#l!noIPb_U<=ghH zLl_E+)Fo3+GO^+E=r!l$*7>dWVZ4-UD=XNd>#!k^-{t4Ml(k;{9eyNT-ID{2YaSVc z%pc9@BQk`^9nfAU|T>sC8hUl&-fRm4K}O4aJstj!2RRBuoAr9@+!KakTe9}xu%I$ z((h7XF`=O)kWsDQB9{b8)x?khti--~#??wNXx~GdY$ko(DvtI%{%q@+?U?6?VEr^IeNZ-3(KKSvGkVd3HP51UOh@16aX zE*dD3^M)#LES?38M{{rAgB{9YAv2KXO}n)zEDh0TpV3|}H#cA{CK2y&3Q?=EB+f6mHizAv>XBMrwkA3;Z;nc@V{@8~BUo;gAB zc%?CN_btkKCUfXjQ^jMz%lM!=4b!1if5khkWm{|M*7y5HPY5Gv71X)Fl7E1RK}6C% zTNc|o8_3Z2<$N_utkHQuHvRV*InGHx@%+{O$u3R1I5i(X%?#tOb~_gN_u88A6}?%u z7nIw>>BOSD!^TP~s&UCenK?A%Yrk66DBiQAW_oa~2>-|v3u*WLn`PhX<@QO#Oxqf9 z@;Ll__fJ{aIAjGv(lzZWUJ#ogb<7zdI@Z5Zy=?w%Fcc?~s*g(l2{8F}HJ3w?b2quHdv!;VeVF1@E86eLODqacq&Mnx4Elk;Y; z@~+b49}y#!fVe5|qs%*P>1C1D^*?l5w^nIsoB4njg2dvS!b$3`ZVzAzW29X@aW;R6 z%0D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 FZb9Q_mW0nQa`|zjX^=^02Ug?S~ME+oD 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

    OaGw0X0$FmR5F_ACy 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≫={Tk{t(BZD7H!-XU6b4sPhqL$hdLS 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

    oV{Ezg6C?D0&7e{@no9S+X1r++~X_7tF)g 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 T#+&My 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%0Dpv=6^sL;#aedv%=`Sz5;aoy8)F1fLkt61I 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

  • K7I{Rt0Ez+YPt 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%0A=z%r)FY!#;I7012uh!8fi}Z@m};c#wAb0 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
    uNBT8?bFx0Z7{W<3l}8j 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

    9C(vwyz$$*DzRy#7SAv(4y4p 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 z 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

    ?@cb#d=SP$whOLZR~aTb;Ya%6ZOUE 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�AB~4Eg1-OKvnr9Y;Q&bum%*bh5 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 z zrzY3`iwFQ-{D*UT73=(_dD<`Qu-Z24GyD~ea?{sL6zYD+Bf^E$4eYCu5ZN6Dy(HAj z1>K(2#)?v6rKepS=Id5G_Rv6!(xXgs8T`WwqNAOy zgHyq3?AVR)Jq;-F&Lg7>DP>zHdD&{JE$ZBp48PR=hZh1$&fL1C71Tmn>gg50JdX{* zHvd#VfuS8-%ddO6IPWR++AJse;W>1u;=-)*jk*dvWc+m&Wa$zF^%A=%eWRBcPF@C* zu-UeUY!}D(%=*igQDUU1L`Xv1#LGvMRIM!yf#FmAg2-(^##!aH(vTquz{89j>$`95 z=W{9H?r+=?UuRB|QGx}i?19eqip@m!u_4DPbG$33*ec{6UsU(q?g@vqiSxASdiSsf z<+z#UX>rHc+dL3hf41wN^nn_03cMF#9;{Y8EwFkC@7(QmQV5F`yKF z7h*+R(jnmPEB?y-nlBD^S>3P;7y?eDlf3^ur`i2nxD3fewA10sN7z=pX~9xO7Pp`5 z65vt^ni^63S56TI{_r~=+Y`F}l3bxK)o6Jk5tA?nlTJU6A94Bv5XJZQg4+G+2<8&f zxk)eiWZ1g`3G754NBcZ<>~==iLEKrt7CJE07|50749=8-bZ@joJ5T#Dw<0Mqx(mtA zzn>Wx@4dzvQ65!l)XkZjLxp)TY;5aOX@mkGOnx#ukJs|m=}-L0_hgC*cqQ-vmL9Wn zZ7p#c({4YcqeY^D3NWxOCuWaf$0C*1@PR`rXOA;%G@S?i{y zWbys6{)d@7wQ6?&H#<%|Vb8(TQqUQFdEenhT?w$Ay}&70dC4Py-0G-VYvJJEG^D9Rs{@QR-irVQ&km*7XJ>pm5 zxK(@a#`IYy*(G1^k2ck(fI4?Bb24`my5F<>0)xm?#oK3L-cz)doa;jiFtbsmjg{;l z^oQPFxv3Y459->MJu0k##LBJfWND>K4{mr<;uRIT5w279WnJ`^z7E9Ey?0Q?8?n1= zjYBB)(#&(Agc7*@FNZTSHdH%G^ys{7<6_=D!bSB}xIbu#WFJsw1hlyFw0ba)3~|-@ zmB@Xg*Hi>#%8I8ESe2auM7%rkx@nKY+E(!gi{VU;v{xy42*IZyqv$otVW zo+bt5a`7qS*^hcCy${s*>xu2Qea{jZr(TmDzFkneq_!ex3o*~L(3FJ_?}N4scd;5T zmIkL4iT@?Xp=jj?nulQFpJLRz4gd7ne46mGZTJ{^4=PGFa9 z{nX~LwyWU8E$$c`8}vCYuP(L>c=#=AtgPvNi;iUcjYYi%q+^B6*DCdnKPOs8Ctxq) z1@AY~T@paZ^U>_!X+(>0Dfzt3 zuhc6~zWF+Y`>V|}ZqHoiBidtLjuX>c&8*!gCX!h6ykn-r7vatpASijtaK3y!pa0ah zo!u|S6L)c*+DfBzKc*ExYq!TgjYpxcJ>e$~zU7Se=kqV_1_bV9I@GqeNS?T&t{lUX zt{2p+6m4Y9O>>rT)1r?R!_DdFpneUh0ch--lcApSsP*G z)m?^RJ7h4c5ML~NqdUhvp`f^cN{kI&XPxrgQGk-a#J&&CXru>)s3avBqAHR&?q9RqNYg+jSK=JmT8 zA03+XvnP~Pz5<8|(hVA`&R(lcdKHN}0h&0Nf9nYd^0<9rmyc`M?GDmIw}LTpmuRE} ziQPJl(E0Q()>b+DIb_d(i(S;GLDRxylJmdq5%~z(fk(ksQ=FKs;|${fu9F!P*7(cs zruU@mu0cP(Hqe68f4U

    *P@Y`qcO~;T+SwX8 zF$kCz;TPHk&^)N9qx88E`;dl;n{Zx7CukIYCQ4i zpx#zf;(_pg3UKiC-;Iu%$8_)-Jl*z5*Wf2W9;3&#V=hsIfj$P!aOBX+9qS z71f^LMq=+0luG8!nNNeIftX?f>FlJ%PAs~*eI1}B+eE%W0iD^g2A(n8&J8~KXv5^FJHe|> z)1~^uf)(}6Y{^ymYO_R!hPC}oNr?>0J~#zIzBz=>)9WWI6o^w@C*QP5p^{eFPHlFk zW}3{w2T+>ubmsilzBd+`<%elj*rk$rqh?Bs;z8iuLqP8+4IDaS!TXhtpY*+k-AtM3 z{%CWM-fM;yx~VU|HHh9pgQkhsuJ!bbwAomT!N&Ye!1~QtE4INSw>C6X3WbZ)+$oY} zA(o}+b@G)H-_DKJ7ZlkQ5V*m-R^eA?I65HsS5oXpg1`4u<>(6+}z*1D8dW=Yq%)%Tf`PhptmAPPc){4M^UtAi*b%!~KjMg~;TB zR(a*&pHNUBT5y5@CSFk4=iEp=Hrch)>4iGO=)NIu_~U?I=@a_Z5}V6yI+q*RO2`R+ z!>FpPe3hIsO7u{c9K_;XGL2rfS71GWYj`$K!OhS*!}fBE3w4^_&ZjKP0`6n+>=E?i z@gK*a{N90=p?mGwt2M1Jb}()A-AL)O|6+II=yut$wp(o0cT(T%CDE66q;gS<&*Qn& z)nh-iw2Rp3T^$HIx}rXS@LyQJV~-AI8kU3Ii>uc_Ftz|nV3%xwI}ly93C^?*XszNT z)=}m_A+lgcA1Z?xX^??!(O*%C;}J#t-GY517SQwD+b@su z1Fp_+7e$^Fu{?1af-oyZD)CF}6V5)r%LsAi$}$^Bo;dbXRwJlgAUL|3`JW_Du#2Y_ z4@9bQb$X;xE3vIA&)pgmNXSXjFg76!nxOGqiyeaUB*fW@fI~Wugvl2yj{Dp!A_wkY zNBj zPwWHZ70oaivB{nwTFCW_xkS@d_SFvl_jh>WaLSU}k}TtPA1&{}Y~IX7U^f)7i`Hf9 z#Q^975*x29_RQ;Vu1TD|#bmeH29qNIhc}Zw+mVxYbu}J?gHnIDyINY5y&cv0<@1kf z+)ABw6x|8P=sHXevFH4F*xurn*CCDuF&_%0^!WquL(*UJFw-ePg}FP5t16TrZOU}Ej`L9R?q zuU+fl^ZMyIPJ_;~@#OF3)PHGrSH1`TTLmcISUoWL9x^R-9l z+PIZYTm0#UlQ!)q$r)9emFNe zs;d~FQnM4QOO!$(5zI^<*)yM|l^$-pQS&MeS=mmdC^g)MlqVfVEuD`J_6{OltBWyb zjB3YCyz-=;l;yQ&C6IP?J^AhT2xRofWj(Lf!B{z3e5eJG@+IB_5$|nV%(~9NQ89t}9t^MQc{H!*Fff-jj=# zM_OT)Dx6cu9NNlH-ToFN;3sXQ%~*s)Nj%%|Gd9mI(kK_ z+HtG!$tbJ(v%s6bilW(qgm z@okzERBrJDY8Yd7j*Kd31pE<>Zy#DFx=a3;!Z9rA`gsr3C{_m9uYOvre)n+s#(Q;| zD9CW_m0l_489vmpTr<<`U3A{ZA3sF`58tGD+Cp~@`gd`oQGDlqs7v*ty-WR-giIp? zx`}rdDR|#w0BPHau6^NI+b1pdAXi=+;DWtmTTsfJ{$*MM4f4uDXGWos5|%)oSnzvh zJ$GC?1I&I|-=F!;Jab77@8mm}<|$tIUyvYsZzs*^iH8XLr?vj)b;fuQt#y1MXqu1P zE$NG#M0iUD_}Yp7R?IF3FYV9vZQj9qd$}BjBmd;~@I$1BJv?~d^%BRtaj z6?t$aVdZri!|8JzFu-84OLzao9NWmF!Ltx`6DK@5D*2+t;{>i=c0kw9z@uJ|eG^gzb|?jKoF|*HxBK=d^`y zu!Euw`j6sk+v*Ku{fMV-r4=NH>vUckLYYowmgjUg0l$a-8fP~Nb3wMh=-F)XfgyVn zX|{1lSk~J(;8#u=P>nCz5RUh3&3CzXjFB7g)w~|m7QH{`SUl>|v7h>P2#tAhD_5nF zCa@pTbu>0;xx>~rw-vakbZ@qeChL<7yW^9H>fiI}QKlv1*Ru`lSMqhm@rEpCN`l&7 zH&pf$`LFWGq87a1H(qx3%pvhoD~YpoY(;a)UbHtaA(nBK(~E31X-*NuTozV_wOX&e zjIE~0&29*cU}0}OXZvKC{O$S3Bh2=JC^d+Ct-L83ZmwZ6U5Ce!{x6juFH6TWdBJxQg^ATW{9ZW zOXJLx0<@u7iEw0{f=GBW02hB??Pga84OZ6XVsnwxUt)mEJjYMj62$ls`xAK92ooB0 z@g_LuLN+j?n}lw1EQUs;`kH$go@nV+OLYLK+@or2 z2eV$XwxSboh$CPfsA1%HOafrmAb-p(8i z0rg7x$$PPa#~UXew&wgbr+v^3KsC3cN_{W>Ato&)vsE}{sM=F=_DYYa7a!a^veIiW zd4Dp$aeJTd^)mNa;%Zjaojm8dxhjT-eB_vyEyHA=rTUuwHLH3N-=a4* zEf{=<1ux3SFIK;%eYR$Xx1Sa#B+5wP4my)l_5Xl;kKyCXz)g+hs(CDF8XaQGv=4KH%@6${tFL@i+Jd)<{ zB*);if@Scvr?hf;etx{r@AU=h^oL5~6SLWC&B-*MH7VfdB+HUzlHj|WigLbqP-^hJ zEh)@)5gz3dLFESwF@`mHbretV-z3&;y9^Z#LooHn`%vHk=D%>O;4_a|XLS`~Z9jYd zblK;~QSbIe%lKLC2#-H8NxMbpqHn=DI7a1pWT*i20OZ0gOX}A0!cMtz5dKC3oy|b= z^$7Z^9FNR0q9g?$ZGH6jc=T)sQM&Wc{#mL1i#B|%aJEZp?*6+EP#Q3^n*wHdeq-Ua z4v20}5548+4^C8ug#}KdPoqIfXZbJ~(7UM$5GiZ>BaiD__hBRBOsi4d9NX8`l#+N}6JyvA##I=S6~w+wdtyUo7a{zo+Y zzv~y?U|)89!L6B1z9J3zmPA_R`DPu$=}&9T0uJwpccJY-%4n}?FeiecEty2tS>l0T zDzndFN7Zmw;_fDwPor>#jv}@mA3oup4>DE0fm6EWVv>zAnbP{wK@ZNvm5(6U6|P>o zq(QlVCo3)st}M8(eCWc(UeHtp*>eLoD zDqlNh8YT~aq&3XReI&2z({Rk1bY2Y#@#a+b-cW-yt>6heqD@r##*_3%2Mdew1e2P%KpqWL3<{m}J!7_#D*U~L!9x6G8I zzoteT6l{_nB;-rTKhu77xqAJN=u z+nZEIwg}C*OY9<)FPOdm@F1_~O$g^I;WRjw%AEKJZ^{P<`dj{ z@87ot9+*m)SlW_NOfe@Y|BHmyh5iVb1x z;bzm{E=6)Z@qo)Lz^)8$ki1P)9zH;F8DJ6FlEPJsW+*3|)VvqAvguvladZ_4Kf@Lq zs5K2=|5f*OReT~@?9*lqmPbZg4{OCrnbv!BMB7cEGI=(AF^LlSllRb{1@P_cy|B5y z(|;WxrrcGC{B&H)xLuGSH2QL`j+%=2@%L<~%PjKDfmd+S0Xa-e#?o@SG%+r12?*XB zMs$p267&W!od&Jm#Y~a%%45v?PQiEVW6WyCMhiw7%s$_OkZ*a?XoA~B6md|=wxvIe zQlcbrmNw%sIRkKSS=3)`dbLn5FlNAmf$A$_TXDeyB!$#gm6^&WvgbHaBH6}Kpdn@E z@bDBhX=gF^fZ9fC;cy27c9I#GnhbfH%0^y}SRKD?!UTn~8=W`sgckSco8-QZ1X5n* zPkN4Wh58a5e`dj!E)`a-Sn7c$xm0mu z`v>|SGi(da$9~@8Pfgi4u6zu9Uzfs|>BLjNY+vB!2=mwhHHiVif;JA#X};O|xqwiJ zDmLhgjxinwR9dsx5qamp!#Br?CAIPNOu7t2aTk4K@5%vP(JgVF&hjdwJOKP6OgO$H znUZfk*Y%3HUCPGlMhhn|sXJ$EUpviES*}HJj59*4xU({NuMh8y>wFIL>wK(}Y#DU` zE3wU6BAD@VIah-Ol)}T{z3vTcQ^Q+?OUF2wm*bL8Adx$l_)|UW7SJ=Zw4ZX7mgs)c z`;tnqoUsXCjr|VHGprc z1o#9vRJ32{r}ryqpVX4qJVenA_}>lthV)l%$`=Iz3D7_fD+_h#bB%n|X+k|Hy1Znh zXuQ)8kBwfSWs!e%bN#FJs1QO~gXFm`@2Ke^kN9}CKmnD ztf@t`Ln|upcw9ZWhtuSKdjeRI|z+`)<~ z1AVVDxjCxJbyF`N6ZH5>4$p5DNC&WsAoL9x$%uCV$Q>9_eN#e&pt!fePtUc+%Su)t zN4V_I35Z!G8RFQj?AmRGHis`$ppl-l8%o7Zr0_{b5}QvcI!D^C7ZBTik2#j^5z}J2 zWe{Gs+KRjSl00L(E3#%!2y!5!ZG!9W!RH?;^-Xd|X|jg+Pkm$8Wst~oTL9$LIMf#f z(Z*rUdSKoAep^2|{P6vA*t!H0i*7o#>aYb;cOlfm4Mo}8Q>!-)Gh*Irw;q#%J(Ra; z&I6I6lH!Pa6qvq@4}_`X0H9ML_>VIFn|1{1MavMb#U&S8*2aClo22I?ye=>R?|DI1 zTfLOlfD19^=)R)sNa3T-b#R?>@w8xY?q}NQzk{XHRg89$*Z6x%%z?ETblt;Se{2@!N# zPAK97e9B=Ji9x=G1Cd*i0!1<9{WXyz^%zl@$NC05f&i`x#NJbw&>|ZvaE=Rq0GNR` zaDTI$e5{8)oZ^PijC?blKyn)L`~srBeo1t9x2Y$XbE11?V=d-r^GT~4Dr!w*yUWK+ zGGo&{3?nnWyK^?>i*p5!k*$WUo)>V#tPRqg{wU=>$t6Ho)$BMu0TgFrNS6@jl$Stu)&8kir`HSe_hoUONhHQ4k+=V9^p#Qv5$igu zn*OKrrP*iswa8Zb^M)^Fp9Om+xA3+5qgGzmHk9~5m|Yg?G^FYzX+vIEZXyM)-$`?6 zV2W+Le^pIL=`^l3YOUl;V!UJWnxN;9cbu$f!~5g%^@m9Z>(^wIxP{_SQz$+5p*^Jj zFAK7Z($il{N}5-;nqm(%UN0bEcZU;4u|mKaw@lV&R-i->oEGXq@(=LGJ*Ogi&0>xmxlf6y7$h*t6t{??{ zWozGs6+IZ_!HxP<`l;)#O^4{WjsT&aaeoUr5)yIP-HqE$i&?{v^thGhqX6pbpV9*Cc9g!<* z7nelTkTe7*)5E`qkj>r0IjfjG1Sa3sPnkT!ue?o8jU1)Jz*DH7d)QUzD!iAqqU5P1 z7?S|ZEn)TA0DyZ|kN^Sjsjl3ezx7+erW&~o;_>-`s?&$Q)p_2v+*t|_E^j=+>8O9+ zlXTd70jB&J=xc3-gAUPbMIx+ViL~rr` zvFSZwD?`QSm;#<#pM_BA zWlMQe=9^&|X5PGqXlk`Gi2u%DE`Oe2F+b(Iuu5~`Yqb8c(*A4UWk^P>a->`GR_hc8I&fMI+@H3xwXyUQvvbb>dQKco`6E$#oJZ zx^v2ebVB{epFzdQH*}LeHtLtE?2vKpJoq&b1Ky<466Om-jvZ>MFl69}GHNlZkbo+X ztXzdIZ(U9B(oC0*Yu@8JuReJBX_EAV$!XTEJu>k@hItxa9&XmjR&4QaW)ENA-COl! zqj1h-E%D2XmCp6(W~Tbr1y|wM8VcCxK&kCkUwPz4)a4DlACJW*KlTJF(?ii343WXG z(&BHz+hj1vGhY6Ar4T*Fy=AL3olq-$r{(fKg!&PwC&*k@1Ca`5(Yq0gfF7+A{xso1 zUTCMRqK88txQb9L9ukzQ|CsDy%6xcYyJ4hHhP1Sdd*@U!{1J*WlzdJbLF8PJmAZN( zt00U0pI;?YpYR#VZ*APB(=vd^FnsM%!4W$D!~@#=TCDR8i&oQ0QY0|}pfGvn8_x$K zz||fY5Z?MPZlkqaRG>-9+R3=XZ_@s@pDV{h@v%xC{m~W(41z-8>6V zcw=4CG*ChH#&GGcY{28RJQ4NCqu8s*#l}-!wds^Y_8Q6;7JV;^79el^v)`?Ik(St~ zdCA{-J*Ph&F?{&7o1@^S7S1@ zTGnDV0(W}F@raL#K!LOZX9z|7qy`98uGNl$%5QNA6z`Argc zvhmymPP^`lp71s9t~U6ehOE+%&Ki90^PO4K+@gCa40KE# zh~m5hVokLfg3GQWt)*4)LEJfl*rX0Hp;)_-UT(uX=z&EgJFFKjX+>{aEpmF=k1;+5 z>qyBZ8+pJ=@29Q(im~0lq+?oO^3okbXJxQ7`}L)h%v2@s9$uht zRg^A)p`lqIHU$Yw2NK ztnI<6r45CWoKaPDL)Ps0E(_C7CW$i5>nwDG{^AgH=>K1$QA@O~lK}5x9~d`ZsOS_w z^Dji&G;CV-q*sd5zRU2zJ<4Ud>;4PMa^x{Sv7n8cFYgE$b?^Cc^PLt9l= zh;j;kLRK!}gBC~m@;(--b^y+_aPBTa#L+l3IhU1)ej1z&Rr#v-#$uW=Qi0; z70d}lX|KtTN;_el1(4FWVd=Jco47w2R3b*OnPsLYLOJcM*yhEgPewB*IK!Z1myQw) zo2}a77B%Z-eo>~&4%L{uqkqsaZoo?SIsJ}NBEwiWp(JAe-GALjH17OMzYMk7G)_(r zEL7(7kSQ%-|0yuygv))}r!)kKZ8i`;q(VQ}AhVSIy3#33G769%yc3qAw(S2v;`>xs zr~2Y-s>JlxBGNU=U!G*OvZ}NniK1-a+UUzeA%#Y_R_b}ot2qhg<2^))r1A?P{BB@2snyeC`sb*|h{Tw}#sAvKjiUQ!l2URV8OeqJvu zYQDMbi2b*p!;e=7e#vCke#&psGqfqz%PnZyrh-#f7a{%}gSpBd9;+Yr0{~&w_)?l# zC$4&MF{!tE6NmVDn-}YIY6@@-6VBheDV6}xzpU7QMSk)Vxc{{pQE%^KIm3cMFFFn} zu2l0qfj^Qd$*c-c__xFRp7LI{g+-`3JydO6tyR3`)tr;y!fNrw$0{p2P=?bcMym~F zPP)B8O|tw>Ep*#=VfJ`0n-nxN-pEU`p4DxfkT&Yx7ROG(MYu=OMAwi9;ZEC@=5Bt6 zY%$|X@*Y2+7M}-T9<8$ zYNpZo$SFsk{bj5VpMOE$Z;(0%i9C%q$0|{oA~EA@86A9De&ftO?9G*H_N#ip>|VOthI#MH!2 z^$F{q&J>LW5V7jFbFuVBaCT|HjyS8)HSj#CjI>DQtMa~)ANS2#WTahU6c zH)w1$y3zFw@=d7)8E5j0;)lIwYQ=srArVyINJUa=#6Qz8T!9vf%`|c!BU+3B*pnY;vaYcaGKwS zNl9Gczs;xll9{t>XQXC-Ro@uBhgkhcj(w%ErTPpcZh9Z~V(H?@w3wD*P+j_3o$7CR z&?j4xx0Ir1KOwDuJD}vla}Eg}2St_*N|R`TREL_DDW?$?qE=jlRoj2ML{_T7%I=P@ z&@hnnX+CG)RF@&$0j896%8#){$*JKqyT9^d&b6$a*y%P_g>mk9f!>)eF+Ynz;qJ#m zF}=n94lrC<@N?lhan@-V{|1PMJoYZ<42yZ~F?=sdU)mHT%6G&QWzI8#JF3CcrQzZ{u7MGVW`8l8tA&b0uQqDI1DQu`E(G9Vo`YM?!5WSOIW=U!!p;!m!P=et)T6 z@Is68cpD`lxRLbI#7?=QkquD=-rvGro4E2O)psbY@1n}F&ERZP0ik74tgQ${&)9%5 z)9|06S9}*jQ^WBfg2T)!0*n+7f=Pc{et#gCWc610xt?uLJN<@w*J*kvf=9S$3v*4Z z#LFqGQ>e#1-ugK;op1l#VaE?2S(8@!Cf$Lhl679?Mf$I!_dwY3l=G3ESI?rlk3pr7Q_ksyG%NEnEtzV zC(>qC-86dK?^nS0hZ5U8E=D-8dzg_Nqxv zYeqYIB4}0{Nu7ZVQk6fl?QV~ApPEVb%%*niS z#S=;|^2Oa3Tnp&^*~#qcqrlmUEVUb^(-7ON6waOFcyL_~L(jALSsBd{$e{MxCgaj8 z3sm8uuLwr^DZ#{yM0Y%=qX^H9e>@xZb008`I%jby^X@FArwn1g_!ZnKedG?5SK!%4 zCiJHihzY*yXQM&7-jA~!zxoYI;e8##dkktik~^m(bLz7Ukf%LskgqZt!e?s((V7Ak z+u82GphnuFeWtNi;oL`^;Gh2$lk-H1#$0stfl}_U#~jxSIFv;e z+%9VY1;EIYxXM{VQ~dTl+{Zc1tWTZTzo%j6Y#H-movvEDiB;y)kHXtPpPoVMwPPYV z8x~(IFEZ;8H)D&kGlcK-mlMt=5WGbhe54K!TR|VmCXcgqT4Ee#S2R%$52jlypk4Pl6DtUUxEOz^8@$$|IvA>%LuMx>)(F>m;e1H3w z`I>mjcdi_}o*r6UzPh!YHQ@&~+|N1b_ctVMq2*t6ZT1UyanMvSMGY|aMR=4uJjXS{ zwMNoE5Za99)Ts^G5|~sZFh8*%$&@cHkL}aE^Zk&W>D?fj>~)kKR9J|2%w{7vZ#_W8 zt7K<3V(53&?|uwP9*2O;6flMKd8+K`~@pr_r6yv4s}yip}QnSm*c!Kosd|}7dr1kF`M^5!Xj2fK_{+R z7PV%;y0OV8S1NQjaY50(eE^)-@J7iwzfDyBGf;Wpuad=meHf3fe3EGWpDw~SCL|L9 z?&~m-Hy}W-Juv4@L{~e{8K~-IfqVNY#VSdn`(N9UuMHXS)0?Eu=KiFKYYdwz`}}z0 zKfaTYq@g(F5R5d^`!%B;TIUBKSq4%AEKkpoSLZb8P2RpPUHcW`bmdp{f(&e+_~Td> zLe|2!B$Mkdu1;d?ngv~a9Zf&7nJ>QK-RbHRm56?TLr3=L3i;+DJtMywC4I~AUNkHQLq-q z_u7Lym0@;>!H;pEDLxn?Gm8j zzZ~@6=JGR}`ndbGVG&jfRJTVf%W7Uv-gKR?I+^--|8SFoI&t%Y*2LdhJJ$KNBFU{Y z!%0fW9tg7bF}&Z9fqu7EanC7vYy3?>y~*@+-^f_g5?=*g7{Ws?R69idC*OQVgwS^$ z7#j;v?z#ifY8y$nEc!`>YIEnlVIjXBQ8G*1KhEnjkPv$$3A!Dhgzf+hk@vmjUUNGq_ z+Z~cnl8ikr`}s(6G0YA^YyD+AY)B6P5rwf1!*bPDi8n0*Em;0&tHN|z+DHcGioyvg ztQA@=_67)mE;q#n&+AEccjl)ctT)7Ws}6%6A;!+bq-(aO30+?>$FjQlSuD7d&+DXi z4*=`n$=`xMmfw9b2fzwhU|c;tn#b<@!rwL9tG+$;gEwR! z6e+CA+4A%6UJA5qzF>@uR3Y{maINTa1#i4fxt+n9>KD0^H{t7{sTrP{qoRI&ma0A;ul#E_YK_8=TKP|sAnZhkqX-1Tb zwi}s0mKO%J`4;LVUfq1@<9Zl{^*SR|;5XpzJ}t;ymr7+H1km?vUpnt0|BbMAKK>hN z-13xp)le0T&*kD?ABTp1Wn1n}n$cNMSYyjk6SsPVE3A6FS&hl&8X7#miFljKYOtCR zqe8kAU3H72;Fe7C{P8fjv%qv07?+9}dv_e%m@^x-+r=zvk(joRjtE4Q)^@jnk`skT z87=y=<7r)0GsUBk4DSsQyLkk%f3~B#NV@$JhL2Euf%T!Xv3M1vyG_Olf98*NUVIc> zO`NN2kWXJGlC5A6^J~nc@q9e_n}D0n>aO8o-wrtMc`Th61m~(XAMq>(7wHhcxVK$M zr8O=E1lD$wo$0AJ6vcF3`TeoYAF4=A|1~e!R_+sVGk#Lb zuCxU__D2SuB-*S@hR$33I9G3is@dU$%0v|Gj6m{Z^d8PPUG=lLl>fO)z8qgoDzl!Z z??K#gW!SJE!LwhaqQ9vf9NqZ7&R_+DrV#-Qbq|*2t#CsJCkm+DW+!HP-LDS!-%fOw zxyLf4$YLaePKh3N|D&=w%94hz?W9liu`;aP|T7Azf*$wVTHYBmAHCKGVIx`p2SK88wszLnnUB+eGPPTNu zOu;EZ5NE7w^^Tzu3^Cvaqa{taDwLz{vcZ!tTn>meS}TLw(Z|eA&;-;x6~}qo=SXjn z!4xq<^=kj3oWnXsN0|&PsWDb{UM-j@7GEc_c+>5!LhInEK5#cx*PZWd?;e z;rE6Ex5;zac1=4UVfgry0_4WnMBT(?D?$G_cO5Vyx0fB@XH%-yJXe1M3mpmk<6s$d zLFwHKYV9UlCGEtL1#cSS9X3713NwT((nj(TvI^%y?_apFM?7kh!K zimK1OeM#=L$N0YV)V?-QK!zm8J3yfc!ycX}y{aMlNxl|SsAoIPLiTnN;@>*mT*CDS z4pN%7VS#?Hn(FIm#KUi+m@aFhewLp6RzOjcrEj^oqAzq3>=5=AGwC5JrcoV2YT&Da zTdR^tCx*e-f3U9ZNs0~tRB^wtC-=0xe2j;X;|6=>-_CxlYSHR_lQbY+wz2^JI`xx# z2KHlJoA1t1?l*2ky@&FTKaCjUG*}!Sf#P@=4)h^vfFx(0>9`n|>Uzw^Kqb+LEYht{Q6MC&+(z`wPLf1{;n<@ro#Hc%!@`@VVFwmX&Zwk~J(4cY6>gqIZoft* zw>l;hJKVxaf)n54$2!StPsscCUYCZOXowM=-w(-TkIoOc>MfvX8w zB(X)nHc7z_h4mTA*!hPbbjix0-1XBJtiKOUF&Q*<_Xod)ku*IoP2L@GYc=BD&Jo)+UC8X*2TbdECv4(q6YWvK5#T}2EximSD2I6{P+UGlx4)&70 zdX0L8B_?~rBNlJdl=qSzJxi`Qv53R&UpiTF44rb*_9B6QBRkkW_}0cgJvUnH;Y&4< zXqG~+)Ee9LySKfL1gX5SjCIgWS~`9cwBQGm@4aj%+*ypV{HU_@O1B#A+wSF+PC4^T z1kd*aT-tg&Z8{J{I!=S_$KI#D&O<03c?iSzaFE1PzmYmmrugcksVI#@x78WfJ+d?J z#!~k7zzJW)lt3ZUUFL{;q<;Zd3YAkuRxeTUt^u}(W^3(%b#HJCl8P=Ig!QT*UdqP{o*eJQ!CzfLBe#gq?V{p^=-M^>)y^U=liRy))v<{4=Fx#Eaa;Dv3SU|4tzqiroF6ja+RWVWe zFy#t5M3p$G^1;;ZsW3-#zcanet>06@tWsQ>0lA#V`w%<9+d5fjEsHViNtdaJ?jam> zBL})gw6;^rYq}01x_=bP-Yrn~QAMLyBsv@Skus?AH6xQx3xK*WP(Cz3-^R#Tz@1j- z3Gujw=z2~K(68New`D8n>e)!tv@4^F6M!5=_PI1wnf@%rW$z;&r?C!^O6T=!gHKhs zn26t01vVvq6;7ij;?qK93h7QR^(8Pj%v15QR;tKs--r6PWz)DT$zyVTkwQ>&h&rj#%E>_C}YMnTb1S(eaQ!M=0FPf`1UKF^8cb3u+czFWCyp&+bldgZ)PMq z&lSizM1ObEHWM#=`@#Hs*7E!vmGD*zQh#M-;RWERKQ)KZDHRl@gyS3qtetvdFRJv^ z17(fI49&XG5%h8yK|X4bBwW`sip1`sSDDQSH@HevN8C@;9kdVQH2fk^Q$eO&dKkG&&>j{x<(rC3d^Fd}(1qFmHYq z{pdagQ$7bcaYgad4rLv!i8Tx>r$JhdcZK$!#b^J3p*wZLg%`Tm+5TLjCU4gj!RKV} z4Qbe+UgDDT&!?D zX~?i?@gwBf@Df3_1?*GeP1?c2fO?~)=CvBsLxC5#(hmsYlEq+56YXn{n1-_FVl6;rNh1IR*{DH$N9iN{{n5X04p&o9WyqqSf~j=-%V|h+QQ7H z=-^FcSge|I&bsYkYb3y}J$_|cHQNF)74pUXgMmQ9x;cc;Av^Tp6D}aD%`zP=5fw44 zuD7VzIt(^lS@((diH>AFf&^q+ICH_htyKfKerO8FSK|{&Ys(!|x|at=OO}+cS3~>v zED=O8tr5CDTi(Kym2Tp|_bM!UgApg-<-0@8!wB)aNPNi1;*_AyW(Va6MiQ~dWm=Dy z<*7&!>iLiRUOMDtdcK(Z*rGn#xO?M-%yCJ6N)2UklJD%TY7?OOK4Y^08fCzt)&d^> z=}C$>x%l}i#}-u z0G8FiEr$S~O%d3SSlGf=MIM*H%^w*1p279?d*y(&p=HOZ^Vk=Ms2)Z=*dtSL%|DAt zTFwLXKi^wTYzlu;XKdSY`p*(1Fb32nfbiGgI>q&$cAo?46pS0uBYF7dF<-cc*bV8? z-;G!5G7L9hiL2%cRA+dF%m@OL()6u7Y1Lv2Sk;#lkkJ2BXh-^|&uhTc0FJi@?50lZ zl45bu1Hq_iITaizBtHvn3SEF%mz(l+4~m-__@r;CcrPLzH}t%C)D*?Z^}vP?pOMK< z$pBp<1a4zl(}A@&>!vGb@ccu{GlwZ>j6}LIFr?LwXl5X<5uC^Ig(4jW*PHgw;zSvM(^JV2&gqc@h+`FYBZQlhO%v7zG^8B+xKxmSwz+jfJ6L2Y}KpqyDyr7 z269S=s_Th#e!(E<;B&IU%7R>f<(}sCm@>9wA>ivK4ibE5GqY(&{x&aZp@uqe{b|9S zNeexUUo*1VC(s^~F80dlge>R4lvy4ahrByOk!{`m>xFa=lITDo++bHJduxw0;&>P2 zb9-C1?g};A+b%{hq6gmgYJ}QlGw-qji0Bl2Jlpe__3q=v=<;^@hi9`VitPAOx6GJ{ z(!XO}iuw7T<%O84qsoIiGF_=^a5saTa^5L;c0I3dVS{FPGNZ;d$Y0s(8%w)w1 ze;5+I8yDX#!sSku>ACI{1Dv#7l(~t7_qGh6a~cpz9zwKx_C$qm6g~hwhpq(bbm2hz zWhaBH73;)`sATVY%T{IfK@js{q7Tbn5uU{2%1XlrSZnK}l+WuyO9s53FDH=VGW52% z<*2sJ;2G*UZAzaSz}lN?y68!Zhwb_#Ig z?$2u?IRd+InEePkXaZM4`hv~TdXewThrJp9%jF$THo2H=zJYRX)%MpcALjmZYN$Wy zv$U_AhW?O|&agq`V+R9PRMCPWZrJkkDwm(nnRhO#3l~;BSqyB%?hl`kk-y;SN&9B5 zd-3{ z%juLA6qidtSVx=A&^P>;FKAM(>cx4z{nqU#M;w@rShv5`r*dIil64l(^=fvyG++?w z+`~%P9@GeAPfo}pmc~k+Qs5xZSfxcRJ2aOPJh=kJBwip$%b*5iNG2xR{}qL_PQu*X z2Ks6u%tSu-(&ZCTDN>SNjDXe`RM;bFH-Jz{zL#pZA{3#qB!mb#1S^$HU#%dV0AnG?o~# z0?Klo-V(@;G%i;L0SS53sQJ&r-{$L%rWZxcug1Mew=h#Ja#G#9WA@1nVbInNP{cFc zF*Nt^F$?VHo%&_w@oKHuM(34mSTTD#Z;~riA?2}DWXh1~InwXlGZ&6L8V~;N`ysx6 z0{{0KifpmeCyR`YQsG_vs8SN4e!@oGr7@H!KeA?;+?W5Clm7?A_?toQfXw!_(!~gPRva zs57B0&O8H!j0m1VuWoqC7E>Jzn9)q{BD$Fwczf?Cr~+W$dHj{|Yh*FFLFM)rwFFS= zgEFrJ?feTKkow0dn6O@YcF)E}(v3&W?9~@eLGFUqta#9S<8b(q88eEX^6272|HZJh{-hU>U#nj< zJ{yNv=Fbu)H^J*8VkcIXwto#{)a9clvtxVt(h1=`5X6)*wh0AbsR#&qYGk)HfX-52 z{!UJ4t#N8GGpr1`G8V4}h{# zfWd*6l5<-n#TkKU|GwvA;2GNDv4nahF)`3gy!oAY=f*;tTUAUsp7ub zPgO;g2tvZzOPs@3qz~sxKb%DhH1jl)zIY4Af7~q)9KuR%^`+ zxfEoVOn2_RHxJUEX*$p~kL@`2?d5sZ@CMB_rfQ<7eXXn(s}bF~Ut~dX~^vBVxik-|G83w!%M7cZh~egM@^(X7b(1O9&J<{#9P93>u~VwUxc}x%s$NDD6bA;} z4JTn%Ib34*fdYHO#(T_&hn6fD{ITo9XP<1=!G7IXcFNa2tN@Jt7zJ`GS^{*O3tsM@ zZ)q{U#Dbcvr&J(4h>$gThlC6JLVQmL^vh?9ec#oUG{a%RqHVUB4Q~?H%0b10)LRMwN};x%1^W1M~gMN{dF3E|xY^i=}g+ z%j%6Z*3J?BGrfMTPv|u-LXJT=z9K1ZM~8B8(^AfZVYH~Y#KERKsUlb;&G_|d3vVoH zSw?gy1J*e{l`Hi>|B_VyQ8!2mKR(N2frmBdpG}+{d^DCq4N%#bZDXbvOqZrJRmhQR z-|-v(gin!Nix`Cy+{k;(>wVT8p?sSZ<)9k+XVdLONQ;ki7{lvsO?Zf@XjSKN>&JNvqKfQEH$zuUk-Tt#t>mWKYkhe%?6OF~l==TF4KoxkAjY5A^UYkhYlMwDO`F-;;yr*R;0o~I7-f>o3vIMKuj`$yN+BEFK;;TVr+do zNBl_gS|gdTc`$U_V)&10no}+bdwZ$sU2Y><9yBMrHf(AZo4J0an>u^DUkz~g$Zf*S zI3#G5BRPJ8xw$j_hwA}zR&%K&#QvKQaWm!`d4B!O{tvkYovK`YUqPK3AnxN8I>x0~ zct!>BY)2h?Y{qa1aUpHT~EO&$9}7dO))Yt1oC5UFSDtj%$n>;QxP@n=7H#<7s> zVje@|;Ss{9-x^Lrg<5MwqWuWp=2Qszkk+D>jkMw~CY=c<0W~7bJ5}xKy-rqiXikarbqYr=F%&`xE@ytn5VW%(r?dqcd&t z^z3bFK9QXmFjfQQGU3n}@F)3eW*o*T+5U_Q%RZo=KX}|Qsq~-fx?z^kKK9FUL>a2$ z!6#(RHaCF&q4Faoz^iLyVer~)&W;%4zN|S}ZPr8*O8d@ei3RsoJLkesCS8du`o!kh z8x%n8(~2*kzrdCY8fjQnJ2Y~~w9&g1$_M{B7(SC<>3-99Zhcph;hG2Hqz*;DG*G0S z`@zR1B#uqmIJJ3=79ere$k0=p4~DR?bt3uQGBGMEH!XV=Epk({m7GdyQG}G_2}|%)a!1s1FIC&eNZBro+<(3JLj)fIRL917tl-if{*={ zDWndq#3*2#wAJcc{EJ?U_qtB0i3i00+mCy{_+dAaOYKtWTxV}85uKZ3`LaVu?lp9Y z_|j&&%DWYdbl;(Lyzgq*oLtv>V3mCCO55 z#Ha%TiWzvDm%yZGU(+L&(8L`WZ+mIlDw&e7z-r(48&$Ep*4HyU>DX6D;wHh}Mj$YC zwo5HG?(_#PVMyCaxdGOCpv^4(h_AZ!Q7d}VWAb~}GNz*CE38NUjM7|IEF4?q=6lt$VHI)=l-lCRTEXY{o-kH2DRg6YcUK4|?5@4IX@SmTfu9O!{!+#%Gpl<^OvwVv5p~6i)AvVstmU1YtU#??a#Y$a6 zuQC`Z*y+U}A(J~@&-6I!$-ft+=;k$5Pjt|)q% z%lcv8$i-JY~@)`q!1RzZ8A5#C@N^jfbvhtWq}Eb zaa1FbsWOAndylM2Ei-joRgKy*pf@O64YyXLBPip%eZ75-wN$#L^6m^9@nm__#X6?s zkpDO8!}9f&%#hh zJ2=IompK>hi9nkPy1U~XjQyj`lz5a=;?JoJ4vaL_( zuT@{+7C$M;#d;`w%A_}Y&XZJEL`6sLsv6EV@FN7T!_w+EOo6u!I_8YFQOq7Ty;5W) z%*8+dlL{bIL2}&Zfy$utv3Bk3#Ug6(Y4RmzQViEM&rE)?L{39jKQ~qQPy4|JL zusM8k{O1n<{%aGxuvfwP55!uDp+KE{}V4Dj~g7i5-7y~)Y=ijlUa zclq4yV_&~JINa7p_a7;p=lm!h7FSHli0qdP zY4H?GQT$5L{;`dY3F?>H@V768x{|p@FjYj;9in6qQ2-ZBO*795J1|cXjRj*Yg`OJOhze) zvw833kM{I9dSM*~K33m5B12#BZ2Ow`?w_(~2ij&pDOX~90zuib8wODdWz)H7Y5&N( zJz?4I@p0muk-9vIOi;J89-O@+xUTXOviVUjrY-c8RiW~Nk;pry%`CHqemrd)li30Z zXhffVsAzRWlTa-T^xzvDGD(J0l|++n=_;rn^AoLORHHtt7e7dR?^KL>Y3A;Hosf@3&+lB#v@nw^_tEW>!xFK~Z4q5lK19a)|3cz9FtBN>uCN!IU z_nW-fhhGq(g0{<{2Y|D#aFyOi>9N+AIztmM9G6zs^^dZ1c+}SBJz+bNA#9y1_KB5{Y=2FKvS^AKn7rE-Z zZxIvZpb7BlKJgO*Kl7A3yebiIeaX|cO*{SLT9G^_BpIz97w285Oh>$Ym3f@rRB%u1 zSeGn}UMr%$kfi>3Pw_q>_Y7&PoR6$O-cYSTuPRjW<5SMkfkrcOeCMbW5p#)kk zhokXo=(E@i^QR7!8Vh5AlMf8pM-hsVx) z))s_M3CBQJq?A=HU#~NUZ1~dc*#BBVrMRa}#2(BT%vfgWa5I*1rYBolL>+4b(V4=x zR1M&awR3QWgy+(>vGcO_wc+dC6{)VLRho8K+Zngd*Id^`J=y9U@uy_gFkc;P+|yrl-yaG3 zwf-dQ=KJR>sg2jj6m2b}*_F4e3w7OThtEm(Bi5SAtEeVI>OQgKvmeUp zw>Skyx!xuV0)d3^P|_F8Z&vQIl%p^+H0@A6mz!<8iz2L0>{S%U%AV>dANy{puV}b1 zk)-`p=vxLUSmkotvQ>C^_WGEJeQsO~?Y~ScgAQBwWOJ`zoeyrf+cZA0EI56_+VatC ziSRunL?g+gp*fzUeP^pQS$R_I%o6W0NP-)8a&4~ExyuB^9-e$%DsghPAoFK3)4#nUgSE-muLiG!9UEI%ktTV(K|FD3ynVXt)h#7Q>2lg7MAQzuGHM(Rw| zxJ6_x^0^INycV_!;c7*G=)q(uH+p8o_P6>>H;|0!xf|Y=Zg{ zteXtazsFrt&;?DOw>KutwXW5BY047>h|5F5FOeEfCT*S++0M`_gHeR{#4 zO$fn_iT>Xq;OetP-==Fz_z^9_9bNG#?$7ZL^3V47#d!}uMMPNkq;2&_egE%Ii`7MU zMXn>fKSE_ja6l#dVgsa3>z+xL=++Kr3gUgUobaLCF*vFxJH(QuP)e1m_5)S6s}7>~ zF)}aD#dCh$qYY(AUUi72k^?-iKkDT75oBb-iW{2KqCC&6t7J&eEta~45fi488LB#l z8>If$o^M<*zb%;rNxNH@qAbk=$E zaH>T0`_sqc-u%Ah1(r}3k-Dj8^6A^xYW-e_gctNN#cF}x_RFjz*^6+_D{kS}{^?gl zn-a+p+hpIKr(7vgm9@@9of==feDm7b(vYv=5xv0!-AMIlnK7@+rcF*-aIsvQD)gAX zd<|8pIh|@0K-9uLTX$*X#<1yF@tv6Q%Xdn;UA}~_b!B{Bs^O%`Ua2zLXv36wb;LsK=VUY^{jX5d8P_h=!6EB|Sh}ZHT z5{GCqTE2dbsTHFFu@873|H@{>^t2kW4KpiH`^~1jP?IFr^bj};LtO$KIXSfTck(lc zicz0NsH@)l9>f>v4iBRI;or~!wPXAT$>_~LiPMi$vM=nSJN4!7Pbf9L*6Vy=Qg|+O z9nrQtY#-y5pCUAH3q5$Hif$+u>@P$&ouUeu9`7Mu6=Hr3yN!~)p~+_;2~|I)Nkng! zcYK%_{rE_~!1)obzUY@&bs-R#w+cbiJ@&a^V)SJiuGZ5I$Tmuh zfz%CekEq~jZRe=Uf+HIXyhqC&-D8%idtyRP3On8%0@8!hxW19QVqGgL=cqlEjsN55 z+yj|>|2J-qMW|GgP^lzE2PI}kl1dsOpCT)YR7i7(*+wTyQX#@NQ3)|pn6a%SBAc8> z#+EP{HfHD3cfa3%`~TT`DmIdy?=Kc8BPk(N*KCp@6fp}qpoYo1 z)~~y-dVNpNtfuU;RV6$j97iK%GNqT*Q!*bh;k{W)u(4sp@pmP5`>#sx^|+Fvj?15p zwU6cBa;sSY?#&GI8;OdEp$L}Sp2+lS)KryOwN>gjnX`S(mt!rUA2x1 zxv>RIihtHbto`z=>aquqcy3O7V4&>>(S0O=TzT2=;DhY0oSxr}s#MoBfyT$KxxPIfq2cM6H zO=~oNwnL%syEdT0AAe#;n;hf)E&s?0f|N-(|HwK&HGg`1D%KKRF(5?zQ~v_1&qcHA zp3FLSi)dGyiX|tzN5s*u{q4>J=tqiY*R08fA5*u>$W0*jpY!TAd~~;^`+`&cP9~zP zop1gvxIW$F|EBBB}X$WMIfTF=xwj{P+B8(f^rn-yEn+RGdn>$9a5_72D{Q^E1O=I znk^Rjt1o1UO4jl{&%JB89N)!SAp&DlOe82dW@w7_av)54@3RP&|9NirE?!RWMu~kP z4_;n|3OY^+2qYo``opLdY(LNmXt2^(qjTi5&s_1-t^&iG9{kpXDGJoVk=pM^&A6@lBot?LEW1iSkCT$m3?953hn(J*0DZJKB;9Nj zI{l_trQ+UBPI#hD`hLwdf4@C+J2TBzrN!hlKzbJKQL!yeNnwGVpoFU|c#|)v-8+~Q zo+ze>3545>z>hA8AQAgo@}fK`WI|A};0?PxF2gs@K(U>az1*v8tWRR`F$4_UWw_1o z)z5{Kn95*0pWBtVUtQAgboG=WP=8=x>*`TH@q~xwylV z!d5qwhYfjh$ez(z0Jd-pse>tTN>CMdNM+!|z9&~>E$Z=RzgGM0Z z3kLfz**vuGjBCy@@>2rfP>!0h)uPb%!c%a3c8ahf^Z=Z4u>>;Q%^_`TvZMT043SiB z&Fw&x%T_I&0lzo5V>TlFFoCg}#qb^E(%qH??7#|bN!YjA*}3B5`n^R%nop|qj$C+v z&c1Pm@6b$+_*cg(lvo}cqjGQGemeubv0t@>7Y*zyZS_*&ZKrX=SOw;m#tMkFzXo5>Z zI{_0H)Sn|+=me`89!{W`&jJ2I%u)P5^)AkycQRQt^Wa%EkWd2&bcIoL-lC;RKZ`b( z!m4A`8A!caNfdb`BZs55b9@R{j?qyB9BYKjQtG8l9CFK&llc-X{TE48JpHs)bG$ZV zHd8bBZNV7WtHgMvsBLZ6XzN>Nvm|+9syL&bq~;>iy(Z71z^=^)bY{Ayb9_9&1CSWiBa zOpv8nth&Sr3*Ul5<&~%6%-#&gPwGuWdNju`zibL&<7M=pNzB0U6R?OdYv{+5^yIvW zDMCoS*&H50aKpaPEXW=PYjHP)cKDMsb*@2*?vcX`mxOO)Gv*te_AvO@%%`YYB}%ip zb53ARF#uT<)+gXG3qq;TJXYK|&yi$jDi60C#`_N(3OTDZz1^ddq*j?E_lNchEw8t{ z!#}`H5s+hvbNEujn;@}ekb`V{w0!%|&(fb1&s<-*!_s}byD-^TOrmmYv_%i;25%C68I+gVo6{H(^a$zKnliB!R z4Q_*=`T>_0YIdVJHQyKD8OaXuvz&F@woT;n8)xPBQVpZkWOpS%Y{C2ZfMQ&2r`#}JAYd>?!Ha2)zzC(H1812XMf z3uRv+He65k*i2PiqC-qNLQvLO4q6uH!lK&`Bp=UG2f^3zvpjh;&^B(OV|NoL+CNYz zAyCuu9p>#^iFdXQdP>wFp2Z7Z^2|;vDZsXP4};htY75R$BR^C79ajH>Aq55hZvIsz zO6p!Q%Q-&2?FFcDGe6`8*Lmn8OsLiDZttS}bz5Opd*(O2Bg&o;u7e`lKbMpx`XQ|@ z*0DZ?Y5$DzT=nBC2XXjP|K(6?kHBl=&c=7NOQ!>7b+*{GbcI9=ACiHr z)tt|6i3VSQTych|w;nqlt@Kkk73qG#DW0J9j$LFqlB`ZM9qs^R(|VPS@JAfIZ%B_+ zYY!Q+F_dc=QV+B6B?WF1q3>8O6r)-m6ek;bip>Y!JyjXSj_V~~w=a{0z`7taXI)Mi zijHdtkefcaw(huOY)M{W87svNZn|w=9XbBJ02DL$bk-DjR-q@JJ(K#kS#366EBLOc z*@nP)YdUDBdndwAyx>*w1iy1^BzeVb``FuRhj-~sDf_4QB&)v@YTxYFx#a`??bp7! zsnKyz?|G1*ToOHbQO5_7nHpH9rucP~PAm^Px=^vBCur3bYK+OQ8sVGuJz1Z$i9^Th zIDZsdZP%fHT@nk?Qbh)sH+7SIjTL>zH>XQ7PP9T={BN z_?@HR~cHxz0!ST}{X+0R5XfT517Cq|RE`Ub4{Ipx8UA%gmNDe)b^fI-| zPCLYMbFcBDbEq(2P96p2qhJflluA#sM7!$ky!%!C$;_c$4N4E+IG5;c0`KL z-OX^(r+%Tg>&=O4M*5QbalNtu<6PsGOTwO{mCfO6_!^qdvb+?UW7Edzr^Yr3;G))d zJUuO0eJUY8BM6sl(&M4BJn*ZU#G4q`C@kQV@EJ<-W1(KDCE@Fyc|ozP*P9$I=*1Vl z%cUA~N5bdgFU0A{lOeN)91n3UfYV!~lIG|e+ zhf38VwuEGs;uXJgm2NJV&EOV2VIpbJ9&TWDxoj{wlE!Lr3s$sjp@mz*qk*$dDzNY4 zl*^l$iN{FZ_7Q6gowujW8m@hFG5^PgPKWU>O#O)~?DB;A`1CdzKf8;|&-Rh``!d}k zQ{($bV22L6VPF0bzz*i|#%EI0lZUo8qpt|BV?x=BVos+W+$*T#$~K^~&Oex%ChWr7jv3BjlGNp2JQc~i z^E?m6Lrs#=PGvjh54t|fuF`8s0+Atn5#nhj6B_VW1r6fKcjmj2FJctumRQJNb zD$O5xSBsx+piZ=iRpr|!e%UwPSVa)F=nZClS6A*kw-5XjgactnjB9Di0P?-yb~Fv> zkU*hatVJ0z*=ugVKeA5wW((FfDpH^_q_=wU!}rr280dbJI3S8VsaAO7fCs%9wJ&jN z1{UkEUPkpB!N>N0t7aX#H{EyFn$h4}$C5IHw?BNs+=#VjL>O_ai-tjFf8C&_5V>AZ zDt7dEeE<9$Mw{@Sc#O=L52Z$VYa+b*uE_|6=+Uk2;5#p7^*_Er=L@Y1smI5s?FaoJ zJa`nv2h7_%Wl7QC#Jl}g9M1TOdimTmUtgketkG*iLdnYsD$-DJHW!-4y#mqLs>%G! z_ZF0G$(I0fM_^>O_z;p~kcLB=HCdD{=#%$-yU=)T+%Sm*m&G@%lxY}>lNwVRNHc3@ zM)lr9=O3kTjAEX{91q-n2+8A_ONESo9x~$8jseuguasETN>zK&Kc~JRPz^v>6`BdYLV5HJYLCNMu@q z-taH-uCTIN-j5HNiX3B*%7sS*;Qc0|Cu)M=bl2W8b=BCru)8z;Ln#byncDdHjpWqi zpDFRi!iz{yN%2HQGre6OyTttC_9q)0dAZ>iCrs^2ipEtJGbezwePQI=YQ>wAC z`tKfwM!6U3`;4!`J9~`%fjU(5SrbHgHcv3EWE)%r!5V5%2^MJ8D*( zCm~+=^_(HBDr@40C)}myQSP!{>v8)pj(~WClLU;_U75aLEN@4+eZ*l1J$Ump<)?8z zY3&|{{6`2GV_N%cwlK4uZwS@J1HB@Naj%+^gi}iwJu`g@^j2J@vPu+}wfGm?QVsv} z;Crs=6TEIL#Nt{XN-#IR@r4iPTRt+aL5W6rj$yu}Y zxJ6phR&ErI$Ghw>te`pfcF@Zdol+U`x}lgj>K@7Zv=fUZ@lOO}F9U#$M;|JxQyFaT z{dtIurhHQz9I@G(TK7C%R;<@pX2xTrVKY!}1vOgp}4>SvkfCAUJ9Kd*{EjX!$;*)c--XA5|25*Qo{{-Upf@-Di>*TIbW}(-J z*ipUp_W>~lwT%MuT>kG2ydnJ~-1Pn;t!O(Je4odgeZDl0D!Wv(O3WRcBYyEMp*=oF z;mzVhf~mi=?DI`+7U7$%SPY$_OSU#4^p>9Rsp9U3=uhR}ZJ9p^709isarPa2i*V!O zr2|aXIwwxsYfUje)k*6%hQppcS-6?kyhV&lCD@MKtF2ovLGkf zqe+1U+T^ z-8q`wQ-kkK^xPXLSlZ7ENiAbx;CzND=H=ybX+dRFK%E)SpL4ymok=?>uE44 ztls!dFsPF`s#3e~>L!w2U)i{xI%@m~6Wj5l09MxgsYAQWL9^_oW!{8A~L%NIJl-Ly%#^c8cB^=Hnm%qh&0_Ff#0~# ztgdG*`9?-n{x9PFDVZY1W>}HCuM%u%)Cg(JyJP-mP8Mt;X$bI7%$~&Mn^p|N%1zra z%9iI0pemSG6%9~sDeXFE!XgCF6|jbS7~H762Xf{`!Wjwtip;hYX%S2Dh-@9R&Fqe8 z4<59!=RDVwowFT;hXl+y&cy3aV?%UWl8jIh(e?28h18Hcp1+$ZiQ*)wv$)36V#E~SC7}UStsiUlSt&5)_ALpMM@! zcn$=x|1@V%cBfv~S7RM2;ic)Pq54d%{^7e0KYJN7roL3MccwVji#dAXX%1Ul6Mll# z@L4^i=UVYuBKY%YKW6_svfZnXQno=;3N)Tnl;?qMX_4B+}t(^ zOsDpL>+$e5&S&q_xNT#sHuT?$jal!TGX(eT-pS;lvFw}YkHQ3AVi?fep8=|h6@T44|sfs?|ncbdhF&Od~Ui>E;$M? zbZO_vE!$A>+RG1FY8Dul@JWlSJjNjn(yUdG=tYzk-72DFwYDw%i_5Q0Bp6Cw>7??_ z`{*s{GK6=hj#9g{-I6?=4X`xt7oXB1gLFl$&mkg(&T@xKJ4@`yyS!kA72*xIuVCC?70KFUCkJ{}eBbhCYn{>09j)fKnyyz^8AKTjwF-8-EIb<0E4PDSKvX5W|W{ljLY9(zH>i4jJ^vpV$d3x*t z%K3-L4*j@`i7Do5BH-%N@IE{-q>m!ZYiw%aAL~5C_nlMh?GT?M{=dh)GG|y)cTy(* z$<qm3uAyDDL0TwPSMh(e$nffC`Lm!a6af$W$f-mU=Io%Y19V(pl$`ZH!Gix8qK8N07 z-2Z60JWd;_SLbe=05vk|y-^6D*q7mpI)j$g_8i+h>_=qnI5`>237NQ^R6w|HFat~l zu^fDEVYQc}ra^Uby#vcD6a4Lle^U66)rl8%_R*5`dMw7iVrb(k^g<_gVFK4^{c0ZN zfuMxUmcLa;wOI5rx4;zySMZ$-Qg(m(mzc!+=%DOoq3QoCF)*M`w*KxjXQXeX`GwWh_{@lhmW@je}GJ=d;}7zE2-uII4;_o&#B$1 zcm0ht0DWOiRip@&agMMty$b9-I?rS<8})|Hs=s6)796NFRCGa=w7^K(dRf*qnxtXD z)wo47oGnu9VI|HxjAErhj*Q0pyQA+1R_M}8HZhm351{V0ob`4HXKY9tZ`d#u+o>D~ zrIypQS_43{V4e=)ki;bjZY#Rmzr#X}y9u zFBax>xKO0!BQH!98fq$b`OsaQG~H~CV%z;AX>4HDv|o5FE1jYVS{cjVqmpm)M)ozr z4VW+R2gWsE?fe+Ma2DM-qX=8K;&$GK?nIFJHrB$Pa|=Skt(6MOhtgKwHfvn-1+KL2 z;SxlhXss*0PzlMuw&wJwF+B+(Kr{Iv+@dePV;NCa>Y4bT- z8VqrAp9B5z((GYbh=qyb_tu-O-+hn_y=BxYjpD01P5p-0dq3vHT-BShZ5gexNTj|I z2h|Vr0>keSuiIYw;w+{lh3@+a(g%F{kIZ>=zEglO_LYxqLqaH_4a%#IGb1+dQBgal zV*kvEA69)sw`u%5@6jt89-K=WJ5&+_wVW@uY;;vvmeS6N7y3~MaBatJ;5<8brR>U1 zI6B=bw;q{<)jlta-v&v;C0}rq;$L~gKiH{5ntm?eQXb5*VD0mklHI@vL{7D^%a|rL zg0U?C7**Hn=V#y;!75qmwnBm()1ZAq;M;m1T@SxUvf#Yu+-ng?Z;zYifrf>zb%P9_ zzg9|-)*Z&k@8=$*jBbjvZEa}6c*cwH+3}#s|_2hP9gNu1ekYcX{40w?jO-M`Z;dUGv&Hckt%b3)@={?5lB&5Ofq5`Sj7sRJ6)X5N_}5KLLma4Mdq=KAF1JL&T@n2$NafY# zbeFG;#f~ZGM#u=!Zq{N=5HXkIct==7iC1c*^jlOa*W8kJVYt$>yi zmnCNUPLOX1PqBfXwbOfShCnBCLSm*z$-6q{WKd6|JPiek2t{&ddQA z2~Pzn|F|R&vBUSl6%#0gx@B-M6H{zVmtTSri=n_RoPHuKjsm%Xn z6JG=JtC;G&88m)4$z(YU}8762S)(YpW` z&(`S^_;FV(fy`|Z%)nQEveu_YFW#2~VQP6bRA1EA+hDs9$GpHh7+gDlZr5vkrvsT} z=|p;~I0V4@>$XVXQ_kgUh7V3^%)?|W=uNBknbxt411zLeqJ7NsmZJiUZ7isIul>HorVBV^SU7+f^%Tc208a zS34x)w}K=3GUgKp*3uY(Mbn|rY1dE3gC}WPl8kqKHQ*Y@NvnVAA@0kA8k>K=5X>8M zB@bnMS@&Ywg-4^D6PBG7pLCwY*hMd#_DrDQ4$jD%m*7w1@)9t_v8~_+hfBv*x8O){ zWz50y7X13D8_!X7OJ}>J>_>-|i#LDg_{EkGpIdTwc`AzMPY`1cZ%V1&zjTr6|AoFQ zf+?o-NrEq~15)e5E+g4T$SVSW3r&*>Oqo50QnUQtRGaSUG=96AZ1+|3x_9af(FZvl9P!zwsNu~r19{|`Ftw?EO?pag;~VKm_h;B^ zS}7Ta2%clw%l|TjtEGmolcInzcuWrPE}kfuNPe36a}f3h|Muy)E&}3>xa^|7*l0JO z$#);io<-)2u!)7L1av)EGz3U3Qr8mNt++#Y)mL+)cMnowEUDwEl~Z)~xY_G%Xbf52 z)R}bb56{Shm0YbwG+!KaO5kK{?1g;4V>5*)A_0=s5BCu}OvUo)x}|Dt8Vo%#ld^M( z4-~al0!I{~0|d@l3O-jjvi-Ih>6lxz1J_Zi$q@P@8HdwISHM87A#QLLm%{o0DDE#kFOv+dP;a1XsYW0Ff z6RH3bZ*Q?6@#9uFrD@F!;gcH2{+*+RSr^#;bxXGG6en(keRDuV{=tR$@}-P@CwkX{ z6Z%3OjOK%i`Nlav^l<)0qn7+M@c)7slpr!e6~uU=f=@nfA{v#z3=@d&^k<4z!ws}j zoO7pp(l2N4Wv)DS5m@N8fm;J0XC-6;8CeBYuf99E9oApNb6GENtT-cZ5p z;FLR-YviIlT&7D>82P8a1oPam*4;}WK2?_&00v>=p0=zDic2giWEP>Sj}T$Y)fRSLZCC+ zC~qGsS8PZ$=P6yI@6{Oo3Sw(O%OQM@x`G-h zYkgbK`)+cxb$Mfu(9uAWce;TzyL~YGs_!^|-Bi)@Agl&G=?tAu1sdX8cylcm8lGS> zOK>F3;U!(>cN-VoR$NO>9qVt5&qel56uQ5WtVA0YES~sHbaDx29WU57$e{JVz%s#zkLOq~uI%AZIbzDO8?&Bw> zTc+I>{-(HMFF7TiAw%w~ahiO76Axb=zqSUQd0~5X#B8W@M|+nzM9Hgzsv|1AuHn9) zWya)xq}2y~XLP*2+9wzRty`)J+$EGK#B(dlTI@l5JoqJevQ8;}2meSVJSY_^$4((|7SGuwoS&3tf- zs)!-xEz)`^cJ$4B_a)$@dqY3|>pY5;WFur41G&OW=}r9nxi^m8$C!~P`vkaFoh4p& zAD-cv2Uk_;eZqw~sXjH?;N$%79V62JwtA6tC9J9m0aWS)bWl2>Lih(oBiIqVA6Udg zqjs}W0+pqptLt}K4>Z2nhK62vFeYqLy*AG(m*)SzdV>Em=!yza`BMi~t~~dPV!>y~ zK3WMTl;vDqoOEb?q@1u1dUAcQdmbp}twA#a(Jmkz&!LPSQbWE63}O$fy99c1gCqSy z8uxRJGOFWtT2DzAPv;9x_m#DrCd>6x!TWq}$_qBkiQD#8gtV^bZvK{Ve1LH`rFlQ| zZo^zio54+l*{mB{&5NKmt#Zp|oqkVb9CH-9iAH+8ZyCH>s^+P&v|rV+vR_#@D9eUq z;-SOLj?rHF^j0#$>`k@OfCqRQccy!#D9WnR(4>9A0{pN#sJWJZIxYIhOHzz1 zEACFbN)^Vp2@7k|!hLVgur-H$p$(3El47D~@;W58j{ri$R@AaQc`%df$6 z5s|#zS0|R-4&0U!b9q9J0}<@OWNowHWrBpqN@5gk6tfDZ4$3M(vnBXPHX-|4*8tS# z0orc;OzdDPs#;Z=bV$UG@+5uO$&~+|lVP0vaUuEWqOu++=P8+{s<}2Pb+ej84}(4f zrSd$Ig_T}1?^*VY`uo8wQgnphEY3Y-@N}I#S#KJ4OaPjHO^1D)Uy1ku%Ihy0%VfYx z97OsOwTH{ElRgu3l{4K7gqya}q=)jznkG^c{@%+ux|2@D+{bg3A!TQFhihYGa z;a+&*t*%Dkt^};pk&mpw<4f@DBlk$*n@eig3^CEAV;mEm>bZJwr&_b+G~T8G8DZ!| z8pvGwWn`VWQfuL}IzyOIb6JK6c1LkfB`nmsTC6jW-DzrK-}rQj2rCGr&^uQ+vsQ>D z@M9Ak$aWNR;QFk^TJGc_GTQ?%TdneQ4R0$PW85$sGz%TZ^GwB3UUr$H!kY~4@B<+Z zauF+2#~RcnQ98w+a$3)@*EJ{(tFo}oSa4u1^a76WmVm^%#@x=hUDhUx|N7Ji#q(tf zI@GKzbUx&*eE*`IL%4N{$m;|5VEw>0HHo{Y7q*>ov0|ute$w%-#|HIM&1?bRFNO+t zhHPlWd|zKkxMeE(r!z6#y0kS={yc@|;6`!G>zJ4>JvKeQez!onD7X0uk-eicz7U2R zS-?x*49Ta!i+IrwDCZuTBq>_bK&2*D>@!iIK5t`29Q)Hzv3Bs_?mAeM4P-++Vt%$3 zHaO@i^esa|`9Ibx^085O`A$PR#K4@{c~dpU(CS+WmgMpaT_d=n!z9)NG8U(i5@$r# z_Fg~g`My&e-)!#?oW10(j;zw+rtJkl8vJi!E%h%WBW!;66Fzs!8(%gk(ucYb=Af+C zoQD1Lhoan*MpfBp!lwu};kQj9mf_UvLVc=QmNxv;yz@He_E76sHp6egLN)+Mb=t60 z4fOj?_exv1c~ZQbMs^tWY5lXhD`wRnPIu@ohH@ttyQ+=Xpym823JPdT6VTh}y*u zx(}f0<|LdkJGaF1ozXe2alNLE^D9|UJfz)VPD~?FAZg%OOK!bs#lj{%nQrKqzbnTd ziOQ5uP_BMQMzRtYw*X_3GZ=oib2-4bzRD|3w^~;z=uXT0r$2;<^N(SB4+0Fs0f!wQ za2Wj)$%&V0#3p1UO9YF!BFeGlvdn{5H)7L9y5!RBC$C^f$XdL`m4}yCi}peJ&@I8bqM(!-YKLG}_6CZqP4aZr=1=B9G;y)I=Fh z)G!WU%E1Q|<`qn;ZB6G;VqhrSv`PdMimNt5MULn4u+abXWrc$P_>fvkKpV71Iu&R% zojjoa1lP{_RiTI6R~iT+LwC>NA65FRvUUcuWKt!M#_v;8zS9Qh`(Y&l{nYHJ760Ma zFrOT^7kZN~e)@)H8SNEQTD`*xqz`94Sk_qS!?KFFDoR}pBX>G^Pq?asb9c1-$yJ4J zt3~0Sbn&yP36(vcvSS_hmu@6G{o${axl>Hk>iTnw(&cSrsL2;#PSx@3IDWuQl7mCP;+wy#L9#C6pb;7<=zh~T^Mt59J>?xstiULBRCr5Myuive zT4FGTDHoqfU_3=XuI^7TMnm^9-#sGp6YFV7HoZDlzDT$s*H9&x9kwoEo0lrSx5?ON zLB|n{uqCuRH&#J^Y;;$MPaVao?yWywG}x8-sca1*|K1ykn?$yQY9~Q|W@RalA2ty+ zE)F~P)RZWXIfrel;aqj-%yP&}03{VP&I9hWs0_ME5H3wP!9tc=&`r6KF@+Wb=^#|A z-AtIYP9FFk_(~)CpW%^6H57PkFPRkcg;HIw**Z;Dd+Qv791e3*F$*r~CFkxPRc>>~ zUeak^ycH?^@Q+M^90k$V+!{MUPP7~mm;CalZgdxKj9?~F{5k*BW%u+5w99*x$*Q%& z(BIT_%j>RBroKZx z9N$OZ<&ePeMfDD9yazj`lAwRB2eLPC0dBb*-3g%?)&N;=l4=yLvEc4s)P^y9Ri@Gb zSF@~o{tAOVNc|~fV_xLAa{&nKbidjmcuO>+e!s)b{XLE6cg{Bl(6~G;l_kgV_#fd4 zUx!Wm911Ck&<;-Kqt}W?{=^XYqgS1DK6h6%aUr^M5Td@yZI4$eFI(VK)11Au+gir| zcs7^kLTZ5qXEOkuS1n4C`1f+{K$^~=tg%t6i;hwSNpdr{P6;*f7dq~|gS)6ct~kJC z+Q?uhj6{~V+tH(cs-9KB%zQZ3@C9){31)Frccj&c>Iy*!5{Evc3|fsbJSnFoH%8vK z%_)L+>H>rp8Y{lq-UY&@(jM~>FZP2!YA)}OQ9}{m;+GT27a4x?BTEYwZ|2#Po`n|j zM_{b%)x5**GRee&gqp6vhG?OlGFs9uNLNmC*cTpH%al45C*<76esAO`&mEoF6<;yH zt-V>5>I`V!Y)_R+Nzz?LSLQ-|%2K>6nf%Yg2!OhZ{Db(EcH{6>D)qXPM+L(;FMN&& zd|zZ=t}vZEmz?@9d4Oi0(?9}_y`5?lTw=krYx!3<^hvVyU%ZStP%sTKoTfT_(nb}1 z{uKXvxRy8T{$3HiaVjh-E%0NR!~#J3Y}{xO@0ntI{8vifE`h9K|3oso+0|BUf8-u! zVeFh3AMLMnPD8_PmrF{16fn1u)XhkbG9N|3vS$IpG@vt67|HSicKG}Rr)d7i-B{}h zxa1sKZI!wg^LWngelYTNQwsB7CmKPa8YTgd+d|Xx`)Y)-YkF{~b`0vBk*0Ft#@g)* zjKBhE|K?jyZP7tB9h`~}RACR?7v*UeD|KR+s%VgE^LvM3Rz6tJM()U^gy@3xsIgVc z-4E5zm~VKKEA-6WyPVQ6CG4@VWhA$Gb~?~{PW}#FJI&4trM8#4T(ZB!tFsS0a_)EX z%fDMtU(I}I?vBF0SgMQN%&02qIpr>G-FY&IV>zu#9BhzUCz6(s4zr9m_oOsD_&}xUDLM@5Y`WTe``mT8Gb3}-A3+shJ!Gj~t z?sS=>3BJ&3iePT|g}T3|6Ewvi-&#{=MJSBJ&DAg1jfp5 zIJrkB8j~}3&f$H>Tv5NW7)7C7f~=pe!g-1yUC0!g8;M$zPXS5Its?P{)-N%mI<=wT zLPHPL4WAKq6QFR@OQ=bE`w7uuFv$wgu9GLex6;@C5akU+4owK2r{mKO+cV(1xO=|& zwuulCy`0N>8}D7a)Pd34)Wac6u0pElGSTL0>XL=T&>20)|SIEA#V5>qIECZvN zlxEN_qYKF=X@K(DZbG~xqr&-|)q4UD&GkI~pa_U+Ruz)IS_ zSWw`(qcxJ<CXa`dmsv?_85@Ok# z=HB4>6*9b`&U*{wMiR+$80NupZNzIfZBL&L(Y>SWCR4XXleCUZH8ipJQ|{5ve%t#X z6|6S}ulW@6m3}vWVj6pS;pFpBYV3Tdcv>&E=K7;gNr7&P52;oyWi7X@2@H++Dxr*D z=CUY1u&e>r*pLq}7k$NKDo^oGF}MiDJv3^UJ4cu^8{3GwTKTYuJG-blM5jB-a@ zk0M`=2NVtc#aY}%2CPqruHF=RJ*#au0<3Z!(@N=S=4d!nr;SF<57%iXZqRYQr7nY5-oRWpGFr| zRW8)z$i%-)H4CZ}{_0z?d_Dt`C6dP5ZvZm(oUA#nqoDs z63GM1-ZSD>gTbn%3uLy_FPV>Vm)=DY!nP)q9nX76KYq;Hf_&neoy(_)`*vJTMl^D% zhOIu`^DdVgEcQbMs{ygbTVmwwv^iPvmR?>tfcQ%5y^#m6|5*%1W5y|8f|%wBd7cyS zJ!%UU8Z%89V3PYpcCflkbAoecHv58ZOP6jP?803~bYALuBM~g;6qocGyk`wJXj@O> zfBN*W>PZCtC4>Fx={!zxir>{v&UsuhK1*m}g@^I}rM-)Hhe2HxNW7jm)gD}U2#xr|&`l8HkSN%x+ zms5e8s;0{g$@n7{ij-mtzOkk{W6d{A)!NhisWs$?pI2Dmo=F_z>S0l2#!8r)4OPp& zAX}1>kR5JMBE_x}`33d|vs5kGht}}Eu!mu0G(S*OYm37DC^EDNJ|y3;e-XOUW$@wLF4FYKU;_I7fBqjv2^rDN)p9 z=LRajF=&-kHxcJ3ITm_t{OtXF(^_6>GtcTLu|E&J5pz0WH`EgPN)?M2a%n@_E*f>gX0wnN z+?DZZ9fKNAqRHc&QSC2R{TD}QJ|D{QE|c5%s?eaCXfAvX1KqoW7O}#a`V5U5x^NS` zDQZh&Syl^d-*p|a|F`>R;#?@}M4sPdlmW<`dWv75pqbbF2R3wZX>-A&`Gdbsz)ES#m1oNmG}4ZM8lM%<65d3y5Zz&3&70*xl)|{Fj>jnI+9X#T_+J zQ%Lz2r+zn1h{&mp}~ zm8v0S^9s?96}^s4anz)>6bHS_!WQU5hIIXaLy(4y?c}QD50IYH!7I6*Yxx~JG@zKT zA&u?YgGiK8F|+yMxY#k(x?D}w!O5^RaFTOAkb-I(JiQ}=;9KZ zDV1-j&elKhb|Nxa>+1g1nZ*eB{+hcs?l+uNC*JgaxtGDyEwmZn6Gv{^V;T}c6J z&#RLTKru}E+~C(siDI$!>ZI3ia&rY$utviq!Y?){{V-Jvvm(nYC%dTT!L^JHV$rk5 zTN}LgwwBLcLLiIe3{6w!ALX%VD;RS*06|HVOhW+*dC$feJBth>*FDbesKU5M0877N zM|4LRyS;jg$KD_2b1T{ey5kwZ=vP1k=#Ds^p0&Z_CqVwp=CyEnFjeFiBZ_vs$kn;j zU(if^F9_-JCo0@WeMIczn9|1RxBiRl(8?%LR5U#(xY66R5 zzFcsR!)?6(1pHx;AgOfW{o(EB*Aa^?h7>ViWqhEvqJNa4j_DuU?=kRbchkk)hRRd- zEucTJ+OW)ioX=4Ios?M^6v^|05@S1w(+*&~ybW~*}`7(bCrhL6$x@=oy{Ng0Jc3L`jmCDab3Zd@8ZAX@ns%jfx4#nE2ML@=7d~xw0B2yiy9TE_s?=^TJPq0zTNd) zLl0=`d+Ez(Vrlqt72HIO!in5UfzkW@%NHXWqRekFj|VmC$Jgl&gOpm#`dZU4dEn1J zQT8+0L1CTpe-H$HLeoaC$zes|57gntp!+o5BHU?4<`D*Sva;=5&Td4xc+iw^9M>UM`5m9Tru@XQ-=6(F%ycrFB}$+DNG0I zvQ$5d4nASqYrT#`KG_d8{9cUO+&7wH)&knf7Tsis+wXakiqa^`oe{cX?za*=QvV!p z?AjW1)AHXi1gm!+yOlUYJ4#I^Tg{@an>Ax5wpNKORiuwiTS?)WW3dHpJ@*dY5plPq z@JNA5=!cym-!+I!^ApM)!yBWv2q&{5q4u#kxK&O_1NWS052f1$58CmUFS|UQZtvNG zYbM<$KFsowuU8q1J|9N;ZeZV!!7JJPar~q- zAJbz8%4)`*G($|4t59%9d7=ts)(6GBtJ;S@ISh8)Q>qJaL315Ew5WUUDGPt=(qGhC zfH6egPGAXkY%;_sKXf>Q)73+#e->S*?}r+vJgTjKDhNpJ6k5=S{WLk}Qx)B9%dq$K z8m7d1z52xkCU44yaPRgbi)Ph+n|zce2z2i=xCUw#_KruWtr$3MiHzZgEPD4sY_ox2{S8>Db-tHxZ*f6_(5(}z zVV!F^)BVPq8wWV|yx7BV4Zdi&3_$%L2uf?Q+YawoCf|v6dgS(R72Iq@g%2xQ?OGkY z!Jqo0DkMvOo5XHi6Lfe(kAJ5G_6M&aoDV_^sTjyOw^g2pETDQIFBv-mnd=vgXD%SW zC}BPj4SFYUxlap<8BW1gP1uv~k}gnLG(918DjufmD^Osb4Ev|`Z5+f9wuqkPF4|8V zIBv4Nhd@3{KA0@TBqp%5?htJ@==(yAr~#K$#d;f#$K{WSzx`wQG1oT;YIPqY?|kNB zc2zQuq)&_ zHwD<~KK_0n8p*gngc()1WBR;C^>;uw1*9a*CMi?Re#XTfeMsIMoi0=xg4oqd5-R(O zXdbGN)M2*3v5$2TpoU1CjSq2Gum$#_S|53}w=`E2r?z!JsG4vuI4eWmb6}%}%PUv- zoWW;bi1&3{apFx?^iHp-AhY?KFU0Uab!CXX9K}!tXzdKquqIe!aT)6Vmq8OD%H8Y-kzQjw=rDX`T9Buy$hV9mEmb0IMRa zNq)&0<8G{a$yw-09jq(7Tf`90sh0$vj@&<8*XsM11n(repTR8#<1^jwN>w=X?)oby zl*x0{h*uRATC6qaZ{tDx~9vAEUu zEd^A2U`HSZKcAN%;7WOPjD!1Ac61;|2^r zrD{8RVh0L2Wq|nZ)yqt*6eZJX`~Z0WDIV0QcHwgwHeZ|l&@hIMdp)=GV4SPJbh7S9 zh7e+u)j9<_N^{bACpePE5FiMe#d;yq*t3>$ti{Kjb{*L@G;|d;=JLu5!#)<0aTdb=ew0p0vfSy2KWBsUlKj${Zs*;>vkb}GCIg@VcKYH^zc*)(WeE`na|V{~ zokE}Vn(n&TB@B@}^p!O1LM=xdyT z4>{Vk0!AobX>~NV!36YhsW3(ZMbTZWy%e8c#wac?s>Ie>Ac4)T3Apdis4XoVTEvju zHqEuJ)FPzyj+NNmii2l+VQKPLc7j0w{}vwgMgS1CwS!sxL^C%r?Ib=)%?l&AFpovb z;=r2+T2FgQN}i^_dMEbV+}eF(!$^u*+<4ro{KDyKxv>@Nm`C4yC`M9HkbV9-G}~NEZnoFp7aeSxQacwOq{;5l zjt{l{3k%uZYIR6|1!wC)d2|dZ`7u#}YAuS8M6SktJ01AB0C(vtV7i($OOrAT9Ifh< ztwWF-*S4L`?O+u5>@LCCpORcRUlvCw@GkYjE^P=xoVJ|S=)70n75jc_suP9c1T+_v(BbI;6UP44HeLB=;Bp-yaR?l@!|Fr(Xh`DT}6zF;ANo!iT zD%JZdMpu3{qoX*+=itKB;;DSsE{cM0I;&W%l@L;w&GKRw~WA+Go}MnTE1d!H{<0f2Q1H{s~eg z&PEI==U702A504L&BXANfgxr$hR!3T7Q)8Szp!wlL36jqvpl@rPM`Bi-fm^~vRg4E zk<&A(kzGB__u~^M^<~F*vD zl<+I$p45o*tXu%#bMTay=hQDmPbU*W8>Ek%yP$O#B;AEXoDf1R6`h<(E)?SjgS-%(qqMM3^Pa2tCk6i zMW72)vYpKdU4$OI#lMF0co?p^QUyNAKOMK~!)SRw)c_$H60 zF|^Cx7ts*k03#~a^z29(pySIG2ypeLdy)eK8{blr^jrPJ`woH)*qKMXn4n`_5=*&* zA1-Kw9licJYPU-j@xF4Kt8WY~V>FFwG@%US8;nFDtzlu8}-?Yn-$)xYai+dA0g^5O{ApjM2 zhF;OJ2(_sp)uAE%!^7EB&B=tiZG-^6&LY@cvBh0&68M%H3R5> z1|}0KpDDPn%Y+76B>jrp$T6E?80VtHBz=A(ZRYudGU$|yy9Z`2OYk1@a#UVHZ|kcG z#^_NGLVpTudO{gx!dGNQRPj;>-DZOaSxfMCw>K1c04#fu_EUUmZZNG8R$DqHP~qui z(u=*B;=ii2D(cW=S_%W*mpc}F)k&VSQWB}NyG$9BP7|}2mn{#`YED@)ho}=34XFT*tX*#x3#>IS65l)@Y zJb=bt%D(4b^%MkBFZNTt{QdyU-{ghV|9g@3zB5 zfXzn%qG&%Z{lpioX|2wI@c1;cViNyG9ZVt0Ukkr2C%*2GK(r%NTdIB9$`H@8jSNTE zL=XkH9P=RbbF{`iZ7yK36VK-%d^Lzp+NOZ+QXUsPDHRuwcZ^AQKpS|0H!U9?cSN3h z2TN7JKH5_v-PaIRoBn)~&=9{oJ4Z$t%$g!2kBM;2f zl`8@59&LexV=sp@>{BGN@PLxgqy#V{@TGFN`dx*?qD%;vvSLWgE-2u4ROt9#FNi9#Wi+K{!?LMu1`e; zUaQU*9guW}m0+0nvXHJ$w;Jz{QY`-MM_Ja9Y?L`k#}+1^0T{)O*McNXFw@-U#~%QZ z%#WR@Xobhiiqb-|DOP3(uMUg0T-ZDUmfQR|5#zCc?=@kM=}@df#t=!3B|z6mdsoN< z0=JI-O-Uv;8)}|c_Gwwa0DVg(1u6ze58PO}5>5FL>RlI^QY3mMd^Hhor5iH&QPA9@ zP7dbpc(Y)x*;(BDG2M+17Yd&ebl{qC0<9{$7_+8JDI{xB-Nv=%Q&RCayWh?5bflfr zJ}5lZ>HFkQ_ejJQU0mHi5IY$683M%iPY|UhPgtp%jk3h?*kAi`sKb#{yo&4zf*%Pj3TpG}CUl)QcRtp=B zETqWk|Mf=f&r6E#k~8p*4GiqK(VRc%xyD?L<1cig(Kx`$p&<(DRYi|f!}b;8P+nW6 zn;TV5-0LN>nk>m&uMRb8aM)dUT~2V5H||b(J}oNNY9S`|c%@D&>4W?eyb?T_j!S>G zNmlSg{7o80hA7BUb34~`mAeQX{h3rHwMN>(qHM8oOFeCqe-<6)lm(#K?pXQm5r6Of zy-N?)vo(U(=xOqTR=pn51#fl$TLG|j`K`L60ZSuR-U;oGkyjxdSWa?(cLKQyIR!mB zfpfSvB`^@3LKK=HnP}A2I^O1CN;)4k7?e020C+z(PW zl_}+4Qf@n%N?;XjXcMfjBBI&@&263tZ-<`Yc)~|ved+L$wtm;?D^9sgI3S>1oNQGRDE+=V_;QW@Tz{5=X2o+6N|$73 zXwM}%Vy7H7cmDk}rgF=f6~j%;tEMkyvhM75q8E+!xtcABxgx+8sI6VwE-t)j!+u}& z)9=j1K3SEn86DwpcbVJj#+KawEU}jtJ7xVTp^uhIe>#Yxlop@h>alHfQO!=^^`yl* zLk0f5|9Jf^c*jK#|7s0(d|7;*{HX{HwUXhtNSs+_WUMQOIZ5Qe$$B|vwXt=;}UE-f=F0liR<+wKm@aoZ1kI{9eZ zjiG31i$GI6y}p5NNjNxz!Rm>RIcCEyRkIT8MPX9$%@>rltw2;f;^Qo1GDKC_u>(;z zyI;<9b|PjoEOgdhpFp_(+SV&#A9O^{?eGP|6Bf@IbP{9+)D3?B2TzCv+}uv#AlKAD zoJVy8-X^kMFZ?0KlVS*g;Jv~>R`UR;Pn{`BdKOLNp;*u4m3_W_m?;m0@AtteWlGXx zjxr3$a`J>T&7*7A@z})p4y*$6Ojy8&N#32D!_YMgUP?;WXs=WaEze}M%4UdH4jfC8 z$kgVkS%N@BnM=$qCj#$%~V6z`JO zD0NKvwh3UVGvyuuh$=Cj1~S?S*L>rPZ?~@|){F_wULZQQuQ~fxfn5SH=B?%Dtr|q3 z6LN8EpCjtnMW!AqhM3L#++Ing6Xs|ArQIvoN+O;ZLnG!MA7LR%fRk zYdIqQ-q}r5{lVcxn^){VCL#_N2~D6cYbZ#AL3>7KGmEUrar~UsDt#jwgZT1Oc>eYZ z=>18GbtXB@>1M7wW%KKh1jhnlNCvV^ehJI8uqN@gvp`X7>n3K~MNxo555I z6+}zMq7Qhks6QTRO<4iVBe4oVk3DsyIg7brEQhvzb0X?Vg&>s*$dsK76S_<}73aRy zqIpJJ{v0;*W4bo)D6$(;DZX7QP|xUN4IHSqX==8I&}sI{iso)Q2hQFwgLOF>CaLtV7X8WTD0sOB#ee z^4c%GT8!?j4#J4)8X))WoKX?-KpE==?|E=I`91moJg_Ndfaxo~8dmhOZX7*P;fpJk zt~BGdraUpSkUsEkl&{KFzs`Vee)X7>auiKhdhz=+ifiiJ;5dD_?OgD*#da?2zO5p# zUSp8#Ed8{tBp@Z`V~cEl{lB4I-VdYJh>4!tqgjYM6r8gRBWu!678Gz7rgx z9v9{=^{ed+95Ey|=*SbP_~zsXqT#sD0Qp&P;~z6A>6WziY58#lqS5-X`#mSb{N}c( zsKQQ8qnW){h`ed1a_aY)Ppry4qzV1U+|u~|jn&`!TZ=`S9Frt3z{96k!OZ{WnZY8D zNK3?KTz8B$1$E3P%;e?_XBFmt5=*mgC_bbaF;bR@HB+kpdm%q1t&?vMlz#2E%*icG z(!?D{n{%^U!R7D&6}lW`KWOl_OL-rMEPTSBJ=GLmpSNg}%jWyu%O2x>3e>iz+gPIx z%$SDm$9-*zWL4wj8yHUPYE}mLOM-CLP6;}>3tV*Yob;l@ygD?Qr+?d-8ERQV$P=tU z6Q0VrveUuiv5AkkSA>qWvycCT(A)(c$kkZYgfPI~h3znhVQUfq!yXDl$YW1k%Px?cMl~XRXUa*0)_W#0MrL9w-J+{A`PX&94^3LxB1Nu2CgIcfE!v%c!S* z{7cqg@>|Z*rYXv%$Hiu_qP;$1o{p~siVZX#ttTuX#Jt*4)a=ffDYa#q=Xo|%yA0c) z$F3#Bhw4^;Q?QN5hFV%2X4!j(F_%vFpke!<(s4~{EG)rz;i6>9i6ORCjWr9DVz&r- zCz{){WP>n2VI?b_mE??XZtq(Py}J$;?*8Au+Qxu=MS(khFslLjk-Ue}Six{R7@+*n z=5OP+j}x@%<9T(>6_Eg`CRN$z0BIsf9~Pc-8rfV-VdbkxKWxUQ?6;#`SdO1D?|3M}|1_e3n(!j*iLhP}SITCMdb**IGLUo^U+_S9ylEVN`AEY{6oR*@>?SZM^%| zPvb7TIDi7|s5s$?+Y8y@n9j3@Wy+6Ed9_(8;~NJRV79rmWTcwS<4 zEJ(EE>jl7Svzs1_3tp{yr;D{7cWuyICFw`*hDdqBRR!76AV)|1R>##zM^)kYR7p8c z*O78alGRGl7xotv_?$wN0(e$Gl$-qyG!cqF_W5@w-9Bs=Z4Yz0r#}Myq!xhGE)m7z zCwbPiywfoqxB4i1Q1Hi4}vh1BX-e_gZvBX(v6Qe z8ycKC)<+Xec%p4V0N!c?V4<-5 z5Zf!+pHTrGO5qTD3|7@XkzK<~+Vc!2a#866IZ*$Rw3AL-fBsdOHagw?CjOtf0Q276 zL&o1fPXNtubIS9vy<@>~Ije9-t5M+ElQ#ILmL5X$x2Htueq*`p+YdR90WxCZ-ClO(u3;suW2t9i{!bj4mE& zVW6wzb#TMRkLNeUd+8eH?Q3T4?e!C+;S(J`i~6Fdb&$~uVJ4TT)_gTVg>ewq+zd_0 zVW6#i#8{Qk6Yhr5;jc~uU$#{q8hO98M!(AYfGB%Pf}Tc|pI8Tzg=(mg&h}y`nF({~?GmFY18T!^jT@MhrU$)#-Zlw}4AiQP0zp#6233 z;k47!Ujb`y-idZf{KCu`ZpfZV&c6B3G>!=Q&Lm6wvECz^#%kfnIRxsG<=Sw>INGWF zq&wguJduvCo>Z8WpOgG>4(aQ+F1{h<3R6&+3Ng`D2VAbDIT7qn+#(_~7G^c~S&8yE zL)}55p*T%+e0h+-g?P8N%W#@Lbs5v{+kicOjydBv-NjNgFqnZ`{eW+2giXXLV>bR| zIXc??RbPlgzFY!Jta>hr59L_--Z7O(tY)(NEjKT|XfO1&$6H<*hYZ?OATEBNxS}kT z|2L!X^pShDDSO!;^Mgd9LwQ~+_8tWQ$~nW8Yf+C-yz|Xzlf(+4j1b#g50&pGWJgvE z3K}3IgC|j7!fJ{KYDjsg^9gm!wPb&}%&*;iiw5Ftrr(=#Lv#IhzO5cITx-S#!SAmI zq20~qV%Ogi3FTWsEw%knGFXN7N`2T*2PB=m@&fF%c@5E7wtJj=?Y%ROq4@as8)|;+ zGAU~Lqf`;t*RAeZY;d)Oh4MXIB(FE!nME#m9_z8SWYs!`&cC?1zo729pKVYbFEYH~ zY3{``IcOklfBd=q;wWv~c$0>CN(>5+GwyiDV$kHIaf=aKHE8)!KEzS+1YKnW;?5zZ z6LcpifBUVQSGUhYY8zope;QxpG%fNqe!^O-h~$@dH~tzlx5$&Fw$LF6S@;SK^8roj zIp#FW6{b-1yzl6WMa`4D-kBW4CCeNub=Qc7hYoOY;;aYj>8>{Nm-@)&$9w*|cOF-E z9;R9YKdG`Clc|R)QF~_YJP#tl{SJqWYDTEmWI4Kr?#^?w2_n|ns7Va}a!0LmM{_v& zOY!PW?}alPf<*nt5+;-7<`UX*YEtxTJLo#@-m-YRi~q!Vge+OcLzZP0bfGT*=<<%p z*lXX+#4inubIZ1bVH(VQ)6c-)=uf2A`!HiFLpxAz-qQx9(*7JE@Yf}dQ$sK<5tbmW z${mZk!0>P34>C#*%Wmvj+BSDqaLg`-zJ8mUK#Lh^8TR}f^69%$#1Bhfe(z??%Z*D&DEVvI^B0;L`C{qf@_Z=)5P`8W-SRO37S`R9i@rULJIK4m88LX41t$ZPIt9_}*;rB;{VtBwVRgInn&LjPalU`=j{`Y`FLdvBD|7KPw7G% zZcmX}P*}Ai3l4TR38AB)>*r)Py{jR zVg8I(08lcQ0v~SiMYd@LH22GsvJ#OOITaT`t(P$o_gjZ~ThNUp@bMihU*z*F|CY)e zEKOzd<<*eQts%Yp=4)k#2okwO<-sylVm#rg!+V1Z>5CH&ak zTKTnlQh9}Tb{RtXNwGDkpE{9GMCr7-TT3ygcj$Mp0EX`;;p~@*=#?|^sRqh~Q|kkj z#_Ws9I9;Ae|6Gm9mmQoGpX9;pz+OVC?|xh45k(Z~G=Ba?pPZU(Mzj4Sg-#u_8=a<% z7{yy|3!)cJD5Jx##XlE~LmD<=f;P+>Z*p4M%tkG`jma)mv^;ICJGT9I-BtLxNPvsX!&%x+t)d~Nf#(nl<d7TcpIF5amIPz_rxBpWq9|UT{7ea@W9xA?bbsPEg=6y*wR^Jyz&Z*(w@1_tp>amY)w2FEs zNG$u0|3=BIf0a5YhJcScMC*gK(4U)+Ecj)0Bqfd(bt6sQb?N&R_1nFkp z$)>&{qG=)Qcm^;K`R#sjxu(3Yad&qPJMNX9FVc#dpRT^cHUR*Q+l8wm-CCranj$3u z?FgVL`*A@7x3_}yR%xrvGfL=R9chlj2sUtiqWp370GnKp{E(|;7xgBRX4dCV98Lt< zu@%;mHq=9&t7o@LLGp%??>F&roi|+-e2qV1e!7m+^Fn5&8^Jru?ccwVJ1LC_w`oIy z+vN$}MY?p!&fX{|^)_-0{)a&&Zd*);n^?MTC3LeqJ(Z`*05d!v<3sIN2G37IxDw@UU{H`c)Yto3e=2%@J=G1eQP^%<56%! zfNXCDZ0ZSB=j%Ja{;QwBt$RqHY}h{^-T#O06$0-uiqliasC=r$?1c^RF zZ_tjK30+7vkn+o5-Ce9W;7!D^lGN{}HIIHsCBU?JaonwI6#V&B zk?(V1$csAMDKu-iBAbC0X|8-y8G`Ng1X^U`5TYy76!$W8=jJ{eTLu2fPhT@B)o;#A zwiLv(COeD!&voz_(JRB_Xu9KA%LBC$=x%UsFNH=dilf9BtX+h1AxB_i(wQA%8037r zWZ+{9?_OUhsx)qNTMW@FJ;fUJ%8&l~DslZeX3bOgZx;mHG{Di%I5D6#`Y84#3u9Wn3?rbO<$r+%|c^V`R<68Rs$` zi07&OPRg~iLQ1RmUz{s*c@vvwA;w%;H%2v@{={cYg=nrsnVjUyyj$o8Uk}uYcGX#Z zm^``$1q*JUwySlHsoRb>yzc=x{aqi<%*t@9(Le+p!mG)H*F=XBP@Vc#P&(CVCvIG6 zsM?HYbG;EJY6_;S?-4#-#hZK$q0yivEtwj!mf0cM-CfaXHI5As!wb2+EiB+3L1<9| zl=+?Y^Pi@)pjKQ6vTFiJxEsCr`?UNQSq@AxRz~1}U zDzPMu(Zjfo=d0F@^ZeBYWtn~uKOkO+QA}F3AY-M{6$X(x{zUi~wisxPC-Awt6T}Sz z7rCmD%kLo|$rxaFc|Fe?S;f-m=J#6TYljtc2X7HYXA;7Xx!TaK9oLDCCD8b{&x_yu z6vy$mF4m>qq%~W5G93_yo})9&;{5CA0E{Gh1&>UA;lve|iO0Q=cXUOiEwzF-rTW<83x6CZl&1%upjo=-l{jg#Qff zS_PbY-CBEtIS;6^g0G+axBrIJtAQ6*{9@koNIAa{6}WurQ(doH41YQEiI@#4;m0W7 zmHODQbpM%az`$*@d~>W7Sr|Q%bBy<6q%!=IM%x3e0N(5GI?nH z&PN%4!9SXK6fk74wO*oL0lL>tkwvbcc19odvy4pZeLV?oxBnNCsXG9-d&dpinSD6$c2ag4z&tn5!AC!bVt!cwrXmTC_ zDr-q7%0#EJ_=WP_T9WhXWbkjIwpy5@%ER7``K-ClKqx@CY}r>d3-0xUL8slwl}x)r zjzve;sG*Y_Yq9@!Etp-+Lf7)$p$_;xMc2ILD@KXtba5EZ9)~%{@{7wP&?^t9xPc3eq-rkvs zw&wr$oy^&Xiu9d|dDZ611+kRv;PDh-=cvv&256j8LlUUY?FwXb307S~&BIW!&2(L} z7`=@pU!O<9>n24rPjA&yT&D}1JSAfD>3*k1(tc3q=rHm!nQiW*a|Kkf!Y94XXJE-+ zol)h3tIG5Q* z^+I$yY#8*3f-JepJgb3DJ{c#=R>Cni4uz~Q#ir@81<6rlMUrWbVH$CKT{NpAGfeVX zeLe?AFV8V;!7+W+?``=X7EcyqLJZ+2bvg>ROv;6yEin0^$R!`;wBa#8Q z#eKMDPg|_4v8$FIqk)z$GMYuskZz}X8Z%CfyrMPzt?nBtD2`|_jP}AMG>dmPQ8ufh zXKwo(E3sOF51L|Lv0GgAOFYYGj>!Aexyr9f#MT|)8Qzuuob>8; zKfxl08aPrS@Ucng9+mIDwIWg1<&M$cG!eleOjhaSOZl~R<=Bt-y*2m3?-(}FeL}VD z>zb^An4$<8q6tvsJN1GSq=9;H9{m6`?ceGF&_dsYR_Ry=vmh3Wt5i8VMErI)j1Y0y zH$7q=)m>3K4UB$|KH0Gkc=S^wW%da3Fza~X>V+LA(c=rhYG#i<2Owh(`m&CvE%t6Q zw(|%BV&B|l+C~)g%IAoSW&D{yw_!ir1`P3;b( zQq3RC5JSVW+f14`TpcrM{Dar5n-1)~+v3T`6jP4P7l8)JedJ~;>C?%sBk4Q;F3|snk5MuUyzbJ5-kJ&$1jK^FwIbiOxvF(~_USKa>4m1 z0cr2?r2jq$v#-d*!l9sHbVTSJ@A$`G;`#M`Iw3E4BNH0vv4i=?P@q3F>F1Kh9iXV}!013mj)&)q{x&uW7|oj-*Y+#6xYSj4xRgtkSc z*grAR2&Khx%bp3;%$bI`6|?zTkv%~$Z(#=%Z!dC;DT)|2%mQPr1g&D{FfvQW8y4s$ zybz~EkBaRJv^$xA;;u&q0%Rbpqi-=c-`)i9p`I4kD_;E--)YJ0qw`tT&%6d!8u0Y^U#ZAm7hENe54 z8J_2UP4bZ96Dc&-If-MjSiHXObaoraH)pfVSmRKKGEG62Qq+j7>LNTk5D_*<6{DQlGm>72l0m z9I-X5f>(@O=0?Hg+CnaYJi_V5kMR;x1!QyUKj{4blJ06u;>?iDiyBf37q+NbDen2!pw8mfpfBea zVmP)`TJcvk3i{A=LX$OkD2)T|G5;FEcXEYaRma1V)Am}U!sRC}noxZU4jw(n5w*(g zaZvv&R9O-+w$~|gDZX2Kj%78VD28naBF!94rbe4moVM2g*tkgC{%%rm_jt#o3Jpn% z<2H~+&wRpRECz3H_$Y2Pe8}^N_tmvHY>IT6p zH6uWGo=7b{r?5NjG;$|G`sqJkc@A>*UIJQ&6+4cNVfbdkr$t$)Otyb(ylkq262cCc zubqAS#J(RMMvew96s%e_7Z0CY_1nAnRp$k-#rO_wgON{oU}{?O;F%zTbl)`LedHy_ z0?xR30<1WQ59(Eg@AzBew&qAbIta!cYy!e0BeM2Ga+68##Wg9WEROPBLA-QG(PH~& zuCT^A{lVAkqmsN}6@lZA$l55wKHz_k{e1hrPIwvbnz>e%MT4q^PpBTQYaPS#K8>Nb zT1drOs#4p~Dmf*BcIv^J1QXjRIS;R7j(`{rRxAGgRLa^HmO7?d_*1$l5zt4PLqzWC zjZu8lAQ6)KVKVBD!?3-!f!tQHVxgn{>eTNY?WmM(rcF49U{exe#~>)LWRgebgb<^2 zO5}BLvGOeL+|CAGh)N>_dKr3k{aVm|N4vY*e1Y$r>u@_(4p>wo0g5Q%>8kR&JN(~Q z-FUQ~C{i_5bZnoT-EQ6}YAL(y4^?JwOCB{;q<{S7SA7vbI6Sx1*uJI5o$|f7;A;|v zbSPzsHRZ;O70oU6>u~E^%Y0q$XF{v$dM?>!4d6HgzI<^iihhMgTMA9)-OkAf zEi(fLY8|0`-BRT1l|q^IF%;9jj@z3*sA{gTkbkpBu#LBm;$y-mzgny=kVCO4J66&T zDF%MsJdxgN3gll&V2!<8)Jfe3J>tWJ1&=)^bbXoOfBJk&bZ;~a-ljcL6P7?Kq;Z z%ejF!s3xzXNJ<{07|C@{k;Bip|IsQHce@&eE*Ti!GP-2OgN&Ee+!PJ4@i|&aQgS z{4{IuJ?%K0&4}S2HXprFLeh*dcp%`8M*mJgpWY6Qc@X_{RBcqjBy@&6U|puLT}KqD$KbiTBbKkw3TQSKH(Onh}L#E%V)62x`a# zkbr8n$rN4$bdDlZ?H%2trO8Sd(ViiIXC)H!J@>)8w!S-kdUh#Gjl)~!i$zGRtK$ts z=w6&T(tLt6b}F#A2~LU7^WAGw{o>O8bm# zlnflDud}msi#f;{o>wTy3GDU{sZl}}Zy&)}cp_ZkMV`cwsRM_buPd{4-a&2fPwtDS z?tH~tfeMPWTrh8%#-zWFln1B}Ra+vNS)JgT*4hE&?3+}2r*%*r=#?-Oj-Quz+FH-E z>OvrktZ)LM`RIq(#}?BTuN3>6)vO=9&*u(3=x4sjSO}g_#>osu4msA-^e#h>-V{Ys z#gdQ%?4{UxUeYE>wl+JoNgP!spW-6H^91heTZB(K^K=m#1{Xd-Zu5}WwZJ#?XtW|V zkH80q*?(LVY6>e*mNPYl3FR%9F_O!Dxf}E+pt@AQ=Za3JR-$P{8ajn%s1rhqes6qy z)5FPq(cvR^tDwe-ERW8<*)d4t<+{Ts(X_koZdpd`ns?8H!8`#JuQ1Gp0PEa?GxO%l zuxLRQ74G#&b4jqhhY8rE5_?MpNv13qp8YlXavTpdbvWaJobj6u=T=G&tF293U2QRb zRAO~rd1ZY4@ffWezkM9+A`R!!6LUqg*rJT)u`U$%Psb|py>yQpnBva;P?Ea04$|?$ctu3qzTWmD}aU^MwM0bS%hthnAwSxvg4>HfgWXy?*fAG2Y z8jP1t^bDSzgl?(tw_g?CR~43Od6S5=I^0niMIy0f99;>-Tp-27>3%`wFT-+51 zYEymrpb7p~C-L^^n?lGqZ?bsSZ0(o(WBVTSD#g_*g|V_-t>!I97xYKQ5ffDF6c@@tDEz$F<}NFr|nS+!Hl82I#E_ zecLFps7PADLs;#Ex7XvbolllvzWbr!)t~+qW^3bU#fNx_v7fm$vCFUlm>00d4oIoe z;Tf)eY%8g2qXWluIH^MAwCy{=h!N)=mJtC(Kk|eHHPMh}9*M6Fz84VM_C&PQJ_>~O zv~DnP&qqFuTaml&_OIx{*X-eei$uP1z)j5&id?dN?E;R10I$g0RpSLkzM=`miVQnCo$!Rub{QfYE80HR{PtI`q{G0OALJmD z7_x6u6jN}GqBjUn%=Nc>9@k~7?(p4k*vR8&NK6d7zp?>>8+m;`%E{cKo)&!INv(gX z5SQV4MP2;r+kbDU*xfpx6dyM}A9HHG_j7W;2C;gO`LAwp6j#dhQD>|EZI5Nd6$riE zD~J^S;i65o)%$r8Gja1%l6C2czNh3mN@fk^493;GiFNBd?`2Vf9ixxn82N@DNA7CN zc{?`wqgd>R37B@>ZM>xYKZ?%9pXvXP;_fP>l2k6MbQda>+_t1r$t{)2t-BB_i<#YA zDwRZ6a@kyx+n04iW4BU~%SJAf%eJshX2xb__uqd1z&?*X9^3o#ex27j=Q)VSomcPF z$9pI%86VOANZxF|`ACI!5=r-uO2@FF&SS|uEM0TPY@cg%J6*T`%U!yd2FdgoE@F%K zCzMH#wWNU8{}k-tIy5}(Uy?t({qOZ-I)L;^r)9TiZo=quM|Z0ucXbu5rD0-#+5&+d zO7LRo6-_JkamZEoRIKoAQ_tppchko}xA)AudoQIqhY2$8H7{z>#DTNGh3mXb^U0cu zl<=O2{q%o$dY)- zO7(_ho`@SUl_`#h-6cT0-d_T<*KLpVN$TQgz(d;5{qJN7@}N9Pm>Vy_MH;$Fmf(pe z{$Vms6=MIa44|)`#qZ!-kG>&Oc@C`MgmM>o9Hk&%mvb>d{cu0~#jPE*SF_`ZlmhX0 zq+~)+b37G0Vs~MSpumP$(n>q=pTN-~KT|U|-g#Sdp0>2J+_+r2F>YFlTz0qSaxr%O zG35~`j2jera00M#zil%9UY~+6c2KzB zx5*phThH`Q3!c7h1sly)ge)pW4QUZqEkVxzooh$W?ka`xS1&O4$2O3R#7h&Z&^9oU z>n9R1?Xu)rzX0P2e05OKd6x{@NUbv;oN(=pL zG#ORbjs%Znd<#7=7Uw(^1YJA3rKOT|MUQ>Kb_ZypBwz4h-fZ#&+|5^qaZ__v{hChj zhozHRGx%^>nyN3C@3#0T%>Uwsu71-}1bp{w<%+}z_U>-ly${G2Rkx5mXTEY>3X3e= zeQ&9H?zSPH=1kbmx#S-e?}wyV&`O%T2n4RJsYtUnsJr+R0}a`Mla!_*a`n1<%@&b? zkCKp&_E~&d_C*w}Qc(M=rI@5`UbK%{)1=I*;>N)v%6Z@R+K7!N9Gka2uq=C_%(&q& z(6r}rLA-ojlQqY0Q^Rqt#EPj+x?lim1bXe4Y}@c+!JT?dGB9e}z}T*jk6AXqUqzZ7uq6617`4qY)BYbC(b(*U9eD3Xc0uDqJA__ zkKFBR9eGhWp8gw&ZSc4U>(WW|zo&x~+Ez0U0a<(c8;>XPx22JNu#eb7ATD=4d=|+I zMxd=_B>?SyU=M*2*>16cJbQo2gRG5yGC6Pko?&_@fp5|&R+>+$5xBbtwkI&bc6pFn zOPUHC$c@iz7Gu}aoWf9@4!o-HQlPFfb64j)MIa5ffau0p@wqISic zKp5I3qMWDp|0hL$+Khr&w&$MsQf@z6t6_0P3_rz+j)EGtb)B8|m5u>lTAch66!AfM zZD(8TuvbFYuEe?lZI$aW!z#3Rg&R|}o6P0?H7QRfU$l;-HM)C>m$_WwC0&|v8p|K~ z&By(Y%99)+BwWOk0%5x&;~#jCb;KVdwdtEMZiYASV5mhv+RPrJR&uRRinl{gXcBPN zS$YOaGFE)Qo(L|pD`pZ8E7t`7pkDoOiK@EzZzgwyma%F8LRgL(NJy+3HfgZA8^lN$ zIh_OjvSr|f<1d%daT@!CLS&jMamZ;DxMpv}sq{3}0&gOxF?%Wju(}cC%zPS^kN726 zr+zGe+UYUpOx$wC$M)04I~%*5SegYyhAfq|Qptm%W98JVkxL;L%`4T$Fw{}i$izv_ML!cydi6sn@oDqL z>7cr*Q-W6pS!yjrEg3wNJ^CrY{}w>G-qWG>837X9o1xGCaNA(ye$rR{*>t<20@s&BzrO)0nW=Xg&Takd!p%g+E#rRM z)}ZiM9d^vN?NE(s@3!w56>u2XnLc&##rBAFMLD2fE**y(e~8v%$u1r0T9J>nSf&Wu zE_b#1RvC`oIU5;N6}Qe$+P_W#`3KM@eLa<5;PwKPyH+2SKXrXG<|>2E7xrJbC-?>iD6oPd1tx4VL`A@7=_d7m<&NFX)|(sDXP=LREN!$oCAHet zPCod7{wvXdSY^brviP7hPAJo0wl1^`iW+9xN4FI{`@c1uUF&TNs2SqM{g+0p0^WZ`ZvASU-Us8%Ip{!1~xQOR*B zeCT1a=_dBhW_D&yNaqz?vrj7Uw)SM?O7M|PJ{-MkBJSr7Xwd=0aXHGS0t|j;s>aWTZyWtaq=!GqB`C!u(UsXY`p}k*;yc2}xO8y~o|6o41cu zm6_rploA^qf|%2f2Z0|qyVTZDYQniROU4Rb_SX@k6Ttbca23VAI{_Soesd3fv@Ric z6tVye0V%Y|6{b?Tse=}pVN21(9;DX&zX#E+HR(XYvGvfRUl&=C13J?cCP@KqzSGkG zjPT9A+P21%BUg1lV3u}ms2F&TCEr5@dwpPR)_3VTcP9IYig@^9dWjQ4s^6@>uA4F4s zifqQ(RVPvH==SxS!n+T{!k&`bf~xd3K4y7Tp8LzCBK+N z(octfdF|nRCF~!jmke^@Njw)EX z6zwG(vv)JZ8S@F=M9}i!-H*nE9L35UOF*10VUedxCInCK4U+@;q`e;m(;^YW zRjw)f%6xotH-;1WB$5U?Jc>voh`PQZvQ8*mc55l!!cf(DFrU|)_W6|} zKRxJDWFn+q&EFic0m8UZ7S9FeX`C6vp8}s(i{?gKXeTzJNyP!T$Tz?nBy%QRrf46j z+U}=aS@lq9gSqjoTho+t)61u4*0=?HF#F-KdVKvQLxabjmHWZkK^D+F1@UFY9bzc3`9PT#u8S*@4%^vje}7g5oPD{y*47a?>-gpsUZ0wJn0B_#QD`R_?_=)>ZCV(<-;4o0tb{r+ z0Iv$BUWkPF8fhgaTx;=i5XDem8ZJ3}1{?lTm~m7ZUU20MGvo>Ftli8z&8pcPp0!@P zX(Q)mC#_~k|L%C5P`n1(+kpKKc@y=RVk9D7agwzEQmme6HEg%^L_c5;ypz%sgyt6iSDN`v%-fY;z;sCOzFM z$#-&xMlQnV@S`=a)#)zYkwE5@R=?cpCCy~uFWN(|=~X5Hdv~qktoKca3_JF=P$!A< z4S#!4qhdQq{V%XS!Q09VgHDCIQ)yM>p5keZx|O!}Z#lo|1tCgRhP%u?(3-sfkp+Tgd{l3#y|c` zP#^Ce4*e?e*SkbNJ|#aYgybI$B;MLL>)3-pan4o)9-T#~L(@lwVjgo5*73FA-w5%+ zb{=DlYrSUPY7Q+CRO6QJM$cL|QP-u?n(dh04e+4_{3=n0O|`h%{Xyt&vw8(*YGxMx z=;no%B30eMTg6R#XgMCgYlHJUmoAIwErQ_pZ&istKx|x9L-vJQX()AJCV$53t}`?p zgSGOw$$V!B5|~Y0O>R=Z;wB-$uFjnWLM2eq6nWSax5^;N;Z!Wz`kQn_YInxb;smUA zJ1KYbB(iLWWJS`UCk>EY>pHmfpY2?C!n+2)0$xsY`rw!W)2D9IQ|%EqQ?gGTwAq_W!FG!IL+>{BqDY8B^*Lk=zWOODJ< zj{y$kO#3K8Tk!JSHg06tGSdDrpYJO>w8*>4l*|scC=OSwqMm)J*7TqZEI*W6lv17; zY^Xz9srNp2s9vxTvh~NDOxF_(@gZfK z%=I}w*CNb>k#YqV#;xZ>lix{y zfcHv}uZyoT{`yDrl2ECIQ6EuqSzQ@dd=`jnah{SdA%vXs|3=#gj-J?2BzMw9`zA5~ zue}(VzbL`V)qC08Ck{(wKlb+F!p@Nsj9yj{;pU4jZaYsCi(O{foHL+^M*>0BNpWa@ z--6r#GSjbZ)UQ=|E`6b_Kug{Ka;l<$2X(j~(jC4^=>9sTcO`Sv!YyCXtDyjufi$N< zfm&`P15BJ9uHiGAU${f-oW#A&m0e*B-(l&Sgbc-mjgZlT=>yXXgeQO9;JI26s}=T(do_eKmC8s!0^TO~~nI!e_h>4=Wc=h_&; zOHS~vG6ipMJ#0AFtM1z}|G$E85<28keI)fgMVAkS8LDk%R&qczJ3D!Y!b5s8$#f$$skVWEaYn=`Lb{6>+Eiun0RcY8 zQh9;VEc){v5;b>UfK`;n#u2Zzfqm8_N2JoEKWRQPKk)?iB|v4%Z2f*04=8T|%*}4Bm(xPTt>v>=?gT`&^$hJz2;a5P z98!%Xz$OXnbjSr65xC3Y^~lAg_*c?J24SkWwg0R9Og3jdoeP)qIwX4<_AFk`l7`Z^ z76UwYF<#R4#~8@4HwYj+zCnJvUVj4Js8e{kYJi-L-&(6J`TUNUADgnR;V&2VS)X{u ztp!sCUW?v4bm+ntnp(J`syV%?ARZRY1`T*tCFl4oqV6urTsUVnDs z9OcIFqH2pLmLXV;0LigqY82pzc+wM5N4-=H-{81=;^jMeU)RYzr`2D&c3%dx`5PNf zA54qj9-P8^2dZ_1%gU-qM_IqhqQjk@g%DesHfy&=kr>M zAd|5*{zz|5k0hr8K2{{f26_Z=hD*2r7$>63!|hIyZ{upuw#LREOLTMESA) z)M-VeE{Y!`KsBXFYPYF#J$)J+k?HOqTQP6LLkbY|%6_Vwb zFe53$E6^Lxwez-wK}}@ags>_l`tU{|7w6@8e$WTU1)ilbIHd^i$vxe;T&-IH&pC-3 zaN*0|heb^_zkV%feQUgE(MkNM=*os}ks>~|v%EJ91((2Dt0@)1!Nc4~>!b1J`!`C8 zw{}VD$_Kz2#0FE3^!C6kGeMi#XBbLP;oP#+Cikzs9Uas%(>ng{4FqJgKe!<1?Uo5n zW2toJHA4NeI5*k^5fxL7j&_V7BfJ@nyfi^>UCP;}D7S1)6v!wsR}Sex+fOt}x&@@r z70KqwQ_kB$uL@3s+OPtDINLA)We)2*RLxzhjrhH!IwVU!F4kB4&ukVBT-hD9iq(2z zA02EkYwc7Ow=XlE>7o#?$@3AP6Qvq2(s9-PRc+p8bsO!wA_i zDIjn6`Zc#FT`3HnHgl$o_?4=GGXxRmP%QTHHUY27Sh33RKE&3K4#7c(T0N z4h0NL5hK$7HC=ZU%D-%zJXRu9`k55|*%_vX_c)d2_#61+mlt__K?S{}P zgrrTZ7Uw>!^mPPI>Q;|*Um4G5iaZKT2{uws67^p;3*e_SEnC+;?!@m`exv=79VLrS ztx6t#E-nyFy7BE5+9Q+O`t5_ZOT5E@ucl&7*Yn4D;8^Lps2pdN7hLqL;M&~lg~sdR zb8@w#Es$~@M9N3-T4j%4`GQoa}W+KxIn!Ia?EH#>N|;E8EtW5^cSvz zd1tof%P$D8$KO@|X2M0X{9K<=vJ=s`SwOq5(gP!~QZ3T_Ga6vhM|o(XT)D`oB?4y= z)1neJ_KqgOqI*V=oFhDvT9LH4_O8;`uSfN!_a#8IiUZe0XWr!Lo~K&e#yFC(&93v$ zP(usYDSuIcP6~I~TiMD~?(*La)I%H9T;tgtk@a1$ENL8f!h#;4VtCw(`{9p56opoE50H*l=5oMp z3F`pX|75Z3i5mqw8dxAmvWTY%L{gOLA&J*McpLW$Mwl#)EY3}KVA57utLAexYF8&XI=)4lCw32vYEtZtj`aw3_fA z8ZIg7dG$AU$!o=kqBhCnt(8DNJR{Hei)(bZYaCMJbD;Bb{FMW@FrSR(Rn{~DijDB zcuh~wO%K1LFYe+?6?T2Ybq0jnD{k}l047B-Z1$P{D=Xe26&lZ_ zQJchI(Aeb^)R*J( zEwSE=j^PKg6Z}-inKp?HbWB`Q1XCg+rLu(P#T}y=7V9Qlq5A40&iNg^Me*Q?ZE&px z-U%fXd0%9?#QjQf#2PQ)e#0mpZl%yGL(3TuJqY~W+9uEZDMTHdg8uxAztF*5yv{1f zGDI`=DDz7ZsF_pUmR$xH(K{5Wm_47X1`pfy{XHdTk0w?7pBIg;6@h{ zn(^tR0o~pG=^Rdy4*J31+-5NtGr@I`=qqagtSB_)x^{|hx`x4_{oma+x3^tsfM|x< zBtO1Ygb|+ol@gR@(rl{Ay-Ai_XYn)=K}0Ee_e&bzXgr~LNw>GRhIIWOpq!cTIqL!A z*-aRE?(CG?QUu+}ofa7_e~JvG-F=?Z$9-QMva+*`<`Lnc9p! zWhl`qA>O+q1zWSLxWa`+I{Ddo_*+CTM7TW9Qjk9akP_2?lcR?DYe>^zCj z>MADVJ;YgVEPP@>NMo2c(4xQRJu+J`fn{sZ9*OTDnRoxvFFcTC)(tK6lv_^F5?@I2 z7xdH{#zw-|O&HFqK4_!4h0C)_ATdvZLLxJAoIbuoVCshoN#o|Yn0)t0P?2O?=X1%$ z!8GP&8*HYmrW-do^njV#e~*l?Jo`pMWY8& zNoXflnth7MDoRzSsX=M*(_|1z@$cMHEcyGU@uU9@3OSH{K-?qC|Ky?rq7%=WJjQNvH1q) z1=$Ej`CWzEZXYd#=|_W`P0KAdGF$S5nQd;@%49Rslva+`t(RuEgZ%2n%uCdMz1@*< zq{!!n^LHM;5NU^ZRW$f<6~fStZv=EecjFp1WZ_3u+?h0C)t0`6H*akl>)#T<5z7?k z%FPAvb#2ymWqcQ@r1$+UCgkYo+d@R#f^cY{sKyput<#@*_U*_CmRo14Jeb3^uPbeE zd{K>vD_M@bwm+6G_fKde*OUzK&YHzD_D7L!+Q;&M{m;N)?IxVQI5s8-)!@LcZ_-9* z4c5DXK*zgV7xZixFKEyi{ibbK)C`$N>$$5^2E>!RJDe<4E1NYdS_}XFJIdZZPDH*R zq$aXbOqy!5&;P}9z7dH0bxZ1s=dMizs+HC$5SvX;F-A#7d+6>>)LfXX)-6(-9~P41 zB3DbCuB|Kzj^8tTt7A3&!W+DNCCK!iG&}gOq!%+aQN<+e#RYH6(^NI(HZNHV)#$>t#J8{QAQv!@ z-+$Hk{|=z9=Ut}#d478mP1ZTTY1QqeV`kr92i`3@G(jwricL$x|Lqj#8Y%8m+*+5A zz4s^-%RVehruIw4bn;WO(?k6@Eh=H7z?`^4QDoK1wf?BL3gsTNp}_9Co(y3-McZqD zGWrnLaL898NO`LGHZ62vc1K`!XLYXx_zlUg=_nvTRCvmcNqZyBnPP1BmhZaL;+-^1 zM}7AEFd=ozM$pH7{IDZ4z#?pJJHM*$t`gzx| zE?hOjzSHHSdG`&=B6&hK!kUZhb54BCh1h7Je|u79>JX%_f!)CXUH98*90op!(V?5v zwj-AN@wWxjnIy#EL5m>Wn-1HTe37n$F&*+`Ee=(DrxR_kP!2rCE5Ni|b#S7pMCRd$ zy4>Bde(73({@~{I-6ngOXHlwm-o?Wn19NiG>bsK22Ob)3P89P%AkcBnL~Kabk5gj= z`kSlxG=&*9M0SFKeySo~%a0Mi61bp(hW)96X#AbLOd4uyklH4pU0YA!{WclNKfc= z20_jAkT`!+*y#2R#2i=~A2qIRdFrLaVF~Hq=Ju<4s^q;O@VC|7OurIUO4Un?LKF8p z2SD6mtLkk-Is%~oFwF3J4(QN-NQ2 z1sIYdKi;1zvr0unSCb?mJN$X`|NO!P=4II?9T-0k}%fZFdzmN$t26 z&f5mwXAcwU1%|AM76O>en$-{G8Qx?&VkKHiKW$-X`k~nh!#TWjB@X1JG;JHD3W;M} zW4Mib!n{u*{eYyKEL|~V`G63$9VDnMrkm2|ke(hK&2O{cwvsHjnFUFzP^yjW*MJl_ zlEvMI(nI<^zsyz2&E~z42JWIa*j#01L&AZ}DyOlOYcO^7a&>nw49785xCwG+f$ATU zrg9!Moa;1swk!U1KXi!<^WRHNN6UdAW`Wa_OPC7>lpN~U@R@0uxMC@hWku4(n(2vHNP>&DOQ3%1Zi`Ots98ZBVp zHfa7OHy?G92>4HcRM&w5wzkavdk+Rd{bh952+Z)Ge;q+H1H{sYe`IrNm0~yC=IIOi z-Kz0J2%Mfa6vTr&YUutn*(sL9DhCZ4KI&99g zyPQ;Xc^}g)v{o&8d@&BDH}QPaqN4M7HGt%UXX>_h+34V;+acr(BIp}o(qD+L2c=`} z*WE_##X2t9LnMuwKbokBYOVB&E&7xvUod-rdl~EFY7*0Ne>xc*)IYHQMEc0vef5YA z&^{1_q=+|6!D`@GS|IgU*m_RU2nLhit`RqtFjSRg#8AZ$Bxnl zZ2wW|NMxV)>M|8|jMA8|&DpMh!Cj>t(Yxk-QGa`OkCq_4M!>|E2ZpVo-A zZDDHLDI@PHzAfzKWeHz+a|q{tES8aO9PiXBRQY;3#&}5~sdE3*!~X1F(f9{J6L{<1 zdrDtJ%r(P7aB72ybWeKHC<@$jSXzk8r1(c%8&q01ua#xxR(0WbP3+udNHYB}zKoJ= z#};9hsZ!2Tpu-{}v(oW)XQ8AUjRxHMA>kfyZKNcktedURm;&_>l}7!%FV+0sANwJ7 z6%&DZ<7pa-X;Hz3**&fO1F?Junsp+*L7imU*0|6&0QriL?Vvy_v<;J^3d~Auu}s5x zdr}|ZpEv%h7JG}16xQH6@0Nzz^-vfRnSwpoAt`d4OQv=ZQ0C&=l0Mp+t%Tr~MHKyZ z3x>8ScoBuThnQg|G__?NQd5i{wwFI@(1KI=o^qHiBR8_DM3edy!}pkQ2g z*5BhfcbSI)T6{I1C(~0aMMwCu-aXFZ9bM$4c8Mvlg~{Vai?aW<{UPWPk8Zj&=e&Uq#{LKGQH0IA(3P0(5mC;mdLPgkL|@I7E4Xsi)M zH+yA3)Hn>(&6LP%E{;WJ+ZK@@g;M^{;(}|sBZXNsleXouuZ0Axt?8sh7(n)7i1O>y zc|QqMwc+*+Sdo)1llD?3G138tEt$QrvL3l$zs!YJDpU}!5Y%l?=vh92#P}$dvR%hD zmksZ24@6$S9?SJ0C+;R#OzDZ#>I$#y=$|}(zDRb$T1Lk1wWmO1S1Qm#x5%DV1MBFI zBLsKslnZ<4rx|X8=Vl}^+a$;yCfyTb*Z)Q~UUwQ}(<%|Q1`#E(EY;E!BS8R2^>oNZ z%D5jo2TzHa=?PyZ^vkY7@t3kqFsI`d9t-h0{z>J0;PPY>Y39Qv&R0oJZqUqH2pZh0~{!AdXag9iZ6^RWfoSx6EX@o(Gd7&7Q`eZHN5>a zkbB4gRd=8d+!TrdWubPCwV9`I?6lzr+vHqp)r?3+RsrtX|0jVJAx2Zgec%t;Nt9#N z{zE$M-0+sVvTwj4O@h9=q|*cI>3x~;qf9X})LKOfW7sk#mR~ZJUdOTjqi^l zL3@qnKQZ3GZO$U*3xa!1lT{D5QLDRtL7sA4iwtKVsZ*%0lyOI$bHq*>&FmdhRbfPQ zZfm};YmgB{hQDn{@Vmm;wd}T!26N`@nl24igBfPfJ4Ji;2*rmJ7R#>G^F<$XOq;d} zaLt&nOx-llqQR`8^I|x!KnS}!4pZ-(ss-yl*vgVmE$_-D86kjUBk&Z!Ob4AmucZhz zm5!_oMC%bouEuh~^Y=Ko4LH@yDL0G|@(ENXdmf3X`({*+{A0(lF>h^cd#{*cV&pfSRsskEK(L!N*p|uj$4eIpT5!D z|AJ2r7mk{5z&38WRCDq(8GaB%yJFXG8utnZPjVCYLbV!4e@OmYh-!YiyAujAn7{j4 z(jrM$FXXs8|F|!ME$Cl4l`nNQG?mm{~gSv;8+5^kR30 z1sg&Rh&X+UBxrV3XcPgPfI?dRZe65iK{WjO8LlAU99eWLz;7^EpEbHuDALhG#%=*N ze%ae^cc!O-Jl(&jkWI|;Kp*iiIIo*WHct9$t{^O3ID;l%B-LSc1tGQi9@p#z^ayXUZ(FmQF6J%=et#hd zX)3&B__>IN=w!NG8iaIA=H36p!$RO3&EfHF+RNRx*(LcBAh*gZ0#KW{`~zJY zO0*IlG}n{;=|>Kr|G3m!ZN z$LhI^`$tQkxyCyO02bazuw{&YMyxrT)rL2sNT)NLBO-%Q0mK^CInvV~fsr!Y#(&Zbs>G;rArtkE26y zUVHMv(FTgoZyH>zeYz118hYTBU){Uj%ssO6<({!K8u`45W%d1GyHI@NtUlU*Kjl`x zR8f>4Eq_|8kN;Y)ZAt$N=TD{1oXwW_4hJB0CIs5pk>ZkT=FCAL}Hbn2M+Fi|Q_o-dJA0pEPWp+RMaLeT>gZKY1>zW_?@ zLu!k5^Ajz1_rtLf9j!fuHT!3E#7G{Wx@Eh>(= zRnx-X@<&?jpK8nSVAprmBJqi0IS#>Um_;o!icp!jSs>@^wHZc9A2?VmKc-|!C zlS*50KM$GN0C}@<;;OY2RaHwsU8+IR(2swH4_oh3!*dDfL}xLMuMM1D!8&3=S@C^m zhLZ&TpZF7ZQOy+p@c&_^KwWH3pBdMl{I_%x^_*tmaa2sKxJzLA1u4Hp0qNg!rzANv zVTH_b;(&G%R`+ue?&tTYHJL^3&$NHb%2c#+@``A=BA3&}P9x*(M##|#yom9X)cgqD3_tx?v}x3V*Ufrmm) zSIfL><7xZ8WQ#q)Ra;p6b1D0Wev>jOOj|X>Tfl~(;|2-SfL=|7i3Lg|Y^w%Hf-2ft%vw6IhQm*s137s~qmwLockExsh$ zhcv(gLbV&~G!zC3oYi&jqn3~Ds2|aGgw^FVj!491s5@vF@SrZ)TT5)KlbY+Q={$?_ zSa@7it~BSSfL&=cdz)plZvoOKmv0lKf94D<m21K5aUlZ@ekbBSS&eRlv#fC#O>Es+i;c;ot5h>x9 zfFe*Z-pd6Gse;_ahk}Z1LIlTbk`z$|k~9yY!mq{+ngX%2Ua@`?{`oaxqc1VbiqGx@ zaign;6lVAqsoJr_Fu6vDAjnA8@4f9SX|WOgS~qW{3EHJxAuuRUrpkRD@Wh1GJH6q( z9aUW?w);b`Ii#L9l7Vka zOi~Z)9{6`i4svVfiutFMYf~L3zeMU^lawKx!GX>;3CS6-L0j9Oqa($G3+&+%Yx6lPqN(ncLCfAw$ zY$Svo_&P-W_vj}=keMSuy;(SyQP~B<*#>WZGUfDbzL}{;Vg5}*je`q#>+52X0y~5% z^j?EYf~`sOj9Nwg>H@OqfsJE=Y#u9)EMpPQP~aEp)_zuD{}ZY_ZD+Hxw7oZ}_L82n z)k3F@mgMlp>0(;vMyCJgcU7?T&M=X`;ISm@eX~!|<!W5uecHP&YGa2_0tJBvD2NZEF;OsB%-pZ zW74AD0V9`O7eT>q?qw=N^u`Idpr3cu<`nl;vqq_3$vJkfTfx80>Z;S?Jk={)U-d<> z9T=Gfx(lp@O!YRwyyBg$Sp!}8i{4j9$l5nJU$_LGj7{omJ_m7`)7Fo`NU2(uQ!FZ z`)P_qXN-(p*plL%`|uJy;@o38d9wSePdekXv6$a3l*-e>-y-e}F|Qx4JFd?ncUA#w zSIxdyGZ``~^6uk^>k>B2C;RQ}>mP86Spq8q3K@65qVE%YA`Ur!FH%OujcK(DOmc9& z(ZwS)kK;4Ab*GeRUOTW(F9ql$MD7ws#Qto0mc^{B>^^~U;~7FN*JH-*ncbFI#fX1| z@Ea&+nMUlS#YQYg{nIeTKRy?VmQy-v%adD7UKYeXZWB5KAqkznhep3WlNIP=9w-QA zm%3_&NSiJSy1^b&2kEkLt@s&^imOA)YMr&BxkUvOH#&nd4o z&bp9W<;yFxiKc61he+8wP1!0?%_b?c%=+2y@Q$K*^v^gvAVW1le3tAHnVm2{#P>py z5i^Eg1<6`Tb{5s9ZpH-Zv*Rrci@Ip#Pw%KW$sQPO?h8sjm7tSsKXMcO-fi|Sw7Nh) z0BDE@7dMD$dcQ<}sDYrvthukzyn50@dam;ny4wZ7%2Na;7tzy=lZ%!GFpZ?3cPSuW zw$kf|!xzGAB+`O)0?#@83?ZpOAyVWtZ&R&MM2%RO2OEOrp<>Y>OQuZe;9-pCt_#@K0Ln&dXyHjd&Q@O9mh(~?@3WpAwh0=5} z8iAZ7+SMm;t#B+Q^4hW;*xWa}Dr5|{Fv0LYJqgE#55|rlN?noTzK4f$kBa6-?^MyzXb%%X_qjflMLB*$YL81in5zL+c2j} z(I`Sk!Y^B}Fr$4DAA*MyHXBJl$opi0)ExK+EY0*zMe-d}meES`hNmJuLVDiaRkMF~ zDBLFYftbI%Nwg@^H;FmS7gX)G0sGGpYmtFscBEK)X@TxfN=6nl4&#oT=OCCCMi86G z-Hova#VvZ_{KpMQ>9Iqk$d?U_?t`(?_ij0j$=3$)M=z-=%bzd@;NT3 zVc#O7TpCK-+hb7FJNUTUw5l@JFW+SXbdCdOoULRKcCtTJYpVmoNHb1GQ{-x0f=?J_ zA~_|U*TLO)iy|LhUl}IJHjcNmJgpA&0;(HUP98%&7AfI?dI_A2HZhwsrOd`P`YWd!$DWoq&c?{rhz=)<*&H$4}w6O3u%I;!{U&Y6v#+_ zgL*u#5xusEjYD84BS-xgr6Oz^L*`Q40jWnmfiJ}yIYy}lYse65dHA?w^D`_cyxUeU z{GZ9J7q~K+&MvCHLfCZfAE%#aof=?XKWg3qdZtvqUOw9Xu>c z>*|w57yc8N2lPO7MdUby{xOJn#ZQWY7HC#N*pZy!=cc<|0L+@Ol&OSex{jLRl=@<< z4;%B&HsU$Gnb(agVU)UFs?A5lIZl!4+456W6^Y@xmTi#mH1&r!DB4-^n$OS@ebBaI zbDZSU+hk+mRIOC;W8thfs+nxm8YecS|MdAVRj# zom&T#q{7zWQ%;Sv7&aRTl_F8fX%0E0m=3Hl+oTXN6)iT~l$k7h&t_)l@4o-Sb^UO? z->=u>`FOS=&NxpM@6=-@-9hJh%3Y7{O$IH;*r4DFt9D({NA(H57vn!fn^x3(kooqh zd_GJ@1Tz*;6?X+3lq2$lYP9)t3RE#yhk*Va|0V|qi^q?*Go&#Lw)012OPUKZ9*5zr zp7lW;2L*$N?u+&asKvw=O>R_QC&HNd^}+yMu;+bdExC=T7Iwpmj|1XLQ8zCTY<_9ebkFEUH|ka~Pb7n`$RaG=t6D}@y~ zg34Z>q;mSZ5_`Z0%)9!Jz`+nFUI%7xKh3}xY6F|XWUA-w(s=!(_5qsJbmz0+29YAd-=LEOT0gV?~eC@0=;&(nNbWgdo`0|}9 zO?-((^7FFv;??n`I<5m2nH3)-`&Fg5FZGxG zpRix7g@$gEr9L{6iEVJf4ZyNzkp`^SJA@Z4rue5`^eMs;XZ{`ZCiq@G<3GpT5|Y8J z*^b5pg+M_sd-%`yt?Gbf#)?$SY~+*YV)*vww6=p*Z;;>1pq%NpkuN~%$y?BC5wfx& zNirKYoK~VQg;%xV>zug6`|x?MFY8;bg;Hy7dv$!l$ZV;ho7#SVq9MOkh5q?hV&8{b z*&ZCvG13V8`G1S9fM@T=fap_0Yu879d{fI*xy3xper$4#h$#)_3N3 zqM`c7)7cJFp;&A^CCO>PANZh%eck(B<2JQyRiD9rkH8(D#<=$fi`X@{^js+5l#lTn>rEc@?PQn68*fZJ81;?M+N34 zbbQT{;n7BVtUd|h*$A;+#!T=SgWv7A4 z{(mnPBi6eW{^{SR;!0+!;LrTr{Gc?Fqp;TR zc}4Om3P9srdP}F4i);iji2DbY%APxnHU)@?hwG>KPs*oNCJmIYllVn5R%xHnQa|0e zj~YMimlg?0!JO7v($|k3a`s^{*7~c~X2j>~sn^ekSVMqm_^_3I5M>}jiJ5o=Xa)B~ ziikXp5Q#V85a`D5G*vi-hqhc?*;yp5=L=P^Oz5rx`FvkFiz5{^wW%il5=F3nBh&m^ z2#flhu75lthb&u(^G{;M!M%uu+*$qLThgGr;~pQ?LgM@1B-=8{1|?>LuE1|ZvU_)( z`f3Jc$dnXe+UU92?y_D~MJ|k|J9}D|5OCpO!@r;Sm7QFB%{PH6zmo1S z*c_c|+h4>H#~y7MT;cSI*H~07fB%{aDeA1#4vkvNNTKZqmi)Q0vOoiHl@cC_WU7kJ#Y`8nJ;wo z^@5DhyUKul>viS73q)y6wntTMztW4_48wDmtY7^~KFFR|GJVY(1Rf{)#&lpV{$zYk z;mFXNf&YPdjx&i@IDldGw`1J`It2%KOou;p!od)x0I+t!C8PMvAz9YPA|BL))V-mV z&!5&IPR2%)zj~14Uwea*{)TGv1(c?yjWV!)YKPlN&3@78QXNJ7v|`fZMws8EBAc{i zN)^PVR|wMUY?ao?5y#|{*g{Jo0eZ@AvMI^-iD0eKsasG3sJ+ z+Vve3zAwi;n(*E`=RF4V$(w-sS)m{6R2bo~HI8vVRm>QHY!~i8?n%ZT=r5CsD7f8$ z=b_ws5wQcep-u2CGDej{d)xG8!5v3P?c$HZM5%e9q>9BOWGx;wy2x-hyT0?zd#T!n z>%_838rt*&PJU`v6Si?(ndge+G@oNsc z(Ae=eaIT1!W1+?9eik8j{X%W4^=6;>Yxt%0`CgGog&UrX#Ot(27_jhX&FI<(1R2DR z9a8vi|0KP9@~Irw*e7-kNr4>7fl@Z$0eY3DVG9f!o+u0(;UFwyMDoUS+IxfCVx=0l zy2r-)4!tuypP%{`X)GIeO$SyNx789Wlc@MM9{u>-KWtj%(0H?=phwxd^8tRsqy?eX z3)lJL*tvA`)Za+TxFj1oPfj;7dm+_d@cF~A6Mju!MJDKX(L9$^>TjPld@)D!?yKEs z=XGm#lOE*qN1F3~Z5`olf|(ajXUkA9BNXb{=lh4d=x=hh&{1W{K3O)w9R1Rm`}n z4^H1VdWTlb@K3BS7ZKUZ*i#S2!Qs1#83rv?R+YJ7)5?gTujND0GhMWatkgX}U#~7X zn)@{#vbRRYda|xHg?@`U#;2AtISQ-zn`*;Xe%6B%X34o>ML8=-0VE6^o)E@T+*31` zr3gwme&h!{B$ln7EOf=6xG3QWax6A-y}e-b#p1NQSIr& zo;!@Hc`QbZ<9|xd*vP|C!*80VRsW5|HWdIJ&-Ir+nYyi#lcC`QQWXAMd|c6RY%7#e zhAG`dNib%=;54`b1s9u}%%{rCkaLxtr!w+?_@@YW_SetpVtSs!DD%3*FKnwZ{BATE zFl(gjf6q%LisW7+5&0|CyJfOJ?saTceqy2YDlkkcl4jhhxHc`V$4R>Bswdc$}Q zV|AcBNQ2tNJ=&e_X3C={tXTl{<--bKZ02&JW`k<6z(snDQ}9LPK6i17>?U_=)*b2y z5HGrI#X1S&+%if(M0enT-F7!}7eD{XaL%nyEnpO}Db6tGxr}iP>p(4WH)M%L$DIPo z^q9s8iH&W15G}Qk%)7T0Y4PsP{P9|1!B|vW;#rRKYV0G*Q!T&SUm`|a+CDFD?~hUZ zXMQ~?WF&R-3!i4_z7!0eLm4TsJ6S$^cwc%LeK@Hi;Rz68E?jdF4aWq`XIDz$hg8OW zR&LWMhthOt-8P9$$(13M-4`FQ!*+d2(oq)s+etX~+qron*sFnDT|Z9JLg3-3Ojspo z@Z-k#u}}sPvo1g!>nBvw5 zv_DO%3+uqm4uJHu0`^%5C^#yc_c?cR@lI>VdOVGi4@Z*(lD;&1!?t@~|tQ*{(%`-Y{u7wNv2mSmZiyq#0;OBK*! z7^R-$+@Y()m5QK^9MIjG114{w#U*&mGSi2!TDhfx!S zG!t*iXJ0Cs77bRy$qIiJA0tb=ODulk zDAm8l2vjvY@SMqnBf~khdUn#%*)LcjP`M*Pli6YB^ycHdM$qpsQEo{{o$E;3Iqvy5 z7q{2ZZq&0x4;$Uj3k*R_n55SP4@K zvrn-Z|B>;|_eU4Kv}Ky*hr!Jjqu(BY2+!mxM`a%N=Q1gEP>1UVXME@(vH%n%0ixIaS&k9#XyHFxHTfS{0c&oMNK&w1( zu?}t5apeTI=PIWMG1W;zq)?tWU7+=1HF;<2TzQZ4nAH~9+Dy|CvgY#rHe&AgMs6-X zGu-;3ogXzU?=73P;vJ>1E$tX)n&v<2oGv6<`BTFp5JOF$$!r2?ku=ppAm*lj4WMgh2EM zjYQhHVrCOjN5Lc3Mn@EqTwor!Hg&}3V#jAq$YYn(6vVdoQrDSb)LMO2@@^I_*m}Y7 zws>%hTVLMER_qw0S_9`@Q4xu=E3T#p90WfQz_Xmx)Pm1>DNJ`HNcIJp0{clk=htC4>Zyf@U+N zfVSny3tgi;dlLfCn6A^q^gj=1ip@&^@;A~BIak+7*6Ky?B=?N90KqC18oPPc9u!py zdv78N5G*0n>q+rxCj~Gwr7#$+zDq#o)oMivQ`0nQm{Gc5P312y+pRF9Y`xGbJcz+I z{m69wEHfMse{?>{-d`sAC&QWao~F3=FB}%J`GzuL-MtG;zKKh`97M@~?y*3&8eLTR z_6?3LJMh;l?q9Mp^RTz&FYx9#fKWcGpA=Q9xL$vN{l8+SGv*k)lV87p$}`1fvoepS zXH?x7`npTTw4RLN2fSy#-&&HN|5MTRKBdB}&tU_O#_p6q>OyM%QyxpAY@sx;f1!1ZFO&ZRgsrD?LlAKSd z*7sfP`PD7Mo55H^iDMvFoK4ZeE*x!DRSa$?gQD&~+|eehjcNkx|6f3t|-=3NjPp#{TN1|MQsoh*`%=Kg-? z{PBd_FV)#A+2!5lf8w`R!2|9*jFFj*Mh(?zWgXs~mz`#dU+YvfxsDP>rg=-FRr2@k zMQnVpv}A-Q@wy4F(G|Wnc}}i36OP?~t|qVGz9>1N_T%h37o={kRhEHT_1FU^=P+e4 zCPXe=o1wgC6vZFxMOS5{e=}Oib3Ky`&eoBx%igcIi}#|*B9G& z2s5;P#BD4HJO5ArK;UUc@41gm(IW3BoqZ<~0}H?tGnMjZvZ>a_T2HTd#ELegV= z=%4g)On*loVu0DDxxK$z|JtPgcx-Bk$gy5VG=+*Jjmg1S8&MuOeE=;}Ta832f+_wR z!JymG-sy!y!_1H=W7-OFW)@q2itenp0rowKU4H>{;x>tAb6B-!Qlw07icobVL64i0u7*U zV%R(ZGTNE56H@;nZ8*)Yp($9o370!k6^Y$a_RF{*vdK=prbXkZoWOL`PYm7)>zFFAR-J9cnahV-IO(3u}vgyEN;|hcNBv>b`6W4-1+lN{o!Mo1G45dKhvvGz<6K2U=mYs*SzOtxf4h)d4QHTbG@jcxKpr|X zQ(OE@{^YBCkT&y;ys8}atxQxqnqe7#{)haF4<&hX_cZsao=RI|;6CZG0M(MYWB23} zCHkP~0(Tzso&9Ix*h5_8*WUn>hJ~b{s7j|!o#I99tvtsu-F}1A-_`QD4#~zD;;Mco z-_&lEc-&O?hHW(*W8m0_dsW@}pi0|E(|r(obH>xUMY6mEvP~GiIfJm?AUSregppsT z+$;KWNhc>vza+1ocr`d9@DM3u|M?B)sV*gWci&#rUy_maF+60#794+1F1>fCPChz@ zj-y*MDNd408eQnc&7+oo=li{EUw_T(r(rKe;q^@t{m$b11Th4za`q93k~sU4=2h6B zz_rT7jtmqqjrv!roDg3#pZ5G?z+$YIa*wIL<)niUlTwSU)P)PPpk1@2&)HSE;+S1`xLpzj*mR8OfXe9Cr^r zY|@Bg56`Pe+i+~hJbCfYw-Gp=aUTTxyvc2*wGCZ!2#j4O@rWOpp8H;KRPlj-`ctK1 zldEFolg0bf1a!Sp<&1#>Yr9#+S;5ilMK`YVGS(Io3IsdF-?CiIyU}m-Kcd52ao~g`D|Y7z*&4UU zIVpjIx_%*sq+3y8=EB3r+XX6*qGEhK9Hu$1zLZ9ddz|h#KJ4BF=OV%qIjWmB-uN&6 ztiYLPZNPLnm#FVWb!HybmZts^P^pMhsbpQ5#}Tch^!WLo3MbNDX{Q8;B!NCp z{4!pe2wg(nwi6m=#PI3HwM4jHym^33Yj14VLu)K-fbOkO7&>IBGw;h1>WMelZ-*pu zg>r=`iN55Xe`5w8FO*l7zIXR30Pr)2S)Pe|;FQ$fJ ztCcH7cOg@XsG{m5-1leS!hLELx2$MY=(VCGgl@T66I?fN>?^t}r3hsCZi$$VnUJ|4 z*QR7;onr44-pG)`Rw!L2H9%nci=#W1F!^bf*s)#(zYMTiu(f@M1886>KMSNK TUH4=%HTny2dXYb>e(C%lSAWI3 diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 554e6a5a1..6adf76079 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1014,7 +1014,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] { // LIBRARY mode: no @main — the profile-declared external // symbols specified by the library IR instead. for (const line of emitter.emitLibDefs(globals, globalReleaseLines, stamps)) out.push(line); - out.push(...emitter.inlineRcTail(flushedDecls)); + for (const line of emitter.inlineRcTail(flushedDecls)) out.push(line); out.push(...functionAttributes(emitter, hasNoInlineRecordClone)); out.push(``); out.push(...emitter.fieldAliasMetadata()); @@ -1160,7 +1160,9 @@ export function emitModuleParts(emitter: LlEmitter): string[] { : [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), `}`, ``, - ...emitter.inlineRcTail(flushedDecls), + ); + for (const line of emitter.inlineRcTail(flushedDecls)) out.push(line); + out.push( // sanitize_address is inert under the plain pipeline; the sanitized // lane's -fsanitize=address link activates instrumentation over the // emitted functions too (the runtime TUs get theirs from clang). diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 31643bdfa..59ef43d7d 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -1219,7 +1219,9 @@ function collectionOps(host: LlvmEmitterContext, key: IrType, value: IrType): st function newCollection(host: LlvmEmitterContext, key: IrType, value: IrType): string { const B = host.B; const arms = key.kind === "union" ? host.unionsById.get(key.unionId)?.arms : undefined; - const kind = mapKeyKindNum(key, arms); + // SCR_MAP_KEY_REF (2): shape-union keys are record pointers. + const kind = + key.kind === "union" && host.nullableUnions.shapes(key.unionId) ? 2 : mapKeyKindNum(key, arms); const m = B.tmp(); host.declare(`declare ptr @scr_map_new(i32, i32, ptr)`); // SCR_MAP_VAL_CREF (3): string keys and compact values (compactMapValues). diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 242f9997c..2f7c31813 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -7,6 +7,8 @@ import { DYN_KIND } from "./dyn.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; import { f64Lit } from "./common.js"; +import { NULLABLE_NULL, SHAPE_WORD_INDEX, shapeWordHeader } from "./nullable-unions.js"; +import { emitUnionTag } from "./union-repr.js"; export function emitControlExpr( host: LlvmEmitterContext, @@ -52,6 +54,8 @@ export function emitControlExpr( case "toBool": return { name: host.truthy(host.emitUnionProjection(e.operand)), type: e.type }; case "logical": { + const tagSet = e.op === "||" ? unionTagSet(e) : null; + if (tagSet) return { name: emitUnionTagSet(host, tagSet), type: e.type }; // JS value semantics: the result is the deciding operand itself. // Left evaluates once, ownership moves into the result slot; when // the branch takes the right operand the stale left releases first @@ -614,3 +618,85 @@ function literalChoice(e: IrExpr): boolean { if (e.kind === "strLit") return true; return e.kind === "ternary" && literalChoice(e.then) && literalChoice(e.else_); } + +type TagSet = { unionId: string; value: IrExpr; tags: Set }; + +/** An `||` chain of two or more tag tests of one local (`isTag(u, 1) || + * isTag(u, 4) || ...`, the shape of a union conversion's identity test). */ +function unionTagSet(e: IrExpr & { kind: "logical" }): TagSet | null { + const leaves: IrExpr[] = []; + const collect = (x: IrExpr): void => { + if (x.kind === "logical" && x.op === "||") { + collect(x.left); + collect(x.right); + } else leaves.push(x); + }; + collect(e); + const first = leaves[0]!; + if (first.kind !== "unionIsTag" || first.value.kind !== "varRef") return null; + const localId = first.value.localId; + const tags = new Set(); + for (const leaf of leaves) { + if ( + leaf.kind !== "unionIsTag" || + leaf.negated || + leaf.unionId !== first.unionId || + leaf.value.kind !== "varRef" || + leaf.value.localId !== localId + ) + return null; + tags.add(leaf.tag); + } + return { unionId: first.unionId, value: first.value, tags }; +} + +/** i1: the union value holds one of the tags. One read of the tag and one + * switch over the set; a shape union switches on the record's shape word + * after its unit encodings. */ +function emitUnionTagSet(host: LlvmEmitterContext, set: TagSet): string { + const B = host.B; + const u = host.emitUnionProjection(set.value).name; + const nullable = host.nullableUnions.get(set.unionId); + const yes = B.newLabel("tags.y"); + const no = B.newLabel("tags.n"); + const join = B.newLabel("tags.j"); + const target = (tag: number): string => (set.tags.has(tag) ? yes : no); + if (nullable?.repr === "shapes") { + if (nullable.unitTag >= 0) { + const isNull = B.tmp(); + const next = B.newLabel("tags.r"); + B.line(`${isNull} = icmp eq ptr ${u}, null`); + B.condBr(isNull, target(nullable.unitTag), next); + B.startBlock(next); + } + if (nullable.nullTag >= 0) { + const isNullArm = B.tmp(); + const next = B.newLabel("tags.r"); + B.line(`${isNullArm} = icmp eq ptr ${u}, ${NULLABLE_NULL}`); + B.condBr(isNullArm, target(nullable.nullTag), next); + B.startBlock(next); + } + const wp = B.tmp(); + const w = B.tmp(); + B.line( + `${wp} = getelementptr inbounds ${shapeWordHeader(host.sizeType)}, ptr ${u}, i32 0, i32 ${SHAPE_WORD_INDEX}`, + ); + B.line(`${w} = load i32, ptr ${wp}`); + const cases = nullable.arms + .filter((arm) => set.tags.has(arm.tag)) + .map((arm) => `i32 ${host.nullableUnions.shapeNumber(arm.shapeId)}, label %${yes}`); + B.terminate(`switch i32 ${w}, label %${no} [ ${cases.join(" ")} ]`); + } else { + const tag = nullable ? emitUnionTag(B, nullable, u) : host.unionTag(u, set.unionId); + const cases = [...set.tags].map((t) => `i32 ${t}, label %${yes}`); + B.terminate(`switch i32 ${tag}, label %${no} [ ${cases.join(" ")} ]`); + } + B.startBlock(yes); + B.br(join); + B.startBlock(no); + B.br(join); + B.startBlock(join); + const t = B.tmp(); + B.line(`${t} = phi i1 [ true, %${yes} ], [ false, %${no} ]`); + return t; +} diff --git a/packages/compiler/src/backend/llvm/nullable-unions.test.ts b/packages/compiler/src/backend/llvm/nullable-unions.test.ts index 47278453e..bf0941652 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.test.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.test.ts @@ -56,10 +56,11 @@ test("unions of two or more records become shape unions", () => { }); test("records of runtime-inspected unions keep every union over them boxed", () => { - const set: IrType = { kind: "set", elem: { kind: "union", unionId: "ab" } }; + const set: IrType = { kind: "set", elem: { kind: "union", unionId: "abf" } }; const unions = new NullableUnions( module( [ + { id: "abf", arms: [a, b, F64] }, { id: "ab", arms: [a, b] }, { id: "abu", arms: [a, b, UNDEFINED_T] }, { id: "bc", arms: [b, c] }, @@ -68,6 +69,7 @@ test("records of runtime-inspected unions keep every union over them boxed", () { globals: [{ id: "%g.s", name: "s", type: set, mutable: false }] }, ), ); + expect(unions.get("abf")).toBeNull(); expect(unions.get("ab")).toBeNull(); expect(unions.get("abu")).toBeNull(); expect(unions.get("bc")).toBeNull(); @@ -75,6 +77,34 @@ test("records of runtime-inspected unions keep every union over them boxed", () expect(unions.shapeNumber("c")).toBe(0); }); +test("unions of records alone stay shape unions as Map keys and Set elements", () => { + const set: IrType = { kind: "set", elem: { kind: "union", unionId: "ab" } }; + const map: IrType = { kind: "map", key: { kind: "union", unionId: "abc" }, value: F64 }; + const unions = new NullableUnions( + module( + [ + { id: "ab", arms: [a, b] }, + { id: "abc", arms: [a, b, c] }, + { id: "ba", arms: [b, a] }, + { id: "abu", arms: [a, b, UNDEFINED_T] }, + ], + { + globals: [ + { id: "%g.s", name: "s", type: set, mutable: false }, + { id: "%g.m", name: "m", type: map, mutable: false }, + ], + }, + ), + ); + expect(unions.shapes("ab")).not.toBeNull(); + expect(unions.shapes("abc")).not.toBeNull(); + expect(unions.shapes("abu")).not.toBeNull(); + // Unions over the same records share one release and trace helper. + expect(unions.helperOwner("ba")).toBe("ab"); + expect(unions.helperOwner("abu")).toBe("ab"); + expect(unions.helperOwner("abc")).toBe("abc"); +}); + test("library, lifetime-region and debug builds keep record unions boxed", () => { const unions = [{ id: "ab", arms: [a, b] }]; expect(new NullableUnions(module(unions), false).get("ab")).toBeNull(); diff --git a/packages/compiler/src/backend/llvm/nullable-unions.ts b/packages/compiler/src/backend/llvm/nullable-unions.ts index d9b9cdfcf..c51f3b5f6 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.ts @@ -118,6 +118,9 @@ export class NullableUnions { readonly retainWrappers = new Map(); /** Some nullable union encodes `null` with NULLABLE_NULL. */ readonly usesNullSentinel: boolean = false; + /** The shape union whose release and trace helpers each shape union + * uses: the first one over the same record arms. */ + private readonly helperOwners = new Map(); /** `shapes`: allow shape unions (debug builds keep boxes, whose * debug-info layouts describe every arm). */ @@ -151,6 +154,24 @@ export class NullableUnions { if (!this.shapeNumbers.has(arm.shapeId)) this.shapeNumbers.set(arm.shapeId, this.shapeNumbers.size + 2); } + // Release and trace dispatch on the shape word, which names a record + // shape module-wide, so unions over the same records share them. + const owners = new Map(); + for (const nullable of this.byId.values()) { + if (nullable.repr !== "shapes") continue; + const key = nullable.arms + .map((arm) => arm.shapeId) + .sort() + .join(" "); + const owner = owners.get(key) ?? nullable.unionId; + owners.set(key, owner); + this.helperOwners.set(nullable.unionId, owner); + } + } + + /** The shape union whose release and trace helpers this one uses. */ + helperOwner(unionId: string): string { + return this.helperOwners.get(unionId) ?? unionId; } get(unionId: string): PointerUnion | null { @@ -277,13 +298,19 @@ function shapeUnion( /** Unions the runtime itself inspects as tagged boxes keep that * representation: Map keys and Set elements hash and compare union keys by - * tag and payload inside the C map. */ + * tag and payload inside the C map. A union of records alone is the + * exception: its keys compare by record identity, which a shape union's + * record pointer carries as well (the map keys it by reference). */ function boxedUnionIds(mod: IrModule, nodes?: IrNodeIndex): Set { const boxed = new Set(); // Only the module's union definitions consult the result. if (!mod.unions?.length) return boxed; + const recordsOnly = new Set(); + for (const def of mod.unions) + if (def.arms.length >= 2 && def.arms.every((arm) => arm.kind === "record")) + recordsOnly.add(def.id); const mark = (t: IrType): void => { - if (t.kind === "union") boxed.add(t.unionId); + if (t.kind === "union" && !recordsOnly.has(t.unionId)) boxed.add(t.unionId); }; if (nodes !== undefined) { for (const t of nodes.types) { diff --git a/packages/compiler/src/backend/llvm/record-union-keys.test.ts b/packages/compiler/src/backend/llvm/record-union-keys.test.ts new file mode 100644 index 000000000..8c3a89b96 --- /dev/null +++ b/packages/compiler/src/backend/llvm/record-union-keys.test.ts @@ -0,0 +1,148 @@ +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 { BOOL, F64, VOID, type IrExpr, type IrModule, type IrType } from "../../ir/ir.js"; +import { validateModule } from "../../ir/validate.js"; +import { emitLlvmModule } from "./emitter.js"; + +const loc = { file: "record-union-keys.ts", start: 0, end: 0 }; + +async function llvmFor(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-record-union-keys-")); + 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 body(ir: string, name: string): string { + const found = new RegExp(`^define internal [^\\n]*@${name}(?:\\.i32)?\\([^]*?^}`, "m").exec(ir); + expect(found, name).not.toBeNull(); + return found![0]; +} + +test("Sets and Maps of record unions key the record pointer", 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; +const one: Node = { kind: "num", value: 1 }; +const nodes: Node[] = [one, { kind: "neg", operand: one }, { kind: "add", left: one, right: one }]; +const seen = new Set(); +const depth = new Map(); +for (const node of nodes) { + seen.add(node); + depth.set(node, node.kind === "num" ? 0 : 1); +} +console.log(seen.size, depth.get(nodes[1]!), seen.has({ kind: "num", value: 1 })); +`); + // SCR_MAP_KEY_REF (2), never SCR_MAP_KEY_UNION_REF (3), and no union box. + expect(ir).toMatch(/call ptr @scr_map_new\(i32 2,/); + expect(ir).not.toMatch(/call ptr @scr_map_new\(i32 3,/); + expect(ir).not.toMatch(/call ptr @scr_union_new_ref/); +}); + +const recordType = (shapeId: string): IrType => ({ kind: "record", shapeId }); +const wide: IrType = { kind: "union", unionId: "wide" }; + +/** `pick(u)` answers whether `u` holds arm 0, 2 or 3 of a five-record union + * through an `||` chain of tag tests; `same` is a second union over the same + * records in another order. */ +function tagChainModule(): IrModule { + const u: IrExpr = { kind: "varRef", localId: "u", type: wide, loc }; + const isTag = (tag: number): IrExpr => ({ + kind: "unionIsTag", + unionId: "wide", + tag, + negated: false, + value: u, + type: BOOL, + loc, + }); + const or = (left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "logical", + op: "||", + left, + right, + type: BOOL, + loc, + }); + const shapes = ["a", "b", "c", "d", "e"]; + return { + irVersion: 17, + sourceFile: loc.file, + entry: "main", + records: shapes.map((id) => ({ id, fields: [{ name: id, type: F64 }] })), + unions: [ + { id: "wide", arms: shapes.map(recordType) }, + { id: "same", arms: [...shapes].reverse().map(recordType) }, + ], + functions: [ + { + name: "main", + params: [], + returnType: VOID, + locals: [], + body: [], + loc, + }, + { + name: "pick", + params: [{ localId: "u", name: "u", type: wide }], + returnType: BOOL, + locals: [{ id: "u", name: "u", type: wide, mutable: false }], + body: [{ kind: "return", value: or(or(isTag(0), isTag(2)), isTag(3)), loc }], + loc, + }, + { + name: "keep", + params: [{ localId: "s", name: "s", type: { kind: "union", unionId: "same" } }], + returnType: VOID, + locals: [ + { + id: "s", + name: "s", + type: { kind: "union", unionId: "same" }, + mutable: false, + }, + ], + body: [], + loc, + }, + ], + }; +} + +test("an || chain of tag tests is one switch on the shape word", () => { + const mod = tagChainModule(); + expect(validateModule(mod)).toEqual([]); + const ir = emitLlvmModule(mod, { pointerBits: 64 }); + const pick = body(ir, "sc_bf_pick"); + const switches = pick.match(/switch i32 [^\n]*/g) ?? []; + expect(switches).toHaveLength(1); + expect(switches[0]!.match(/label %tags\.y/g)).toHaveLength(3); + expect(pick).not.toMatch(/@sc_stag\./); +}); + +test("unions over the same records share one release", () => { + const ir = emitLlvmModule(tagChainModule(), { pointerBits: 64 }); + expect(ir.match(/^define internal void @sc_srelease\.[^\n]*noinline/gm)).toHaveLength(1); + expect(ir).toMatch(/^define internal void @sc_srelease\.wide\(/m); + expect(ir).not.toMatch(/^define internal void @sc_srelease\.same\(/m); +}); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index a966ff305..d5a77c76d 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -233,7 +233,7 @@ export { computeTraced } from "../cycle-analysis.js"; * `_v`-shaped. */ export function vAdapters(host: ShapeHost, t: IrType): { retain: string; release: string } { const nullable = host.nullableUnions.of(t); - if (nullable?.repr === "shapes") return shapeUnionRc(nullable); + if (nullable?.repr === "shapes") return shapeUnionRc(host, nullable); if (nullable) { const arm = vAdapters(host, nullable.arm); return { retain: nullableRetainSym(host, nullable.arm), release: arm.release }; @@ -290,7 +290,7 @@ export function vAdapters(host: ShapeHost, t: IrType): { retain: string; release export function retainSym(host: ShapeHost, t: IrType): string { if (t.kind === "jsval") return jsvalRcSym(host, "retain"); const nullable = host.nullableUnions.of(t); - if (nullable?.repr === "shapes") return shapeUnionRc(nullable).retain; + if (nullable?.repr === "shapes") return shapeUnionRc(host, nullable).retain; if (nullable) return nullableRetainSym(host, nullable.arm); const stem = runtimeRcStem(t); if (host.rcHelpers !== null && stem !== null && inlineRcFamily(host, stem, "retain") !== null) { @@ -305,10 +305,10 @@ export function retainSym(host: ShapeHost, t: IrType): string { /** A shape union's RC entry points: every record retain is the same * NULL- and immortal-skipping increment (the null sentinel is immortal), * so the first arm's serves; release dispatches on the arm. */ -function shapeUnionRc(union: ShapeUnion): { retain: string; release: string } { +function shapeUnionRc(host: ShapeHost, union: ShapeUnion): { retain: string; release: string } { return { retain: `@${mangleRecordRetain(union.arms[0]!.shapeId)}`, - release: shapeReleaseFn(union.unionId), + release: shapeReleaseFn(host.nullableUnions.helperOwner(union.unionId)), }; } @@ -352,38 +352,72 @@ export function emitShapeUnionHelpers(host: ShapeHost): string[] { `}`, ``, ); - // release: unit encodings are NULL or immortal; each arm releases - // through its own (NULL- and immortal-tolerant) helper. - out.push( - `define internal void ${shapeReleaseFn(union.unionId)}(ptr %u) ${FN_ATTRS} { ; shape union release`, - `entry:`, - ` %t = call i32 ${tagFn}(ptr %u)`, - ` switch i32 %t, label %done [ ${union.arms.map((arm) => `i32 ${arm.tag}, label %a${arm.tag}`).join(" ")} ]`, - ...union.arms.flatMap((arm) => [ - `a${arm.tag}:`, - ` call void @${mangleRecordRelease(arm.shapeId)}(ptr %u)`, - ` br label %done`, - ]), + // 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. + if (host.nullableUnions.helperOwner(union.unionId) !== union.unionId) continue; + const units = [ + ` %isnull = icmp eq ptr %o, null`, + ` br i1 %isnull, label %done, label %${host.nullableUnions.usesNullSentinel ? "notnull" : "rec"}`, + ...(host.nullableUnions.usesNullSentinel + ? [ + `notnull:`, + ` %isnullarm = icmp eq ptr %o, ${NULLABLE_NULL}`, + ` br i1 %isnullarm, label %done, label %rec`, + ] + : []), + `rec:`, + ]; + const armSwitch = (call: (shapeId: string) => string): string[] => [ + ` %wp = getelementptr inbounds ${header}, ptr %o, i32 0, i32 ${SHAPE_WORD_INDEX}`, + ` %w = load i32, ptr %wp`, + ` switch i32 %w, label %done [ ${union.arms.map((arm) => `i32 ${host.nullableUnions.shapeNumber(arm.shapeId)}, label %a${arm.tag}`).join(" ")} ]`, + ...union.arms.flatMap((arm) => [`a${arm.tag}:`, call(arm.shapeId), ` br label %done`]), `done:`, ` ret void`, `}`, ``, - ); + ]; + // Every record arm shares the release fast path (the count at the same + // offset, one collector header choice for the whole union), so only a + // release that may free dispatches on the arm. The release stays a call: + // inlined at every release site, it grows code more than it saves. + const traced = union.arms.map((arm) => host.tracedShapes.has(`record:${arm.shapeId}`)); + const release = shapeReleaseFn(union.unionId); + if (traced.every((t) => t === traced[0])) + out.push( + `define internal void ${release}(ptr %o) noinline ${FN_ATTRS} { ; shape union release`, + `entry:`, + ...units, + ` %rc = load ${S}, ptr %o`, + ` %rcm2 = sub ${S} %rc, 2`, + ` %shared = icmp ult ${S} %rcm2, -3`, + ` br i1 %shared, label %dec, label %slow`, + `dec:`, + ` %n = sub ${S} %rc, 1`, + ` store ${S} %n, ptr %o`, + ...(traced[0] ? cycleRootLines(host, "done") : [` br label %done`]), + `slow:`, + ` %imm = icmp eq ${S} %rc, -1`, + ` br i1 %imm, label %done, label %free`, + `free:`, + ...armSwitch((shapeId) => ` call void @${mangleRecordRelease(shapeId)}_slow(ptr %o)`), + ); + else + out.push( + `define internal void ${release}(ptr %o) ${FN_ATTRS} { ; shape union release`, + `entry:`, + ...units, + ...armSwitch((shapeId) => ` call void @${mangleRecordRelease(shapeId)}(ptr %o)`), + ); if (shapeUnionTraced(host, union)) out.push( - `define internal void ${shapeTraceFn(union.unionId)}(ptr %u, ptr %visit, ptr %ctx) ${FN_ATTRS} { ; shape union trace`, + `define internal void ${shapeTraceFn(union.unionId)}(ptr %o, ptr %visit, ptr %ctx) ${FN_ATTRS} { ; shape union trace`, `entry:`, - ` %t = call i32 ${tagFn}(ptr %u)`, - ` switch i32 %t, label %done [ ${union.arms.map((arm) => `i32 ${arm.tag}, label %a${arm.tag}`).join(" ")} ]`, - ...union.arms.flatMap((arm) => [ - `a${arm.tag}:`, - ` call void @${mangleRecordTrace(arm.shapeId)}(ptr %u, ptr %visit, ptr %ctx)`, - ` br label %done`, - ]), - `done:`, - ` ret void`, - `}`, - ``, + ...units, + ...armSwitch( + (shapeId) => ` call void @${mangleRecordTrace(shapeId)}(ptr %o, ptr %visit, ptr %ctx)`, + ), ); } return out; @@ -435,7 +469,7 @@ export function emitNullableRetainWrappers(host: ShapeHost): string[] { export function releaseSym(host: ShapeHost, t: IrType): string { if (t.kind === "jsval") return jsvalRcSym(host, "release"); const nullable = host.nullableUnions.of(t); - if (nullable?.repr === "shapes") return shapeUnionRc(nullable).release; + if (nullable?.repr === "shapes") return shapeUnionRc(host, nullable).release; if (nullable) return releaseSym(host, nullable.arm); const stem = runtimeRcStem(t); if (host.rcHelpers !== null && stem !== null && inlineRcFamily(host, stem, "release") !== null) { @@ -985,7 +1019,9 @@ export function traceAdapter(host: ShapeHost, t: IrType): string | null { case "union": { const nullable = host.nullableUnions.get(t.unionId); if (nullable?.repr === "shapes") - return shapeUnionTraced(host, nullable) ? shapeTraceFn(nullable.unionId) : null; + return shapeUnionTraced(host, nullable) + ? shapeTraceFn(host.nullableUnions.helperOwner(nullable.unionId)) + : null; if (nullable) return traceAdapter(host, nullable.arm); if (!host.tracedUnions.has(t.unionId)) return null; host.declare(`declare void @scr_union_trace_v(ptr, ptr, ptr)`); diff --git a/packages/compiler/src/backend/llvm/test-boxed-unions.ts b/packages/compiler/src/backend/llvm/test-boxed-unions.ts index 15cf5aee9..8847d53a3 100644 --- a/packages/compiler/src/backend/llvm/test-boxed-unions.ts +++ b/packages/compiler/src/backend/llvm/test-boxed-unions.ts @@ -1,14 +1,29 @@ -import type { IrModule, IrType } from "../../ir/ir.js"; +import { F64, type IrModule, type IrType, type IrUnionDef } from "../../ir/ir.js"; /** Test helper: a Set of each union keeps it a tagged `%ScrUnion` box (Set * elements are runtime-inspected boxes), so stack-box tests exercise the - * paths that nullable-pointer unions (nullable-unions.ts) bypass. */ + * paths that nullable-pointer unions (nullable-unions.ts) bypass. A union of + * records alone is a Set element by record identity, so for those the Set + * holds a companion union with a number arm, which keeps the records out of + * every shape union. */ export function keepUnionsBoxed(mod: IrModule): IrModule { - const keep = (mod.unions ?? []).map((union) => ({ - id: `%g.keep.${union.id}`, - name: `keep_${union.id}`, - type: { kind: "set", elem: { kind: "union", unionId: union.id } } as IrType, - mutable: false, - })); - return { ...mod, globals: [...(mod.globals ?? []), ...keep] }; + const companions: IrUnionDef[] = []; + const keep = (mod.unions ?? []).map((union) => { + let elem = union.id; + if (union.arms.every((arm) => arm.kind === "record")) { + elem = `%keep.${union.id}`; + companions.push({ id: elem, arms: [...union.arms, F64] }); + } + return { + id: `%g.keep.${union.id}`, + name: `keep_${union.id}`, + type: { kind: "set", elem: { kind: "union", unionId: elem } } as IrType, + mutable: false, + }; + }); + return { + ...mod, + unions: [...(mod.unions ?? []), ...companions], + globals: [...(mod.globals ?? []), ...keep], + }; } From 53bddc6ed7b62eb3f9e9bbb81296ae7b64792f39 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 18:46:36 -0500 Subject: [PATCH 143/395] Give the module namespace handle its own tag --- packages/runtime/src/scr_dyn.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 04bb9bb22..a3264b5d0 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -186,7 +186,7 @@ typedef enum { X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \ X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ - X(MODULE_NAMESPACE, 78) /* module namespace object read through its exports (scr_module_namespace.c) */ \ + X(MODULE_NAMESPACE, 79) /* module namespace object read through its exports (scr_module_namespace.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ From b7491676bbd23afbe423b406133321886c68637c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 19:01:18 -0500 Subject: [PATCH 144/395] Read the runtime ABI in the runtime pack test fixture from the compiler --- packages/compiler/src/backend/runtime-pack-native.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/backend/runtime-pack-native.test.ts b/packages/compiler/src/backend/runtime-pack-native.test.ts index 293d814b8..7a8a2e6c4 100644 --- a/packages/compiler/src/backend/runtime-pack-native.test.ts +++ b/packages/compiler/src/backend/runtime-pack-native.test.ts @@ -16,6 +16,7 @@ import { afterEach, expect, test, vi } from "vitest"; import { nativeCodegenDiag } from "../diagnostics/diagnostic.js"; import { compilerReleaseVersion } from "../library/sidecar.js"; import type { NativeLinkFeatures } from "./native-link-info.js"; +import { RUNTIME_ABI_MARKER, RUNTIME_ABI_VERSION } from "./runtime-abi.js"; import { RuntimePackError, type RuntimePackManifest } from "./runtime-pack-core.js"; import { runtimePackManifest, @@ -114,7 +115,7 @@ async function packFixture() { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: RUNTIME_ABI_VERSION, marker: RUNTIME_ABI_MARKER }, compiler: { command: "clang", identity: "fixture clang", target: target.llvmTriple }, macros: { executable: [], From c41af6c36dc2a741ee762dbc57c485f39cb6225f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 19:04:38 -0500 Subject: [PATCH 145/395] Move the MIME handle tags to 81 and 82 Main now uses tag 78 (STRING_DECODER), 79 (MODULE_NAMESPACE, after #2161) and 80; 81 and 82 are the next free values. --- packages/runtime/src/scr_dyn.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 4fcba7bfe..f7014f259 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -186,8 +186,8 @@ typedef enum { X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \ X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ - X(MIME_PARAMS, 78) /* util.MIMEParams (scr_mime.c) */ \ - X(MIME_TYPE, 79) /* util.MIMEType (scr_mime.c) */ \ + X(MIME_PARAMS, 81) /* util.MIMEParams (scr_mime.c) */ \ + X(MIME_TYPE, 82) /* util.MIMEType (scr_mime.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ From 7464f12e6b4649b2aa67489b812c2c5a0e58d652 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:01:49 -0500 Subject: [PATCH 146/395] Emit unwinding code for textual LLVM and assembly output on Linux --- packages/compiler/src/index.ts | 3 +++ packages/compiler/src/native/prepare.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 51230703c..345e7565e 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -809,6 +809,9 @@ async function prepareExecutableInput( pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: outputKind === "obj", + // Textual output shows the code an executable gets; objects keep + // the pending flag, since their link may not add an unwinder. + unwindAbi: buildPlatform === "linux" && outputKind !== "obj", objectAudit: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index c15bfbf9b..13d32fd3b 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -408,7 +408,11 @@ export function prepareNativeExecutable( pointerBits: toolchain.target.pointerBits, wasi: toolchain.target.platform === "wasi", runtimeAbiMarker: outputKind === "obj" || outputKind === "exe", - unwindAbi: outputKind === "exe" && toolchain.target.platform === "linux", + // As for the Node-hosted build: executables and their textual + // LLVM and assembly, not objects. + unwindAbi: + (outputKind === "exe" || outputKind === "llvm" || outputKind === "asm") && + toolchain.target.platform === "linux", objectAudit: options.sanitize === true, lowRegion: lowRegionTarget(toolchain.target.platform, options.sanitize === true), functionWorkers: emitWorkers(), From e2db1ef344d3e09c8db683b4f32fddff8751de6f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:07:36 -0500 Subject: [PATCH 147/395] Hold Reflect.ownKeys's function value in the Reflect namespace object Reading `ownKeys` from Reflect read as a value (`const r = Reflect; r.ownKeys`) gave the member function that throws SC1090 when called, although the spelled `Reflect.ownKeys` read already has a function value. The namespace object now holds that function value, so calls through the alias run and `r.ownKeys === Reflect.ownKeys` is true, as in Node. The spelled read and the namespace member share one lowering (reflectMemberValue). --- .../frontend/lowering/builtin-namespaces.ts | 3 ++- .../frontend/lowering/builtins/reflection.ts | 19 ++++++++++++++++++- .../lowering/expressions/property-access.ts | 16 +++------------- tests/corpus/7186-namespace-object-values.js | 5 +++++ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtin-namespaces.ts b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts index bfe3c5e39..c1e40d2e6 100644 --- a/packages/compiler/src/frontend/lowering/builtin-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts @@ -24,6 +24,7 @@ import type { Lowerer } from "./lowerer.js"; import { InternalCompilerError } from "../../errors.js"; import { mathMemberValue } from "./lower-stdlib.js"; import { jsonMemberValue } from "./builtins/json.js"; +import { reflectMemberValue } from "./builtins/reflection.js"; /** A function member with its `length`, or a constant (`length` -1). */ interface NamespaceMember { @@ -244,7 +245,7 @@ function functionValue(lowerer: Lowerer, canonical: string, key: string, loc: Sr ? mathMemberValue(lowerer, key, loc) : canonical === "JSON" ? jsonMemberValue(lowerer, key, loc) - : null; + : reflectMemberValue(lowerer, key, loc); if (value !== null) return value.type.kind === "dyn" ? value diff --git a/packages/compiler/src/frontend/lowering/builtins/reflection.ts b/packages/compiler/src/frontend/lowering/builtins/reflection.ts index 8e1e6fd94..5171ebfdb 100644 --- a/packages/compiler/src/frontend/lowering/builtins/reflection.ts +++ b/packages/compiler/src/frontend/lowering/builtins/reflection.ts @@ -3,7 +3,24 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; import { builtinModuleFnOf } from "../surfaces.js"; -import { BOOL, DYN, type IrExpr, STRING, arrayOf } from "../../../ir/ir.js"; +import { BOOL, DYN, type IrExpr, STRING, type SrcLoc, arrayOf } from "../../../ir/ir.js"; + +/** The function value of the Reflect member `member` (ownKeys, on native + * computed-key reflection), or null for members without one. */ +export function reflectMemberValue(lowerer: Lowerer, member: string, loc: SrcLoc): IrExpr | null { + if (member !== "ownKeys") return null; + const value = lowerer.lowerNativeCallableValue( + { + fn: "dyn.ownKeys", + params: [DYN], + result: DYN, + valueParams: [{ mode: "required", type: DYN }], + }, + "Reflect.ownKeys", + loc, + )!; + return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc }; +} /** Reflect.ownKeys uses native computed-key reflection. Reflect.apply(target, * thisArg, argsList) where the TARGET is a builtin diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 536ef93c9..7c931aae6 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -161,6 +161,7 @@ import { lowerNetAddressStaticRead } from "../builtins/net-address.js"; import { STATS_TYPE_TESTS } from "../builtins/filesystem.js"; import { arrayValueRead, arrayValueType } from "../array-values.js"; import { lowerClassMethodValue } from "../class-method-values.js"; +import { reflectMemberValue } from "../builtins/reflection.js"; import { objLitGenericFnInfoOf, objLitGenericFnNodeOf, @@ -1691,19 +1692,8 @@ export function lowerIntrinsicProperty( ); } } - if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") { - const value = lowerer.lowerNativeCallableValue( - { - fn: "dyn.ownKeys", - params: [DYN], - result: DYN, - valueParams: [{ mode: "required", type: DYN }], - }, - "Reflect.ownKeys", - locOf(expr), - )!; - return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc: locOf(expr) }; - } + if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") + return reflectMemberValue(lowerer, "ownKeys", locOf(expr)); if (lowerer.chainBlocked(expr)) return null; // Object.prototype.constructor: the Object constructor value. if ( diff --git a/tests/corpus/7186-namespace-object-values.js b/tests/corpus/7186-namespace-object-values.js index c61d96e11..33c30e12f 100644 --- a/tests/corpus/7186-namespace-object-values.js +++ b/tests/corpus/7186-namespace-object-values.js @@ -20,3 +20,8 @@ console.log(parse("[1,2]").length, JSON.stringify({ a: [1] })); console.log(typeof Math === "object" && typeof Reflect === "object"); console.log(Math.max.length, Math.min.length, Math.hypot.length, Math.abs.length); + +const r = Reflect; +const ownKeys = r.ownKeys; +console.log(ownKeys === Reflect.ownKeys, r.ownKeys === Reflect.ownKeys, typeof ownKeys); +console.log(r.ownKeys({ a: 1, b: 2 }).length, ownKeys({ c: 3 })[0], ownKeys.name, ownKeys.length); From 8032e6c6c67211e4e8d5187a9a74d6444af49b3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:08:30 -0500 Subject: [PATCH 148/395] Free frozen records when a shape union drops their last reference A frozen record's rc word carries SCR_RC_FROZEN beside its count. The shared shape-union release compared the whole word, so the last reference to a frozen record took the shared-reference branch: it decremented the count and never freed the record or its fields. When an arm is freezable, the release now compares the count without the frozen bit, as the record's own release does. --- .../backend/llvm/record-union-keys.test.ts | 21 ++++++ packages/compiler/src/backend/llvm/shapes.ts | 13 +++- .../8291-frozen-record-union-keys.ts.json | 6 ++ tests/corpus/8291-frozen-record-union-keys.ts | 65 +++++++++++++++++++ .../8291-frozen-record-union-keys.ts.json | 1 + 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8291-frozen-record-union-keys.ts.json create mode 100644 tests/corpus/8291-frozen-record-union-keys.ts create mode 100644 tests/harness/corpus-costs/8291-frozen-record-union-keys.ts.json diff --git a/packages/compiler/src/backend/llvm/record-union-keys.test.ts b/packages/compiler/src/backend/llvm/record-union-keys.test.ts index 8c3a89b96..3c961cabb 100644 --- a/packages/compiler/src/backend/llvm/record-union-keys.test.ts +++ b/packages/compiler/src/backend/llvm/record-union-keys.test.ts @@ -146,3 +146,24 @@ test("unions over the same records share one release", () => { expect(ir).toMatch(/^define internal void @sc_srelease\.wide\(/m); expect(ir).not.toMatch(/^define internal void @sc_srelease\.same\(/m); }); + +test("a union with a freezable arm compares its count without the frozen bit", async () => { + const ir = await llvmFor(` +type Name = { kind: "name"; text: string }; +type Count = { kind: "count"; value: number }; +type Key = Name | Count; +function name(text: string): Key { + const built: Name = { kind: "name", text }; + return Object.freeze(built); +} +const seen = new Set(); +seen.add(name("a")); +seen.add({ kind: "count", value: 1 }); +console.log(seen.size); +`); + const release = /^define internal void @sc_srelease\.[^\n]*\n[^]*?^}/m.exec(ir); + expect(release).not.toBeNull(); + // SCR_RC_FROZEN is bit 62: a frozen record's last reference has count 1. + expect(release![0]).toContain("and i64 %rc, -4611686018427387905"); + expect(release![0]).toContain("icmp ult i64 %rcm2, -4611686018427387907"); +}); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index d5a77c76d..ab623f934 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -383,6 +383,10 @@ export function emitShapeUnionHelpers(host: ShapeHost): string[] { // release that may free dispatches on the arm. The release stays a call: // inlined at every release site, it grows code more than it saves. const traced = union.arms.map((arm) => host.tracedShapes.has(`record:${arm.shapeId}`)); + // A freezable arm's count is the word without its frozen bit, as in + // releaseFastPath: a frozen record's last reference takes the slow path. + const frozen = union.arms.some((arm) => host.recordsById.get(arm.shapeId)?.freezable === true); + const notFrozen = ~recordFrozenBit(S); const release = shapeReleaseFn(union.unionId); if (traced.every((t) => t === traced[0])) out.push( @@ -390,8 +394,13 @@ export function emitShapeUnionHelpers(host: ShapeHost): string[] { `entry:`, ...units, ` %rc = load ${S}, ptr %o`, - ` %rcm2 = sub ${S} %rc, 2`, - ` %shared = icmp ult ${S} %rcm2, -3`, + ...(frozen + ? [ + ` %refs = and ${S} %rc, ${sizeLiteral(S, notFrozen)}`, + ` %rcm2 = sub ${S} %refs, 2`, + ` %shared = icmp ult ${S} %rcm2, ${sizeLiteral(S, notFrozen - 2n)}`, + ] + : [` %rcm2 = sub ${S} %rc, 2`, ` %shared = icmp ult ${S} %rcm2, -3`]), ` br i1 %shared, label %dec, label %slow`, `dec:`, ` %n = sub ${S} %rc, 1`, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8291-frozen-record-union-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8291-frozen-record-union-keys.ts.json new file mode 100644 index 000000000..3dcadeb0a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8291-frozen-record-union-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8291-frozen-record-union-keys.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8291-frozen-record-union-keys.ts b/tests/corpus/8291-frozen-record-union-keys.ts new file mode 100644 index 000000000..b6173ef4e --- /dev/null +++ b/tests/corpus/8291-frozen-record-union-keys.ts @@ -0,0 +1,65 @@ +// Frozen records in record-only unions used as Set elements and Map keys: +// the union's release frees a frozen record when it drops the last +// reference, for unions the cycle collector traces and ones it does not. +type Leaf = { kind: "leaf"; label: string }; +type Pair = { kind: "pair"; left: Node; right: Node }; +type Node = Leaf | Pair; + +type Name = { kind: "name"; text: string }; +type Count = { kind: "count"; value: number; unit: string }; +type Key = Name | Count; + +function leaf(label: string): Node { + const built: Leaf = { kind: "leaf", label }; + return Object.freeze(built); +} + +function pair(left: Node, right: Node): Node { + const built: Pair = { kind: "pair", left, right }; + return Object.freeze(built); +} + +function key(i: number): Key { + if (i % 2 === 0) { + const name: Name = { kind: "name", text: "n" + i }; + return Object.freeze(name); + } + const count: Count = { kind: "count", value: i, unit: "u" + i }; + return Object.freeze(count); +} + +function nodes(n: number): number { + const seen = new Set(); + const weight = new Map(); + let last = leaf("start"); + for (let i = 0; i < n; i++) { + const next = i % 3 === 0 ? leaf("l" + i) : pair(last, leaf("r" + i)); + seen.add(next); + weight.set(next, i); + last = next; + } + let total = 0; + for (const node of seen) total += (weight.get(node) ?? 0) + (node.kind === "pair" ? 1 : 0); + seen.clear(); + weight.clear(); + return total + (last.kind === "pair" ? 1 : 0); +} + +function keys(n: number): number { + const seen = new Set(); + const size = new Map(); + for (let i = 0; i < n; i++) { + const k = key(i); + seen.add(k); + size.set(k, k.kind === "name" ? k.text.length : k.value); + } + let total = 0; + for (const k of seen) total += size.get(k) ?? 0; + seen.clear(); + size.clear(); + return total; +} + +let sum = 0; +for (let r = 0; r < 50; r++) sum += nodes(40) + keys(40); +console.log(sum); diff --git a/tests/harness/corpus-costs/8291-frozen-record-union-keys.ts.json b/tests/harness/corpus-costs/8291-frozen-record-union-keys.ts.json new file mode 100644 index 000000000..77d20b756 --- /dev/null +++ b/tests/harness/corpus-costs/8291-frozen-record-union-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 5000 } From c091adc0e4ff0a333d163318bb7d288254439e91 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:09:28 -0500 Subject: [PATCH 149/395] Build symbol-keyed fields into frozen class views whatever their attributes A JavaScript class whose instances track which fields they have copies the instance's property bag into its view first, attributes included, and then wrote each symbol-keyed field with an ordinary assignment. Once the instance was frozen, its symbol entry was read-only and the copy non-extensible, so any later reflection that rebuilt the view (Object.isFrozen, a property read) threw "Cannot assign to a read only symbol property". String-keyed fields already use scr_dyn_obj_set_field, which replaces a field's value whatever the attributes copied into the view. The new scr_dyn_symbol_field_set does the same for symbol-keyed fields, and the view uses it when it copies the bag or carries integrity attributes. --- .../src/backend/llvm/expr-stream-bridges.ts | 10 ++++-- ...7408-frozen-js-class-symbol-fields.js.json | 6 ++++ packages/runtime/src/scr_dyn.h | 4 +++ packages/runtime/src/scr_json.c | 31 ++++++++++++++++++ .../7408-frozen-js-class-symbol-fields.js | 32 +++++++++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7408-frozen-js-class-symbol-fields.js.json create mode 100644 tests/corpus/7408-frozen-js-class-symbol-fields.js diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 1f66324ad..0a502eea7 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -1004,9 +1004,15 @@ export function streamTypedRefMaterializeAdapter( host.declare(`declare void @scr_union_release(ptr)`); B.line(`call void @scr_union_release(ptr ${fieldValue})`); } - host.declare(`declare void @scr_dyn_symbol_key_set(ptr, ptr, ptr)`); + // As for string-keyed fields, attributes copied from the bag (a frozen + // instance's) do not stop the view taking the value. + const symbolSetter = + meta.def.tracksOwnFields || withAttributes + ? "scr_dyn_symbol_field_set" + : "scr_dyn_symbol_key_set"; + host.declare(`declare void @${symbolSetter}(ptr, ptr, ptr)`); host.declare(`declare void @scr_dyn_release_v(ptr)`); - B.line(`call void @scr_dyn_symbol_key_set(ptr ${out}, ptr ${key}, ptr ${value})`); + B.line(`call void @${symbolSetter}(ptr ${out}, ptr ${key}, ptr ${value})`); B.line(`call void @scr_dyn_release_v(ptr ${value})`); if (afterSymbol) { B.terminate(`br label %${afterSymbol}`); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7408-frozen-js-class-symbol-fields.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7408-frozen-js-class-symbol-fields.js.json new file mode 100644 index 000000000..a57d1c73b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7408-frozen-js-class-symbol-fields.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7408-frozen-js-class-symbol-fields.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 3e5cdd4a5..691453783 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -870,6 +870,10 @@ bool scr_disposable_stack_use(const ScrDyn *stack, const ScrDyn *value); bool scr_disposable_stack_use_callback(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *dispose); bool scr_disposable_stack_adopt(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *on_dispose); void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored); +/* scr_dyn_obj_set_field for a symbol-keyed field of a class instance's + * view: replaces the value whatever the attributes copied into the view. + * Does not consume stored. */ +void scr_dyn_symbol_field_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored); void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key); bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key); bool scr_dyn_has_own_computed(const ScrDyn *value, const ScrDyn *key); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index e3f27cf61..a32958e51 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -7989,6 +7989,37 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { scr_dyn_release(receiver); } +/* scr_dyn_obj_set_field for a symbol-keyed field of a class instance's + * view: the view takes the native value whatever the attributes copied into + * it from the instance's property bag (a frozen instance's view is built the + * same way as any other). An accessor entry takes the ordinary assignment. + * Does not consume value. */ +void scr_dyn_symbol_field_set(ScrDyn *obj, ScrDyn *key, ScrDyn *value) { + ScrDynEntry *entry = obj->kind == SCR_DYN_OBJ && obj->symbol_properties + ? scr_dyn_symbol_entry(obj, key, true) + : NULL; + if (entry && !entry->accessor) { + scr_dyn_region_store(obj->symbol_properties, value); + ScrDyn *old = entry->value; + entry->value = scr_dyn_retain(value); + scr_dyn_release(old); + return; + } + if (entry || obj->kind != SCR_DYN_OBJ) { + scr_dyn_symbol_key_set(obj, key, value); + return; + } + bool non_extensible = obj->non_extensible; + ScrDyn *table = obj->symbol_properties; + bool table_non_extensible = table && table->non_extensible; + obj->non_extensible = false; + if (table) table->non_extensible = false; + scr_dyn_symbol_key_set(obj, key, value); + obj->non_extensible = non_extensible; + if (obj->symbol_properties) + obj->symbol_properties->non_extensible = table ? table_non_extensible : non_extensible; +} + static ScrDyn *scr_dyn_define_symbol(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) { ScrDyn *receiver = scr_dyn_symbol_receiver(target); if (!receiver) { diff --git a/tests/corpus/7408-frozen-js-class-symbol-fields.js b/tests/corpus/7408-frozen-js-class-symbol-fields.js new file mode 100644 index 000000000..9ce5a1c53 --- /dev/null +++ b/tests/corpus/7408-frozen-js-class-symbol-fields.js @@ -0,0 +1,32 @@ +// A JavaScript class whose instances track which fields they have (one +// field is created by a method) and carry a symbol-keyed field. Freezing +// or sealing such an instance makes its symbol entry read-only; later +// reflection rebuilds the instance's view and takes the field's value. +const tag = Symbol("tag"); +class Box { + constructor(v) { + this.v = v; + this[tag] = v; + } + later() { + this.w = 3; + } +} + +const frozen = new Box(1); +frozen.extra = 2; +Object.freeze(frozen); +console.log(Object.isFrozen(frozen)); +console.log(Object.isFrozen(frozen), Object.isSealed(frozen), Object.isExtensible(frozen)); +console.log(frozen[tag], frozen.v, frozen.extra, frozen.w); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(frozen, tag))); + +const sealed = new Box(2); +sealed.later(); +Object.seal(sealed); +console.log(Object.isSealed(sealed), Object.isFrozen(sealed), sealed[tag], sealed.w); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(sealed, tag))); + +const closed = new Box(4); +Object.preventExtensions(closed); +console.log(Object.isExtensible(closed), Object.isSealed(closed), closed[tag]); From 1547fd47cd31249b0417a5237f5c79d8fbb0d518 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:10:29 -0500 Subject: [PATCH 150/395] Keep record factory parameters that a closure in the record captures findRecordFieldProducers counted only direct reads of a parameter, so a factory such as `(text) => ({ text, upper: () => text.toUpperCase() })` counted as storing `text` only in its field. A caller's slice passed to it was written into a stack buffer, and the closure kept by the caller then read whatever the next iteration wrote there. Captures by closures and classes now count as reads, so such a factory is not a producer. --- .../src/backend/llvm/string-slices.test.ts | 17 ++++++++++++++- .../src/backend/llvm/string-slices.ts | 3 +++ tests/corpus/8114-record-field-slices.ts | 21 +++++++++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/backend/llvm/string-slices.test.ts b/packages/compiler/src/backend/llvm/string-slices.test.ts index 43bec955a..e53bf38c6 100644 --- a/packages/compiler/src/backend/llvm/string-slices.test.ts +++ b/packages/compiler/src/backend/llvm/string-slices.test.ts @@ -365,17 +365,32 @@ test.each([ "escapes-callee", "captured", "producer-reuse", + "producer-capture", ])("a slice stored in a record that may be kept (%s) stays owned", (reason) => { + // The factory also stores a closure over its text parameter. + const capturing = producerHelper("capturing"); + const literal = (capturing.body[0] as IrStmt & { kind: "return" }).value as IrExpr & { + kind: "recordLit"; + }; + literal.fields.push({ + name: "get", + value: { kind: "closure", fnName: "get", captures: ["text"], type: F64, loc }, + }); const helpers = [ reads, keepsText, escapes, producerHelper("make"), producerHelper("twice", [keepIn(ref("text"))]), + capturing, ]; const init = slice("source", 1, 5); const declare = - reason === "producer-reuse" ? callOf("twice", ref("word")) : tokenLit(ref("word")); + reason === "producer-reuse" + ? callOf("twice", ref("word")) + : reason === "producer-capture" + ? callOf("capturing", ref("word")) + : tokenLit(ref("word")); if (declare.kind === "call") declare.type = TOKEN; const uses: IrStmt[] = [{ kind: "exprStmt", expr: equals(textOf(record("tok"))), loc }]; if (reason === "kept-field") uses.push(keepIn(textOf(record("tok")))); diff --git a/packages/compiler/src/backend/llvm/string-slices.ts b/packages/compiler/src/backend/llvm/string-slices.ts index 81a4a5a3e..d1bb197ee 100644 --- a/packages/compiler/src/backend/llvm/string-slices.ts +++ b/packages/compiler/src/backend/llvm/string-slices.ts @@ -419,6 +419,9 @@ export function findRecordFieldProducers( everyStmtList(fn.body, { expr: (value) => { if (value.kind === "varRef") reads.set(value.localId, (reads.get(value.localId) ?? 0) + 1); + // A closure or class that captures a parameter keeps it too. + if (value.kind === "closure" || value.kind === "classRef") + for (const id of value.captures ?? []) reads.set(id, (reads.get(id) ?? 0) + 1); return true; }, stmt: () => true, diff --git a/tests/corpus/8114-record-field-slices.ts b/tests/corpus/8114-record-field-slices.ts index 69d41a1c8..110cd3acb 100644 --- a/tests/corpus/8114-record-field-slices.ts +++ b/tests/corpus/8114-record-field-slices.ts @@ -2,8 +2,9 @@ // whose fields are only read for their length, comparisons and keyword // searches, next to records that are kept after the iteration that built // them (pushed into an array, held in an outer binding, captured by a -// closure, read into a kept array, returned from a helper). Slices of -// non-ASCII text and slices longer than a short token are included. +// closure, read into a kept array, returned from a helper, captured by a +// closure the factory stores beside it). Slices of non-ASCII text and slices +// longer than a short token are included. interface Token { kind: string; text: string; @@ -16,6 +17,15 @@ function token(kind: string, text: string, pos: number): Token { return { kind, text, pos }; } +interface LazyToken { + text: string; + upper: () => string; +} + +function lazyToken(text: string): LazyToken { + return { text, upper: () => text.toUpperCase() }; +} + function classify(text: string): string { return KEYWORDS.includes(text) ? "keyword" : "ident"; } @@ -74,6 +84,8 @@ function keptTokens(source: string): string { const texts: string[] = []; const later: (() => string)[] = []; const returned: string[] = []; + const uppers: (() => string)[] = []; + let lazyLength = 0; let last: Token | null = null; for (let i = 0, start = -1; i <= source.length; i++) { const c = i < source.length ? source.charCodeAt(i) : 32; @@ -93,6 +105,10 @@ function keptTokens(source: string): string { later.push(() => captured.text); const viaHelper = token("helper", source.slice(start, i), start); returned.push(textOf(viaHelper)); + const lazyText = source.slice(start, i); + const lazy = lazyToken(lazyText); + lazyLength += lazy.text.length; + uppers.push(lazy.upper); start = -1; } return [ @@ -101,6 +117,7 @@ function keptTokens(source: string): string { `texts ${texts.join("|")}`, `later ${later.map((f) => f()).join("|")}`, `returned ${returned.join("|")}`, + `upper ${lazyLength} ${uppers.map((f) => f()).join("|")}`, ].join("\n"); } From d64f18d2a8e17539afef33f4d3019c0c0cb7c584 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:11:25 -0500 Subject: [PATCH 151/395] Evaluate constructor arguments before the class-value check `new kinds[i](args)` over an array of class values read the element and narrowed it to the class value before the arguments ran, so an index past the end threw "is not a constructor" without evaluating them. JavaScript evaluates the arguments first: the construction is now a call of a lifted helper (checkedElementConstruction) that takes the element read and the arguments, in that order, then runs the check and constructs. Keeping the arguments in one call also keeps a spread's argument pack alive until every argument has read it. A checked class-value boundary accepted every subclass's class value into a base class's slot, so construction through the slot could call a subclass constructor that takes different arguments with the base constructor's. It now accepts only the subclasses whose class-value constructor takes the same arguments (classValueAbiEquals, the rule the classval upcast validator already applied); any other subclass fails the check with a TypeError. --- packages/compiler/src/backend/llvm/dyn.ts | 10 +- packages/compiler/src/backend/llvm/emitter.ts | 26 ++-- .../src/frontend/lowering/lower-classes.ts | 113 +++++++++++++++--- packages/compiler/src/ir/ir.ts | 22 ++++ packages/compiler/src/ir/validate.ts | 21 ++-- tests/corpus/6937-class-value-arrays.mjs | 23 ++++ tests/corpus/6938-class-value-arrays.ts | 20 ++++ 7 files changed, 198 insertions(+), 37 deletions(-) diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 1d2a14964..24db045c8 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -174,6 +174,10 @@ export interface DynHost extends WalkerHost { dynPromiseAdapter(t: IrType): string; isErrorClass(className: string): boolean; classSubtypes(className: string): readonly string[]; + /** The strict subclasses whose class-value constructor takes the same + * arguments as `className`'s (classValueAbiEquals): the class values a + * checked `classval` slot accepts besides its own class. */ + classValueSubtypes(className: string): readonly string[]; /** emitter.ts's pendingTestLines: the inline pending-exception test. */ pendingTestLines(dest: string): string[]; } @@ -1808,8 +1812,10 @@ export class LlDyn { const B = new BlockBuilder(); if (t.kind === "classval") { host.declare(`declare ptr @scr_dyn_class_check(ptr, ptr, ptr)`); - // A subclass's class value checks into the base class's slot. - const subclasses = host.classSubtypes(t.className).filter((name) => name !== t.className); + // A subclass's class value checks into the base class's slot when + // construction through the slot can call its thunk with the base + // constructor's arguments; any other subclass fails the check. + const subclasses = host.classValueSubtypes(t.className); if (subclasses.length > 0) { host.declare(`declare zeroext i1 @scr_dyn_class_is(ptr, ptr)`); for (const className of subclasses) { diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 6760f3b74..26641c667 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -55,6 +55,7 @@ import { typeKey, VOID, classJsName, + classValueAbiEquals, } from "../../ir/ir.js"; import { moduleRuntimeFeatures, type RuntimeFeatures } from "../../ir/runtime-features.js"; import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; @@ -1895,14 +1896,14 @@ export class LlEmitter { const meta = this.classMeta.get(t.className); return meta !== undefined && programLifetimeClass(this.shapeHost, meta); }, - classSubtypes: (name) => { - const target = this.classMetaOf(name); - const intervals = classMembershipIntervals(this.classMeta, target.def.name); - return [...this.classMeta.values()] - .filter((meta) => - intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post), - ) - .map((meta) => meta.def.name); + classSubtypes: (name) => this.classSubtypes(name), + classValueSubtypes: (name) => { + const target = this.classMetaOf(name).def; + return this.classSubtypes(name).filter( + (sub) => + sub !== target.name && + classValueAbiEquals(this.classMetaOf(sub).def, target, this.fnByName), + ); }, pendingTestLines: (dest) => this.pendingTestLines(dest), virtualEntry: (implFn) => this.virtualEntry(implFn), @@ -3965,6 +3966,15 @@ export class LlEmitter { // ── class plumbing ────────────────────────────────────────────────────── + /** `name` and every class whose membership interval falls in its own. */ + private classSubtypes(name: string): string[] { + const target = this.classMetaOf(name); + const intervals = classMembershipIntervals(this.classMeta, target.def.name); + return [...this.classMeta.values()] + .filter((meta) => intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post)) + .map((meta) => meta.def.name); + } + classMetaOf(className: string): LlClassMeta { const meta = this.classMeta.get(className); if (!meta) throw new InternalCompilerError(`llvm emitter bug: unknown class ${className}`); diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index a91b3c30a..e1b4e890e 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -9782,6 +9782,75 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType return lowerer.currentClass?.fields.get(parent.left.name.text) ?? null; } +/** An array element read that construction narrows to a class value with a + * check (`%new.callee`, which throws "is not a constructor"). */ +interface NarrowedConstructorRead { + read: IrExpr; + check: string; + unionT: IrType & { kind: "union" }; + calleeT: IrType & { kind: "classval" }; + message: IrExpr; +} + +/** `new arr[i](args)` through a narrowed element read: an interned lifted + * function takes the read and the completed arguments, which evaluate in + * that order as call arguments, then runs the check and constructs. JS + * evaluates the arguments before it checks that the callee constructs, and + * the arguments stay in the one call, where a spread's argument pack lives + * until every argument has read it. */ +function checkedElementConstruction( + lowerer: Lowerer, + info: ClassInfo, + narrowed: NarrowedConstructorRead, + args: IrExpr[], + loc: SrcLoc, +): IrExpr { + const type: IrType = { kind: "object", className: info.def.name }; + const key = `newChecked:${narrowed.check}:${info.def.name}:${args.map((a) => typeKey(a.type)).join(",")}`; + let name = lowerer.valueHelpers.get(key); + if (!name) { + name = keyedHelperName(lowerer, "%new.checked", key); + lowerer.valueHelpers.set(key, name); + const params: IrParam[] = [ + { localId: "value", name: "value", type: narrowed.unionT }, + { localId: "message", name: "message", type: STRING }, + ...args.map((arg, i) => ({ localId: `arg.${i}`, name: `arg${i}`, type: arg.type })), + ]; + lowerer.liftedFns.push({ + name, + params, + returnType: type, + locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + body: [ + { + kind: "return", + value: { + kind: "newValue", + callee: { + kind: "call", + callee: narrowed.check, + args: [varRef("value", narrowed.unionT, loc), varRef("message", STRING, loc)], + type: narrowed.calleeT, + loc, + }, + args: params.slice(2).map((p) => varRef(p.localId, p.type, loc)), + type, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + kind: "call", + callee: name, + args: [narrowed.read, narrowed.message, ...args], + type, + loc, + }; +} /** Map, Set and the error classes construct in lowerNew's identifier * branch, which a module member alias of one (`new mod.M()`) also takes. */ function isCollectionOrErrorClass(ref: BuiltinClassRef | null): boolean { @@ -11045,6 +11114,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } let callee = lowerer.lowerExpr(expr.expression); if (callee.type.kind === "dyn") return lowerCheckedConstructor(lowerer, expr, callee); + /** The union-typed element read a narrowing check guards, and the + * check (`%new.callee`) that yields the class value or throws. */ + let narrowedCallee: NarrowedConstructorRead | null = null; // An array element read (`new T[0]()` over `[Dog, Animal]`, whose // checker type is the common base) keeps the undefined an index past // the end reads: the class value is narrowed out with a check, which @@ -11118,18 +11190,25 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }); } + // JavaScript evaluates the arguments before it checks that the + // callee constructs, so the check runs at the construction + // (checkedElementConstruction). + narrowedCallee = { + read: callee, + check: name, + unionT, + calleeT, + message: { + kind: "strLit", + value: `${expr.expression.getText()} is not a constructor`, + type: STRING, + loc, + }, + }; callee = { kind: "call", callee: name, - args: [ - callee, - { - kind: "strLit", - value: `${expr.expression.getText()} is not a constructor`, - type: STRING, - loc, - }, - ], + args: [callee, narrowedCallee.message], type: calleeT, loc, }; @@ -11175,13 +11254,15 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const built = constructedFromValue( lowerer, info, - { - kind: "newValue", - callee, - args, - type: { kind: "object", className: info.def.name }, - loc, - }, + narrowedCallee !== null + ? checkedElementConstruction(lowerer, info, narrowedCallee, args, loc) + : { + kind: "newValue", + callee, + args, + type: { kind: "object", className: info.def.name }, + loc, + }, expr, ); // A runtime class slot widened from a union (`[Readable, Writable]`'s diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 3818433f8..b936e265e 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1630,6 +1630,28 @@ export function classValueConstructor(def: { name: string; runtime?: true }): { : { fn: `%${def.name}.constructor`, firstArg: 1 }; } +/** Whether construction through a class value typed as `sup` may call + * `sub`'s construct thunk: both class-value constructors exist and take the + * same argument types after the receiver. The classval upcast rule and the + * checked class-value boundary both rest on it. */ +export function classValueAbiEquals( + sub: { name: string; runtime?: true }, + sup: { name: string; runtime?: true }, + functions: ReadonlyMap, +): boolean { + const subValueCtor = classValueConstructor(sub); + const supValueCtor = classValueConstructor(sup); + const subCtor = functions.get(subValueCtor.fn); + const supCtor = functions.get(supValueCtor.fn); + if (!subCtor || !supCtor) return false; + const subArgs = subCtor.params.slice(subValueCtor.firstArg); + const supArgs = supCtor.params.slice(supValueCtor.firstArg); + return ( + subArgs.length === supArgs.length && + subArgs.every((p, i) => typeEquals(p.type, supArgs[i]!.type)) + ); +} + /** The JavaScript `.name` of a class: its jsName, or the runtime-provided * class's own name (`%TypeError` is TypeError). */ export function classJsName(def: { name: string; runtime?: true; jsName?: string }): string { diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 5bae241b1..928b1647b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -58,7 +58,12 @@ import { unionContainerArmsOk, VOID, } from "./ir.js"; -import { DYN_CLASS_PROPERTIES, callSiteReturnType, classValueConstructor } from "./ir.js"; +import { + DYN_CLASS_PROPERTIES, + callSiteReturnType, + classValueAbiEquals, + classValueConstructor, +} from "./ir.js"; import { unionWideningTags } from "./analysis.js"; import { alwaysReturns } from "./control-flow.js"; import { dynamicOperationOf, functionTier } from "./tiers.js"; @@ -2997,19 +3002,13 @@ function validateFunction( } const subClass = classes.get(sub); const supClass = classes.get(sup); - const subValueCtor = subClass ? classValueConstructor(subClass) : null; - const supValueCtor = supClass ? classValueConstructor(supClass) : null; - const subCtor = subValueCtor ? functions.get(subValueCtor.fn) : undefined; - const supCtor = supValueCtor ? functions.get(supValueCtor.fn) : undefined; - if (!subCtor || !supCtor || !subValueCtor || !supValueCtor) { + const subCtor = subClass ? functions.get(classValueConstructor(subClass).fn) : undefined; + const supCtor = supClass ? functions.get(classValueConstructor(supClass).fn) : undefined; + if (!subClass || !supClass || !subCtor || !supCtor) { err(`classval upcast: "${sub}"/"${sup}" lack constructor functions`, e.loc); break; } - const subArgs = subCtor.params.slice(subValueCtor.firstArg); - const supArgs = supCtor.params.slice(supValueCtor.firstArg); - const abiEqual = - subArgs.length === supArgs.length && - subArgs.every((p, i) => typeEquals(p.type, supArgs[i]!.type)); + const abiEqual = classValueAbiEquals(subClass, supClass, functions); if (!abiEqual) { err(`classval upcast: "${sub}" and "${sup}" constructor ABIs differ`, e.loc); } diff --git a/tests/corpus/6937-class-value-arrays.mjs b/tests/corpus/6937-class-value-arrays.mjs index a563d7514..8fcfcb815 100644 --- a/tests/corpus/6937-class-value-arrays.mjs +++ b/tests/corpus/6937-class-value-arrays.mjs @@ -28,3 +28,26 @@ try { } const byName = { dog: Dog, cat: Cat }; console.log(new byName.dog().speak(), new byName["cat"]().speak()); +// The arguments are evaluated before the missing element throws. +class Point { + constructor(x, y) { + this.sum = x + y; + } +} +class Point3 extends Point {} +const points = [Point3, Point]; +const evaluated = []; +const arg = (v) => (evaluated.push(v), v); +console.log(new points[0](arg(1), arg(2)).sum, evaluated.join()); +try { + new points[2](arg(3), arg(4)); +} catch (e) { + console.log(e.name, e.message, evaluated.join()); +} +// Spread arguments too, through a valid index and past the end. +console.log(new points[1](...[5, 6]).sum); +try { + new points[3](...[arg(7), arg(8)]); +} catch (e) { + console.log(e.name, e.message, evaluated.join()); +} diff --git a/tests/corpus/6938-class-value-arrays.ts b/tests/corpus/6938-class-value-arrays.ts index 672700cbf..db695bc0a 100644 --- a/tests/corpus/6938-class-value-arrays.ts +++ b/tests/corpus/6938-class-value-arrays.ts @@ -26,3 +26,23 @@ try { } catch (e) { console.log((e as Error).name, (e as Error).message); } +// A class value checked into a `typeof Shape` slot: a subclass whose +// constructor takes the same arguments constructs, and one whose constructor +// takes an argument the slot does not pass throws a TypeError (Node's from +// reading the missing argument, the compiled program's from the check). +class Sized extends Shape { + label: string; + constructor(side: number) { + super(); + this.label = side.toFixed(1); + } +} +const values: any[] = [Square, Sized, Shape]; +for (const value of values) { + try { + const S: typeof Shape = value; + console.log(new S().area()); + } catch (e) { + console.log((e as Error).name); + } +} From 320a97bf3b8dd57c35376dfedcb5cb67aef44051 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:19:50 -0500 Subject: [PATCH 152/395] Throw Node's TypeError for util.diff of two distinct empty sequences util.diff([], []) with two arrays read one element past its trace row while backtracking. Node's myersDiff answers undefined for two empty sequences, and util.diff's ArrayPrototypeReverse throws "Cannot convert undefined or null to object"; scr_util_diff now throws the same TypeError before it allocates the trace. --- packages/runtime/src/scr_util_callable.c | 11 +++++++++++ tests/corpus/7705-util-diff.cjs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_util_callable.c b/packages/runtime/src/scr_util_callable.c index df6a68272..574b9d423 100644 --- a/packages/runtime/src/scr_util_callable.c +++ b/packages/runtime/src/scr_util_callable.c @@ -881,6 +881,17 @@ ScrDyn *scr_util_diff(ScrDyn *actual, ScrDyn *expected) { scr_throw_error_msg_code(SCR_ERR_RANGE, message, (size_t)n, "ERR_OUT_OF_RANGE"); return NULL; } + /* Two distinct empty sequences: Node's myersDiff reads past its + * one-element state, finds no end and answers undefined, which the + * ArrayPrototypeReverse of util.diff rejects. The trace below would be read + * out of bounds. */ + if (max == 0) { + uc_diff_seq_free(&a); + uc_diff_seq_free(&e); + static const char message[] = "Cannot convert undefined or null to object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; + } size_t width = 2 * max + 1; int32_t *v = calloc(width, sizeof *v); int32_t **trace = malloc((max + 1) * sizeof *trace); diff --git a/tests/corpus/7705-util-diff.cjs b/tests/corpus/7705-util-diff.cjs index 9e52fd165..489586a3b 100644 --- a/tests/corpus/7705-util-diff.cjs +++ b/tests/corpus/7705-util-diff.cjs @@ -7,7 +7,7 @@ const show = (a, e) => { try { console.log(JSON.stringify(util.diff(a, e))); } catch (err) { console.log(err.name, err.code, err.message); } }; show("abc", "abd"); show("kitten", "sitting"); show("", "x"); show("same", "same"); show("", ""); -show(["a", "b", "c"], ["a", "c", "d"]); show(["x"], []); show([], ["y", "z"]); +show(["a", "b", "c"], ["a", "c", "d"]); show(["x"], []); show([], ["y", "z"]); show([], []); const arr = ["p"]; show(arr, arr); show(1, 1); show(1, 2); show("a", 2); show(["a", 1], ["a"]); show(["a"], [, "b"]); show(null, "x"); show("x", undefined); show("héllo😀", "hello😀"); show({}, "x"); show("the quick brown fox", "the quack brown box"); From e6a8d735ca0355d24c3e57fb3a5320119aeab75f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:20:39 -0500 Subject: [PATCH 153/395] Bound strict throttle state and evaluate MIME arguments before the call A strict throttler kept every invocation timestamp until its whole window emptied, so sustained traffic grew the array without bound; ticks that left the window now give their slots back, and at most limit ticks are live. new MIMEType, new MIMEParams and MIMEType.parse in JavaScript evaluated surplus arguments after the call. All arguments now evaluate first, in order, as in JavaScript, so a surplus argument's effects happen before the constructor coerces or throws. --- .../src/frontend/lowering/builtins/mime.ts | 80 +++++++++---------- packages/runtime/src/scr_util_timing.c | 7 ++ tests/corpus/7707-util-mime-type.cjs | 8 ++ 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/mime.ts b/packages/compiler/src/frontend/lowering/builtins/mime.ts index da3d16712..27f95226c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/mime.ts +++ b/packages/compiler/src/frontend/lowering/builtins/mime.ts @@ -2,7 +2,7 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; import { dynUndefinedExpr, numLit } from "../../../ir/build.js"; -import { BOOL, DYN, type IrExpr } from "../../../ir/ir.js"; +import { BOOL, DYN, type IrExpr, type IrLibFn } from "../../../ir/ir.js"; import { builtinImportOf } from "./module-bindings.js"; /* node:util's MIMEType and MIMEParams are native checked-dynamic handles @@ -34,38 +34,51 @@ function mimeValueClass(lowerer: Lowerer, node: ts.Expression): MimeClass | null return lowerer.isStdlibSymbol(sym) ? name : null; } -/** Every argument in order, as checked-dynamic values; the first `count` - * fill the call, missing ones as undefined, and the others still - * evaluate. */ -function mimeArguments( +/** The call of `fn` over the first `count` arguments, as checked-dynamic + * values (missing ones as undefined). Every argument evaluates in order + * before the call, as in JavaScript: with surplus arguments, the used ones + * are held in hidden locals while the others evaluate. */ +function mimeCall( lowerer: Lowerer, + fn: IrLibFn, nodes: readonly ts.Expression[], count: number, loc: ReturnType, -): { used: IrExpr[]; extra: IrExpr[] } { - const values = nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)); - const used = values.slice(0, count); - while (used.length < count) used.push(dynUndefinedExpr(loc)); - return { used, extra: values.slice(count) }; -} - -/** The call, after which any surplus arguments evaluate. */ -function withSurplus( - lowerer: Lowerer, - call: IrExpr, - extra: IrExpr[], - loc: ReturnType, ): IrExpr { - if (extra.length === 0) return call; - const local = lowerer.declareHiddenLocal("%mimeResult", call.type); + const values = nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)); + if (values.length <= count) { + const args = [...values]; + while (args.length < count) args.push(dynUndefinedExpr(loc)); + return { kind: "libCall", fn, args, type: DYN, loc }; + } + const locals = values.slice(0, count).map((value) => ({ + value, + local: lowerer.declareHiddenLocal("%mimeArg", DYN), + })); return { kind: "seqExpr", stmts: [ - { kind: "varDecl", localId: local.id, init: call, loc }, - ...extra.map((arg) => ({ kind: "exprStmt" as const, expr: arg, loc: arg.loc })), + ...locals.map(({ value, local }) => ({ + kind: "varDecl" as const, + localId: local.id, + init: value, + loc, + })), + ...values.slice(count).map((arg) => ({ kind: "exprStmt" as const, expr: arg, loc: arg.loc })), ], - result: { kind: "varRef", localId: local.id, type: call.type, loc }, - type: call.type, + result: { + kind: "libCall", + fn, + args: locals.map(({ local }): IrExpr => ({ + kind: "varRef", + localId: local.id, + type: DYN, + loc, + })), + type: DYN, + loc, + }, + type: DYN, loc, }; } @@ -77,15 +90,8 @@ export function lowerMimeNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | const nodes = expr.arguments?.nodes ?? []; if (nodes.some(ts.isSpreadElement)) return null; const loc = locOf(expr); - const { used, extra } = mimeArguments(lowerer, nodes, cls === "MIMEType" ? 2 : 0, loc); - const call: IrExpr = { - kind: "libCall", - fn: cls === "MIMEType" ? "mime.typeNew" : "mime.paramsNew", - args: used, - type: DYN, - loc, - }; - return withSurplus(lowerer, call, extra, loc); + const fn: IrLibFn = cls === "MIMEType" ? "mime.typeNew" : "mime.paramsNew"; + return mimeCall(lowerer, fn, nodes, cls === "MIMEType" ? 2 : 0, loc); } const INSTANCE_METHODS: Record> = { @@ -113,13 +119,7 @@ export function lowerMimeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr const name = access.name.text; const loc = locOf(call); if (name === "parse" && mimeClassOf(lowerer, access.expression) === "MIMEType") { - const { used, extra } = mimeArguments(lowerer, call.arguments.nodes, 1, loc); - return withSurplus( - lowerer, - { kind: "libCall", fn: "mime.typeParse", args: used, type: DYN, loc }, - extra, - loc, - ); + return mimeCall(lowerer, "mime.typeParse", call.arguments.nodes, 1, loc); } const cls = mimeValueClass(lowerer, access.expression); if (cls === null || !INSTANCE_METHODS[cls].has(name)) return null; diff --git a/packages/runtime/src/scr_util_timing.c b/packages/runtime/src/scr_util_timing.c index c5393da27..a8a700d47 100644 --- a/packages/runtime/src/scr_util_timing.c +++ b/packages/runtime/src/scr_util_timing.c @@ -392,6 +392,13 @@ static void th_reserve(UtState *s, double now) { s->window_count++; return; } + /* Ticks that left the window free their slots: at most limit ticks are + * live, so the storage stays within twice that. */ + if (s->tick_len == s->tick_cap && s->tick_head > 0) { + s->tick_len -= s->tick_head; + memmove(s->ticks, s->ticks + s->tick_head, s->tick_len * sizeof *s->ticks); + s->tick_head = 0; + } if (s->tick_len == s->tick_cap) { size_t cap = s->tick_cap ? s->tick_cap * 2 : 8; double *grown = realloc(s->ticks, cap * sizeof *grown); diff --git a/tests/corpus/7707-util-mime-type.cjs b/tests/corpus/7707-util-mime-type.cjs index 06d159306..e860d1d30 100644 --- a/tests/corpus/7707-util-mime-type.cjs +++ b/tests/corpus/7707-util-mime-type.cjs @@ -76,4 +76,12 @@ console.log(seen, `${m}`); const bare = new MIMEType("a/b"); console.log(Object.keys(bare), bare.foo, bare.params.size); console.log(format("%s|%s", bare, bare.params), "%s", new MIMEType("c/d;e=f")); + // Every argument evaluates before the constructor or parse runs. + const order = []; + const note = (label) => { order.push(label); return label; }; + const coerced = { toString() { order.push("coerce"); return "a/b"; } }; + show("surplus, then coercion", () => { new MIMEType(coerced, undefined, note("third")); return order.splice(0).join(","); }); + show("surplus, then parse error", () => { try { new MIMEType("bad", undefined, note("x")); } catch (e) { order.push(e.code); } return order.splice(0).join(","); }); + show("parse surplus", () => { MIMEType.parse(coerced, note("second")); return order.splice(0).join(","); }); + show("params surplus", () => { new MIMEParams(note("p")); return order.splice(0).join(","); }); } From 120c65bfbbd3c3298dc0232aacc5a3b2e85c8e08 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:24:11 -0500 Subject: [PATCH 154/395] Read an iterator's next once, and await return()'s value in for await JavaScript reads an iterator's `next` when the iterator opens and keeps calling that function, so a `next` the iterator assigns while it runs is not called. The for-of loop and the spread drain over an object-literal iterator read the field on every step; they now read it once after the open (classIteratorNextRead) and call the function they read. The checked-dynamic spread drain (%iterator.pack.checked) does the same, using the stepping the for-of and Array.from drains already use. `for await` over a synchronous iterator closes it on an early exit the way the async-from-sync iterator does: return()'s result is read for done and value, and the value is awaited, so a pending promise settles before the loop continues and a rejected one rejects the loop (after a throw from the body, the body's error still wins). --- .../lowering/expressions/array-literals.ts | 123 ++++++++++++++---- .../src/frontend/lowering/lower-classes.ts | 45 ++++++- .../src/frontend/lowering/lower-stmts.ts | 41 +++++- .../src/frontend/lowering/lower-stream.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 14 +- tests/corpus/6928-class-iterator-protocol.ts | 19 +++ tests/corpus/6929-class-iterator-protocol.mjs | 19 +++ .../6935-class-async-iterator-protocol.ts | 40 ++++++ .../6936-class-async-iterator-protocol.mjs | 39 ++++++ 9 files changed, 306 insertions(+), 36 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index 6153b87f2..09f3e1a2c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -29,9 +29,10 @@ import { lowerCollectionSpread } from "../containers/collection-methods.js"; import { strCharsCall } from "../containers/array-construction.js"; import { generatorDrain } from "../iterator-adapters.js"; import { isSearchParamsIterable } from "../builtins/url.js"; -import { varRef } from "../../../ir/build.js"; +import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; import { arrayValueStore } from "../array-values.js"; import { classGeneratorIterable } from "../class-iterables.js"; +import { iteratorCanStep } from "../iterator-consumption.js"; /** JavaScript: whether `expr` is or holds an existing checked-dynamic * object — a binding stored as a dyn value whose checker type is an @@ -717,13 +718,17 @@ function lowerTupleSpreadLiteral( return { kind: "seqExpr", stmts, result: { kind: "recordLit", fields, type, loc }, type, loc }; } -/** Drain using checked dispatch so native class and generator iterators keep their methods. - * `spell` is the TypeError message for a source that is not iterable - * (notIterableSpelling). */ +/** Drain a checked-dynamic iterable: `next` is read once when the iterator + * opens, as JS keeps it in the iterator record, so a `next` the iterator + * assigns while it runs is not called. Runtime iterators step natively, and + * other iterators call the `next` read (class methods keep their emitted + * dispatch). `spell` is the TypeError message for a source that is not + * iterable (notIterableSpelling). */ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, loc: SrcLoc): IrExpr { const name = "%iterator.pack.checked"; if (!lowerer.liftedFns.some((fn) => fn.name === name)) { const ref = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: DYN, loc }); + const flag = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: BOOL, loc }); const property = (key: string): IrExpr => ({ kind: "dynKeyGet", value: ref("step"), @@ -731,6 +736,7 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo type: DYN, loc, }); + const finished: IrStmt = { kind: "return", value: ref("result"), loc }; lowerer.liftedFns.push({ name, params: [ @@ -739,12 +745,13 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo ], returnType: DYN, locals: [ - ...["source", "iterator", "step", "result"].map((id) => ({ + ...["source", "iterator", "next", "step", "value", "result"].map((id) => ({ id, name: id, type: DYN, mutable: id !== "source", })), + { id: "fast", name: "fast", type: BOOL, mutable: false }, { id: "spell", name: "spell", type: STRING, mutable: false }, ], body: [ @@ -760,6 +767,24 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo }, loc, }, + { + kind: "varDecl", + localId: "next", + init: { + kind: "dynKeyGet", + value: ref("iterator"), + key: { kind: "strLit", value: "next", type: STRING, loc }, + type: DYN, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: "fast", + init: iteratorCanStep(ref("iterator"), ref("next"), loc), + loc, + }, { kind: "varDecl", localId: "result", @@ -770,25 +795,77 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo kind: "while", cond: { kind: "boolLit", value: true, type: BOOL, loc }, body: [ - { - kind: "varDecl", - localId: "step", - init: { - kind: "dynInvoke", - recv: ref("iterator"), - method: "next", - calleeName: "iterator.next", - args: [], - type: DYN, - loc, - }, - loc, - }, + // Declared before the branches, which each assign it: a local + // declared in a branch is released when the branch ends. + { kind: "varDecl", localId: "value", init: dynUndefinedExpr(loc), loc }, { kind: "if", - cond: { kind: "dynTest", test: "truthy", value: property("done"), type: BOOL, loc }, - then: [{ kind: "return", value: ref("result"), loc }], - else_: null, + cond: flag("fast"), + then: [ + { + kind: "assign", + localId: "value", + value: { + kind: "libCall", + fn: "dyn.iteratorStep", + args: [ref("iterator")], + type: DYN, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "libCall", + fn: "dyn.iteratorStepDone", + args: [ref("iterator")], + type: BOOL, + loc, + }, + then: [finished], + else_: null, + loc, + }, + ], + else_: [ + { + kind: "varDecl", + localId: "step", + init: { + kind: "libCall", + fn: "dyn.iteratorResult", + args: [ + { + kind: "dynCall", + callee: ref("next"), + receiver: ref("iterator"), + calleeName: "iterator.next", + args: [], + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "dynTest", + test: "truthy", + value: property("done"), + type: BOOL, + loc, + }, + then: [finished], + else_: null, + loc, + }, + { kind: "assign", localId: "value", value: property("value"), loc }, + ], loc, }, { @@ -796,7 +873,7 @@ function checkedIteratorPack(lowerer: Lowerer, source: IrExpr, spell: string, lo expr: { kind: "libCall", fn: "dyn.packPush", - args: [ref("result"), property("value")], + args: [ref("result"), ref("value")], type: VOID, loc, }, diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 9d994b76b..2e077bfcf 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -8016,15 +8016,42 @@ export function classIteratorOf( }; } -/** The `it.next()` step of a class iterator as an ordinary (possibly - * virtual) method call. */ -export function classIteratorNextCall( +/** The `next` an object-literal iterator's protocol calls, read once when + * the iterator opens: JS keeps the `next` it reads then in the iterator + * record, so a `next` the iterator assigns while it runs is not called. + * Null for an iterator class, whose `next` is a method. */ +export function classIteratorNextRead( lowerer: Lowerer, cit: ClassIteratorInfo, itRef: IrExpr, loc: SrcLoc, +): (IrExpr & { type: IrType & { kind: "func" } }) | null { + const iterT = cit.iterT; + if (iterT.kind === "object") return null; + const field = lowerer.shapes.get(iterT.shapeId)!.fields.find((f) => f.name === "next")!; + return { + kind: "recordGet", + obj: itRef, + shapeId: iterT.shapeId, + field: "next", + type: field.type as IrType & { kind: "func" }, + loc, + }; +} + +/** The `it.next()` step of a class iterator: an ordinary (possibly + * virtual) method call, or the call of `next`, the object-literal + * iterator's `next` that classIteratorNextRead read when it opened. */ +export function classIteratorNextCall( + lowerer: Lowerer, + cit: ClassIteratorInfo, + itRef: IrExpr, + next: IrExpr | null, + loc: SrcLoc, ): IrExpr { - return classIteratorMethodCall(lowerer, cit, "next", itRef, loc); + if (next === null || next.type.kind !== "func") + return classIteratorMethodCall(lowerer, cit, "next", itRef, loc); + return { kind: "callValue", callee: next, args: [], type: next.type.ret, loc }; } /** `it.next()` or `it.return()` of a class iterator: a (possibly virtual) @@ -8108,6 +8135,8 @@ export function classIteratorDrainCall( const recvT: IrType = { kind: "object", className: cit.className }; const recvRef: IrExpr = { kind: "varRef", localId: "r.0", type: recvT, loc }; const itRef: IrExpr = { kind: "varRef", localId: "it.0", type: cit.iterT, loc }; + const nextRead = classIteratorNextRead(lowerer, cit, itRef, loc); + const nextRef: IrExpr | null = nextRead ? varRef("next.0", nextRead.type, loc) : null; const outRef: IrExpr = { kind: "varRef", localId: "out.0", type: outT, loc }; const resRef: IrExpr = { kind: "varRef", localId: "res.0", type: cit.resultT, loc }; const valueRead: IrExpr = { @@ -8122,7 +8151,7 @@ export function classIteratorDrainCall( { kind: "varDecl", localId: "res.0", - init: classIteratorNextCall(lowerer, cit, itRef, loc), + init: classIteratorNextCall(lowerer, cit, itRef, nextRef, loc), loc, }, ...(cit.hasDone @@ -8163,6 +8192,7 @@ export function classIteratorDrainCall( locals: [ { id: "r.0", name: "r", type: recvT, mutable: false }, { id: "it.0", name: "it", type: cit.iterT, mutable: false }, + ...(nextRead ? [{ id: "next.0", name: "next", type: nextRead.type, mutable: false }] : []), { id: "out.0", name: "out", type: outT, mutable: false }, { id: "res.0", name: "res", type: cit.resultT, mutable: true }, ], @@ -8179,6 +8209,9 @@ export function classIteratorDrainCall( init: classIteratorOpenCall(lowerer, cit, recvRef, loc), loc, }, + ...(nextRead + ? [{ kind: "varDecl", localId: "next.0", init: nextRead, loc } satisfies IrStmt] + : []), { kind: "while", cond: { kind: "boolLit", value: true, type: BOOL, loc }, @@ -8226,7 +8259,7 @@ export function classIteratorRestDrainCall( { kind: "varDecl", localId: "res.0", - init: classIteratorNextCall(lowerer, cit, itRef, loc), + init: classIteratorNextCall(lowerer, cit, itRef, null, loc), loc, }, ...(cit.hasDone diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index bee887e0f..b942a538b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2578,7 +2578,7 @@ export function lowerBindingPattern( const itRef = (): IrExpr => ({ kind: "varRef", localId: it.id, type: citPat.iterT, loc }); for (const el of pattern.elements.nodes) { const stepLoc = locOf(el); - const step = lowerer.classIteratorNextCall(citPat, itRef(), stepLoc); + const step = lowerer.classIteratorNextCall(citPat, itRef(), null, stepLoc); if (ts.isOmittedExpression(el) || el.name === undefined) { out.push({ kind: "exprStmt", expr: step, loc: stepLoc }); continue; @@ -10726,6 +10726,8 @@ function lowerForOfClassIterator( const r = lowerer.declareHiddenLocal("%cir", cit.resultT); const recvRef: IrExpr = { kind: "varRef", localId: recv.id, type: iterable.type, loc }; const itRef = (): IrExpr => ({ kind: "varRef", localId: it.id, type: cit.iterT, loc }); + const nextRead = lowerer.classIteratorNextRead(cit, itRef(), loc); + const next = nextRead ? lowerer.declareHiddenLocal("%cin", nextRead.type) : null; const rRef = (): IrExpr => ({ kind: "varRef", localId: r.id, type: cit.resultT, loc }); const done = cit.hasReturn ? lowerer.declareHiddenLocal("%cid", BOOL) : null; if (done) done.mutable = true; @@ -10756,7 +10758,12 @@ function lowerForOfClassIterator( { kind: "varDecl", localId: r.id, - init: lowerer.classIteratorNextCall(cit, itRef(), loc), + init: lowerer.classIteratorNextCall( + cit, + itRef(), + next ? varRef(next.id, next.type, loc) : null, + loc, + ), loc, }, ...(cit.hasDone @@ -10820,6 +10827,9 @@ function lowerForOfClassIterator( init: lowerer.classIteratorOpenCall(cit, recvRef, loc), loc, }, + ...(next && nextRead + ? [{ kind: "varDecl", localId: next.id, init: nextRead, loc } satisfies IrStmt] + : []), ...(done ? [ { @@ -10887,6 +10897,7 @@ function lowerForOfDyn( done.mutable = true; const value = lowerer.declareHiddenLocal("%dofValue", DYN); const returned = lowerer.declareHiddenLocal("%dofReturn", DYN); + const closed = lowerer.declareHiddenLocal("%dofClosed", DYN); const needsClose = lowerer.declareHiddenLocal("%dofNeedsClose", BOOL); needsClose.mutable = true; const error = lowerer.declareHiddenLocal("%dofError", CAUGHT); @@ -10952,9 +10963,10 @@ function lowerForOfDyn( }, then: [ { - kind: "exprStmt", + kind: "varDecl", + localId: closed.id, // return()'s (awaited) result must be an object, as for next(). - expr: asyncLoop + init: asyncLoop ? check( awaitDyn({ kind: "dynCall", @@ -10969,6 +10981,27 @@ function lowerForOfDyn( : call(varRef(returned.id, DYN, loc)), loc, }, + // A synchronous iterator's return() result is read as the + // async-from-sync iterator reads it: done, then the value, which is + // awaited, so a rejected promise there rejects the loop. + ...(syncIterator + ? [ + { + kind: "if", + cond: varRef(syncIterator.id, BOOL, loc), + then: [ + { kind: "exprStmt", expr: get(varRef(closed.id, DYN, loc), "done"), loc }, + { + kind: "exprStmt", + expr: awaitDyn(get(varRef(closed.id, DYN, loc), "value")), + loc, + }, + ], + else_: null, + loc, + } satisfies IrStmt, + ] + : []), ], else_: null, loc, diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 90bdf917e..e17dee274 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -1982,7 +1982,7 @@ function classIteratorObject( { kind: "varDecl", localId: r.id, - init: classIteratorNextCall(lowerer, cit, varRef(it.id, cit.iterT, loc), loc), + init: classIteratorNextCall(lowerer, cit, varRef(it.id, cit.iterT, loc), null, loc), loc, }, { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ea503e1f1..76c4e5c0d 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -362,6 +362,7 @@ import { classIteratorDrainCall, classIteratorMethodCall, classIteratorNextCall, + classIteratorNextRead, classIteratorOf, classIteratorOpenCall, classIteratorRestDrainCall, @@ -13063,8 +13064,17 @@ export class Lowerer { return classIteratorOpenCall(this, cit, recv, loc); } - classIteratorNextCall(cit: ClassIteratorInfo, itRef: IrExpr, loc: SrcLoc): IrExpr { - return classIteratorNextCall(this, cit, itRef, loc); + classIteratorNextRead(cit: ClassIteratorInfo, itRef: IrExpr, loc: SrcLoc): IrExpr | null { + return classIteratorNextRead(this, cit, itRef, loc); + } + + classIteratorNextCall( + cit: ClassIteratorInfo, + itRef: IrExpr, + next: IrExpr | null, + loc: SrcLoc, + ): IrExpr { + return classIteratorNextCall(this, cit, itRef, next, loc); } classIteratorDrainCall(src: IrExpr, loc: SrcLoc, elemT?: IrType): IrExpr | null { diff --git a/tests/corpus/6928-class-iterator-protocol.ts b/tests/corpus/6928-class-iterator-protocol.ts index f097368de..2c2eecccc 100644 --- a/tests/corpus/6928-class-iterator-protocol.ts +++ b/tests/corpus/6928-class-iterator-protocol.ts @@ -91,3 +91,22 @@ class Range { let total = 0; for (const v of new Range(4)) total += v; console.log(total, [...new Range(3)].join()); + +// An object-literal iterator that replaces its next() while it runs: the +// loop and the spread keep calling the next() they read when it opened. +class Swapping { + [Symbol.iterator]() { + let i = 0; + const it = { + next(): { value: number; done: boolean } { + i++; + if (i === 2) it.next = () => ({ value: -1, done: true }); + return { value: i, done: i > 4 }; + }, + }; + return it; + } +} +const swapped: number[] = []; +for (const v of new Swapping()) swapped.push(v); +console.log(swapped.join(), [...new Swapping()].join()); diff --git a/tests/corpus/6929-class-iterator-protocol.mjs b/tests/corpus/6929-class-iterator-protocol.mjs index b5255ba35..6a7827f15 100644 --- a/tests/corpus/6929-class-iterator-protocol.mjs +++ b/tests/corpus/6929-class-iterator-protocol.mjs @@ -76,3 +76,22 @@ try { } catch (e) { console.log("throw:", e.message); } + +// An object-literal iterator that replaces its next() while it runs: the +// loop and the spread keep calling the next() they read when it opened. +class Swapping { + [Symbol.iterator]() { + let i = 0; + const it = { + next() { + i++; + if (i === 2) it.next = () => ({ value: -1, done: true }); + return { value: i, done: i > 4 }; + }, + }; + return it; + } +} +const swapped = []; +for (const v of new Swapping()) swapped.push(v); +console.log(swapped.join(), [...new Swapping()].join()); diff --git a/tests/corpus/6935-class-async-iterator-protocol.ts b/tests/corpus/6935-class-async-iterator-protocol.ts index 9ec5a61a5..25626a773 100644 --- a/tests/corpus/6935-class-async-iterator-protocol.ts +++ b/tests/corpus/6935-class-async-iterator-protocol.ts @@ -56,5 +56,45 @@ async function main(): Promise { console.log("found", await find()); for await (const p of new Pages()) console.log(p); for await (const l of new Letters()) console.log("letter", l); + // return()'s value is awaited when the loop leaves early: a pending + // promise settles first, and a rejection rejects the loop after a break + // (after a throw from the body, the body's error wins). + for await (const v of new Closing((resolve) => setTimeout(() => resolve(1), 5))) + if (v === 1) break; + console.log("after break"); + try { + for await (const v of new Closing((_, reject) => reject(new Error("closing failed")))) + if (v === 1) break; + } catch (e) { + console.log("break:", (e as Error).message); + } + try { + for await (const v of new Closing((_, reject) => reject(new Error("closing failed")))) + if (v === 1) throw new Error("body failed"); + } catch (e) { + console.log("throw:", (e as Error).message); + } +} +class Closing { + readonly settle: (resolve: (v: number) => void, reject: (e: Error) => void) => void; + constructor(settle: (resolve: (v: number) => void, reject: (e: Error) => void) => void) { + this.settle = settle; + } + [Symbol.iterator]() { + let i = 0; + const settle = this.settle; + return { + next(): IteratorResult { + return { value: i++, done: false }; + }, + return(): IteratorResult { + console.log("return called"); + const value = new Promise((resolve, reject) => + settle((v) => (console.log("value settled"), resolve(v)), reject), + ); + return { value: value as unknown as number, done: true }; + }, + }; + } } main(); diff --git a/tests/corpus/6936-class-async-iterator-protocol.mjs b/tests/corpus/6936-class-async-iterator-protocol.mjs index cb7b1e68a..4cc67b0d1 100644 --- a/tests/corpus/6936-class-async-iterator-protocol.mjs +++ b/tests/corpus/6936-class-async-iterator-protocol.mjs @@ -56,5 +56,44 @@ async function main() { console.log("found", await find()); for await (const p of new Pages()) console.log(p); for await (const l of new Letters()) console.log("letter", l); + // return()'s value is awaited when the loop leaves early: a pending + // promise settles first, and a rejection rejects the loop after a break + // (after a throw from the body, the body's error wins). + for await (const v of new Closing((resolve) => setTimeout(() => resolve(1), 5))) + if (v === 1) break; + console.log("after break"); + try { + for await (const v of new Closing((_, reject) => reject(new Error("closing failed")))) + if (v === 1) break; + } catch (e) { + console.log("break:", e.message); + } + try { + for await (const v of new Closing((_, reject) => reject(new Error("closing failed")))) + if (v === 1) throw new Error("body failed"); + } catch (e) { + console.log("throw:", e.message); + } +} +class Closing { + constructor(settle) { + this.settle = settle; + } + [Symbol.iterator]() { + let i = 0; + const settle = this.settle; + return { + next() { + return { value: i++, done: false }; + }, + return() { + console.log("return called"); + const value = new Promise((resolve, reject) => + settle((v) => (console.log("value settled"), resolve(v)), reject), + ); + return { value, done: true }; + }, + }; + } } main(); From e73dbe0bb3842e18d793600a449a9b3ae62c8d40 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:41:45 -0500 Subject: [PATCH 155/395] Run the three.js nodes fixture unsanitized in the sanitized lane The sanitized nodes build compiles one 1.14 GB LLVM module, and its clang compile peaks at about 16 GB, more than the CI runners have, so the fixture failed in every san shard that ran it. It now builds without the sanitizer there, as in the plain lane; every other three.js fixture keeps it. The fixture goes back under the sanitizer once sanitized dev builds compile large programs in shards. --- tests/harness/three.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/harness/three.test.ts b/tests/harness/three.test.ts index 27fb86d1f..967c8654a 100644 --- a/tests/harness/three.test.ts +++ b/tests/harness/three.test.ts @@ -72,7 +72,11 @@ test.for(cases)( const previousTarget = process.env["SCRIPTC_TARGET"]; const previousCc = process.env["SCRIPTC_CC"]; const wasm = target === "wasm32-wasi"; - const sanitize = !wasm && process.env["SCRIPTC_SAN"] === "1"; + // The sanitized nodes program is one 1.14 GB LLVM module, whose clang + // compile needs about 16 GB, more than the CI runners have. It runs + // unsanitized until sanitized dev builds compile large programs in + // shards; every other fixture keeps the sanitizer. + const sanitize = !wasm && fixture !== "nodes" && process.env["SCRIPTC_SAN"] === "1"; try { if (wasm) { process.env["SCRIPTC_TARGET"] = target; From 0eb7cc12e5e74822145c4a50430bf999c3b302a6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:51:37 -0500 Subject: [PATCH 156/395] Reuse symbol storage keys and checked-dynamic symbol boxes Every read or write of a symbol-keyed property on a checked-dynamic value formatted the symbol's address into a new string to find its storage key, and every conversion of a symbol to a checked value allocated a new box. Programs that key hot properties by symbols, such as Effect's type ids checked on each combinator, spent a large share of their time there. The storage key is a function of the address, so a 256-entry per-thread cache by address now returns the existing string, released at exit. A symbol also keeps a weak pointer to its live box: conversions share it, and the box clears the pointer when it is freed. --- .../corpus/symbol-property-key-reuse.mjs.json | 6 ++ packages/runtime/src/scr_json.c | 39 ++++++++++- packages/runtime/src/scr_symbol.c | 16 ++++- tests/corpus/symbol-property-key-reuse.mjs | 69 +++++++++++++++++++ .../symbol-property-key-reuse.mjs.json | 1 + 5 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-property-key-reuse.mjs.json create mode 100644 tests/corpus/symbol-property-key-reuse.mjs create mode 100644 tests/harness/corpus-costs/symbol-property-key-reuse.mjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-property-key-reuse.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-property-key-reuse.mjs.json new file mode 100644 index 000000000..e8fb5f353 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/symbol-property-key-reuse.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/symbol-property-key-reuse.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 9b7c13483..8da68a5a5 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6615,11 +6615,44 @@ ScrDyn *scr_dyn_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr /* Symbol descriptors use the ordinary descriptor implementation in an * inaccessible table. Keep a retained key list so identities cannot be - * recycled while properties exist, and reflection returns real symbols. */ + * recycled while properties exist, and reflection returns real symbols. + * + * The storage key is the symbol's address in text, which depends on the + * address alone, so a small cache by address answers repeated defines and + * lookups without formatting a new string each time. A recycled address + * names the same key text, so entries never go stale. */ +#define SCR_SYMBOL_KEY_CACHE 256 +static SCR_TL struct { + const void *symbol; + ScrStr *key; +} scr_symbol_key_cache[SCR_SYMBOL_KEY_CACHE]; +static SCR_TL bool scr_symbol_key_cache_registered; + +static void scr_symbol_key_cache_cleanup(void) { + for (size_t i = 0; i < SCR_SYMBOL_KEY_CACHE; i++) { + scr_str_release(scr_symbol_key_cache[i].key); + scr_symbol_key_cache[i].key = NULL; + scr_symbol_key_cache[i].symbol = NULL; + } + scr_symbol_key_cache_registered = false; +} + static ScrStr *scr_dyn_symbol_storage_key(const ScrDyn *key) { + const void *symbol = key->v.symbol.value; + size_t slot = ((uintptr_t)symbol >> 4) & (SCR_SYMBOL_KEY_CACHE - 1); + if (scr_symbol_key_cache[slot].key && scr_symbol_key_cache[slot].symbol == symbol) + return scr_str_retain(scr_symbol_key_cache[slot].key); char buffer[2 * sizeof(void *) + 4]; - int length = snprintf(buffer, sizeof buffer, "%p", (void *)key->v.symbol.value); - return scr_str_new(buffer, (size_t)length); + int length = snprintf(buffer, sizeof buffer, "%p", (void *)symbol); + ScrStr *name = scr_str_new(buffer, (size_t)length); + if (!scr_symbol_key_cache_registered) { + scr_symbol_key_cache_registered = true; + scr_atexit(scr_symbol_key_cache_cleanup); + } + scr_str_release(scr_symbol_key_cache[slot].key); + scr_symbol_key_cache[slot].symbol = symbol; + scr_symbol_key_cache[slot].key = scr_str_retain(name); + return name; } static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value) { diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 727e008b5..3169d6b13 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -22,6 +22,10 @@ struct ScrSym { ScrStr *desc; /* owned; NULL = no description (Symbol()) */ ScrStr *reg_key; /* owned; non-NULL exactly for Symbol.for symbols */ struct ScrSym *reg_next; /* the registry's intrusive chain */ + /* The symbol's checked-dynamic box while one is alive (not retained: the + * box retains the symbol and clears this when it is freed), shared by + * every conversion of the symbol to a checked value. */ + ScrDyn *box; }; ScrSym *scr_sym_retain(ScrSym *s) { @@ -50,6 +54,7 @@ ScrSym *scr_sym_new(ScrStr *desc) { s->desc = desc ? scr_str_retain(desc) : NULL; s->reg_key = NULL; s->reg_next = NULL; + s->box = NULL; return s; } @@ -142,8 +147,17 @@ ScrStr *scr_sym_desc(ScrSym *s) { return s->desc ? scr_str_retain(s->desc) : NUL ScrStr *scr_sym_key_for(ScrSym *s) { return s->reg_key ? scr_str_retain(s->reg_key) : NULL; } +/* The release of a symbol's shared box: the box is going away. */ +static void scr_sym_release_box(ScrSym *s) { + s->box = NULL; + scr_sym_release(s); +} + ScrDyn *scr_dyn_new_symbol(ScrSym *value) { - return scr_dyn_symbol_ref(scr_sym_retain(value), scr_sym_release, scr_sym_to_string, scr_sym_desc); + if (value->box) return scr_dyn_retain(value->box); + ScrDyn *box = scr_dyn_symbol_ref(scr_sym_retain(value), scr_sym_release_box, scr_sym_to_string, scr_sym_desc); + value->box = box; + return box; } ScrStr *scr_sym_to_string(ScrSym *s) { diff --git a/tests/corpus/symbol-property-key-reuse.mjs b/tests/corpus/symbol-property-key-reuse.mjs new file mode 100644 index 000000000..f4fc2a0c4 --- /dev/null +++ b/tests/corpus/symbol-property-key-reuse.mjs @@ -0,0 +1,69 @@ +// Symbol-keyed properties read and written many times: on plain objects, on +// prototypes and class instances, with Symbol.for symbols, and with symbols +// created in a loop (each a distinct key, even after earlier ones are +// unreachable), plus the same symbol converted to a checked value repeatedly. +const tag = Symbol("tag"); +const shared = Symbol.for("shared"); +const proto = { [tag]: "from proto" }; +const child = Object.create(proto); +child[shared] = 0; +for (let i = 0; i < 1000; i++) child[shared] += i; +console.log(child[tag], child[shared], Object.getOwnPropertySymbols(child).length); + +const count = Symbol("count"); +class Counter { + constructor() { + this[count] = 0; + } + bump() { + this[count]++; + return this; + } + get count() { + return this[count]; + } +} +const c = new Counter(); +for (let i = 0; i < 500; i++) c.bump(); +console.log(c.count, c[count], count.toString()); + +const keys = []; +const holder = {}; +for (let i = 0; i < 300; i++) { + const key = Symbol(`k${i}`); + holder[key] = i; + if (i % 100 === 0) keys.push(key); +} +let sum = 0; +for (const key of Object.getOwnPropertySymbols(holder)) sum += holder[key]; +console.log(sum, keys.map((k) => holder[k]).join(), keys[1].description); + +// Symbols that become unreachable between rounds: a new symbol never reads +// a property stored under an earlier one. +const scratch = {}; +let stale = 0; +for (let round = 0; round < 200; round++) { + const key = Symbol("round"); + if (scratch[key] !== undefined) stale++; + scratch[key] = round; +} +console.log(stale, Object.getOwnPropertySymbols(scratch).length); + +// A symbol stored, deleted and dropped each round, so its storage can be +// reused by the next round's symbol: the new symbol still reads nothing. +const persist = {}; +let reused = 0; +for (let round = 0; round < 200; round++) { + const key = Symbol("dropped"); + if (key in persist) reused++; + persist[key] = round; + delete persist[key]; +} +console.log(reused, Object.getOwnPropertySymbols(persist).length); + +const viaFor = {}; +for (let i = 0; i < 10; i++) viaFor[Symbol.for("same")] = i; +console.log(viaFor[Symbol.for("same")], Object.getOwnPropertySymbols(viaFor).length); +console.log(Symbol.for("same") === Symbol.for("same"), Symbol("x") === Symbol("x"), tag === tag); +const boxes = [tag, tag, shared]; +console.log(boxes[0] === boxes[1], boxes.indexOf(shared), new Set(boxes).size); diff --git a/tests/harness/corpus-costs/symbol-property-key-reuse.mjs.json b/tests/harness/corpus-costs/symbol-property-key-reuse.mjs.json new file mode 100644 index 000000000..ddd7f7805 --- /dev/null +++ b/tests/harness/corpus-costs/symbol-property-key-reuse.mjs.json @@ -0,0 +1 @@ +{ "plain": 5174, "san": 9089 } From ab93f728edbeea66d4c2777515603e70d36e4786 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:53:18 -0500 Subject: [PATCH 157/395] Initialize a deferred module binding that a reached body assigns A module binding whose initializer only allocates data (a function, an arrow function, a literal or a registry object) is initialized only when a reached body uses the binding. Only reads and updates counted as uses, so a binding that reached code only assigned was never initialized, and the assignment, which checks that the binding is initialized and releases its value, threw "Cannot access '' before initialization" where Node runs. Assignments now count as uses. Corpus 8320 assigns two such bindings, one through an assignment expression, without reading them. --- .../deferred-module-initializers.test.ts | 30 +++++++++++++++---- .../lowering/deferred-module-initializers.ts | 24 ++++++++++----- .../main.ts.json | 7 +++++ .../8320-assign-only-deferred-binding/main.ts | 8 +++++ .../scanner.ts | 21 +++++++++++++ .../main.ts.json | 1 + 6 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8320-assign-only-deferred-binding/main.ts.json create mode 100644 tests/corpus/8320-assign-only-deferred-binding/main.ts create mode 100644 tests/corpus/8320-assign-only-deferred-binding/scanner.ts create mode 100644 tests/harness/corpus-costs/8320-assign-only-deferred-binding/main.ts.json diff --git a/packages/compiler/src/frontend/lowering/deferred-module-initializers.test.ts b/packages/compiler/src/frontend/lowering/deferred-module-initializers.test.ts index 4696659bc..4786585d9 100644 --- a/packages/compiler/src/frontend/lowering/deferred-module-initializers.test.ts +++ b/packages/compiler/src/frontend/lowering/deferred-module-initializers.test.ts @@ -168,17 +168,19 @@ test("reports the functions whose placeholders it fills", () => { expect(filled).toEqual([functions[0]]); }); -test("an update of the binding demands its initializer", () => { +test("an update or an assignment of the binding demands its initializer", () => { const { initializers, pending, lowerer } = fixture(); const updated = block(loc), - written = block({ ...loc, start: 2, end: 3 }); + written = block({ ...loc, start: 2, end: 3 }), + assigned = block({ ...loc, start: 4, end: 5 }), + unused = block({ ...loc, start: 6, end: 7 }); const update = (id: string, prefix: boolean): IrStmt => ({ kind: "exprStmt", expr: { kind: "incDec", op: "+", prefix, localId: id, type: F64, loc }, loc, }); const functions = [ - fn("init", [updated, written]), + fn("init", [updated, written, assigned, unused]), fn("use", [ update("updated", false), { @@ -187,13 +189,29 @@ test("an update of the binding demands its initializer", () => { value: { kind: "numLit", value: 1, type: F64, loc }, loc, }, + { + kind: "exprStmt", + expr: { + kind: "assignExpr", + localId: "assigned", + value: { kind: "numLit", value: 2, type: F64, loc }, + type: F64, + loc, + }, + loc, + }, ]), ]; pending.set("updated", [{ loc, lower: () => [value(2)] }]); pending.set("written", [{ loc: written.loc, lower: () => [value(3)] }]); + pending.set("assigned", [{ loc: assigned.loc, lower: () => [value(4)] }]); + pending.set("unused", [{ loc: unused.loc, lower: () => [value(5)] }]); expect(initializers.process(lowerer, functions)).toBe(true); expect(updated.body).toEqual([value(2)]); - // A plain write never observes the initial value. - expect(written.body).toEqual([]); - expect(pending.has("written")).toBe(true); + // An assignment checks that the binding is initialized and releases its + // value, so the declaration must have run. + expect(written.body).toEqual([value(3)]); + expect(assigned.body).toEqual([value(4)]); + expect(unused.body).toEqual([]); + expect(pending.has("unused")).toBe(true); }); diff --git a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts index a44df8f75..3af054dd6 100644 --- a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts +++ b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts @@ -387,18 +387,22 @@ function scanStatements( const placeholders: PlaceholderBlock[] = []; const typeEvents = lowerer ? typeScanner(lowerer, body) : null; const reads = new Set(); + const observe = (id: string): void => { + if (deferred!.has(id) && !reads.has(id)) { + reads.add(id); + body.push({ kind: "read", id }); + } + }; everyStmtList(statements, { stmt: (statement) => { if (statement.kind === "block" && statement.body.length === 0) placeholders.push(statement); + if (typeEvents !== null && statement.kind === "assign") observe(statement.localId); return true; }, expr: (expr) => { if (typeEvents === null) return true; typeEvents(expr.type); - if (readsBinding(expr) && deferred!.has(expr.localId) && !reads.has(expr.localId)) { - reads.add(expr.localId); - body.push({ kind: "read", id: expr.localId }); - } + if (observesBinding(expr)) observe(expr.localId); // Every call, as the helpers are visited from a stack in call order. if ( expr.kind === "call" && @@ -411,10 +415,14 @@ function scanStatements( return { body, placeholders }; } -/** A read of a binding's current value: a plain read, or an update - * (`n++`, `--n`) that reads before it writes. */ -function readsBinding(expr: IrExpr): expr is IrExpr & { kind: "varRef" | "incDec" } { - return expr.kind === "varRef" || expr.kind === "incDec"; +/** A use that needs the binding initialized: a plain read, an update + * (`n++`, `--n`) that reads before it writes, or an assignment, which + * checks that the binding is initialized and releases its current value. + * Assignment statements count the same way (scanStatements). */ +function observesBinding( + expr: IrExpr, +): expr is IrExpr & { kind: "varRef" | "incDec" | "assignExpr" } { + return expr.kind === "varRef" || expr.kind === "incDec" || expr.kind === "assignExpr"; } function registryInitializer( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8320-assign-only-deferred-binding/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8320-assign-only-deferred-binding/main.ts.json new file mode 100644 index 000000000..399baa239 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8320-assign-only-deferred-binding/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/8320-assign-only-deferred-binding/scanner.ts", + "/tests/corpus/8320-assign-only-deferred-binding/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8320-assign-only-deferred-binding/main.ts b/tests/corpus/8320-assign-only-deferred-binding/main.ts new file mode 100644 index 000000000..d35a863ae --- /dev/null +++ b/tests/corpus/8320-assign-only-deferred-binding/main.ts @@ -0,0 +1,8 @@ +import { installCount, useFormat, useScanner } from "./scanner.ts"; + +useScanner((bytes, start) => start + bytes.length * 2); +console.log("scanner installed"); +const formatter = useFormat((value) => `[${value}]`); +console.log(formatter(7)); +useScanner((bytes, start) => start); +console.log("installs", installCount()); diff --git a/tests/corpus/8320-assign-only-deferred-binding/scanner.ts b/tests/corpus/8320-assign-only-deferred-binding/scanner.ts new file mode 100644 index 000000000..601df60ba --- /dev/null +++ b/tests/corpus/8320-assign-only-deferred-binding/scanner.ts @@ -0,0 +1,21 @@ +// Module bindings that only assignments use after their declarations. +export function defaultEnd(bytes: Uint8Array, start: number): number { + return start + bytes.length; +} + +let valueEnd: (bytes: Uint8Array, start: number) => number = defaultEnd; +let format: (value: number) => string = (value) => `#${value}`; +let installs = 0; + +export function useScanner(scan: (bytes: Uint8Array, start: number) => number): void { + valueEnd = (bytes, start) => scan(bytes, start) + 1; + installs++; +} + +export function useFormat(next: (value: number) => string): (value: number) => string { + return (format = next); +} + +export function installCount(): number { + return installs; +} diff --git a/tests/harness/corpus-costs/8320-assign-only-deferred-binding/main.ts.json b/tests/harness/corpus-costs/8320-assign-only-deferred-binding/main.ts.json new file mode 100644 index 000000000..0606e0393 --- /dev/null +++ b/tests/harness/corpus-costs/8320-assign-only-deferred-binding/main.ts.json @@ -0,0 +1 @@ +{ "plain": 4500, "san": 48000 } From 4e9a4311b00c2c2983ae1a7c140663f0e64d492e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:15:01 -0500 Subject: [PATCH 158/395] Prove a typed-array loop's views disjoint and give each its own alias scope A loop that reads one typed array and writes another kept every element in memory: separate views can share a buffer, so LLVM had to assume a store to one could change the other's elements. A loop of eight lanes held in Float64Arrays (zr[k], zi[k], tr[k], ti[k]) ran as scalar loads and stores of each lane on every step. When a typed-array loop version covers at least two views and writes at least one of them, its guard now also requires every pair of the views' byte ranges to be disjoint: one ends at or before the other starts. In the fast copy, each view's element reads and writes carry an alias scope of their own and no-alias with the others' scopes, so LLVM keeps the elements in registers and vectorizes across lanes. Views that share bytes, or one array passed twice, take the checked copy as before. The scopes are string-named, written inline, and named after the function and the version, so every copy of a loop has its own. Worker programs scope only views the guard proves thread-owned. Corpus 8182 runs such loops over disjoint, adjacent, overlapping and identical views, views of different element types over one buffer, and empty views. --- .../compiler/src/backend/llvm/bytes-alias.ts | 4 +- .../src/backend/llvm/bytes-loop-versions.ts | 81 ++++++++++++++++- .../src/backend/llvm/emit-function.ts | 1 + .../src/backend/llvm/emit-statements.ts | 38 +++++++- packages/compiler/src/backend/llvm/emitter.ts | 25 +++++- .../compiler/src/backend/llvm/expr-bytes.ts | 42 +++++++++ .../8182-typed-array-alias-scopes.ts.json | 6 ++ .../typed-array-alias-scope-emission.test.ts | 57 ++++++++++++ tests/corpus/8182-typed-array-alias-scopes.ts | 89 +++++++++++++++++++ .../8182-typed-array-alias-scopes.ts.json | 1 + 10 files changed, 336 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8182-typed-array-alias-scopes.ts.json create mode 100644 packages/compiler/test/typed-array-alias-scope-emission.test.ts create mode 100644 tests/corpus/8182-typed-array-alias-scopes.ts create mode 100644 tests/harness/corpus-costs/8182-typed-array-alias-scopes.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-alias.ts b/packages/compiler/src/backend/llvm/bytes-alias.ts index d43620fd9..b7a672754 100644 --- a/packages/compiler/src/backend/llvm/bytes-alias.ts +++ b/packages/compiler/src/backend/llvm/bytes-alias.ts @@ -18,8 +18,10 @@ export function bytesHeaderAlias(host: LlvmEmitterContext): string { return tag === "" ? "" : `${tag}, !invariant.load !{}`; } +/** Element storage's tag, and the alias scope of the access being emitted + * in a loop version's fast copy (bytesAccessScope). */ export function bytesElementAlias(host: LlvmEmitterContext): string { - return host.memoryAliasAttachment("bytes:elements"); + return host.memoryAliasAttachment("bytes:elements") + host.bytesAccessScope; } /** A module global's slot is its own category for whole-slot loads of a diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index 301f65de2..ac280df00 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -1,4 +1,11 @@ -import { F64, type IrExpr, type IrLocal, type IrStmt } from "../../ir/ir.js"; +import { + BYTES_ELEMENT_SIZE, + F64, + type IrExpr, + type IrLocal, + type IrStmt, + type IrType, +} from "../../ir/ir.js"; import type { IntegerRange, IntegerRanges } from "../../ir/integer-ranges.js"; import type { IntegerCountedForLoop, IntegerPairedForLoop } from "../../ir/integer-loops.js"; import type { LlvmEmitterContext } from "./expr-context.js"; @@ -10,7 +17,7 @@ import { emitCountedLoopSafety, emitExactIntegerTest, } from "./counted-loops.js"; -import { emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; +import { emitBytesData, emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; import { f64Lit } from "./common.js"; import { emitStringAsciiFlag, @@ -106,6 +113,12 @@ export interface BytesLoopVersion { /** Integer ranges of affine index operands that hold in the unchecked * loop (where the guard bounds the counter). */ ranges: ReadonlyMap; + /** Typed-array receivers whose element ranges the guard proves disjoint, + * and the receiver of each of their element reads and writes. The fast + * copy gives each receiver its own alias scope, so a store to one view + * leaves the elements of the others in registers. Null when the loop + * writes no element, or reads and writes a single view. */ + scopes: { receivers: number[]; nodes: ReadonlyMap } | null; } export interface BytesLoopFacts { @@ -136,6 +149,8 @@ export interface BytesLoopFacts { const MAX_COST = 400; const MAX_FIXED_END = 2 ** 31; const MAX_CHECKS = 8; +/** Alias-scoped views per loop: the guard tests every pair's ranges. */ +const MAX_SCOPED_RECEIVERS = 6; const MAX_OFFSET = 2 ** 31; const SAFE = Number.MAX_SAFE_INTEGER; /** The range the guard requires of an invariant index term without a @@ -547,11 +562,24 @@ export function matchBytesLoopVersion( group.nodes.push(node); } else fixed.set(receiver, { check: { receiver, end }, nodes: [node] }); }; + // Element reads and writes of each typed-array receiver, for alias scopes. + const scoped = new Map(); + const writtenViews = new Set(); + const scope = (node: Node, array: IrExpr, write: boolean): void => { + const ref = root(array); + if (!ref || ref.type.kind !== "bytes") return; + const receiver = receiverOf(ref); + scoped.set(node, receiver); + if (write) writtenViews.add(receiver); + }; const visitExpr = (e: IrExpr): boolean => { if (e.kind === "bytesIntrinsic") { const numeric = byteNumberAccess(e); if (numeric) access(e, e.receiver, e.args[numeric.offsetArg]!, numeric.width); - else if (e.method === "get" && e.args[0]) access(e, e.receiver, e.args[0], 1); + else if (e.method === "get" && e.args[0]) { + access(e, e.receiver, e.args[0], 1); + scope(e, e.receiver, false); + } } else if ( e.kind === "strIntrinsic" && (e.method === "charCodeAt" || e.method === "charAt") && @@ -561,7 +589,10 @@ export function matchBytesLoopVersion( return everyExprChild(e, visitExpr, visitStmt); }; const visitStmt = (s: IrStmt): boolean => { - if (s.kind === "bytesSet") access(s, s.arr, s.index, 1); + if (s.kind === "bytesSet") { + access(s, s.arr, s.index, 1); + scope(s, s.arr, true); + } return everyStmtChild(s, visitExpr, visitStmt); }; loop.body.every(visitStmt); @@ -684,6 +715,17 @@ export function matchBytesLoopVersion( : [], }); } + // Alias scopes: at least two views, one of them written, each read only + // through plain loads (in a worker program, a view the guard proves + // thread-owned). + const scopedReceivers = [...new Set(scoped.values())].sort((a, b) => a - b); + const ownedSet = new Set(ownedKept.map(([receiver]) => receiver)); + const scoping = + scopedReceivers.length >= 2 && + scopedReceivers.length <= MAX_SCOPED_RECEIVERS && + scopedReceivers.some((receiver) => writtenViews.has(receiver)) && + (!facts.workers || scopedReceivers.every((receiver) => ownedSet.has(receiver))); + if (scoping) for (const receiver of scopedReceivers) used.add(receiver); // Renumber the receivers the kept checks use. const order = [...used].sort((a, b) => a - b); const renumber = new Map(order.map((old, index) => [old, index])); @@ -737,6 +779,12 @@ export function matchBytesLoopVersion( : null, counterBound: needsBound ? bound : null, ranges, + scopes: scoping + ? { + receivers: scopedReceivers.map((receiver) => renumber.get(receiver)!), + nodes: new Map([...scoped].map(([node, receiver]) => [node, renumber.get(receiver)!])), + } + : null, }; } @@ -1126,6 +1174,31 @@ export function emitBytesLoopVersionGuard( B.line(`${ok} = icmp ugt i64 ${lengths[check.receiver]}, ${check.end}`); conditions.push(ok); } + if (version.scopes) { + // Every pair of scoped views covers disjoint bytes: one ends at or + // before the other starts. An empty view covers none. + const spans = version.scopes.receivers.map((index) => { + const type = version.receivers[index]!.ref.type as IrType & { kind: "bytes" }; + const start = B.tmp(), + size = B.tmp(), + end = B.tmp(); + B.line(`${start} = ptrtoint ptr ${emitBytesData(host, receivers[index]!)} to i64`); + B.line(`${size} = mul nuw i64 ${lengths[index]}, ${BYTES_ELEMENT_SIZE[type.elem]}`); + B.line(`${end} = add nuw i64 ${start}, ${size}`); + return { start, end }; + }); + spans.forEach((a, i) => { + for (const b of spans.slice(i + 1)) { + const before = B.tmp(), + after = B.tmp(), + apart = B.tmp(); + B.line(`${before} = icmp ule i64 ${a.end}, ${b.start}`); + B.line(`${after} = icmp ule i64 ${b.end}, ${a.start}`); + B.line(`${apart} = or i1 ${before}, ${after}`); + conditions.push(apart); + } + }); + } for (const counter of version.counters) { const value = host.emitExpr(counter.ref); // A counter held as an integer (an int32 slot) is exact already and diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index ca45a6624..b79e2406d 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -64,6 +64,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { emitter.currentFunctionName = fn.name; emitter.currentUnwindAbi = emitter.unwindAbi.has(fn.name); emitter.usesLandingPads = false; + emitter.aliasScopedVersions = 0; emitter.findInt32Allocations(fn); emitter.currentTier = fn.tier; // Fiber switches restore the active exception cell before control diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 3d3aa6781..36c28bf97 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -570,6 +570,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { s.value, emitter.bytesBounds.has(s), emitter.bytesOwned.has(s), + emitter.bytesAliasScopes.get(s), ); break; } @@ -2080,6 +2081,32 @@ function emitSlowCopy(emitter: LlEmitter, s: IrStmt): void { * stops trying it. */ const SPECULATION_BAILS = 8; +/** The attachment of each scoped access in a version's fast copy: the + * scope of its own view, and no alias with the scopes of the other views, + * whose bytes the guard proved disjoint. The scopes are named after the + * function and the version, so every copy of a loop has its own, and + * written inline (string-named scopes need no numbered metadata). */ +function bytesAliasScopes( + emitter: LlEmitter, + scopes: NonNullable, +): Map { + const version = emitter.aliasScopedVersions++; + const name = emitter.currentFunctionName.replace(/[^A-Za-z0-9_.$%]/g, "_"); + const domain = '!{!"scriptc typed-array views"}'; + const scope = (receiver: number): string => `!{!"${name}.${version}.${receiver}", ${domain}}`; + const attachments = new Map(); + for (const receiver of scopes.receivers) { + const others = scopes.receivers.filter((other) => other !== receiver).map(scope); + attachments.set( + receiver, + `, !alias.scope !{${scope(receiver)}}, !noalias !{${others.join(", ")}}`, + ); + } + const result = new Map(); + for (const [node, receiver] of scopes.nodes) result.set(node, attachments.get(receiver)!); + return result; +} + /** Emit a loop's condition, body and update through `emitCopy`, twice * when speculative-integers.ts plans a fast integer copy. Both copies * share the loop's setup and exit; the ordinary copy comes first so the @@ -2104,7 +2131,8 @@ function emitVersionedLoop( originalOwned = emitter.bytesOwned, originalRanges = emitter.integerRanges, originalUnitReads = emitter.stringUnitReads, - originalLengthReads = emitter.stringLengthReads; + originalLengthReads = emitter.stringLengthReads, + originalScopes = emitter.bytesAliasScopes; LOOPS.of(emitter).bytesLoopVersioning.add(s); try { B.startBlock(slow); @@ -2113,6 +2141,13 @@ function emitVersionedLoop( if (version.owned.size > 0) emitter.bytesOwned = new Set([...originalOwned, ...version.owned]); if (version.ranges.size > 0) emitter.integerRanges = new Map([...originalRanges, ...version.ranges]); + // Both fast copies run under the guard's span test, so they share the + // version's scopes. + if (version.scopes && emitter.debug === null) + emitter.bytesAliasScopes = new Map([ + ...originalScopes, + ...bytesAliasScopes(emitter, version.scopes), + ]); if (version.counted?.guarded || version.paired?.loop.guarded) LOOPS.of(emitter).provenCountedGuards.add(s); B.startBlock(fast); @@ -2146,6 +2181,7 @@ function emitVersionedLoop( emitter.integerRanges = originalRanges; emitter.stringUnitReads = originalUnitReads; emitter.stringLengthReads = originalLengthReads; + emitter.bytesAliasScopes = originalScopes; LOOPS.of(emitter).provenCountedGuards.delete(s); LOOPS.of(emitter).bytesLoopVersioning.delete(s); } diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index bcb8a5ba2..d5964f1ba 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1624,6 +1624,14 @@ export class LlEmitter { /** Worker programs: typed-array accesses whose views an enclosing version * guard proved thread-owned. */ bytesOwned: ReadonlySet = new Set(); + /** 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). */ + bytesAliasScopes: ReadonlyMap = new Map(); + bytesAccessScope = ""; + /** Loop versions with alias scopes in the current function; it names + * their scopes. */ + aliasScopedVersions = 0; integerViews = new Map(); /** Module globals the current function owns outright (initializer * bindings): their function-local slot, which stands in for the global. */ @@ -7275,8 +7283,9 @@ export class LlEmitter { inBounds = false, invalidNaN = false, owned = false, + scope = "", ): LlValue { - return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN, owned); + return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN, owned, scope); } emitToUint32(value: string, expr?: IrExpr, uint32?: string): string { @@ -7340,8 +7349,20 @@ export class LlEmitter { expr?: IrExpr, inBounds = false, owned = false, + scope = "", ): void { - return emitBytesSet(this, elem, receiver, index, value, indexExpr, expr, inBounds, owned); + return emitBytesSet( + this, + elem, + receiver, + index, + value, + indexExpr, + expr, + inBounds, + owned, + scope, + ); } emitBytesIntrinsic(e: IrExpr & { kind: "bytesIntrinsic" }): LlValue { diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index 23025d00c..a50464cd6 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -235,6 +235,26 @@ export function emitBytesGet( inBounds = false, invalidNaN = false, owned = false, + scope = "", +): LlValue { + const outer = host.bytesAccessScope; + host.bytesAccessScope = scope; + try { + return emitScopedBytesGet(host, elem, receiver, index, expr, inBounds, invalidNaN, owned); + } finally { + host.bytesAccessScope = outer; + } +} + +function emitScopedBytesGet( + host: LlvmEmitterContext, + elem: IrBytesElem, + receiver: string, + index: LlValue, + expr: IrExpr | undefined, + inBounds: boolean, + invalidNaN: boolean, + owned: boolean, ): LlValue { const B = host.B; // Any invalid index traps (the array runtime's discipline). @@ -547,6 +567,27 @@ export function emitBytesSet( expr?: IrExpr, inBounds = false, owned = false, + scope = "", +): void { + const outer = host.bytesAccessScope; + host.bytesAccessScope = scope; + try { + emitScopedBytesSet(host, elem, receiver, index, value, indexExpr, expr, inBounds, owned); + } finally { + host.bytesAccessScope = outer; + } +} + +function emitScopedBytesSet( + host: LlvmEmitterContext, + elem: IrBytesElem, + receiver: string, + index: LlValue, + value: LlValue, + indexExpr: IrExpr | undefined, + expr: IrExpr | undefined, + inBounds: boolean, + owned: boolean, ): void { const B = host.B; // Invalid integer-indexed writes are ignored after evaluating the RHS. @@ -768,6 +809,7 @@ export function emitBytesIntrinsic( host.bytesBounds.has(e), e.invalidNaN === true, host.bytesOwned.has(e), + host.bytesAliasScopes.get(e), ); case "slice": return call( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8182-typed-array-alias-scopes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8182-typed-array-alias-scopes.ts.json new file mode 100644 index 000000000..c941e8b5d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8182-typed-array-alias-scopes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8182-typed-array-alias-scopes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/typed-array-alias-scope-emission.test.ts b/packages/compiler/test/typed-array-alias-scope-emission.test.ts new file mode 100644 index 000000000..a32df03e7 --- /dev/null +++ b/packages/compiler/test/typed-array-alias-scope-emission.test.ts @@ -0,0 +1,57 @@ +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 typed-array loop version proves its views disjoint and scopes their accesses", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-alias-scopes-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile( + entry, + ` +function copy(dst: Float64Array, src: Float64Array, n: number): void { + for (let i = 0; i < n; i++) dst[i] = src[i]! + 1; +} +function scale(xs: Float64Array, n: number): void { + for (let i = 0; i < n; i++) xs[i] = xs[i]! * 2; +} +function dot(a: Float64Array, b: Float64Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) sum += a[i]! * b[i]!; + return sum; +} +const a = new Float64Array(8), b = new Float64Array(8); +copy(a, b, 8); +scale(a, 8); +console.log(dot(a, b, 8)); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const mod = deserializeModule(await readFile(outPath, "utf8")); + const ll = emitLlvmModule(mod); + const fn = (name: string): string => + new RegExp(`^define internal [^\\n]*@sc_bf_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec(ll)![0]; + // Two views, one written: the guard compares their byte ranges, and + // each view's accesses in the fast copy carry its own scope. + const copy = fn("copy"); + expect(copy).toMatch(/ptrtoint ptr %\w+ to i64/); + expect(copy).toMatch(/icmp ule i64 %\w+, %\w+/); + expect(copy).toMatch( + /load double, ptr %\w+, align 1[^\n]*!alias\.scope !\{!\{!"copy\.0\.\d", !\{!"scriptc typed-array views"\}\}\}, !noalias/, + ); + expect(copy).toMatch(/store double %\w+, ptr %\w+, align 1[^\n]*!alias\.scope/); + // One view, or views that are only read, need no scopes. + for (const name of ["scale", "dot"]) { + expect(fn(name)).not.toContain("!alias.scope"); + expect(fn(name)).not.toContain("ptrtoint"); + } + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/tests/corpus/8182-typed-array-alias-scopes.ts b/tests/corpus/8182-typed-array-alias-scopes.ts new file mode 100644 index 000000000..4dbf52e47 --- /dev/null +++ b/tests/corpus/8182-typed-array-alias-scopes.ts @@ -0,0 +1,89 @@ +// Loops that read one typed array and write another, where the two views +// may share bytes: overlapping and disjoint views of one buffer, the same +// array passed twice, views of different element types over one buffer, +// and empty views. A loop over views that overlap must see its own writes. +function shiftAdd(dst: Float64Array, src: Float64Array, n: number): void { + for (let i = 0; i < n; i++) dst[i] = src[i]! + 1; +} + +function mixed(bytes: Uint8Array, words: Float64Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + words[i] = i * 0.5 + 1; + sum += bytes[i]!; + } + return sum; +} + +function lanes(zr: Float64Array, zi: Float64Array, tr: Float64Array, ti: Float64Array): number { + for (let step = 0; step < 6; step++) + for (let k = 0; k < 4; k++) { + const r = zr[k]!; + const i = zi[k]!; + zi[k] = 2 * r * i + 0.25; + zr[k] = tr[k]! - ti[k]! - 0.5; + tr[k] = zr[k]! * zr[k]!; + ti[k] = zi[k]! * zi[k]!; + } + let sum = 0; + for (let k = 0; k < 4; k++) sum += zr[k]! + zi[k]! + tr[k]! + ti[k]!; + return sum; +} + +function show(a: Float64Array): string { + const parts: string[] = []; + for (let i = 0; i < a.length; i++) parts.push(a[i]!.toFixed(3)); + return parts.join(","); +} + +// Disjoint arrays. +const a = new Float64Array([1, 2, 3, 4]); +const b = new Float64Array(4); +shiftAdd(b, a, 4); +console.log("disjoint", show(b)); + +// One array as both views: each element reads its own old value. +const same = new Float64Array([1, 2, 3, 4]); +shiftAdd(same, same, 4); +console.log("same", show(same)); + +// Overlapping views of one buffer: the destination starts one element +// after the source, so every read sees the previous iteration's write. +const buffer = new ArrayBuffer(8 * 8); +const whole = new Float64Array(buffer); +whole.fill(1); +shiftAdd(new Float64Array(buffer, 8, 6), new Float64Array(buffer, 0, 6), 6); +console.log("overlap forward", show(whole)); +whole.fill(1); +shiftAdd(new Float64Array(buffer, 0, 6), new Float64Array(buffer, 8, 6), 6); +console.log("overlap backward", show(whole)); + +// Adjacent views of one buffer share no bytes. +whole.set([1, 2, 3, 4, 5, 6, 7, 8]); +shiftAdd(new Float64Array(buffer, 32, 4), new Float64Array(buffer, 0, 4), 4); +console.log("adjacent", show(whole)); + +// A byte view over the doubles it writes. +const shared = new ArrayBuffer(64); +console.log("mixed", mixed(new Uint8Array(shared), new Float64Array(shared), 8), show(new Float64Array(shared))); +console.log("mixed disjoint", mixed(new Uint8Array(new ArrayBuffer(64)), new Float64Array(8), 8)); + +// Empty views. +shiftAdd(new Float64Array(0), new Float64Array(0), 0); +console.log("empty", new Float64Array(0).length); + +// Four lanes in separate arrays, and the same arrays aliased. +const lane = (): Float64Array => new Float64Array([0.1, -0.2, 0.3, -0.4]); +console.log("lanes", lanes(lane(), lane(), lane(), lane()).toFixed(6)); +const one = lane(); +console.log("lanes aliased", lanes(one, one, lane(), lane()).toFixed(6)); +const pool = new Float64Array(16); +pool.set([0.1, -0.2, 0.3, -0.4, 0.1, -0.2, 0.3, -0.4, 0.1, -0.2, 0.3, -0.4, 0.1, -0.2, 0.3, -0.4]); +console.log( + "lanes subarrays", + lanes(pool.subarray(0, 4), pool.subarray(4, 8), pool.subarray(8, 12), pool.subarray(12, 16)).toFixed(6), +); +console.log( + "lanes overlapping", + lanes(pool.subarray(0, 4), pool.subarray(2, 6), pool.subarray(8, 12), pool.subarray(9, 13)).toFixed(6), +); diff --git a/tests/harness/corpus-costs/8182-typed-array-alias-scopes.ts.json b/tests/harness/corpus-costs/8182-typed-array-alias-scopes.ts.json new file mode 100644 index 000000000..974b44e1e --- /dev/null +++ b/tests/harness/corpus-costs/8182-typed-array-alias-scopes.ts.json @@ -0,0 +1 @@ +{ "plain": 2600, "san": 3800 } From 5f602ff3b85989be80e44c6f82d01bbce84c9e61 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:16:59 -0500 Subject: [PATCH 159/395] Decode asserted optional typed arrays other than Uint8Array `decoder.decode(view!)` on a `Uint16Array | undefined` (or another typed array that may be undefined) was refused with SC2020: the assertion keeps the undefined arm for decode, and the decode input only read the undefined arm of `Uint8Array | undefined`. The undefined arm of any typed array that may be undefined now reads as an empty array of the same type, so a defined input decodes its bytes and an undefined one decodes no bytes, as in Node. --- .../frontend/lowering/builtins/text-codecs.ts | 30 ++++++++++++------- tests/corpus/text-decoder-undefined-input.ts | 7 +++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts index 34cc7aa14..39156940e 100644 --- a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts +++ b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts @@ -16,7 +16,6 @@ import { type IrType, STRING, type SrcLoc, - typeEquals, } from "../../../ir/ir.js"; import { lowerBuiltinValuePreservingUndefined, @@ -27,21 +26,30 @@ import { type BuiltinModuleFn } from "../surfaces.js"; /** The input of `decoder.decode(input)`. An undefined input decodes no bytes, * as `decode()` does in Node: a statically undefined argument (after its - * effects) and the undefined arm of `Uint8Array | undefined` read as an - * empty Uint8Array. */ + * effects) reads as an empty Uint8Array, and the undefined arm of a typed + * array or view that may be undefined (`Uint8Array | undefined`, + * `Uint16Array | undefined`) as an empty view of the same type. */ function lowerDecodeInput(lowerer: Lowerer, node: ts.Expression, loc: SrcLoc): IrExpr { - const empty: IrExpr = { kind: "bytesNew", source: null, type: BYTES_U8, loc }; const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node); - if (undefinedArg) return defaultAfterUndefined(undefinedArg, empty); + if (undefinedArg) + return defaultAfterUndefined(undefinedArg, { + kind: "bytesNew", + source: null, + type: BYTES_U8, + loc, + }); const value = lowerer.lowerExpr(node); if (value.type.kind === "union") { const def = lowerer.unions.get(value.type.unionId); - if ( - def?.arms.length === 2 && - def.arms.some((arm) => arm.kind === "undefinedT") && - def.arms.some((arm) => typeEquals(arm, BYTES_U8)) - ) - return { kind: "nullish", left: value, right: empty, type: BYTES_U8, loc }; + const view = def?.arms.find((arm) => arm.kind === "bytes"); + if (def?.arms.length === 2 && view && def.arms.some((arm) => arm.kind === "undefinedT")) + return { + kind: "nullish", + left: value, + right: { kind: "bytesNew", source: null, type: view, loc }, + type: view, + loc, + }; } return value; } diff --git a/tests/corpus/text-decoder-undefined-input.ts b/tests/corpus/text-decoder-undefined-input.ts index 6a3be354c..32bca7f3a 100644 --- a/tests/corpus/text-decoder-undefined-input.ts +++ b/tests/corpus/text-decoder-undefined-input.ts @@ -29,3 +29,10 @@ const streaming = new TextDecoder(); let text = ""; for (const chunk of chunks) text += streaming.decode(chunk.value, { stream: !chunk.done }); console.log(JSON.stringify(text), text.length); + +// Other typed arrays decode their bytes, with or without the assertion. +const wide = new Map([["present", new Uint16Array([0x6968])]]); +const asserted = wide.get("present")!; +console.log(JSON.stringify(decoder.decode(asserted)), JSON.stringify(decoder.decode(wide.get("present")!))); +console.log(JSON.stringify(decoder.decode(wide.get("present"))), JSON.stringify(decoder.decode(wide.get("absent")))); +console.log(JSON.stringify(new TextDecoder("utf-8").decode(wide.get("absent")!))); From 031c6172f905f45e9838961d350f88deaf73ea62 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:20:10 -0500 Subject: [PATCH 160/395] Emit serialized IR with the unwinding ABI in the serialized IR emission test compile() emits textual LLVM on Linux with the unwinding ABI since #2163, so the test's emission from serialized IR passes the same option. --- .../compiler/src/backend/llvm/serialized-ir-emission.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts index cdbba146c..6d2457c57 100644 --- a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts +++ b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts @@ -41,6 +41,8 @@ test("sort-heavy programs emit equal LLVM from memory and from serialized IR", a pointerBits: 64, wasi: false, runtimeAbiMarker: false, + // compile() emits Linux textual LLVM with the unwinding ABI. + unwindAbi: process.platform === "linux", objectAudit: sanitize, ...inlineRcOption(options.optimization, sanitize), }); From 492d1558be1a4884dd3c22c4b796361614791b91 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:22:54 -0500 Subject: [PATCH 161/395] Complete consecutive synchronous writes in one afterWrite step Node's onwrite queues one afterWrite nextTick for a synchronous write completion and lets later synchronous completions with the same callback join it (afterWriteTickInfo.count); writes without a callback share one. The step then checks 'drain' once, calls the callback once per write and runs finishMaybe once. The runtime queued a stream tick and a nextTick entry per write, which ran the callbacks of later writes after nextTicks queued between them. The queued afterWrite tick now counts the writes that join it, as Node's does. --- .../7514-stream-after-write-order.ts.json | 6 ++ packages/runtime/src/scr_stream.c | 42 ++++++++-- tests/corpus/7514-stream-after-write-order.ts | 76 +++++++++++++++++++ .../7514-stream-after-write-order.ts.json | 1 + 4 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7514-stream-after-write-order.ts.json create mode 100644 tests/corpus/7514-stream-after-write-order.ts create mode 100644 tests/harness/corpus-costs/7514-stream-after-write-order.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7514-stream-after-write-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7514-stream-after-write-order.ts.json new file mode 100644 index 000000000..a6c705cd7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7514-stream-after-write-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7514-stream-after-write-order.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 1214ce5fd..cc0b277f3 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -176,6 +176,9 @@ struct ScrStreamState { size_t n, cap, head; int corked; bool writing, wsync, need_drain, after_scheduled; + /* the queued SCR_ST_AFTER_WRITE tick later synchronous writes with + * the same callback join (Node's afterWriteTickInfo), or NULL */ + struct ScrStreamTick *after_tick; /* Deferred afterWrites Node also defers (a user callback, a 'drain' * or a destroyed stream): its pendingcb, which holds the finish until * they ran — afterWrite's finishMaybe then puts 'finish' on a tick @@ -543,6 +546,9 @@ typedef struct ScrStreamTick { ScrError *err; /* owned or NULL (SCR_ST_ERROR) */ ScrClosure *cb; /* owned or NULL (SCR_ST_AFTER_WRITE / SCR_ST_WCB_ERR / SCR_ST_WRITE_ERR) */ bool hold; /* SCR_ST_AFTER_WRITE: counted in w.after_holds */ + /* SCR_ST_AFTER_WRITE: the synchronous writes it completes, which share + * cb (Node's afterWriteTickInfo.count) */ + size_t count; /* The AsyncLocalStorage context active when the tick was scheduled (+1, * NULL outside a store): Node schedules these steps with * process.nextTick, so their events and callbacks run in it. */ @@ -2117,7 +2123,7 @@ static void scr_stream_error_buffer(ScrStream *s) { * empty), THEN the user's write(chunk, cb) callback, then finishMaybe. * The buffered queue was already restarted by write_done (Node's onwrite * calls clearBuffer before afterWrite). cb moves. */ -static void scr_stream_after_write(ScrStream *s, ScrClosure *cb) { +static void scr_stream_after_write(ScrStream *s, ScrClosure *cb, size_t count) { ScrStreamState *st = s->st; if (st->w.length == 0 && st->w.need_drain && !st->w.ending && !st->destroyed) { st->w.need_drain = false; @@ -2137,7 +2143,11 @@ static void scr_stream_after_write(ScrStream *s, ScrClosure *cb) { } } if (cb) { - ((void (*)(ScrClosure *))cb->fn)(cb); + /* afterWrite: the callback once per write the tick completes */ + for (size_t i = 0; i < count; i++) { + ((void (*)(ScrClosure *))cb->fn)(cb); + if (scr_exc_pending()) break; + } scr_closure_release(cb); if (scr_exc_pending()) return; } @@ -7170,13 +7180,24 @@ void scr_stream_write_done(ScrStream *s, ScrError *err /*moves*/) { } if (was_sync) { bool hold = ucb != NULL || (st->w.need_drain && st->w.length == 0) || st->destroyed; - scr_st_tick(s, SCR_ST_AFTER_WRITE, NULL, ucb); - if (hold) { - scr_st_tail->hold = true; + ScrStreamTick *pending = st->w.after_tick; + if (pending && pending->cb == ucb) { + /* Node's onwrite: a synchronous write with the callback of the + * afterWrite tick already queued joins it (count++) */ + pending->count++; + if (ucb) scr_closure_release(ucb); + } else { + scr_st_tick(s, SCR_ST_AFTER_WRITE, NULL, ucb); + pending = scr_st_tail; + pending->count = 1; + st->w.after_tick = pending; + } + if (hold && !pending->hold) { + pending->hold = true; st->w.after_holds++; } } else { - scr_stream_after_write(s, ucb); + scr_stream_after_write(s, ucb, 1); } } @@ -7722,13 +7743,17 @@ static void scr_stream_run_tick(ScrStreamTick *t) { case SCR_ST_AFTER_WRITE: { st->w.after_scheduled = false; if (t->hold) st->w.after_holds--; + if (st->w.after_tick == t) st->w.after_tick = NULL; ScrClosure *cb = t->cb; t->cb = NULL; if (!st->destroyed) { - scr_stream_after_write(s, cb); + scr_stream_after_write(s, cb, t->count); } else { if (cb) { - ((void (*)(ScrClosure *))cb->fn)(cb); + for (size_t i = 0; i < t->count; i++) { + ((void (*)(ScrClosure *))cb->fn)(cb); + if (scr_exc_pending()) break; + } scr_closure_release(cb); if (scr_exc_pending()) break; } @@ -7898,6 +7923,7 @@ static void scr_stream_run_tick_in_context(ScrStreamTick *t) { } static void scr_stream_tick_free(ScrStreamTick *t) { + if (t->s->st && t->s->st->w.after_tick == t) t->s->st->w.after_tick = NULL; scr_stream_release(t->s); if (t->err) scr_error_release(t->err); if (t->cb) scr_closure_release(t->cb); diff --git a/tests/corpus/7514-stream-after-write-order.ts b/tests/corpus/7514-stream-after-write-order.ts new file mode 100644 index 000000000..5647f5bc8 --- /dev/null +++ b/tests/corpus/7514-stream-after-write-order.ts @@ -0,0 +1,76 @@ +// Write callbacks of synchronous writes run in Node's order: consecutive +// writes with the same callback complete in one afterWrite step, ahead of +// a process.nextTick queued between them, and a write with another +// callback (or none) starts the next step; end's callback and 'finish' +// follow. A PassThrough read by 'data' runs each write's own callback. +import { PassThrough, Writable } from "node:stream"; + +const log: string[] = []; + +function run(): Promise { + return new Promise((resolve) => { + const w = new Writable({ + highWaterMark: 4, + write(chunk: Buffer, _enc, cb) { + log.push(`write ${String(chunk)}`); + cb(); + }, + }); + const shared = (): void => { + log.push("shared callback"); + }; + w.on("finish", () => { + log.push("finish"); + resolve(); + }); + w.write("a", shared); + w.write("b", shared); + process.nextTick(() => log.push("tick between")); + w.write("c", shared); + w.write("d", () => log.push("own callback d")); + w.write("e"); + w.write("f"); + queueMicrotask(() => log.push("microtask")); + w.write("g", shared); + w.write("h", shared); + process.nextTick(() => log.push("tick after")); + w.end(() => log.push("end callback")); + }); +} + +function passThrough(): Promise { + return new Promise((resolve) => { + const pt = new PassThrough({ highWaterMark: 8 }); + let bytes = 0; + pt.on("data", (chunk: Buffer) => { + bytes += chunk.length; + }); + pt.on("end", () => { + log.push(`passthrough end ${bytes}`); + resolve(); + }); + let i = 0; + const more = (): void => { + while (i < 40) { + i++; + const n = i; + if (!pt.write(`chunk${n}`, () => { + if (n % 10 === 0) log.push(`callback ${n}`); + })) { + pt.once("drain", more); + return; + } + } + pt.end(); + }; + more(); + }); +} + +async function main(): Promise { + await run(); + await passThrough(); + console.log(log.join("\n")); +} + +main(); diff --git a/tests/harness/corpus-costs/7514-stream-after-write-order.ts.json b/tests/harness/corpus-costs/7514-stream-after-write-order.ts.json new file mode 100644 index 000000000..56aa0985f --- /dev/null +++ b/tests/harness/corpus-costs/7514-stream-after-write-order.ts.json @@ -0,0 +1 @@ +{ "plain": 2600, "san": 3600 } From dd2e53f00cad23959425d645324dc6ef2f5de365 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:40:50 -0500 Subject: [PATCH 162/395] Look up a number Map key on its integer view A Map or Set operation on a number key that the emitter knows is an integer (exactInteger: an integer view, a range proof or a literal) now looks the key up through sc_map_entry_f64_int, which tests the map's direct index with an unsigned compare of that integer. Before, the lookup converted the double back to an integer and compared it with the original to prove it integral. The hashed probe still takes the double, so hashing and SameValueZero are unchanged. --- .../src/backend/llvm/emit-function.ts | 1 + .../compiler/src/backend/llvm/emit-module.ts | 36 +++++++++++ packages/compiler/src/backend/llvm/emitter.ts | 5 +- .../src/backend/llvm/expr-containers.ts | 8 ++- .../backend/llvm/local-union-storage.test.ts | 3 +- .../backend/llvm/map-number-lookup.test.ts | 28 ++++++++- .../src/backend/llvm/map-number-lookup.ts | 24 ++++++- .../src/backend/llvm/map-read-lifetimes.ts | 2 + .../6308-number-map-integer-keys.ts.json | 6 ++ tests/corpus/6308-number-map-integer-keys.ts | 62 +++++++++++++++++++ .../6308-number-map-integer-keys.ts.json | 1 + 11 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6308-number-map-integer-keys.ts.json create mode 100644 tests/corpus/6308-number-map-integer-keys.ts create mode 100644 tests/harness/corpus-costs/6308-number-map-integer-keys.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index ca45a6624..d7c5696cf 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -200,6 +200,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { emitter.callLifetimes, ); emitter.readNumberMapWrites = findReadNumberMapWrites(fn); + emitter.numberMapKeyIntegers = new Map(); emitter.mapEntryFusion = emitter.debug === null ? findMapEntryFusions(fn) : null; emitter.localStackUnions = findLocalStackUnions( fn, diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 6adf76079..c6d3dbff4 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1301,6 +1301,42 @@ export function helperDefs(emitter: LlEmitter): string[] { ` ret ptr %hashed`, `}`, ``, + // The same lookup for a key the emitter knows is an integer (`%ki`, + // the double `%k`'s exact value): the direct index tests the integer + // itself, so the double is converted only by the hashed probe. A + // negative key is not below the index's size as an unsigned value. + `define internal ptr @sc_map_entry_f64_int(ptr %m, i64 %ki, double %k) ${FN_ATTRS} {`, + `entry:`, + ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 16`, + ` %nd32 = load i32, ptr %ndp`, + ` %nd = zext i32 %nd32 to i64`, + ` %inside = icmp ult i64 %ki, %nd`, + ` br i1 %inside, label %slot, label %outside`, + `slot:`, + sz === "i64" ? ` %i = add i64 %ki, 0` : ` %i = trunc i64 %ki to ${sz}`, + ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, + ` %dense = load ptr, ptr %dp`, + ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, + ` %s = load i32, ptr %sp`, + ` %empty = icmp eq i32 %s, 0`, + ` br i1 %empty, label %miss, label %hit`, + `hit:`, + ` %e32 = sub i32 %s, 1`, + sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, + ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %entries = load ptr, ptr %ep`, + ` %found = getelementptr inbounds %ScrMapNumEntry, ptr %entries, ${sz} %e`, + ` ret ptr %found`, + `miss:`, + ` ret ptr null`, + `outside:`, + ` %dense.any = icmp ne i64 %nd, 0`, + ` br i1 %dense.any, label %miss, label %slow`, + `slow:`, + ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, + ` ret ptr %hashed`, + `}`, + ``, // Hashing (scr_map_find_f64): the SameValueZero key word, its // scr_key_mix hash (zero remapped to one) and the absent-key filter. `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index bcb8a5ba2..0dae48868 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -123,7 +123,7 @@ import { } from "./int32-abi.js"; import { type Int32Local } from "./int32-locals.js"; import { findIntegerClones, NO_INTEGER_CLONES, type IntegerClones } from "./integer-clones.js"; -import { exactInteger, narrowInteger, widenInteger } from "./integer-values.js"; +import { exactInteger, narrowInteger, widenInteger, type LlInteger } from "./integer-values.js"; import { findInitializerBindings } from "../../ir/initializer-bindings.js"; import { findConstantNumericTables, type ConstantNumericTable } from "../../ir/constant-tables.js"; import { @@ -1241,6 +1241,9 @@ export class LlEmitter { callArrayReads = new Map(); mapReadLifetimes: MapReadLifetimes = { locals: new Map(), arguments: new Map() }; readNumberMapWrites: ReadonlySet = new Set(); + /** The integer views of the current function's number Map/Set keys, by + * the key's double operand (noteNumberMapKey, map-number-lookup.ts). */ + numberMapKeyIntegers = new Map(); /** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */ mapEntryFusion: MapEntryFusion | null = null; readonly callLifetimes: CallLifetimes; diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 59ef43d7d..98389b2e5 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -42,6 +42,7 @@ import { emitStringMapGetRef, emitStringMapGetScalar, emitStringMapHas, + noteNumberMapKey, } from "./map-number-lookup.js"; import { emitBorrowedInput } from "./borrowed-inputs.js"; import { emitFusedMapSet, rememberMapEntry } from "./map-entry-fusion.js"; @@ -1548,6 +1549,7 @@ export function emitMapLikeIntrinsic( // Key borrowed (the runtime retains stored string keys); the // value MOVES in (replacement releases the old value inside). const k = emitBorrowedInput(host, e.args[0]!); + if (kAcc === "f64") noteNumberMapKey(host, k, e.args[0]!); const v = host.emitExpr(e.args[1]!); const vAcc = elemAccess(receiverType.value); if (vAcc === "ref") host.toHeap(v); @@ -1574,6 +1576,7 @@ export function emitMapLikeIntrinsic( // Element borrowed (the runtime retains stored strings); the unit // value is 0. Re-adding overwrites in place, preserving insertion. const k = emitBorrowedInput(host, e.args[0]!); + if (kAcc === "f64") noteNumberMapKey(host, k, e.args[0]!); if (kAcc === "f64" && host.readNumberMapWrites.has(e)) emitNumberMapSetScalar(host, r.name, k.name, null, () => host.mapSet(r.name, kAcc, "f64", k.name, f64Lit(0)), @@ -1583,7 +1586,10 @@ export function emitMapLikeIntrinsic( } case "has": { const k = borrowInputs ? host.emitReadReceiver(e.args[0]!) : host.emitExpr(e.args[0]!); - if (kAcc === "f64") return { name: emitNumberMapHas(host, r.name, k.name), type: e.type }; + if (kAcc === "f64") { + noteNumberMapKey(host, k, e.args[0]!); + return { name: emitNumberMapHas(host, r.name, k.name), type: e.type }; + } if (kAcc === "str") return { name: emitStringMapHas(host, r.name, k.name, compact), type: e.type }; host.declare(`declare zeroext i1 @scr_map_has_${kAcc}(ptr, ${kTy})`); diff --git a/packages/compiler/src/backend/llvm/local-union-storage.test.ts b/packages/compiler/src/backend/llvm/local-union-storage.test.ts index b8f44408d..81019cadc 100644 --- a/packages/compiler/src/backend/llvm/local-union-storage.test.ts +++ b/packages/compiler/src/backend/llvm/local-union-storage.test.ts @@ -113,7 +113,8 @@ test("conditional and reassigned locals own payload snapshots without heap wrapp expect(facts(mod).get("item")?.ownerType).toEqual(STRING); for (const bits of [32, 64] as const) { const ir = body(mod, bits); - expect(ir).toContain("call ptr @sc_map_entry_f64("); // inline number-key read + // The inline number-key read, on the integer view of the literal key. + expect(ir).toMatch(/call ptr @sc_map_entry_f64_int\(ptr %\S+, i64 \d+, double /); expect(ir).toContain("@scr_str_release"); expect(ir).not.toMatch(/@scr_union_(?:new|retain|release)/); expect(ir).toContain("store i64 0"); diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts index 989b8b3b0..28b6a0552 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts @@ -118,7 +118,7 @@ test("string keys and reference values are retained inline, boolean slots compar }); /** work(map, k): map.has(k) on a number-keyed map. */ -function lookupFixture(): IrModule { +function lookupFixture(key: IrExpr = ref("k", F64)): IrModule { const map: IrType = { kind: "map", key: F64, value: F64 }; const params = [ { localId: "map", name: "map", type: map }, @@ -128,7 +128,7 @@ function lookupFixture(): IrModule { kind: "mapIntrinsic", method: "has", receiver: ref("map", map), - args: [ref("k", F64)], + args: [key], type: BOOL, loc, }; @@ -182,6 +182,30 @@ test("hashed number-key lookups probe inline and call the runtime only for wide } }); +test("a key with an integer view tests the direct index on the integer", () => { + for (const bits of [32, 64] as const) { + const sz = bits === 64 ? "i64" : "i32"; + const integral = emitted(lookupFixture({ kind: "numLit", value: 12, type: F64, loc }), bits); + expect(integral.work).toMatch(/call ptr @sc_map_entry_f64_int\(ptr %\S+, i64 12, double /); + const helper = + /^define internal ptr @sc_map_entry_f64_int\(ptr %m, i64 %ki, double %k\)[^]*?^}/m.exec( + integral.module, + )![0]; + // An unsigned compare also rejects negative keys; the hashed probe takes + // the double. + expect(helper).toContain("icmp ult i64 %ki, %nd"); + expect(helper).toContain(`getelementptr inbounds i32, ptr %dense, ${sz} %i`); + expect(helper).toContain("call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)"); + expect(helper).not.toContain("fptosi"); + // A fractional key and a key without a proof keep the double lookup. + for (const key of [{ kind: "numLit", value: 0.5, type: F64, loc } as IrExpr, ref("k", F64)]) { + const { work } = emitted(lookupFixture(key), bits); + expect(work).toContain("call ptr @sc_map_entry_f64("); + expect(work).not.toContain("@sc_map_entry_f64_int("); + } + } +}); + /** work(map): map.size, and keep(map): map returned as an owned value. */ function sizeFixture(): IrModule { const map: IrType = { kind: "map", key: F64, value: F64 }; diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.ts b/packages/compiler/src/backend/llvm/map-number-lookup.ts index 6d219b50a..6cb7f1ea3 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.ts @@ -28,16 +28,34 @@ * guard excludes. for-of starts at scr_map_iter_start, past a deleted * prefix. */ -import type { IrType } from "../../ir/ir.js"; -import type { LlvmEmitterContext } from "./expr-context.js"; +import type { IrExpr, IrType } from "../../ir/ir.js"; +import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; +import { exactInteger, widenInteger } from "./integer-values.js"; import { mapKeyAccess } from "./shapes.js"; export const NUMBER_MAP_ENTRY_DECL = "declare ptr @scr_map_entry_f64(ptr, double)"; +/** Record the integer view of a number Map/Set key operand, when the + * emitter has one (exactInteger), for the entry lookups of that key. */ +export function noteNumberMapKey(host: LlvmEmitterContext, key: LlValue, expr: IrExpr): void { + if (key.type.kind !== "f64" || host.numberMapKeyIntegers.has(key.name)) return; + const integer = exactInteger(host, key, expr); + if (integer) host.numberMapKeyIntegers.set(key.name, integer); +} -/** The entry for `key`, or null when absent. Valid until the next mutation. */ +/** The entry for `key`, or null when absent. Valid until the next mutation. + * A key with an integer view (noteNumberMapKey) tests the direct index on + * that integer instead of converting the double back. */ export function emitNumberMapEntry(host: LlvmEmitterContext, map: string, key: string): string { host.declare(NUMBER_MAP_ENTRY_DECL); const entry = host.B.tmp(); + const integer = host.numberMapKeyIntegers.get(key); + if (integer) { + const wide = widenInteger(host, integer); + host.B.line( + `${entry} = call ptr @sc_map_entry_f64_int(ptr ${map}, i64 ${wide}, double ${key})`, + ); + return entry; + } host.B.line(`${entry} = call ptr @sc_map_entry_f64(ptr ${map}, double ${key})`); return entry; } diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index 6053d70eb..c44e28cb2 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -16,6 +16,7 @@ import { emitStringMapGetScalar, emitStringMapGetRef, compactMapValues, + noteNumberMapKey, } from "./map-number-lookup.js"; import { mapKeyAccess, mapKeyParamType } from "./shapes.js"; import { rememberMapEntry } from "./map-entry-fusion.js"; @@ -265,6 +266,7 @@ export function emitMapLookupKey( const value = borrow ? host.emitReadReceiver(key) : host.emitExpr(key); const access = mapKeyAccess(key.type); const type = mapKeyParamType(access); + if (access === "f64") noteNumberMapKey(host, value, key); return { access, types: type, args: `${type} ${value.name}`, value: value.name }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6308-number-map-integer-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6308-number-map-integer-keys.ts.json new file mode 100644 index 000000000..b58240a3e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6308-number-map-integer-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6308-number-map-integer-keys.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6308-number-map-integer-keys.ts b/tests/corpus/6308-number-map-integer-keys.ts new file mode 100644 index 000000000..16110f80a --- /dev/null +++ b/tests/corpus/6308-number-map-integer-keys.ts @@ -0,0 +1,62 @@ +// Number-keyed Map and Set operations whose keys the compiler knows are +// integers (loop counters, `| 0` results, typed-array bytes, integer +// arithmetic) test the map's direct index on the integer. Results must +// match Node while the index covers the keys, after a key outside it moves +// the map to hashing, for negative keys and for keys past the index, and +// when a double key and an integer key name the same entry. + +const counts = new Map(); +for (let i = 0; i < 200; i++) { + const key = (i * 7) % 13; + counts.set(key, (counts.get(key) ?? 0) + 1); +} +const dense: string[] = []; +for (let k = -2; k < 16; k++) dense.push(`${k}:${counts.has(k) ? counts.get(k) : "-"}`); +console.log(dense.join(" ")); + +// A key past the direct index moves the map to hashing; integer reads still +// find every key. +counts.set(1_000_000, 5); +let found = 0; +for (let k = -2; k < 16; k++) if (counts.has(k)) found += counts.get(k)!; +console.log(found, counts.get(1_000_000), counts.has(999_999), counts.size); + +// Negative keys, and keys built with `| 0` and shifts. +const signed = new Map(); +for (let i = -5; i <= 5; i++) signed.set((i * 3) | 0, `v${i}`); +const reads: string[] = []; +for (let i = -16; i <= 16; i += 4) reads.push(`${i}:${signed.get(i) ?? "-"}`); +console.log(reads.join(" "), signed.get(-0), signed.get(0)); + +// Bytes of a typed array as keys, in a Set. +const bytes = new Uint8Array([3, 250, 3, 0, 17, 250, 255, 0]); +const seen = new Set(); +let repeats = 0; +for (let i = 0; i < bytes.length; i++) { + if (seen.has(bytes[i]!)) repeats++; + seen.add(bytes[i]!); +} +console.log([...seen].join(","), repeats); + +// A double key and an integer key name the same entry. +const mixed = new Map(); +mixed.set(2.0, 1); +mixed.set(0.5, 2); +for (let i = 0; i < 4; i++) mixed.set(i, (mixed.get(i) ?? 0) + 10); +console.log([...mixed.entries()].map(([k, v]) => `${k}=${v}`).join(" "), mixed.get(0.5)); + +// Deletion through integer keys, then reinsertion order. +const order = new Map(); +for (let i = 0; i < 10; i++) order.set(i, i * i); +for (let i = 0; i < 10; i += 3) order.delete(i); +for (let i = 9; i >= 0; i -= 2) order.set(i, -i); +console.log([...order.entries()].map(([k, v]) => `${k}=${v}`).join(" ")); + +// Large integer keys near 2^53, from integer arithmetic. +const wide = new Map(); +let key = 2 ** 52; +for (let i = 0; i < 5; i++) { + wide.set(key, i); + key = key * 2 - 1; +} +console.log([...wide.keys()].join(" "), wide.get(2 ** 52), wide.get(2 ** 53 - 1)); diff --git a/tests/harness/corpus-costs/6308-number-map-integer-keys.ts.json b/tests/harness/corpus-costs/6308-number-map-integer-keys.ts.json new file mode 100644 index 000000000..a6e4996ff --- /dev/null +++ b/tests/harness/corpus-costs/6308-number-map-integer-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 3500 } From f025b5243345daa0b2855173f93806be557e6b3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:50:36 -0500 Subject: [PATCH 163/395] Declare a typed-array loop version's alias scopes where its fast copy starts The guard proves the views disjoint for one entry of the loop. An enclosing loop may pass other views on its next pass, such as two buffers swapped after every pass, so one pass's write and the next pass's read can be the same bytes under different scopes. Without a declaration, a scope holds across the whole function, and LLVM could apply one entry's no-alias facts to another's accesses once it unrolled the enclosing loop. Each fast copy now begins with llvm.experimental.noalias.scope.decl for each of its scopes. A declared scope holds from its declaration, and LLVM gives each copy of the declaration its own scopes when it unrolls or duplicates the code around it. Corpus 8182 adds ping-pong passes over swapped Float64Array and Int32Array views; the emission test checks the declarations. --- .../src/backend/llvm/emit-statements.ts | 32 +++++++++---- .../typed-array-alias-scope-emission.test.ts | 7 +++ tests/corpus/8182-typed-array-alias-scopes.ts | 47 ++++++++++++++++++- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 36c28bf97..9b7ed9d8f 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2085,11 +2085,12 @@ const SPECULATION_BAILS = 8; * scope of its own view, and no alias with the scopes of the other views, * whose bytes the guard proved disjoint. The scopes are named after the * function and the version, so every copy of a loop has its own, and - * written inline (string-named scopes need no numbered metadata). */ + * written inline (string-named scopes need no numbered metadata). Also + * the scopes, which each fast copy declares on entry (declareAliasScopes). */ function bytesAliasScopes( emitter: LlEmitter, scopes: NonNullable, -): Map { +): { attachments: Map; scopes: string[] } { const version = emitter.aliasScopedVersions++; const name = emitter.currentFunctionName.replace(/[^A-Za-z0-9_.$%]/g, "_"); const domain = '!{!"scriptc typed-array views"}'; @@ -2104,7 +2105,20 @@ function bytesAliasScopes( } const result = new Map(); for (const [node, receiver] of scopes.nodes) result.set(node, attachments.get(receiver)!); - return result; + return { attachments: result, scopes: scopes.receivers.map(scope) }; +} + +/** Declares a version's scopes where a fast copy starts. The guard proves + * the views disjoint for this entry only: an enclosing loop may pass other + * views on its next pass (`[cur, next] = [next, cur]`). A declared scope + * holds from its declaration, and LLVM gives each copy of the declaration + * its own scopes when it unrolls or duplicates the enclosing code, so the + * no-alias facts of one entry never apply to the accesses of another. */ +function declareAliasScopes(emitter: LlEmitter, scopes: readonly string[]): void { + if (scopes.length === 0) return; + emitter.declare("declare void @llvm.experimental.noalias.scope.decl(metadata)"); + for (const scope of scopes) + emitter.B.line(`call void @llvm.experimental.noalias.scope.decl(metadata !{${scope}})`); } /** Emit a loop's condition, body and update through `emitCopy`, twice @@ -2142,15 +2156,14 @@ function emitVersionedLoop( if (version.ranges.size > 0) emitter.integerRanges = new Map([...originalRanges, ...version.ranges]); // Both fast copies run under the guard's span test, so they share the - // version's scopes. - if (version.scopes && emitter.debug === null) - emitter.bytesAliasScopes = new Map([ - ...originalScopes, - ...bytesAliasScopes(emitter, version.scopes), - ]); + // version's scopes, and each declares them on entry. + const scoped = + version.scopes && emitter.debug === null ? bytesAliasScopes(emitter, version.scopes) : null; + if (scoped) emitter.bytesAliasScopes = new Map([...originalScopes, ...scoped.attachments]); if (version.counted?.guarded || version.paired?.loop.guarded) LOOPS.of(emitter).provenCountedGuards.add(s); B.startBlock(fast); + declareAliasScopes(emitter, scoped?.scopes ?? []); emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); // Both fast copies read the string's length the guard took. const length = guard?.length; @@ -2164,6 +2177,7 @@ function emitVersionedLoop( if (unitsFast !== undefined && units !== null) { // The string reads take the receiver's UTF-16 units. B.startBlock(unitsFast); + declareAliasScopes(emitter, scoped?.scopes ?? []); emitter.bytesBounds = new Set([ ...originalBounds, ...[...version.bounds].filter((node) => !version.stringReads.has(node)), diff --git a/packages/compiler/test/typed-array-alias-scope-emission.test.ts b/packages/compiler/test/typed-array-alias-scope-emission.test.ts index a32df03e7..c94b58d59 100644 --- a/packages/compiler/test/typed-array-alias-scope-emission.test.ts +++ b/packages/compiler/test/typed-array-alias-scope-emission.test.ts @@ -46,6 +46,13 @@ console.log(dot(a, b, 8)); /load double, ptr %\w+, align 1[^\n]*!alias\.scope !\{!\{!"copy\.0\.\d", !\{!"scriptc typed-array views"\}\}\}, !noalias/, ); expect(copy).toMatch(/store double %\w+, ptr %\w+, align 1[^\n]*!alias\.scope/); + // The fast copy declares both scopes where it starts, so they hold for + // this entry only, and declares nothing else. + for (const receiver of [0, 1]) + expect(copy).toContain( + `call void @llvm.experimental.noalias.scope.decl(metadata !{!{!"copy.0.${receiver}", !{!"scriptc typed-array views"}}})`, + ); + expect(copy.match(/noalias\.scope\.decl/g)).toHaveLength(2); // One view, or views that are only read, need no scopes. for (const name of ["scale", "dot"]) { expect(fn(name)).not.toContain("!alias.scope"); diff --git a/tests/corpus/8182-typed-array-alias-scopes.ts b/tests/corpus/8182-typed-array-alias-scopes.ts index 4dbf52e47..f56face07 100644 --- a/tests/corpus/8182-typed-array-alias-scopes.ts +++ b/tests/corpus/8182-typed-array-alias-scopes.ts @@ -1,7 +1,9 @@ // Loops that read one typed array and write another, where the two views // may share bytes: overlapping and disjoint views of one buffer, the same // array passed twice, views of different element types over one buffer, -// and empty views. A loop over views that overlap must see its own writes. +// and empty views, and passes whose views an enclosing loop swaps. A loop +// over views that overlap must see its own writes, and each pass the +// previous pass's writes. function shiftAdd(dst: Float64Array, src: Float64Array, n: number): void { for (let i = 0; i < n; i++) dst[i] = src[i]! + 1; } @@ -87,3 +89,46 @@ console.log( "lanes overlapping", lanes(pool.subarray(0, 4), pool.subarray(2, 6), pool.subarray(8, 12), pool.subarray(9, 13)).toFixed(6), ); + +// Ping-pong passes: an enclosing loop swaps the two views after every pass, +// so each pass reads what the previous one wrote. +function pingPongPairs(a: Float64Array, b: Float64Array, steps: number): string { + let cur = a; + let next = b; + for (let s = 0; s < steps; s++) { + for (let i = 0; i < 2; i++) next[i] = cur[i]! + cur[1 - i]! * 2 + s; + const t = cur; + cur = next; + next = t; + } + return Array.from(cur).join(","); +} +function pingPongWords(a: Int32Array, b: Int32Array): string { + let cur = a; + let next = b; + for (let s = 0; s < 4; s++) { + for (let i = 0; i < 4; i++) next[i] = (cur[i]! * 3 + cur[(i + 1) & 3]! + 1) | 0; + const t = cur; + cur = next; + next = t; + } + return Array.from(cur).join(","); +} +function pingPongRows(a: Float64Array, b: Float64Array, steps: number): number { + let cur = a; + let next = b; + for (let s = 0; s < steps; s++) { + for (let i = 0; i < cur.length; i++) next[i] = cur[i]! * 0.5 + 1; + const t = cur; + cur = next; + next = t; + } + let sum = 0; + for (let i = 0; i < cur.length; i++) sum += cur[i]!; + return sum; +} +for (let k = 0; k < 3; k++) { + console.log("ping-pong pairs", pingPongPairs(new Float64Array([1, 2]), new Float64Array(2), 3 + k)); + console.log("ping-pong words", pingPongWords(new Int32Array([1, 2, 3, 4]), new Int32Array(4))); +} +console.log("ping-pong rows", pingPongRows(new Float64Array(37).fill(8), new Float64Array(37), 9)); From d6c8b0f260e3d18829f67ce0c0808a68bf66a549 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:52:18 -0500 Subject: [PATCH 164/395] Close the afterWrite batch whenever an afterWrite tick runs Node's afterWriteTick sets afterWriteTickInfo to null before it runs the callbacks, whichever tick it is. The runtime cleared the queued batch only when the running tick was that batch, so a write made from an earlier tick's callback joined a later queued tick with the same callback and completed ahead of nextTicks queued between them. Any afterWrite tick now closes the batch, and such a write queues its own. --- packages/runtime/src/scr_stream.c | 4 ++- tests/corpus/7514-stream-after-write-order.ts | 28 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index cc0b277f3..406d91599 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -7743,7 +7743,9 @@ static void scr_stream_run_tick(ScrStreamTick *t) { case SCR_ST_AFTER_WRITE: { st->w.after_scheduled = false; if (t->hold) st->w.after_holds--; - if (st->w.after_tick == t) st->w.after_tick = NULL; + /* afterWriteTick: the batch closes when any afterWrite tick runs, so + * a write its callback makes queues a new tick */ + st->w.after_tick = NULL; ScrClosure *cb = t->cb; t->cb = NULL; if (!st->destroyed) { diff --git a/tests/corpus/7514-stream-after-write-order.ts b/tests/corpus/7514-stream-after-write-order.ts index 5647f5bc8..1c14de6eb 100644 --- a/tests/corpus/7514-stream-after-write-order.ts +++ b/tests/corpus/7514-stream-after-write-order.ts @@ -2,7 +2,9 @@ // writes with the same callback complete in one afterWrite step, ahead of // a process.nextTick queued between them, and a write with another // callback (or none) starts the next step; end's callback and 'finish' -// follow. A PassThrough read by 'data' runs each write's own callback. +// follow. A write made from a write callback starts a new afterWrite step +// even when its callback matches a later queued one. A PassThrough read by +// 'data' runs each write's own callback. import { PassThrough, Writable } from "node:stream"; const log: string[] = []; @@ -38,6 +40,29 @@ function run(): Promise { }); } +function nested(): Promise { + return new Promise((resolve) => { + const w = new Writable({ + write(chunk: Buffer, _enc, cb) { + log.push(`nested write ${String(chunk)}`); + cb(); + }, + }); + let laterCalls = 0; + const later = (): void => { + log.push("later callback"); + if (++laterCalls === 2) resolve(); + }; + w.write("a", () => { + log.push("first callback"); + w.write("from first", later); + }); + process.nextTick(() => log.push("nested tick 1")); + w.write("b", later); + process.nextTick(() => log.push("nested tick 2")); + }); +} + function passThrough(): Promise { return new Promise((resolve) => { const pt = new PassThrough({ highWaterMark: 8 }); @@ -69,6 +94,7 @@ function passThrough(): Promise { async function main(): Promise { await run(); + await nested(); await passThrough(); console.log(log.join("\n")); } From 4241ca533f86e2c9bb7df0854f610fbd2f4dc1d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 21:59:28 -0500 Subject: [PATCH 165/395] Read elements of native arrays passed to untyped code by index A typed array such as a number[] reaches untyped code as a capsule of the native array. A keyed read `xs[i]` there missed the inline cache, which answers only dyn arrays, and went to the member helper, which converted the index to a string; the capsule's key lookup then parsed the string back into a number for every element. Keyed sites now read an element of a native number[] capsule in place when the index is an integer below the array's hole-free prefix, and `length` sites read the native array's length. Other capsule elements (holes, out-of-range and fractional indices, arrays of other element types) are answered by the keyed probe through scr_dyn_index_get without forming the key, or by the helper as before. --- packages/compiler/src/backend/llvm/js-ic.ts | 128 ++++++++++++++++-- packages/compiler/test/js-ic-emission.test.ts | 41 ++++++ .../main.ts.json | 7 + packages/runtime/src/scr_dyn.h | 4 +- packages/runtime/src/scr_js_object.c | 3 + .../8300-native-array-capsule-reads/lib.cjs | 36 +++++ .../8300-native-array-capsule-reads/main.ts | 59 ++++++++ .../package.json | 1 + .../main.ts.json | 1 + 9 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8300-native-array-capsule-reads/main.ts.json create mode 100644 tests/corpus/8300-native-array-capsule-reads/lib.cjs create mode 100644 tests/corpus/8300-native-array-capsule-reads/main.ts create mode 100644 tests/corpus/8300-native-array-capsule-reads/package.json create mode 100644 tests/harness/corpus-costs/8300-native-array-capsule-reads/main.ts.json diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index 7516673cd..b8be1ecb1 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -384,6 +384,15 @@ function emitGet(host: Host, e: ExprOf<"call">, callee: IrFunction, key: string) incoming.push(`[ ${numberResult(host, count, e)}, %${B.currentLabel()} ]`); B.br(join); B.startBlock(other); + // So is the length of a native array passed in from typed code. + const rest = B.newLabel("icg.rest"); + const arr = nativeArray(host, receiver.name, rest); + const native = load(host, "i32", arr, 8); + const nativeCount = B.tmp(); + B.line(`${nativeCount} = uitofp i32 ${native} to double`); + incoming.push(`[ ${numberResult(host, nativeCount, e)}, %${B.currentLabel()} ]`); + B.br(join); + B.startBlock(rest); } // Inline hits (borrowed values) and probe hits (owned ones) share one // conversion to the result type. @@ -623,6 +632,98 @@ function numberKey(host: Host, key: IrExpr): NumberKey | null { }; } +/** Branches to `yes` when `d` is a capsule of a native array passed in + * from typed code (a TYPED_REF bound to live array ops, not a record or + * class instance), with the ScrArr in `yes`; else to `no`. */ +function nativeArray(host: Host, d: string, no: string): string { + const B = host.B; + const capsule = B.newLabel("ica.cap"); + const bound = B.newLabel("ica.ops"); + const yes = B.newLabel("ica.arr"); + kindIs(host, d, DYN_KIND.TYPED_REF, capsule, no); + B.startBlock(capsule); + // The flag word after `casts`: record, klass and bag_class are its top + // three bits; any of them means `array` holds other ops. + const flags = load(host, "i32", d, 88); + const kinds = B.tmp(); + const plain = B.tmp(); + B.line(`${kinds} = lshr i32 ${flags}, 29`); + B.line(`${plain} = icmp eq i32 ${kinds}, 0`); + B.condBr(plain, bound, no); + B.startBlock(bound); + const ops = load(host, "ptr", d, 64); + const live = B.tmp(); + B.line(`${live} = icmp ne ptr ${ops}, null`); + B.condBr(live, yes, no); + B.startBlock(yes); + return load(host, "ptr", d, 16); +} + +/** `xs[i]` on a native number[] passed in from typed code: an integral + * index below the array's hole-free prefix reads the element in place. + * The other cases branch to `miss`, whose probe and helper answer them as + * before. The result joins `incoming` at `join`. */ +function emitNativeNumberElement( + host: Host, + receiver: string, + e: IrExpr, + indexIn: (next: string) => string, + miss: string, + join: string, + incoming: string[], +): void { + const B = host.B; + const arr = nativeArray(host, receiver, miss); + const numbers = B.newLabel("ica.f64"); + const elem = load(host, "i8", arr, 16); + const f64 = B.tmp(); + B.line(`${f64} = icmp eq i8 ${elem}, 0 ; SCR_ELEM_F64`); + B.condBr(f64, numbers, miss); + B.startBlock(numbers); + const index = indexIn(B.newLabel("ica.idx")); + const filled = load(host, "i32", arr, 20); + const filledD = B.tmp(); + const nonNegative = B.tmp(); + const below = B.tmp(); + const ok = B.tmp(); + B.line(`${filledD} = uitofp i32 ${filled} to double`); + B.line(`${nonNegative} = fcmp oge double ${index}, 0.0`); + B.line(`${below} = fcmp olt double ${index}, ${filledD}`); + B.line(`${ok} = and i1 ${nonNegative}, ${below}`); + const ranged = B.newLabel("ica.rng"); + B.condBr(ok, ranged, miss); + B.startBlock(ranged); + const i = B.tmp(); + const back = B.tmp(); + const integral = B.tmp(); + B.line(`${i} = fptoui double ${index} to i64`); + B.line(`${back} = uitofp i64 ${i} to double`); + B.line(`${integral} = fcmp oeq double ${back}, ${index}`); + const hit = B.newLabel("ica.hit"); + B.condBr(integral, hit, miss); + B.startBlock(hit); + const data = load(host, "ptr", arr, 24); + const slot = B.tmp(); + const value = B.tmp(); + B.line(`${slot} = getelementptr inbounds double, ptr ${data}, i64 ${i}`); + B.line(`${value} = load double, ptr ${slot}`); + let out: string; + if (e.type.kind === "jsval") { + const raw = B.tmp(); + const nan = B.tmp(); + const bits = B.tmp(); + out = B.tmp(); + B.line(`${raw} = bitcast double ${value} to i64`); + B.line(`${nan} = fcmp uno double ${value}, ${value}`); + B.line(`${bits} = select i1 ${nan}, i64 ${JS_BITS.canonicalNan}, i64 ${raw}`); + B.line(`${out} = inttoptr i64 ${bits} to ptr`); + } else { + out = numberResult(host, value, e); + } + incoming.push(`[ ${out}, %${B.currentLabel()} ]`); + B.br(join); +} + function emitKeyed(host: Host, e: ExprOf<"call">, callee: IrFunction): LlValue { const B = host.B; const receiver = emitBorrowedInput(host, e.args[0]!); @@ -645,18 +746,23 @@ function emitKeyed(host: Host, e: ExprOf<"call">, callee: IrFunction): LlValue { B.line(`store ptr null, ptr ${keySlot}`); host.ownSlot(keySlot, DYN); } - kindIs(host, receiver.name, DYN_KIND.ARR, array, probe); + /** The key as a double in block `next`; other keys go to the probe. */ + const indexIn = (next: string): string => { + if (numeric === null) { + kindIs(host, key!.name, DYN_KIND.NUM, next, probe); + B.startBlock(next); + return load(host, "double", key!.name, 16); + } + B.condBr(numeric.present, next, probe); + B.startBlock(next); + return numeric.value; + }; + const other = B.newLabel("ick.other"); + kindIs(host, receiver.name, DYN_KIND.ARR, array, other); + B.startBlock(other); + emitNativeNumberElement(host, receiver.name, e, indexIn, probe, join, incoming); B.startBlock(array); - let index: string; - if (numeric === null) { - kindIs(host, key!.name, DYN_KIND.NUM, number, probe); - B.startBlock(number); - index = load(host, "double", key!.name, 16); - } else { - B.condBr(numeric.present, number, probe); - B.startBlock(number); - index = numeric.value; - } + const index = indexIn(number); const len = load(host, "i64", receiver.name, 16); const i = B.tmp(); const back = B.tmp(); diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 54ea13d26..7a1d44f34 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -134,3 +134,44 @@ console.log(len2({ x: 3, y: 4 }), typeof parsed); await rm(dir, { recursive: true, force: true }); } }); + +test("keyed and length sites read native number arrays in place", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-js-ic-native-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile(join(dir, "package.json"), `{ "type": "module" }`); + await writeFile( + join(dir, "lib.cjs"), + `exports.total = function (xs) { + let s = 0; + for (let i = 0; i < xs.length; i++) s += xs[i]; + return s; +}; +`, + ); + await writeFile( + entry, + `import { createRequire } from "node:module"; +const require = createRequire(import.meta.url); +const lib: { total(xs: number[]): number } = require("./lib.cjs"); +console.log(lib.total([1, 2, 3])); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const llvm = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + const fn = llvm.split("\ndefine ").find((f) => f.includes("ica.f64")) ?? ""; + // The keyed read tests the capsule's element kind and loads the double + // in place; other receivers keep the probe. + expect(fn).toMatch(/icmp eq i8 %t\d+, 0 ; SCR_ELEM_F64/); + expect(fn).toMatch(/load double, ptr %t\d+/); + expect(fn).toContain("@scr_js_keyed_probe("); + // The length read answers from the native array's length. + expect(fn).toContain("icg.rest"); + expect(fn).toContain("@scr_js_load_probe("); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8300-native-array-capsule-reads/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8300-native-array-capsule-reads/main.ts.json new file mode 100644 index 000000000..fb6e3a0bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8300-native-array-capsule-reads/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/8300-native-array-capsule-reads/lib.cjs", + "/tests/corpus/8300-native-array-capsule-reads/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 6270c8cdc..2f9d7eb82 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -523,7 +523,9 @@ struct ScrDyn { * identity while the source reference remains observable. */ ScrDyn *materialized; ScrDynTypedCast *casts; - /* One word keeps this, the largest payload, at its previous size. */ + /* One word keeps this, the largest payload, at its previous size. + * Dynamic-tier inline caches read record, klass and bag_class as + * the word's top three bits (backend/llvm/js-ic.ts nativeArray). */ uint32_t type_key_len : 27; uint32_t traced : 1; /* native referent has a cycle header */ uint32_t observed : 1; /* native release reports this identity's disposal */ diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index 4b6e3141e..e09226fd1 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -355,6 +355,9 @@ bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJ /* ── keyed loads ──────────────────────────────────────────────────────── */ ScrDyn *scr_js_index_probe(ScrDyn *receiver, double index) { + /* A native array passed in from typed code answers its elements in + * place, without forming the index's key string. */ + if (receiver->kind == SCR_DYN_TYPED_REF) return scr_dyn_index_get(receiver, index); if (receiver->kind != SCR_DYN_ARR || !(index >= 0) || index >= (double)receiver->v.arr.len) return NULL; size_t i = (size_t)index; diff --git a/tests/corpus/8300-native-array-capsule-reads/lib.cjs b/tests/corpus/8300-native-array-capsule-reads/lib.cjs new file mode 100644 index 000000000..6f386216b --- /dev/null +++ b/tests/corpus/8300-native-array-capsule-reads/lib.cjs @@ -0,0 +1,36 @@ +"use strict"; +// Reads and writes the arrays the typed program passes in. +exports.total = function (xs) { + let s = 0; + for (let i = 0; i < xs.length; i++) s += xs[i]; + return s; +}; +exports.at = function (xs, i) { + return xs[i]; +}; +exports.describe = function (xs, keys) { + const out = []; + for (const k of keys) { + const v = xs[k]; + out.push(typeof v === "number" && Object.is(v, -0) ? "-0" : String(v)); + } + return out.join(" "); +}; +exports.has = function (xs, i) { + return i in xs; +}; +exports.write = function (xs, i, v) { + xs[i] = v; + return xs[i]; +}; +exports.grow = function (xs, v) { + xs.push(v); + return xs.length; +}; +exports.cut = function (xs, n) { + xs.length = n; + return xs.length; +}; +exports.lengthOf = function (xs) { + return xs.length; +}; diff --git a/tests/corpus/8300-native-array-capsule-reads/main.ts b/tests/corpus/8300-native-array-capsule-reads/main.ts new file mode 100644 index 000000000..5b1513ee4 --- /dev/null +++ b/tests/corpus/8300-native-array-capsule-reads/main.ts @@ -0,0 +1,59 @@ +// A typed program passes its arrays to untyped CommonJS functions, which +// read their elements and lengths, write them and resize them. Element reads +// cover integral, negative, fractional, non-finite and out-of-range indexes, +// string keys, holes, frozen arrays and arrays of other element types. +import { createRequire } from "node:module"; + +interface Lib { + total(xs: readonly unknown[]): number; + at(xs: readonly unknown[], i: unknown): unknown; + describe(xs: readonly unknown[], keys: unknown[]): string; + has(xs: readonly unknown[], i: number): boolean; + write(xs: unknown[], i: number, v: unknown): unknown; + grow(xs: unknown[], v: unknown): number; + cut(xs: unknown[], n: number): number; + lengthOf(xs: readonly unknown[]): number; +} +const require = createRequire(import.meta.url); +const lib: Lib = require("./lib.cjs"); + +// Element reads of a number[] at integral, negative, fractional, +// non-finite and out-of-range indexes, and through string keys. +const nums: number[] = [1, 2.5, -0, NaN, Infinity, -7]; +console.log(lib.total([1, 2, 3, 4]), lib.total(nums), lib.total([])); +console.log(lib.describe(nums, [0, 1, 2, 3, 4, 5, 6, -1, 1.5, -0, NaN, Infinity, 2 ** 32, "1", "01", "length"])); +console.log(lib.at(nums, 5), lib.at(nums, 6), typeof lib.at(nums, 2), Object.is(lib.at(nums, 2), -0)); + +// Holes: below and past the hole-free prefix, and an array cut longer. +const holey: number[] = [1, 2]; +holey[4] = 5; +console.log(lib.describe(holey, [0, 1, 2, 3, 4, 5]), lib.has(holey, 3), lib.has(holey, 4), lib.lengthOf(holey)); +const longer: number[] = [3, 4, 5]; +longer.length = 6; +console.log(lib.describe(longer, [0, 2, 3, 5]), lib.lengthOf(longer)); + +// Writes from the package and from the program are seen by both. +const live: number[] = [10, 20, 30]; +console.log(lib.write(live, 0, 11), lib.write(live, 3, 40), lib.grow(live, 50), live.join(",")); +live.push(60); +live[1] = 21; +console.log(lib.total(live), lib.lengthOf(live), lib.at(live, 5)); +console.log(lib.cut(live, 2), lib.total(live), live.length, lib.at(live, 2)); + +// Frozen and empty arrays. +const frozen: readonly number[] = Object.freeze([4, 5, 6]); +console.log(lib.total(frozen), lib.at(frozen, 1), lib.at([], 0), lib.lengthOf([])); + +// Arrays of other element types read through the same sites. +const words: string[] = ["a", "b", "c"]; +const flags: boolean[] = [true, false]; +const points: { x: number }[] = [{ x: 1 }, { x: 2 }]; +console.log(lib.describe(words, [0, 2, 3]), lib.describe(flags, [0, 1, 2]), (lib.at(points, 1) as { x: number }).x); +console.log(lib.at(words, 1.5), lib.has(words, 2), lib.lengthOf(points)); + +// A loop over a larger array. +const big: number[] = []; +for (let i = 0; i < 1000; i++) big.push(i % 7); +let sum = 0; +for (let round = 0; round < 50; round++) sum += lib.total(big); +console.log(sum); diff --git a/tests/corpus/8300-native-array-capsule-reads/package.json b/tests/corpus/8300-native-array-capsule-reads/package.json new file mode 100644 index 000000000..5ffd9800b --- /dev/null +++ b/tests/corpus/8300-native-array-capsule-reads/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/tests/harness/corpus-costs/8300-native-array-capsule-reads/main.ts.json b/tests/harness/corpus-costs/8300-native-array-capsule-reads/main.ts.json new file mode 100644 index 000000000..a37fc12d7 --- /dev/null +++ b/tests/harness/corpus-costs/8300-native-array-capsule-reads/main.ts.json @@ -0,0 +1 @@ +{ "plain": 4860, "san": 7734 } From d1432eeb187e2cf269cd06ddddb9374813724aaa Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 13:14:39 -0700 Subject: [PATCH 166/395] Compile namespace-style npm packages: guarded declarations and var exports Two refusals kept pre-module JavaScript packages from compiling: - `var ns = ns || {}` (and `?? {}`): the read in the variable's own initializer made the checker type the namespace `any`, and requirers then failed to reach the binding (SC1030). In a module, the read sees the hoisted `undefined` whenever nothing else writes the variable, so a new package-source normalization respells the declaration as its default (`var ns = ({})`) when no other declaration, assignment, update, function, class, parameter or local of that name exists in the file and no earlier top-level statement mentions it. - `module.exports = { ns }` with a `var` binding was refused as exporting a mutable binding by reference. A `var` (or `let`) whose initialized declaration precedes the export statement at top level and that nothing reassigns exports exactly the value Node copies, so it now aliases like a const. The namespace benchmark (results/design-shape-inference/nsbench) now compiles and matches Node; the new npm case legacy-namespaces pins a namespace library with guarded declarations, constructor functions with prototype methods, enums, a revealing module and a handler registry. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stmts.ts | 20 ++- .../npm-static-namespace-defaults.test.ts | 63 ++++++++ .../frontend/npm-static-namespace-defaults.ts | 138 ++++++++++++++++++ packages/compiler/src/frontend/npm-static.ts | 5 + packages/compiler/src/frontend/services.ts | 11 ++ .../npm/cases/legacy-namespaces/main.ts.json | 6 + .../npm/cases/legacy-namespaces/main.ts | 26 ++++ .../node_modules/legacy-ns/index.d.ts | 2 + .../node_modules/legacy-ns/index.js | 97 ++++++++++++ .../node_modules/legacy-ns/package.json | 1 + .../npm/cases/legacy-namespaces/package.json | 1 + 11 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-namespace-defaults.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/legacy-namespaces/main.ts.json create mode 100644 tests/fixtures/npm/cases/legacy-namespaces/main.ts create mode 100644 tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.d.ts create mode 100644 tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.js create mode 100644 tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/package.json create mode 100644 tests/fixtures/npm/cases/legacy-namespaces/package.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index c50676863..851cae4c9 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -7445,7 +7445,7 @@ function lowerCjsExportStatement( const fenceMutable = (nameNode: ts.Identifier): void => { const sym = lowerer.resolveValueSymbol(nameNode); const g = sym ? lowerer.globalsBySymbol.get(sym) : undefined; - if (g?.mutable) { + if (g?.mutable && !(sym && settledBeforeExport(lowerer, sym, cjs.expr))) { lowerer.unsupported( "SC1090", nameNode, @@ -7605,7 +7605,7 @@ function lowerCjsSingleValueExport( } const g = lowerer.globalsBySymbol.get(sym); if (g) { - if (g.mutable) { + if (g.mutable && !settledBeforeExport(lowerer, sym, rhs)) { lowerer.unsupported( "SC1090", rhs, @@ -12707,3 +12707,19 @@ function isAssertionInitializer(node: ts.Expression): boolean { while (ts.isParenthesizedExpression(e)) e = e.expression; return ts.isAsExpression(e) || ts.isTypeAssertion(e); } + +/** A `var` (or `let`) whose initialized declaration precedes the export + * statement in the module's top-level statements and that nothing ever + * reassigns: exporting it by reference reads the value Node copies. The + * legacy `var ns = ns || {}; …; module.exports = { ns }` shape. */ +function settledBeforeExport(lowerer: Lowerer, sym: ts.Symbol, exportNode: ts.Node): boolean { + const decl = sym.valueDeclaration; + if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return false; + const stmt = decl.parent?.parent; + if (!stmt || !ts.isVariableStatement(stmt) || !ts.isSourceFile(stmt.parent)) return false; + let exportStmt: ts.Node = exportNode; + while (exportStmt.parent !== undefined && !ts.isSourceFile(exportStmt.parent)) + exportStmt = exportStmt.parent; + if (exportStmt.parent !== stmt.parent || stmt.end > exportStmt.pos) return false; + return bindingNeverReassigned(lowerer, sym, decl); +} diff --git a/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts b/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts new file mode 100644 index 000000000..93a776f7e --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts @@ -0,0 +1,63 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function rewrite(source: string): string | null { + return nodeFrontendServices().namespaceDefaults(source, "/pkg/index.js"); +} + +/** Node prints the same for the original and the rewritten module. */ +function expectSameOutput(source: string): void { + const result = rewrite(source); + expect(typeof result).toBe("string"); + const dir = mkdtempSync(join(tmpdir(), "scriptc-namespace-defaults-")); + dirs.push(dir); + const outputs = [source, result!].map((text, i) => { + const file = join(dir, `m${i}.cjs`); + writeFileSync(file, text); + const r = spawnSync(process.execPath, [file], { encoding: "utf8" }); + expect(r.status).toBe(0); + return r.stdout; + }); + expect(outputs[1]).toBe(outputs[0]); +} + +test("a guarded namespace declaration becomes its default", () => { + const source = `var app = app || {}; +app.util = app.util || {}; +app.util.twice = function (x) { return 2 * x; }; +var cfg = cfg ?? { depth: 1 }; +console.log(app.util.twice(3), cfg.depth, Object.keys(app).join(",")); +`; + expect(rewrite(source)).toBe(`var app = {}; +app.util = app.util || {}; +app.util.twice = function (x) { return 2 * x; }; +var cfg = { depth: 1 }; +console.log(app.util.twice(3), cfg.depth, Object.keys(app).join(",")); +`); + expectSameOutput(source); +}); + +test("any other write, declaration or earlier mention keeps the text", () => { + // Redeclared: the second declaration reads the first one's object. + expect(rewrite("var ns = ns || {};\nvar ns = ns || {};\n")).toBeNull(); + // Assigned elsewhere, updated, or declared as a function. + expect(rewrite("var ns = ns || {};\nfunction reset() { ns = {}; }\n")).toBeNull(); + expect(rewrite("var n = n || 0;\nn++;\n")).toBeNull(); + // A local of the same name somewhere in the file. + expect(rewrite("var ns = ns || {};\nfunction f(ns) { return ns; }\n")).toBeNull(); + // Mentioned by an earlier statement. + expect(rewrite("console.log(typeof ns);\nvar ns = ns || {};\n")).toBeNull(); + // let and const read their own binding in its temporal dead zone. + expect(rewrite("let ns = ns || {};\n")).toBeNull(); + // Not the declared name on the left. + expect(rewrite("var ns = other || {};\n")).toBeNull(); +}); diff --git a/packages/compiler/src/frontend/npm-static-namespace-defaults.ts b/packages/compiler/src/frontend/npm-static-namespace-defaults.ts new file mode 100644 index 000000000..2e4bf046c --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-namespace-defaults.ts @@ -0,0 +1,138 @@ +/* The guarded namespace declaration of pre-module JavaScript: + * + * var ns = ns || {}; + * + * In a module (CommonJS or ES), `var` is module-local and hoisted, so the + * read in its own initializer sees `undefined` unless something writes the + * variable first. When nothing else in the file ever writes it (no other + * declaration, assignment or update of that name anywhere, no function or + * class of that name) and no earlier top-level statement mentions it, the + * initializer is its right operand: `var ns = {};`. Inference then types + * the namespace from that literal and the members later statements add, + * instead of `any` (the self-reference makes the checker give up). `??` + * is the same. Anything else keeps the original text. */ +import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; +import { applyEdits, unparen, type Edit } from "./npm-static-bundled-chunks.js"; + +/** A cheap probe: some `var X = X ||` or `?? ` may be present. */ +export function mayHoldNamespaceDefault(source: string): boolean { + return /\bvar\s+([A-Za-z_$][\w$]*)\s*=\s*\(?\s*\1\s*(\|\||\?\?)/.test(source); +} + +/** The module text with each guarded namespace declaration respelled as a + * plain one, or null when there is none. */ +export function rewriteNamespaceDefaults(sf: ts.SourceFile): string | null { + const stmts = sf.statements.nodes; + const edits: Edit[] = []; + for (let i = 0; i < stmts.length; i++) { + const stmt = stmts[i]!; + if (!ts.isVariableStatement(stmt)) continue; + const list = stmt.declarationList; + if ((list.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0) continue; + for (const decl of list.declarations.nodes) { + if (!ts.isIdentifier(decl.name) || decl.initializer === undefined) continue; + const name = decl.name.text; + const init = unparen(decl.initializer); + if ( + !ts.isBinaryExpression(init) || + (init.operatorToken.kind !== ts.SyntaxKind.BarBarToken && + init.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) + ) + continue; + const left = unparen(init.left); + if (!ts.isIdentifier(left) || left.text !== name) continue; + if (mentionedBefore(stmts, i, name) || writtenElsewhere(sf, name, decl)) continue; + const right = init.right; + edits.push({ + start: tokenStartOfNode(decl.initializer), + end: decl.initializer.end, + // A literal stays bare: inference types `var ns = {}` from the + // members later statements add (a parenthesized one it does not). + text: + ts.isObjectLiteralExpression(unparen(right)) || + ts.isArrayLiteralExpression(unparen(right)) + ? sf.text.slice(tokenStartOfNode(right), right.end) + : `(${sf.text.slice(tokenStartOfNode(right), right.end)})`, + }); + } + } + if (edits.length === 0) return null; + return applyEdits(sf.text, 0, sf.text.length, edits); +} + +/** Whether a top-level statement before index `at` mentions `name`. */ +function mentionedBefore(stmts: readonly ts.Statement[], at: number, name: string): boolean { + for (let i = 0; i < at; i++) { + let found = false; + ts.walkPreorder(stmts[i]!, (n) => { + if (found) return "stop"; + if (ts.isIdentifier(n) && n.text === name) found = true; + return undefined; + }); + if (found) return true; + } + return false; +} + +/** Whether anything other than `decl` declares or writes `name`. */ +function writtenElsewhere(sf: ts.SourceFile, name: string, decl: ts.VariableDeclaration): boolean { + let written = false; + ts.walkPreorder(sf, (n) => { + if (written) return "stop"; + if (n === decl.name) return undefined; + if (ts.isBinaryExpression(n) && isAssignmentOperator(n.operatorToken.kind)) { + if (isName(n.left, name)) written = true; + } else if ( + (ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) && + (n.operator === ts.SyntaxKind.PlusPlusToken || + n.operator === ts.SyntaxKind.MinusMinusToken) && + isName(n.operand, name) + ) { + written = true; + } else if ( + (ts.isFunctionDeclaration(n) || ts.isClassDeclaration(n)) && + n.name !== undefined && + n.name.text === name + ) { + written = true; + } else if ( + (ts.isVariableDeclaration(n) || ts.isParameter(n) || ts.isBindingElement(n)) && + n !== decl && + n.name !== undefined && + bindsName(n.name, name) + ) { + // Another declaration of the name (a redeclaration, or a local that + // shadows it) — keep the original text rather than reason about it. + written = true; + } else if ( + (ts.isForInStatement(n) || ts.isForOfStatement(n)) && + !ts.isVariableDeclarationList(n.initializer) && + isName(n.initializer, name) + ) { + written = true; + } + return undefined; + }); + return written; +} + +function bindsName(binding: ts.Node, name: string): boolean { + if (ts.isIdentifier(binding)) return binding.text === name; + let found = false; + ts.walkPreorder(binding, (n) => { + if (found) return "stop"; + if (ts.isIdentifier(n) && n.text === name) found = true; + return undefined; + }); + return found; +} + +function isName(node: ts.Node, name: string): boolean { + const n = unparen(node as ts.Expression); + return ts.isIdentifier(n) && n.text === name; +} + +function isAssignmentOperator(kind: ts.SyntaxKind): boolean { + return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment; +} diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index b00e14aa0..f51c6bbe2 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -505,6 +505,10 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow // literals they build. const enums = original === null ? null : services.enumIifes(original, path); if (enums !== null) original = enums; + // Guarded namespace declarations (`var ns = ns || {}`) read the + // hoisted `undefined`: the declaration is its default. + const namespaces = original === null ? null : services.namespaceDefaults(original, path); + if (namespaces !== null) original = namespaces; // A module that names its export object through a variable reaches // its members through `exports`. const exportsVar = original === null ? null : services.cjsExportsVar(original, path); @@ -517,6 +521,7 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow aliased !== null || fnExports !== null || enums !== null || + namespaces !== null || exportsVar !== null; if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index 14b366bfa..11fa0de72 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -14,6 +14,10 @@ import { } from "./npm-static-builtin-aliases.js"; import { mayHoldUmdWrapper, unwrapUmdFactory } from "./npm-static-umd.js"; import { mayHoldEnumIife, rewriteEnumIifes } from "./npm-static-enum-iife.js"; +import { + mayHoldNamespaceDefault, + rewriteNamespaceDefaults, +} from "./npm-static-namespace-defaults.js"; import { mayHoldTscBuiltinImport, unwrapTscBuiltinImports } from "./npm-static-tsc-helpers.js"; import { builtinPackageAliasFor } from "./package-shims.js"; import * as syntax from "./npm-static-declaration-syntax.js"; @@ -132,6 +136,13 @@ export class FrontendServices { if (!mayHoldEnumIife(source)) return null; return rewriteEnumIifes(this.parse(path, source, "js")); } + /** A package module with its guarded namespace declarations + * (`var ns = ns || {}`) respelled as plain ones (null: none). */ + namespaceDefaults(source: string, path: string): string | null { + this.ensureOpen(); + if (!mayHoldNamespaceDefault(source)) return null; + return rewriteNamespaceDefaults(this.parse(path, source, "js")); + } /** A package module with its requires of builtin-aliased polyfill * packages respelled as the builtins (null: none). */ builtinAliasRequires(source: string, path: string): string | null { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/legacy-namespaces/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/legacy-namespaces/main.ts.json new file mode 100644 index 000000000..7f50545d6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/legacy-namespaces/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/legacy-namespaces/main.ts" + ], + "diags": [] +} diff --git a/tests/fixtures/npm/cases/legacy-namespaces/main.ts b/tests/fixtures/npm/cases/legacy-namespaces/main.ts new file mode 100644 index 000000000..2f9483ba9 --- /dev/null +++ b/tests/fixtures/npm/cases/legacy-namespaces/main.ts @@ -0,0 +1,26 @@ +// A namespace-style library in the pre-module idiom (guarded namespace +// declarations, constructor functions with prototype methods, enums as +// objects, a revealing module, a handler registry, exported through +// `module.exports = { app }`), used from TypeScript. +import { app } from "legacy-ns"; + +const v = new app.geom.Vec(3, 4); +console.log(v.length(), v.add(new app.geom.Vec(1, 1)).scale(2).x, v instanceof app.geom.Vec); +console.log(Object.keys(app).join(","), Object.keys(app.math).join(","), Object.keys(app.geom.Kind).join(",")); +console.log(app.math.clamp(12, 0, 10), app.math.lerp(0, 10, 0.25), app.text.pad(7, 3), app.text.label(1, 42)); + +const shapes = []; +for (let i = 0; i < 12; i++) { + const center = new app.geom.Vec(app.math.lerp(-5, 5, (i % 4) / 3), app.math.lerp(-5, 5, Math.floor(i / 4) / 2)); + shapes.push(new app.geom.Shape(i % 3, center, 1 + (i % 4))); +} +let area = 0; +let hits = 0; +for (let step = 0; step < 200; step++) { + const probe = new app.geom.Vec(app.math.clamp(Math.sin(step) * 7, -5, 5), app.math.clamp(Math.cos(step * 0.7) * 7, -5, 5)); + for (const s of shapes) { + area += app.registry.run("area", s, probe); + hits += app.registry.run("hits", s, probe); + } +} +console.log(Math.round(area), hits, shapes[4].contains(shapes[4].center), shapes[0].area()); diff --git a/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.d.ts b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.d.ts new file mode 100644 index 000000000..16c9fa62a --- /dev/null +++ b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.d.ts @@ -0,0 +1,2 @@ +declare const app: any; +export { app }; diff --git a/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.js b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.js new file mode 100644 index 000000000..2efa926f5 --- /dev/null +++ b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/index.js @@ -0,0 +1,97 @@ +// A namespace-style geometry and text library in the pre-module idiom: +// guarded namespace declarations, constructor functions with prototype +// methods, enums as objects, a revealing module, and a static registry. +var app = app || {}; +app.math = app.math || {}; +app.geom = app.geom || {}; + +app.math.EPSILON = 1e-9; + +app.math.clamp = function (value, lo, hi) { + return value < lo ? lo : value > hi ? hi : value; +}; + +app.math.lerp = function (a, b, t) { + return a + (b - a) * t; +}; + +app.math.hypot = function (x, y) { + return Math.sqrt(x * x + y * y); +}; + +app.geom.Kind = { POINT: 0, CIRCLE: 1, RECT: 2 }; + +/** @constructor */ +app.geom.Vec = function (x, y) { + this.x = x; + this.y = y; +}; + +app.geom.Vec.prototype.add = function (other) { + return new app.geom.Vec(this.x + other.x, this.y + other.y); +}; + +app.geom.Vec.prototype.scale = function (k) { + return new app.geom.Vec(this.x * k, this.y * k); +}; + +app.geom.Vec.prototype.length = function () { + return app.math.hypot(this.x, this.y); +}; + +/** @constructor */ +app.geom.Shape = function (kind, center, size) { + this.kind = kind; + this.center = center; + this.size = size; +}; + +app.geom.Shape.prototype.area = function () { + switch (this.kind) { + case app.geom.Kind.CIRCLE: + return Math.PI * this.size * this.size; + case app.geom.Kind.RECT: + return this.size * this.size; + default: + return 0; + } +}; + +app.geom.Shape.prototype.contains = function (p) { + var d = p.add(this.center.scale(-1)).length(); + return d <= this.size + app.math.EPSILON; +}; + +app.text = (function () { + var digits = "0123456789"; + function pad(n, width) { + var s = ""; + var v = n; + do { + s = digits.charAt(v % 10) + s; + v = Math.floor(v / 10); + } while (v > 0); + while (s.length < width) s = "0" + s; + return s; + } + function label(kind, n) { + return (kind === app.geom.Kind.CIRCLE ? "c" : kind === app.geom.Kind.RECT ? "r" : "p") + pad(n, 4); + } + return { pad: pad, label: label }; +})(); + +app.registry = { + handlers: { + area: function (shape) { + return shape.area(); + }, + hits: function (shape, probe) { + return shape.contains(probe) ? 1 : 0; + }, + }, + run: function (name, shape, probe) { + return app.registry.handlers[name](shape, probe); + }, +}; + +module.exports = { app }; diff --git a/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/package.json b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/package.json new file mode 100644 index 000000000..cde18c613 --- /dev/null +++ b/tests/fixtures/npm/cases/legacy-namespaces/node_modules/legacy-ns/package.json @@ -0,0 +1 @@ +{ "name": "legacy-ns", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/legacy-namespaces/package.json b/tests/fixtures/npm/cases/legacy-namespaces/package.json new file mode 100644 index 000000000..140c08a1c --- /dev/null +++ b/tests/fixtures/npm/cases/legacy-namespaces/package.json @@ -0,0 +1 @@ +{ "name": "legacy-namespaces-case", "private": true, "type": "module" } From fe8ba8900f45c0d12615142f210feec282ce4a5f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:16:05 -0500 Subject: [PATCH 167/395] Start integer loops from the integers their starts carry --- .../backend/llvm/bytes-loop-versions.test.ts | 3 +- .../src/backend/llvm/bytes-loop-versions.ts | 5 +- .../src/backend/llvm/counted-loops.ts | 55 ++++++++++++++++++- .../src/backend/llvm/dense-array-loops.ts | 4 +- .../src/backend/llvm/emit-statements.ts | 13 ++--- .../8196-counted-loops-integer-starts.ts.json | 6 ++ .../8196-counted-loops-integer-starts.ts | 50 +++++++++++++++++ 7 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8196-counted-loops-integer-starts.ts.json create mode 100644 tests/corpus/8196-counted-loops-integer-starts.ts 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 0c0f1aaa0..096712c97 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -701,7 +701,8 @@ test("a two-counter loop versions the typed arrays it indexes at either counter" }; const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; const guard = body.slice(0, body.search(/^bytes\.version\.(fast|slow)\d+:$/m)); - expect(guard).toMatch(/icmp sge i64 %\S+, 0/); + // `i` starts at the literal 0, so its test compares the integer itself. + expect(guard).toMatch(/icmp sge i64 0, 0/); expect(guard).toMatch(/icmp slt i64 %\S+, %\S+/); const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; // The fast copy keeps only the `src[i + 1]` check, and no second guard. diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index 301f65de2..faec650a4 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -9,6 +9,7 @@ import { emitCountedLoopLimit, emitCountedLoopSafety, emitExactIntegerTest, + emitStartIntegerTest, } from "./counted-loops.js"; import { emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; import { f64Lit } from "./common.js"; @@ -1251,8 +1252,8 @@ export function emitBytesLoopVersionGuard( // access at either one is in bounds when `lo >= 0` and `hi < length`. const { loop } = version.paired; const [low, high] = [loop.low.start, loop.high.start].map((expr) => { - const tested = emitExactIntegerTest(host, host.emitExpr(expr).name, -SAFE, SAFE); - conditions.push(tested.ok); + const tested = emitStartIntegerTest(host, expr, -SAFE, SAFE); + if (tested.ok !== "true") conditions.push(tested.ok); return tested.integer; }); const nonNegative = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/counted-loops.ts b/packages/compiler/src/backend/llvm/counted-loops.ts index bb402d5b8..39f97717b 100644 --- a/packages/compiler/src/backend/llvm/counted-loops.ts +++ b/packages/compiler/src/backend/llvm/counted-loops.ts @@ -7,6 +7,7 @@ import { FOLLOWER_GUARD_LIMIT } from "../../ir/integer-loops.js"; import type { IrExpr } from "../../ir/ir.js"; import type { LlvmEmitterContext } from "./expr-context.js"; import { f64Lit } from "./common.js"; +import { exactInteger, widenInteger } from "./integer-values.js"; /** The caller has proved or guarded the direction's finite endpoint. Clamp * the opposite infinity before integer conversion. Fractional limits become @@ -121,6 +122,7 @@ export function emitPairedLoopSafety(host: LlvmEmitterContext, loop: IntegerPair } function andSafe(host: LlvmEmitterContext, safe: string, ok: string): string { + if (ok === "true") return safe; if (safe === "true") return ok; const both = host.B.tmp(); host.B.line(`${both} = and i1 ${safe}, ${ok}`); @@ -129,10 +131,25 @@ function andSafe(host: LlvmEmitterContext, safe: string, ok: string): string { /** An exact integer of at most 2^53 - 1 in magnitude, and not -0 (the bit * patterns of the value and its round trip agree). NaN and infinities fail - * the ordered range tests. */ + * the ordered range tests. A start with an integer form (an int32 local, + * `p0 - 1` of one) is an exact integer already: its range answers the test, + * or two integer compares do. */ function emitExactStart(host: LlvmEmitterContext, expr: IrExpr): string { const B = host.B; const start = host.emitExpr(expr); + const integer = exactInteger(host, start, expr); + if (integer) { + const limit = Number.MAX_SAFE_INTEGER; + if (integer.range.min >= -limit && integer.range.max <= limit) return "true"; + const wide = widenInteger(host, integer), + low = B.tmp(), + high = B.tmp(), + ok = B.tmp(); + B.line(`${low} = icmp sge i64 ${wide}, ${-limit}`); + B.line(`${high} = icmp sle i64 ${wide}, ${limit}`); + B.line(`${ok} = and i1 ${low}, ${high}`); + return ok; + } const low = B.tmp(), high = B.tmp(), raw = B.tmp(), @@ -188,3 +205,39 @@ export function emitExactIntegerTest( B.line(`${ok} = and i1 ${inRange}, ${exact}`); return { ok, integer }; } + +/** The i64 start of an integer counter: the integer form the start value + * carries (an int32 local, `p0 - 1` of one), or the double's conversion, + * which the loop's guard or proof makes exact. */ +export function emitCountedStart(host: LlvmEmitterContext, expr: IrExpr): string { + const value = host.emitExpr(expr); + const integer = exactInteger(host, value, expr); + if (integer) return widenInteger(host, integer); + const start = host.B.tmp(); + host.B.line(`${start} = fptosi double ${value.name} to i64`); + return start; +} + +/** emitExactIntegerTest of a start expression: when the value carries an + * integer form (an exact integer, never -0), its range answers the test or + * two integer compares do, and the integer is the form's. */ +export function emitStartIntegerTest( + host: LlvmEmitterContext, + expr: IrExpr, + min: number, + max: number, +): { ok: string; integer: string } { + const value = host.emitExpr(expr); + const integer = exactInteger(host, value, expr); + if (!integer) return emitExactIntegerTest(host, value.name, min, max); + const wide = widenInteger(host, integer); + if (integer.range.min >= min && integer.range.max <= max) return { ok: "true", integer: wide }; + const B = host.B; + const low = B.tmp(), + high = B.tmp(), + ok = B.tmp(); + B.line(`${low} = icmp sge i64 ${wide}, ${min}`); + B.line(`${high} = icmp sle i64 ${wide}, ${max}`); + B.line(`${ok} = and i1 ${low}, ${high}`); + return { ok, integer: wide }; +} diff --git a/packages/compiler/src/backend/llvm/dense-array-loops.ts b/packages/compiler/src/backend/llvm/dense-array-loops.ts index b806939b5..020ea582a 100644 --- a/packages/compiler/src/backend/llvm/dense-array-loops.ts +++ b/packages/compiler/src/backend/llvm/dense-array-loops.ts @@ -36,7 +36,7 @@ import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; import type { IntegerCountedForLoop, IntegerPairedForLoop } from "../../ir/integer-loops.js"; import type { LlvmEmitterContext } from "./expr-context.js"; import { loadArrCount, SCR_ARR_FIELD } from "./common.js"; -import { emitCountedLoopLimit, emitExactIntegerTest } from "./counted-loops.js"; +import { emitCountedLoopLimit, emitStartIntegerTest } from "./counted-loops.js"; export const INT32_MAX = 2147483647; @@ -453,7 +453,7 @@ function emitPairedDenseGuard( ): string { const B = host.B; const start = (expr: IrExpr, min: number, max: number) => - emitExactIntegerTest(host, host.emitExpr(expr).name, min, max); + emitStartIntegerTest(host, expr, min, max); const low = start(pair.lowStart, 0, INT32_MAX - 1); const high = start(pair.highStart, -1, INT32_MAX - 1); let valid = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 3d3aa6781..89e36a203 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -61,6 +61,7 @@ import { import { emitCountedLoopLimit, emitCountedLoopSafety, + emitCountedStart, emitPairedLoopSafety, } from "./counted-loops.js"; import { emitBytesLoopVersionGuard, type BytesLoopVersion } from "./bytes-loop-versions.js"; @@ -1112,9 +1113,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.entryAllocas.push( `${integerSlot} = alloca i64 ; integer induction ${emitter.currentLocals.get(countedLoop.localId)!.name}`, ); - const startNumber = emitter.emitExpr(countedLoop.start); - const start = B.tmp(); - B.line(`${start} = fptosi double ${startNumber.name} to i64`); + const start = emitCountedStart(emitter, countedLoop.start); B.line(`store i64 ${start}, ptr ${integerSlot}`); emitter.integerLoopBindings.set(countedLoop.localId, { slot: integerSlot, @@ -1130,9 +1129,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.entryAllocas.push( `${slot} = alloca i64 ; integer induction ${emitter.currentLocals.get(follower.localId)!.name}`, ); - const value = emitter.emitExpr(follower.start); - const followerStart = B.tmp(); - B.line(`${followerStart} = fptosi double ${value.name} to i64`); + const followerStart = emitCountedStart(emitter, follower.start); B.line(`store i64 ${followerStart}, ptr ${slot}`); followerSlots.push(slot); emitter.integerLoopBindings.set(follower.localId, { @@ -1149,9 +1146,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.entryAllocas.push( `${slot} = alloca i64 ; integer induction ${emitter.currentLocals.get(counter.localId)!.name}`, ); - const value = emitter.emitExpr(counter.start); - const start = B.tmp(); - B.line(`${start} = fptosi double ${value.name} to i64`); + const start = emitCountedStart(emitter, counter.start); B.line(`store i64 ${start}, ptr ${slot}`); pairedSlots.push(slot); const dense = LOOPS.of(emitter).denseLoop; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8196-counted-loops-integer-starts.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8196-counted-loops-integer-starts.ts.json new file mode 100644 index 000000000..6569a8326 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8196-counted-loops-integer-starts.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8196-counted-loops-integer-starts.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8196-counted-loops-integer-starts.ts b/tests/corpus/8196-counted-loops-integer-starts.ts new file mode 100644 index 000000000..d608bc4b5 --- /dev/null +++ b/tests/corpus/8196-counted-loops-integer-starts.ts @@ -0,0 +1,50 @@ +// Counted, paired and dense array loops whose start is an int32 value +// (an int32 array element, `p0 | 0` or one minus either): starts at -1, 0, +// inside the array, at its end, and at the int32 extremes, where the loop +// must not run or must stop at the array's end as Node does. Number arrays +// and an Int32Array both run their paired loops. +function reverse(pp: number[], p0: number): number { + let swaps = 0; + for (let i = 1, j = (p0 | 0) - 1; i < j; i++, j--) { + const t = pp[i]!; + pp[i] = pp[j]!; + pp[j] = t; + swaps++; + } + return swaps; +} + +function reverseBytes(values: Int32Array, p0: number): number { + let swaps = 0; + for (let i = 0, j = (p0 | 0) - 1; i < j; i++, j--) { + const t = values[i]!; + values[i] = values[j]!; + values[j] = t; + swaps++; + } + return swaps; +} + +function sumFrom(values: number[], start: number): number { + let total = 0; + for (let k = start - 1; k < values.length; k++) total += values[k] ?? 100; + return total; +} + +function countdown(values: Int32Array, from: number): number { + let total = 0; + for (let k = from; k >= 0; k--) total += values[k] ?? 1000; + return total; +} + +const starts: number[] = [0, 1, 2, 5, 9, 10, 11, 2147483647, -2147483648]; +const ints = new Int32Array(starts); +for (let s = 0; s < ints.length; s++) { + const p0 = ints[s]!; + const pp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + const swaps = p0 <= 11 ? reverse(pp, p0) : 0; + const bytes = new Int32Array(pp); + const byteSwaps = p0 <= 11 ? reverseBytes(bytes, p0) : 0; + console.log(p0, swaps, pp.join(""), p0 <= 11 && p0 >= -1 ? sumFrom(pp, p0) : "-", p0 <= 11 ? countdown(ints, p0) : "-"); + console.log(" bytes", byteSwaps, bytes.join("")); +} From 4a8a6567ca309fa6220a3945140c1d42751d44ff Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 14:44:50 -0700 Subject: [PATCH 168/395] Add the shape overlay: typed JavaScript from analysis-proven facts (opt-in) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shape overlay (DESIGN.md, approved route) rewrites the JavaScript of opted-in npm packages with JSDoc types the shape analysis proves: record typedefs for non-escaping literal sites (optional fields for spread sources), class field, parameter, return and local types, receiver casts, and branches the analysis proved unreachable replaced by checks. The lowering trusts only annotations carrying the overlay's marker; package JSDoc stays a hint (its type tags are neutralized in overlaid files). - Inferred-fact checks: every checked conversion lowered in an overlaid file records its site, and a failure throws a distinct internal error ("scriptc internal error: an inferred type was contradicted at … This is a bug in scriptc's shape analysis, not in the program's data") naming the opt-outs; SCRIPTC_SHAPE_CHECK_LOG makes checks log instead (validation mode). - Opt-outs: `--no-shape-overlay`, and `"scriptc": {"packages": {"": {"shapes": false}}}` per package. - Build report: SC6103 counts the declarations typed, the checks inserted and the branches replaced. - `x instanceof Promise` on plain data folds to false. The overlay runs only with SCRIPTC_SHAPE_OVERLAY=1 while it is new. Co-Authored-By: Claude Opus 5.5 --- packages/cli/src/bootstrap.ts | 6 + .../compiler/src/backend/llvm/expr-async.ts | 14 +- packages/compiler/src/cli/command.ts | 1 + packages/compiler/src/cli/usage.ts | 5 + packages/compiler/src/compile-types.ts | 4 + .../compiler/src/diagnostics/diagnostic.ts | 5 +- .../src/executable/executable-cache.ts | 3 + .../src/frontend/lowering/call-signatures.ts | 3 + .../lowering/expressions/relational.ts | 33 + .../frontend/lowering/generic-functions.ts | 6 + .../src/frontend/lowering/lower-classes.ts | 20 +- .../compiler/src/frontend/lowering/lowerer.ts | 12 + .../compiler/src/frontend/package-shims.ts | 18 + packages/compiler/src/frontend/pipeline.ts | 5 +- packages/compiler/src/frontend/program.ts | 27 +- .../src/frontend/shape-inference/analyze.ts | 160 ++++- .../src/frontend/shape-inference/domain.ts | 2 + .../src/frontend/shape-inference/index.ts | 144 +++- .../src/frontend/shape-inference/overlay.ts | 615 ++++++++++++++++++ packages/compiler/src/index.ts | 12 + .../src/ir/function-references.test.ts | 3 + packages/compiler/src/ir/ir.ts | 9 + packages/runtime/src/scr_dyn.h | 4 + packages/runtime/src/scr_json.c | 46 ++ tests/harness/shape-overlay.test.ts | 62 ++ 25 files changed, 1208 insertions(+), 11 deletions(-) create mode 100644 packages/compiler/src/frontend/shape-inference/overlay.ts create mode 100644 tests/harness/shape-overlay.test.ts diff --git a/packages/cli/src/bootstrap.ts b/packages/cli/src/bootstrap.ts index f14866a9c..485d62528 100644 --- a/packages/cli/src/bootstrap.ts +++ b/packages/cli/src/bootstrap.ts @@ -141,6 +141,11 @@ async function tryFastPath(): Promise { ...(values["detached-exit"] ? { detachedExit: true as const } : {}), ...(checker === undefined ? {} : { checker }), npmStatic, + // Mirrors the full compiler's key: shape inference's overlay is opt-in + // (SCRIPTC_SHAPE_OVERLAY=1) and --no-shape-overlay turns it off. + ...(process.env["SCRIPTC_SHAPE_OVERLAY"] === "1" && values["shape-overlay"] !== false + ? { shapeOverlay: true as const } + : {}), ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! }, target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${ helperObjectRoute ? "runtime-pack" : "driver-tu" @@ -212,6 +217,7 @@ async function coverageFastPath( values["provenance-sources"] || values["emit-ir"] || (values["npm-static"] ?? []).length > 0 || + values["shape-overlay"] === false || (values["external-types"] ?? []).length > 0 ) return null; diff --git a/packages/compiler/src/backend/llvm/expr-async.ts b/packages/compiler/src/backend/llvm/expr-async.ts index ee9ff2983..0e0191af4 100644 --- a/packages/compiler/src/backend/llvm/expr-async.ts +++ b/packages/compiler/src/backend/llvm/expr-async.ts @@ -394,12 +394,24 @@ export function emitSerializationExpr( } const dynV = emitBorrowedInput(host, e.value); const helper = host.dyn.dynCheckHelper(e.type, e.preserveRefs); - if (isDynTypedRefType(e.type)) return emitClassCheck(host, e.type, dynV.name, helper); + // A check of an analysis-proven fact (shape inference's overlay) + // names its site, so a failure reports a compiler bug. + const site = e.inferredSite !== undefined ? host.cstr(e.inferredSite) : null; + if (site !== null) { + host.declare(`declare void @scr_dyn_inferred_site(ptr)`); + B.line(`call void @scr_dyn_inferred_site(ptr ${site})`); + } + if (isDynTypedRefType(e.type)) { + const checked = emitClassCheck(host, e.type, dynV.name, helper); + if (site !== null) B.line(`call void @scr_dyn_inferred_site(ptr null)`); + return checked; + } const ty = host.llType(e.type); const t = B.tmp(); B.line( `${t} = call ${ty === "i1" ? "zeroext i1" : ty} @${helper}(ptr ${dynV.name}, ptr null)`, ); + if (site !== null) B.line(`call void @scr_dyn_inferred_site(ptr null)`); const out = host.own({ name: t, type: e.type }); host.emitPendingCheck(); return out; diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 57b15349e..a57885ca8 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -534,6 +534,7 @@ async function main(args: string[], host: CliHost): Promise { ? {} : { profileUse: resolve(values["profile-use"]) }), ...(npmStatic !== undefined ? { npmStatic } : {}), + ...(values["shape-overlay"] === false ? { shapeOverlay: false } : {}), ...(ffiProfilePath !== undefined ? { ffiProfilePath } : {}), ...(printNativeLinkInfo ? { nativeLinkInfo: true } : {}), ...(tier !== undefined ? { tier } : {}), diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index 01afbdd41..292755981 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -87,6 +87,10 @@ Options: "pkg..." also names every package pkg depends on). Packages that fail preflight are refused with a diagnostic. Eligible dependencies compile automatically. + --no-shape-overlay + compile npm packages' JavaScript without the types shape + inference proves (per package: "scriptc": {"packages": + {"": {"shapes": false}}} in package.json) --provenance-sources EXPERIMENTAL: compile npm dependencies from their provenance-attested SOURCE (fetched at the attested @@ -138,6 +142,7 @@ export const CLI_OPTIONS = { sanitize: { type: "boolean", default: false }, ffi: { type: "string" }, "npm-static": { type: "string", multiple: true }, + "shape-overlay": { type: "boolean" }, "provenance-sources": { type: "boolean", default: false }, "external-types": { type: "string", multiple: true }, tier: { type: "string" }, diff --git a/packages/compiler/src/compile-types.ts b/packages/compiler/src/compile-types.ts index c50931c6a..2c9ba44d7 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -55,6 +55,10 @@ export interface CompileBaseOptions { /** Eligible packages compile automatically. Explicit names override automatic selection heuristics. */ npmStatic?: readonly string[] | "auto"; + /** Shape inference's typed overlay for opted-in packages' JavaScript. + * False disables it (`--no-shape-overlay`); a package opts out with + * `"scriptc": { "packages": { "": { "shapes": false } } }`. */ + shapeOverlay?: boolean; /** Outbound native FFI manifest. Its signature-only TypeScript bindings * lower to direct C ABI calls. Source outputs retain those declarations; * archive/system-library inputs join only an executable link. */ diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 2fd8b6fb0..34657008d 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -64,7 +64,10 @@ * parses date strings with V8's grammar. SC61xx are performance * notes, also warnings (coverage --fail-on=dynamic opts in): * likely-hot functions that use untyped values (SC6101, - * diagnostics/dynamic-tier.ts) + * diagnostics/dynamic-tier.ts), package files whose projected + * declaration types were dropped (SC6102), and shape inference's + * overlay report: what it typed and how many inferred facts the + * build checks (SC6103, frontend/shape-inference) * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 754c20648..2bfed90f3 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -152,6 +152,9 @@ export interface EarlyExecutableCacheOptions { /** Omitted unless SCRIPTC_YOUNG_NURSERY=1 (backend/young-nursery.ts). */ youngNursery?: true; npmStatic: readonly string[] | "auto" | null; + /** Omitted unless shape inference's overlay compiles the packages + * (frontend/shape-inference). */ + shapeOverlay?: true; /** Raw manifest identity: path and bytes. Native archives remain under the * stricter native cache's independent dependency validation. */ ffiProfile: { path: string; bytes: Uint8Array } | null; diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index c9fd954b5..9d8096c62 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -1,3 +1,4 @@ +import { shapeOverlayAnnotated } from "../shape-inference/index.js"; import { typedArgumentsCandidate, typedArgumentsShapes, @@ -181,6 +182,8 @@ export function checkedJsDocumentedParameter( lowerer: Lowerer, param: ts.ParameterDeclaration, ): boolean { + if (param.parent !== undefined && shapeOverlayAnnotated(param.parent) && documentedParameter(param)) + return true; return lowerer.checkedJsFile(ts.getSourceFileOfNode(param)) && documentedParameter(param); } diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 47030777a..9477fb4b3 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -420,6 +420,22 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc loc, }; } + // Plain data (records, arrays, strings, numbers, booleans, and unions + // of them) is never a promise: false after the operand's evaluation. + const neverPromise = (t: IrType): boolean => + NEVER_PROMISE_KINDS.has(t.kind) || + (t.kind === "union" && (lowerer.unions.get(t.unionId)?.arms ?? [t]).every(neverPromise)); + if (neverPromise(left.type)) { + const no: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; + if (isSafeToDiscard(left)) return no; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: left, loc }], + result: no, + type: BOOL, + loc, + }; + } lowerer.unsupported( "SC1090", expr, @@ -2271,3 +2287,20 @@ function nodeDeclaredMember(lowerer: Lowerer, t: ts.Type, key: string): boolean ); }); } + +const NEVER_PROMISE_KINDS: ReadonlySet = new Set([ + "record", + "array", + "string", + "f64", + "bool", + "bigint", + "date", + "map", + "set", + "regex", + "bytes", + "symbol", + "nullT", + "undefinedT", +]); diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 7896da578..b60cb4a60 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1,3 +1,5 @@ +import { shapeOverlayAnnotated } from "../shape-inference/index.js"; +import { documentedParameter } from "./call-signatures.js"; import { staticNameBindings } from "./static-function-names.js"; import { positionTag } from "./structural-positions.js"; import { keyedHelperName } from "./stable-names.js"; @@ -1855,10 +1857,14 @@ export function implicitAnyParamSymbolsOf( // The variadic-`arguments` form keeps its dynRest story whole. if (bodyReadsArguments(decl)) return null; let hasImplicitParams = false; + const overlayTyped = shapeOverlayAnnotated(decl); const out = decl.parameters.nodes.map((param): ts.Symbol | null => { if (!ts.isIdentifier(param.name)) return null; if (param.dotDotDotToken || param.questionToken || param.initializer) return null; if (param.name.text === "this") return null; + // A parameter typed by shape inference's overlay is a proven type, + // not an implicit type parameter. + if (overlayTyped && documentedParameter(param)) return null; const t = lowerer.typeOf(param.name); const broadFunction = lowerer.checker.typeToString(t) === "Function"; const broadArray = lowerer.checkerAnyArrayType(t); diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b829b6ddc..573bb0c69 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1,4 +1,5 @@ import { lowerAsyncLocalStorageOptions } from "./builtins/async-context.js"; +import { shapeOverlayAnnotated } from "../shape-inference/index.js"; import { DATE_TIME_FIELD, dateRootOf, @@ -1152,7 +1153,11 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E ) return DYN; } - if (type.kind === "array" || type.kind === "record") return DYN; + // Shape inference's overlay types a field as a record or array only when + // the analysis proves its values never reach code that could observe a + // copy, so those fields keep their native layout. + if ((type.kind === "array" || type.kind === "record") && !shapeOverlayAnnotated(expression.parent?.parent ?? expression)) + return DYN; // JavaScript documentation cannot guarantee that a call or an indexed // read returns a present number. Numeric fields receiving those values // must retain undefined, so later arithmetic performs JS coercion (NaN). @@ -3316,6 +3321,7 @@ export function collectClassShapeInner( !functionLocalClass(decl) && ts.isIdentifier(member.name) && !lowerer.virtualJsMethods.has(member) && + !lowerer.checkedJsOverrideMethods.has(member) && !lowerer.prototypeMethodAccesses.has(member.name.text) && inst === undefined && decl.typeParameters === undefined && @@ -7597,6 +7603,16 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if (!ts.isIdentifier(member.name)) continue; const property = lowerer.checker.getPropertyOfType(base, member.name.text); for (const declaration of property ? property.declarations : []) { + if ( + ts.isMethodDeclaration(declaration) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && + checkedJsOverride(lowerer, declaration, member) + ) { + // A checked override pair dispatches through the vtable, + // so neither side may become an implicit generic. + lowerer.checkedJsOverrideMethods.add(declaration); + lowerer.checkedJsOverrideMethods.add(member); + } if ( ts.isMethodDeclaration(declaration) && isJsSourceFile(ts.getSourceFileOfNode(declaration)) && @@ -7683,7 +7699,7 @@ function checkedJsOverride( ): boolean { return [base, member].every( (method) => - lowerer.checkedJsFile(ts.getSourceFileOfNode(method)) && + (lowerer.checkedJsFile(ts.getSourceFileOfNode(method)) || shapeOverlayAnnotated(method)) && method.parameters.nodes.every(documentedParameter), ); } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 988b4b804..f8ad0a559 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,5 @@ +import { markInferredFactChecks, shapeOverlayFileNames } from "../shape-inference/index.js"; +import { npmStaticPackageOfPath } from "../npm-static.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject } from "./module-namespace-elements.js"; @@ -3095,6 +3097,8 @@ export class Lowerer { /** Inferred JS methods participating in an override chain keep a vtable * ABI instead of call-site specialization. Filled before class collection. */ readonly virtualJsMethods = new Set(); + /** Methods of checked-JavaScript override pairs (vtable dispatch). */ + readonly checkedJsOverrideMethods = new Set(); /** Descendant writes must widen the original JS field before collecting * any class layout; base and derived instances share its storage ABI. */ readonly inheritedJsFieldWrites = new Map< @@ -7885,6 +7889,7 @@ export class Lowerer { // every producer and consumer agrees. Programs with no unregistered // reference are untouched — byte-stability holds. checkRequireBindingAccesses(this, functions); + markInferredFactChecks(functions, this.program); const module: IrModule | null = this.diags.length > 0 ? null @@ -7900,6 +7905,13 @@ export class Lowerer { ...(this.usesWorkers ? { workers: true } : {}), ...(this.usesLifetimeRegions ? { lifetimeRegions: true as const } : {}), ...(this.declBoundary ? { declBoundary: true as const } : {}), + ...(shapeOverlayFileNames().length > 0 + ? { + inferredFactFiles: shapeOverlayFileNames() + .map((file) => ({ file, pkg: npmStaticPackageOfPath(file) ?? "" })) + .sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0)), + } + : {}), ...(this.threadSlotKeys.size > 0 ? { threadSlotKeys: [...this.threadSlotKeys].sort() } : {}), diff --git a/packages/compiler/src/frontend/package-shims.ts b/packages/compiler/src/frontend/package-shims.ts index 10beeaae8..d26579719 100644 --- a/packages/compiler/src/frontend/package-shims.ts +++ b/packages/compiler/src/frontend/package-shims.ts @@ -202,6 +202,7 @@ let unloadedPackages: ReadonlySet = new Set(); * map of the entry's nearest package.json. Called once per program load. */ export function configurePackageShims(entryPath: string): void { const packages = new Set(); + const noShapes = new Set(); for (let dir = dirname(resolve(entryPath)); ;) { const text = trackedReadFile(join(dir, "package.json")); if (text !== null) { @@ -222,6 +223,14 @@ export function configurePackageShims(entryPath: string): void { if (map !== null && typeof map === "object") { for (const [name, kind] of Object.entries(map as Record)) { if (kind === "unloaded") packages.add(name); + // `{ "shapes": false }`: compile the package without shape + // inference's typed overlay. + if ( + kind !== null && + typeof kind === "object" && + (kind as Record)["shapes"] === false + ) + noShapes.add(name); } } break; @@ -231,6 +240,15 @@ export function configurePackageShims(entryPath: string): void { dir = parent; } unloadedPackages = packages; + shapelessPackages = noShapes; +} + +let shapelessPackages: ReadonlySet = new Set(); + +/** Packages the project compiles without shape inference's overlay + * (`"scriptc": { "packages": { "": { "shapes": false } } }`). */ +export function isShapelessPackage(name: string): boolean { + return shapelessPackages.has(name); } /** True for a package name the registry implements or the project marks diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index c2f528b25..92bafe54d 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -1,3 +1,4 @@ +import { shapeOverlayReport } from "./shape-inference/index.js"; /** Shared frontend orchestration. Hosts supply a program loader so Node * and native callers use the same npm selection, preflight and lowering. */ import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; @@ -699,8 +700,8 @@ function loadFrontend( }); // The declaration-typed boundary's notes (SC6102) join the // performance warnings; they never fail a build. - const notes = finalLoad.declBoundaryNotes; - if (notes === undefined) return lowered; + const notes = [...(finalLoad.declBoundaryNotes ?? []), ...shapeOverlayReport(finalLoad.entry.fileName)]; + if (notes.length === 0) return lowered; return { ...lowered, performance: [...notes, ...(lowered.performance ?? [])] }; }, npmStatic: statuses, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 8e582af89..daeb61ded 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -140,7 +140,12 @@ import { } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; -import { reportShapes } from "./shape-inference/index.js"; +import { + reportShapes, + resetShapeOverlay, + shapeOverlayEnabled, + shapeOverlayer, +} from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { containsJsx, @@ -857,6 +862,9 @@ function loadProgram7( /** The declaration-typed boundary's projection (decl-projection.ts): * answers rewritten user files for the lowering world, or null. */ projector?: (program: ts.Program) => DeclProjection | null, + /** Shape inference's typed overlay (shape-inference/overlay.ts): + * rewritten package JavaScript for the lowering world, or null. */ + overlayer?: (program: ts.Program) => ReadonlyMap | null, ): LoadResult & { disposeAll: () => void } { const config = adoptProjectConfig7(host, entryPath); const configuredPaths = config.options["paths"]; @@ -998,6 +1006,22 @@ function loadProgram7( } } } + // Shape inference's overlay: package files typed from proven facts. The + // tsc gate keeps reading the files as written. + // A load without the overlay must not see an earlier load's overlay + // files (the registry is per process). + resetShapeOverlay(); + if (overlayer !== undefined && jsx.refusal === null) { + const overlay = overlayer(program); + if (overlay !== null && overlay.size > 0) { + const before = program; + for (const [file, text] of overlay) host.addVirtualFile(file, text); + program = host.createProgram([...programRoots, overridesDtsPath()], options); + if (tscWorld === undefined) + tscWorld = { program: before, diagnostics: ts.getPreEmitDiagnostics(before), projectWorld: null }; + else if (tscWorld.program !== before) before.dispose(); + } + } const entry = program.getSourceFile(entryPath); if (!entry) throw new Error(`could not load ${entryPath}`); const externalTypeSpecifiersByFile = externalTypeFileClosure7(program, externalTypes); @@ -1260,6 +1284,7 @@ export function loadProgram( externalTypes, npmStaticPackages.length > 0, projector, + npmStaticPackages.length > 0 && shapeOverlayEnabled() ? shapeOverlayer : undefined, ); // Shape inference, stage S1: a report only (SCRIPTC_DEBUG_SHAPES). const shapes = process.env["SCRIPTC_DEBUG_SHAPES"]; diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 8b1101da2..fe87a8c53 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -70,6 +70,8 @@ export interface LiteralSite { readonly kind: "object" | "array"; /** Static keys in creation order; null when a spread or computed key makes them unknown. */ readonly keys: string[] | null; + /** The literal's own static keys (without spread contributions). */ + readonly ownKeys: string[]; readonly hasAccessor: boolean; readonly hasProto: boolean; readonly hasComputedKey: boolean; @@ -129,6 +131,15 @@ export interface ShapeAnalysis { readonly escapedFunctions: ReadonlySet; /** ES5 constructors: each function's default prototype object. */ readonly protoOf: ReadonlyMap; + /** Every value a declaration (parameter, variable, binding element) holds, + * merged over the analysis contexts it was analyzed in. */ + declValues(decl: Node): Set; + /** The classes a class declaration's chain contains, base last. */ + classChain(decl: Node): Node[]; + /** `if` statements in walked code whose condition is known: the branch + * whose condition values never allowed it is absent from `walkedBranches`. */ + readonly branchIfs: ReadonlySet; + readonly walkedBranches: ReadonlySet; } /** Array.prototype members: other names on an array are own properties. */ @@ -358,6 +369,10 @@ class Analyzer { nodeLabel: this.nodeLabel, escapedFunctions: this.escapedFunctionCalls, protoOf: this.protoOf, + declValues: (decl) => this.declValues(decl), + classChain: (decl) => this.classChain(decl), + branchIfs: this.branchIfs, + walkedBranches: this.walkedBranches, }; } @@ -450,6 +465,78 @@ class Analyzer { return p; } + /** A boolean literal: labeled `boolean` like any boolean, but its + * truthiness is known, so `if` statements on it walk one branch. */ + boolLiteral(value: boolean): Atom { + return this.atoms.intern(`prim:boolean:${value}`, () => ({ + kind: "prim", + label: "boolean", + literal: value, + })); + } + + /** The analysis environment a deferred walk restores. */ + private env(): WalkEnv { + return { + ctx: this.ctx, + ctxRoot: this.ctxRoot, + currentFn: this.currentFn, + loopDepth: this.loopDepth, + }; + } + + private inEnv(env: WalkEnv, walk: () => void): void { + const saved = this.env(); + this.ctx = env.ctx; + this.ctxRoot = env.ctxRoot; + this.currentFn = env.currentFn; + this.loopDepth = env.loopDepth; + try { + walk(); + } finally { + this.ctx = saved.ctx; + this.ctxRoot = saved.ctxRoot; + this.currentFn = saved.currentFn; + this.loopDepth = saved.loopDepth; + } + } + + readonly branchIfs = new Set(); + readonly walkedBranches = new Set(); + /** Ifs whose branches wait for condition values; at the fixpoint, an + * `if` whose condition node stayed empty walks every waiting branch. */ + private readonly pendingIfs: PendingIf[] = []; + + /** An `if` walks a branch once a condition value allows it: the then + * branch on a value that may be truthy, the else branch on one that may + * be falsy. A branch no value allows is never walked, so its effects + * (returns, calls, writes) stay out of the facts; the overlay turns it + * into a checked internal error. */ + private ifStatement(s: ts.IfStatement): void { + const cond = this.expr(s.expression); + this.branchIfs.add(s); + const pending: PendingIf = { + s, + cond, + env: this.env(), + thenDone: false, + elseDone: s.elseStatement === undefined, + }; + this.pendingIfs.push(pending); + this.on(cond, (a) => { + if (!pending.thenDone && mayBeTruthy(a)) this.walkBranch(pending, "then"); + if (!pending.elseDone && mayBeFalsy(a)) this.walkBranch(pending, "else"); + }); + } + + private walkBranch(pending: PendingIf, which: "then" | "else"): void { + const branch = which === "then" ? pending.s.thenStatement : pending.s.elseStatement!; + if (which === "then") pending.thenDone = true; + else pending.elseDone = true; + this.walkedBranches.add(branch); + this.inEnv(pending.env, () => this.stmt(branch)); + } + builtin(path: string, receiver?: Atom): Atom { const key = receiver === undefined ? `builtin:${path}` : `builtin:${path}@${receiver.id}`; return this.atoms.intern(key, () => ({ kind: "builtin", label: path, path, receiver })); @@ -718,6 +805,24 @@ class Analyzer { return `${inside ? this.ctx : ""}|${this.nodeKey(node)}`; } + private declIndex: Map | null = null; + declValues(decl: Node): Set { + if (this.declIndex === null) { + this.declIndex = new Map(); + for (const [key, id] of this.bindings) { + const k = Number(key.slice(key.lastIndexOf("|") + 1)); + let l = this.declIndex.get(k); + if (l === undefined) this.declIndex.set(k, (l = [])); + l.push(id); + } + } + const out = new Set(); + const k = this.nodeKeys.get(decl); + if (k === undefined) return out; + for (const id of this.declIndex.get(k) ?? []) for (const a of this.nodes[id]!.atoms) out.add(a); + return out; + } + receiverCtx(atom: Atom): string { return atom.kind === "instance" || atom.kind === "class" ? `C${atom.id}` : ""; } @@ -1223,6 +1328,10 @@ class Analyzer { this.expr(s.expression); return; } + if (ts.isIfStatement(s) && this.currentFn !== null) { + this.ifStatement(s); + return; + } // Blocks, ifs, switches, labels: walk children, evaluating expressions. ts.forEachChild(s, (child) => { if (ts.isExpression(child)) this.expr(child); @@ -1317,8 +1426,9 @@ class Analyzer { case ts.SyntaxKind.BigIntLiteral: return this.constNode(this.prim("bigint")); case ts.SyntaxKind.TrueKeyword: + return this.constNode(this.boolLiteral(true)); case ts.SyntaxKind.FalseKeyword: - return this.constNode(this.prim("boolean")); + return this.constNode(this.boolLiteral(false)); case ts.SyntaxKind.NullKeyword: return this.constNode(this.prim("null")); case ts.SyntaxKind.RegularExpressionLiteral: @@ -1750,6 +1860,7 @@ class Analyzer { node: e, kind: "object", keys: keysKnown ? keys : null, + ownKeys: keys, hasAccessor, hasProto, hasComputedKey, @@ -1916,6 +2027,7 @@ class Analyzer { node: e, kind: "array", keys: null, + ownKeys: [], hasAccessor: false, hasProto: hasHole, hasComputedKey: false, @@ -3202,6 +3314,21 @@ class Analyzer { const before = this.steps; for (const c of this.lateCopies) c(); this.solve(); + // A condition with no values at the fixpoint is one the analysis + // could not evaluate: walk both of its branches. + let walked = false; + for (const p of this.pendingIfs) { + if (this.nodes[p.cond]!.atoms.size > 0) continue; + if (!p.thenDone) { + this.walkBranch(p, "then"); + walked = true; + } + if (!p.elseDone) { + this.walkBranch(p, "else"); + walked = true; + } + } + if (walked) continue; if (this.steps === before || this.budgetExceeded) return; } } @@ -3223,4 +3350,35 @@ function cjsExportName(decl: Node): string | null { ) return decl.left.name.text; return null; + } + +interface WalkEnv { + readonly ctx: string; + readonly ctxRoot: Node | null; + readonly currentFn: FunctionInfo | null; + readonly loopDepth: number; +} + +interface PendingIf { + readonly s: ts.IfStatement; + readonly cond: number; + readonly env: WalkEnv; + thenDone: boolean; + elseDone: boolean; +} + +/** Whether a value of the atom may be truthy (only `null`, `undefined` and + * the literal `false` never are; ⊤ may be anything). */ +function mayBeTruthy(a: Atom): boolean { + if (a.kind !== "prim") return true; + if (a.label === "null" || a.label === "undefined") return false; + return a.literal !== false; +} + +/** Whether a value of the atom may be falsy (objects, arrays, functions, + * classes, promises and collections never are). */ +function mayBeFalsy(a: Atom): boolean { + if (a.kind === "top") return true; + if (a.kind !== "prim") return false; + return a.literal !== true; } diff --git a/packages/compiler/src/frontend/shape-inference/domain.ts b/packages/compiler/src/frontend/shape-inference/domain.ts index d90a83cf2..21e52346c 100644 --- a/packages/compiler/src/frontend/shape-inference/domain.ts +++ b/packages/compiler/src/frontend/shape-inference/domain.ts @@ -38,6 +38,8 @@ export interface Atom { /** function atoms: the analysis context of the function's clone (a * method's receiver class), "" for the context-insensitive default. */ readonly ctx?: string | undefined; + /** prim `boolean` atoms of a literal: its value. */ + readonly literal?: boolean | undefined; } export class AtomTable { diff --git a/packages/compiler/src/frontend/shape-inference/index.ts b/packages/compiler/src/frontend/shape-inference/index.ts index 402a788b1..e2f7add04 100644 --- a/packages/compiler/src/frontend/shape-inference/index.ts +++ b/packages/compiler/src/frontend/shape-inference/index.ts @@ -1,7 +1,13 @@ /* Shape inference for untyped npm JavaScript - * (results/design-shape-inference/DESIGN.md). Stage S1: report only. */ + * (results/design-shape-inference/DESIGN.md): the report (S1) and the typed + * overlay (overlay.ts). */ import type * as ts from "../ts7/adapter.js"; -import { analyzeShapes } from "./analyze.js"; +import type { IrFunction } from "../../ir/ir.js"; +import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import { npmStaticPackageOfPath } from "../npm-static.js"; +import { isShapelessPackage } from "../package-shims.js"; +import { analyzeShapes, isPackageJsFile } from "./analyze.js"; +import { OVERLAY_MARK, planShapeOverlay } from "./overlay.js"; import { buildShapeReport } from "./report.js"; import { computeConstantGraphs, constantGraphReport } from "./constant-graphs.js"; @@ -10,7 +16,8 @@ export { buildShapeReport } from "./report.js"; /** SCRIPTC_DEBUG_SHAPES: `1` reports package code in hot functions, * `verbose` every package site, `all` user files too, any other value only - * files whose path contains it. Writes to stderr; changes nothing else. */ + * files whose path contains it. Writes to stderr; changes nothing else. The + * build cache does not key on the variable: use SCRIPTC_NO_CACHE=1. */ export function reportShapes(program: ts.Program, filter: string): void { const start = performance.now(); const analysis = analyzeShapes(program, program.getTypeChecker()); @@ -22,3 +29,134 @@ export function reportShapes(program: ts.Program, filter: string): void { const ms = (performance.now() - start).toFixed(0); process.stderr.write(`[shapes] ${ms} ms\n${lines.map((l) => `[shapes] ${l}`).join("\n")}\n`); } + +/* The overlay's files for the current load: the lowering treats them as + * checked JavaScript (their JSDoc types are the analysis's proven facts). */ +let overlayFiles: ReadonlySet = new Set(); +let overlayAnnotations = 0; +let overlayDeadBranches = 0; + +export function isShapeOverlayFile(fileName: string): boolean { + return overlayFiles.has(fileName); +} + +/** Whether a statement or declaration carries an overlay annotation. */ +export function shapeOverlayAnnotated(node: ts.Node): boolean { + const sf = node.getSourceFile(); + if (!overlayFiles.has(sf.fileName)) return false; + return sf.text.slice(node.pos, node.getStart(sf)).includes(OVERLAY_MARK); +} + +export function shapeOverlayFileNames(): readonly string[] { + return [...overlayFiles]; +} + +export function shapeOverlayAnnotationCount(): number { + return overlayAnnotations; +} + +export function resetShapeOverlay(): void { + overlayFiles = new Set(); + overlayAnnotations = 0; + overlayDeadBranches = 0; + inferredFactChecks = 0; +} + +/** Whether the overlay runs. While it is new it is opt-in + * (SCRIPTC_SHAPE_OVERLAY=1); `--no-shape-overlay` (the `shapeOverlay: + * false` build option) sets the variable to 0 for the build. */ +export function shapeOverlayEnabled(): boolean { + return process.env["SCRIPTC_SHAPE_OVERLAY"] === "1"; +} + +/** loadProgram7's overlayer: analyze, plan, and register the overlay files. */ +export function shapeOverlayer(program: ts.Program): ReadonlyMap | null { + resetShapeOverlay(); + const analysis = analyzeShapes(program, program.getTypeChecker()); + if (analysis.budgetExceeded) return null; + const report = buildShapeReport(analysis, "1"); + const packageFiles = analysis.files.filter((sf) => { + if (!isPackageJsFile(sf)) return false; + const pkg = npmStaticPackageOfPath(sf.fileName); + return pkg === null || !isShapelessPackage(pkg); + }); + const overlay = planShapeOverlay(analysis, report.sites, packageFiles); + overlayFiles = new Set(overlay.files.keys()); + overlayAnnotations = overlay.annotations; + overlayDeadBranches = overlay.deadBranches; + if (process.env["SCRIPTC_DEBUG_SHAPE_OVERLAY"] !== undefined) { + for (const [file, text] of overlay.files) + process.stderr.write(`[shape-overlay] ${file}\n${text}\n`); + } + return overlay.files; +} + +/** Marks the checked conversions lowered in overlay files: each checks an + * analysis-proven fact, so its failure names the site as an analysis bug. */ +export function markInferredFactChecks( + functions: readonly IrFunction[], + program: ts.Program, +): number { + if (overlayFiles.size === 0) return 0; + let count = 0; + const lines = new Map number>(); + const lineOf = (file: string, pos: number): number => { + let f = lines.get(file); + if (f === undefined) { + const sf = program.getSourceFile(file); + f = sf === undefined ? () => 0 : (p: number) => sf.getLineAndCharacterOfPosition(p).line + 1; + lines.set(file, f); + } + return f(pos); + }; + const visit = (x: unknown): void => { + if (Array.isArray(x)) { + for (const v of x) visit(v); + return; + } + if (x === null || typeof x !== "object") return; + const node = x as { + kind?: unknown; + loc?: { file: string; start: number }; + inferredSite?: string; + }; + if (node.kind === "dynCheck" && node.loc !== undefined && overlayFiles.has(node.loc.file)) { + const pkg = npmStaticPackageOfPath(node.loc.file) ?? ""; + const rel = node.loc.file.split("/node_modules/").pop() ?? node.loc.file; + node.inferredSite = `${pkg} ${rel}:${lineOf(node.loc.file, node.loc.start)}`; + count++; + } + for (const v of Object.values(x)) if (v !== null && typeof v === "object") visit(v); + }; + for (const fn of functions) visit(fn.body); + inferredFactChecks = count; + return count; +} + +let inferredFactChecks = 0; + +/** Inferred-fact checks inserted by the last build (the build report). */ +export function shapeInferredFactCheckCount(): number { + return inferredFactChecks; +} + +/** SC6103, the overlay's build report: what the overlay typed and how many + * checks of inferred facts the build inserted. A note, never a failure. */ +export function shapeOverlayReport(entryFile: string): ScrDiagnostic[] { + if (overlayFiles.size === 0) return []; + const packages = new Set([...overlayFiles].map((f) => npmStaticPackageOfPath(f) ?? f)); + return [ + { + code: "SC6103", + message: + `shape inference typed ${overlayAnnotations} declarations in ${overlayFiles.size} files of ${packages.size} ` + + `package${packages.size === 1 ? "" : "s"} (${[...packages].sort().join(", ")}); the build checks ` + + `${inferredFactChecks} inferred fact${inferredFactChecks === 1 ? "" : "s"} at run time` + + (overlayDeadBranches > 0 + ? ` and replaces ${overlayDeadBranches} branch${overlayDeadBranches === 1 ? "" : "es"} it proved unreachable with checks` + : ""), + loc: { file: entryFile, start: 0, end: 0 }, + hint: "a failing check reports a scriptc analysis bug; --no-shape-overlay compiles without the overlay", + }, + ]; +} diff --git a/packages/compiler/src/frontend/shape-inference/overlay.ts b/packages/compiler/src/frontend/shape-inference/overlay.ts new file mode 100644 index 000000000..28b5336cf --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/overlay.ts @@ -0,0 +1,615 @@ +/* Shape inference: the typed overlay (results/design-shape-inference, + * maintainer decision 2026-10-23). The analysis's proven facts become JSDoc + * types in a rewritten copy of each package JavaScript file, and the + * lowering treats the rewritten file like a checked JavaScript file, so the + * static tier compiles the package's hot code from those types. + * + * Only proven facts are written: a position whose value set contains ⊤ + * (anything from islands, JSON.parse, the network or the file system, + * reflection, globals, `process`, caught exceptions, or code the analysis + * cannot see) stays untyped. A check the lowering inserts where a value + * meets an inferred type therefore fails only when the analysis is wrong; + * such checks carry the inferred-fact failure message. + * + * The package's own JSDoc is a hint, not a declaration: its tags are + * neutralized in the rewritten file, so only the analysis's types remain. + * + * Every edit is inline: no line breaks are added or removed, so positions + * keep their lines. */ +import * as ts from "../ts7/adapter.js"; +import type { Atom } from "./domain.js"; +import type { ShapeAnalysis } from "./analyze.js"; +import type { SiteVerdict } from "./report.js"; + +export interface ShapeOverlay { + /** Rewritten text per package file. */ + readonly files: Map; + /** Annotations written, for the build report. */ + readonly annotations: number; + /** Branches the analysis proved unreachable, replaced by checks. */ + readonly deadBranches: number; +} + +interface Edit { + readonly pos: number; + readonly text: string; + /** Replaces pos..end instead of inserting at pos. */ + readonly end?: number; +} + +/** Marks the overlay's own comments (an unknown JSDoc tag, ignored). */ +export const OVERLAY_MARK = "@scriptcShape"; +const MARK = OVERLAY_MARK; + +/** Above 1, values of several record key sets type as one merged record + * with optional fields (SCRIPTC_SHAPE_RECORD_ARMS; experimental). */ +const MAX_RECORD_ARMS = Number(process.env.SCRIPTC_SHAPE_RECORD_ARMS ?? 1); + +/** The most class or record arms an inferred union may have. */ +const MAX_ARMS = 6; + +function list(arr: { readonly nodes: readonly T[] } | undefined): readonly T[] { + return arr?.nodes ?? []; +} + +let debugIndex = 0; + +export function planShapeOverlay( + analysis: ShapeAnalysis, + verdicts: readonly SiteVerdict[], + packageFiles: readonly ts.SourceFile[], +): ShapeOverlay { + const pSites = new Map(); + const siteVerdicts = new Set(verdicts.map((v) => v.site.atom)); + // Records and arrays print only for sites whose objects never escape: a + // native record that reached untyped code would be seen as a copy. + const siteKeys = new Map }>(); + for (const v of verdicts) { + if (v.verdict !== "P-local") continue; + if (v.site.kind === "array") { + pSites.set(v.site.atom, v); + continue; + } + const keys = keysOfSite(analysis, v); + if (keys === null) continue; + siteKeys.set(v.site.atom, keys); + pSites.set(v.site.atom, v); + } + const files = new Map(); + let annotations = 0; + let deadBranches = 0; + debugIndex = 0; + // `if` branches no condition value allowed, per file: the overlay + // replaces each with a throwing check, so the checker and the lowering + // see the code the analysis saw. + const deadByFile = new Map(); + if (process.env["SCRIPTC_SHAPE_DEAD_BRANCHES"] !== "0") + for (const s of analysis.branchIfs) { + for (const branch of [s.thenStatement, s.elseStatement]) { + if (branch === undefined || analysis.walkedBranches.has(branch)) continue; + const sf = s.getSourceFile(); + let list = deadByFile.get(sf); + if (list === undefined) deadByFile.set(sf, (list = [])); + list.push(branch); + } + } + for (const sf of packageFiles) { + const typedefs = new Map(); + const typedefText: string[] = []; + const edits: Edit[] = []; + + const className = (decl: ts.Node): string | null => { + if (decl.getSourceFile() !== sf) return null; + const own = (decl as ts.ClassLikeDeclaration).name; + if (own !== undefined && ts.isClassDeclaration(decl) && decl.parent === sf) return own.text; + const p = decl.parent; + if ( + p !== undefined && + ts.isVariableDeclaration(p) && + ts.isIdentifier(p.name) && + p.parent?.parent?.parent === sf + ) + return p.name.text; + return null; + }; + + // `inner`: inside a record field, where ⊤ and unprintable values are `any`. + const typeOf = (atoms: ReadonlySet, inner: boolean, depth: number): string | null => { + if (atoms.size === 0) return inner ? "any" : null; + const arms = new Set(); + let objects = 0; + // Every array arm becomes one Array<…> of the joined element values. + const elements = new Set(); + let hasArray = false; + const promised = new Set(); + let hasPromise = false; + const recordSites: Atom[] = []; + for (const a of atoms) { + let t: string | null = null; + switch (a.kind) { + case "prim": + t = + a.label === "null" || + a.label === "undefined" || + a.label === "string" || + a.label === "number" || + a.label === "boolean" || + a.label === "bigint" + ? a.label + : null; + break; + case "instance": + t = a.node !== undefined ? className(a.node) : null; + objects++; + break; + case "object": { + objects++; + const v = pSites.get(a); + if (v === undefined || depth >= 4 || !siteKeys.has(a)) return inner ? "any" : null; + recordSites.push(a); + continue; + } + case "array": { + objects++; + if (analysis.escaped.has(a) || (siteVerdicts.has(a) && !pSites.has(a))) + return inner ? "any" : null; + const el = analysis.elements.get(a); + if (el !== undefined) for (const x of analysis.nodes[el]!.atoms) elements.add(x); + hasArray = true; + continue; + } + case "promise": { + // One promise arm: the values it resolves to, joined. + const value = analysis.fields.get(a)?.get("value"); + if (value !== undefined) for (const x of analysis.nodes[value]!.atoms) promised.add(x); + hasPromise = true; + continue; + } + default: + t = null; + } + if (t === null) return inner ? "any" : null; + arms.add(t); + } + // Records of one key set print as their typedef; records of several + // key sets merge into one record whose fields missing from some sites + // are optional (unions of overlapping records would need the lowering + // to pick an arm per value). + if (recordSites.length > 0) { + const keySets = new Set(recordSites.map(keySetOf)); + if (keySets.size === 1) arms.add(recordType(recordSites[0]!, depth)); + else if (MAX_RECORD_ARMS > 1) arms.add(mergedRecordType(recordSites, depth)); + else return inner ? "any" : null; + } + if (hasArray) { + const inside = depth < 4 ? typeOf(elements, true, depth + 1) : "any"; + arms.add(`Array<${inside ?? "any"}>`); + } + // A promise beside other values cannot cross into untyped code + // (maybe-promise unions stay typed-only), so it stays untyped here. + if (hasPromise) return inner ? "any" : null; + if (objects > MAX_ARMS) return inner ? "any" : null; + if (arms.has("any")) return inner ? "any" : null; + return [...arms].join(" | "); + }; + + // One typedef per key set: sites with the same keys intern to the same + // structural record, so their field types are joined. + const keySetOf = (site: Atom): string => { + const k = siteKeys.get(site)!; + return k.keys + .map((key) => (k.optional.has(key) ? `${key}?` : key)) + .sort() + .join(","); + }; + const sitesByKeys = new Map(); + for (const site of pSites.keys()) { + if (pSites.get(site)!.site.kind !== "object") continue; + const k = keySetOf(site); + let l = sitesByKeys.get(k); + if (l === undefined) sitesByKeys.set(k, (l = [])); + l.push(site); + } + const recordType = (site: Atom, depth: number): string => { + const keySet = keySetOf(site); + const existing = typedefs.get(keySet); + if (existing !== undefined) return existing; + const name = `__shape_${typedefs.size}`; + typedefs.set(keySet, name); + const { keys, optional } = siteKeys.get(site)!; + const parts = keys.map((k) => { + const atoms = new Set(); + for (const other of sitesByKeys.get(keySet) ?? [site]) { + const id = analysis.fields.get(other)?.get(k); + if (id !== undefined) for (const a of analysis.nodes[id]!.atoms) atoms.add(a); + } + const t = typeOf(atoms, true, depth + 1); + return `${/^[A-Za-z_$][\w$]*$/.test(k) ? k : JSON.stringify(k)}${optional.has(k) ? "?" : ""}: ${t ?? "any"}`; + }); + typedefText.push(`/** @typedef {{${parts.join(", ")}}} ${name} ${MARK} */`); + return name; + }; + + const mergedRecordType = (sites: readonly Atom[], depth: number): string => { + const all = new Set(); + for (const site of sites) + for (const other of sitesByKeys.get(keySetOf(site)) ?? [site]) all.add(other); + const keys: string[] = []; + const optional = new Set(); + for (const site of all) { + const k = siteKeys.get(site)!; + for (const key of k.keys) { + if (!keys.includes(key)) keys.push(key); + if (k.optional.has(key)) optional.add(key); + } + } + for (const site of all) { + const own = siteKeys.get(site)!.keys; + for (const key of keys) if (!own.includes(key)) optional.add(key); + } + const keySet = `merged:${keys + .map((k) => (optional.has(k) ? `${k}?` : k)) + .sort() + .join(",")}`; + const existing = typedefs.get(keySet); + if (existing !== undefined) return existing; + const name = `__shape_${typedefs.size}`; + typedefs.set(keySet, name); + const parts = keys.map((k) => { + const atoms = new Set(); + for (const site of all) { + const id = analysis.fields.get(site)?.get(k); + if (id !== undefined) for (const a of analysis.nodes[id]!.atoms) atoms.add(a); + } + const t = typeOf(atoms, true, depth + 1); + return `${/^[A-Za-z_$][\w$]*$/.test(k) ? k : JSON.stringify(k)}${optional.has(k) ? "?" : ""}: ${t ?? "any"}`; + }); + typedefText.push(`/** @typedef {{${parts.join(", ")}}} ${name} ${MARK} */`); + return name; + }; + + // Return values merged over every analysis context of each function. + const returns = new Map>(); + for (const info of analysis.functions.values()) { + if (info.node.getSourceFile() !== sf || info.isAsync || info.isGenerator) continue; + let set = returns.get(info.node); + if (set === undefined) returns.set(info.node, (set = new Set())); + for (const a of analysis.nodes[info.ret]!.atoms) set.add(a); + } + + // Receivers each method runs on, merged over its analysis contexts. + const thisAtoms = new Map>(); + for (const info of analysis.functions.values()) { + if (info.node.getSourceFile() !== sf || !ts.isMethodDeclaration(info.node)) continue; + let set = thisAtoms.get(info.node); + if (set === undefined) thisAtoms.set(info.node, (set = new Set())); + for (const a of analysis.nodes[info.thisNode]!.atoms) set.add(a); + } + const declaresMember = (cls: ts.Node, name: string): boolean => + analysis.classChain(cls).some((c) => + (c as ts.ClassLikeDeclaration).members.nodes.some((m) => { + const n = (m as { name?: ts.Node }).name; + return ( + n !== undefined && (ts.isIdentifier(n) || ts.isPrivateIdentifier(n)) && n.text === name + ); + }), + ); + // `this.m(…)` in an inherited method whose class does not declare `m` + // (zod's ZodType._parseSync calls the subclasses' _parse): the receiver + // is cast to the classes the method runs on, so the call dispatches over + // them instead of through an untyped call. + const receiverCasts = (method: ts.MethodDeclaration): void => { + const cls = method.parent; + const receivers = thisAtoms.get(method); + if (cls === undefined || receivers === undefined || receivers.size === 0) return; + if (![...receivers].every((a) => a.kind === "instance")) return; + const t = typeOf(receivers, false, 0); + if (t === null) return; + const scan = (node: ts.Node): void => { + if (ts.isFunctionLike(node) && !ts.isArrowFunction(node)) return; + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + node.expression.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(node.expression.name) && + !declaresMember(cls, node.expression.name.text) + ) { + const self = node.expression.expression; + edits.push({ pos: ts.tokenStartOfNode(self), text: `/** @type {${t}} ${MARK} */ (` }); + edits.push({ pos: self.end, text: ")" }); + annotations++; + } + ts.forEachChild(node, (c) => { + scan(c); + return undefined; + }); + }; + if (method.body !== undefined) for (const st of method.body.statements.nodes) scan(st); + }; + + // Same-named instance methods of the file's classes form a family: a + // call on a union of those classes needs one signature, so each member's + // parameters and result join the whole family's values. + const families = new Map(); + const familyVisit = (node: ts.Node): void => { + if ( + ts.isMethodDeclaration(node) && + ts.isIdentifier(node.name) && + !list((node as { modifiers?: { nodes: readonly ts.Node[] } }).modifiers).some( + (m) => m.kind === ts.SyntaxKind.StaticKeyword, + ) + ) { + let l = families.get(node.name.text); + if (l === undefined) families.set(node.name.text, (l = [])); + l.push(node); + } + ts.forEachChild(node, (c) => { + familyVisit(c); + return undefined; + }); + }; + familyVisit(sf); + // Members the program runs (they have analysis contexts); members it + // never reaches would only widen the family's types. + const reached = new Set(); + for (const info of analysis.functions.values()) + if (info.node.getSourceFile() === sf) reached.add(info.node); + const familyOf = (fn: ts.FunctionLikeDeclaration): readonly ts.MethodDeclaration[] => { + if (!ts.isMethodDeclaration(fn) || !ts.isIdentifier(fn.name)) + return [fn as ts.MethodDeclaration]; + const members = (families.get(fn.name.text) ?? [fn]).filter((m) => reached.has(m)); + return members.length > 0 ? members : [fn]; + }; + const paramValues = (fn: ts.FunctionLikeDeclaration, index: number): Set => { + const out = new Set(); + for (const member of familyOf(fn)) { + const p = list(member.parameters)[index]; + if (p === undefined) continue; + for (const a of analysis.declValues(p)) out.add(a); + } + return out; + }; + const returnValues = (fn: ts.FunctionLikeDeclaration): Set | undefined => { + let out: Set | undefined; + for (const member of familyOf(fn)) { + const r = returns.get(member); + if (r === undefined) continue; + out ??= new Set(); + for (const a of r) out.add(a); + } + return out; + }; + + const paramTags = (fn: ts.FunctionLikeDeclaration): string | null => { + const tags: string[] = []; + if (!ts.isConstructorDeclaration(fn)) { + const ret = returnValues(fn); + const t = ret !== undefined && ret.size > 0 ? typeOf(ret, false, 0) : null; + const why = process.env["SCRIPTC_DEBUG_SHAPE_OVERLAY_WHY"]; + if ( + why !== undefined && + t === null && + ret !== undefined && + ts.isMethodDeclaration(fn) && + ts.isIdentifier(fn.name) && + fn.name.text === why + ) + process.stderr.write( + `[shape-overlay-why] ${fn.name.text}: ${[...ret].map((a) => `${a.kind}:${a.label}${analysis.escaped.has(a) ? "(escaped)" : ""}`).join(" | ")}\n`, + ); + if (t !== null) tags.push(`@returns {${t}}`); + } + list(fn.parameters).forEach((p, index) => { + if ( + !ts.isIdentifier(p.name) || + p.dotDotDotToken !== undefined || + p.initializer !== undefined || + p.questionToken !== undefined + ) + return; + const t = typeOf(paramValues(fn, index), false, 0); + if (t !== null) tags.push(`@param {${t}} ${p.name.text}`); + }); + return tags.length > 0 ? `${tags.join(" ")} ${MARK}` : null; + }; + + const visit = (node: ts.Node): void => { + if (ts.isMethodDeclaration(node)) receiverCasts(node); + if ( + ts.isMethodDeclaration(node) || + ts.isConstructorDeclaration(node) || + ts.isFunctionDeclaration(node) + ) { + if (node.body !== undefined) { + const tags = paramTags(node); + if (tags !== null) { + edits.push({ pos: ts.tokenStartOfNode(node), text: `/** ${tags} */ ` }); + annotations++; + } + } + // `this.f = …` fields of a constructor's top-level statements. + if (ts.isConstructorDeclaration(node) && node.body !== undefined) { + const cls = node.parent; + const seen = new Set(); + for (const st of list(node.body.statements)) { + if (!ts.isExpressionStatement(st) || !ts.isBinaryExpression(st.expression)) continue; + const b = st.expression; + if ( + b.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isPropertyAccessExpression(b.left) + ) + continue; + if ( + b.left.expression.kind !== ts.SyntaxKind.ThisKeyword || + !ts.isIdentifier(b.left.name) + ) + continue; + const name = b.left.name.text; + if (seen.has(name)) continue; + seen.add(name); + const t = cls !== undefined ? fieldType(cls, name) : null; + if (t !== null) { + edits.push({ pos: ts.tokenStartOfNode(st), text: `/** @type {${t}} ${MARK} */ ` }); + annotations++; + } + } + } + } + // Locals only: module-level bindings are initialized once (and a + // record-typed module constant can turn a return type into a union of + // overlapping records, which the lowering width-coerces). + if ( + ts.isVariableStatement(node) && + (node.parent !== sf || process.env["SCRIPTC_SHAPE_OVERLAY_MODULE_VARS"] === "1") && + node.declarationList.declarations.nodes.length === 1 + ) { + const d = node.declarationList.declarations.nodes[0]!; + const init = d.initializer; + if (ts.isIdentifier(d.name) && init !== undefined && !literalInitializer(init)) { + const t = typeOf(analysis.declValues(d), false, 0); + if (t !== null) { + edits.push({ pos: ts.tokenStartOfNode(node), text: `/** @type {${t}} ${MARK} */ ` }); + annotations++; + } + } + } + ts.forEachChild(node, (c) => { + visit(c); + return undefined; + }); + }; + + const fieldType = (cls: ts.Node, name: string): string | null => { + const atoms = new Set(); + for (const a of analysis.atoms.atoms) { + if ( + a.kind !== "instance" || + a.node === undefined || + !analysis.classChain(a.node).includes(cls) + ) + continue; + const id = analysis.fields.get(a)?.get(name); + if (id !== undefined) for (const x of analysis.nodes[id]!.atoms) atoms.add(x); + } + return typeOf(atoms, false, 0); + }; + + visit(sf); + // Dead branches: outermost first; edits inside a replaced range drop. + const dead = (deadByFile.get(sf) ?? []).sort((a, b) => a.pos - b.pos); + const ranges: [number, number][] = []; + for (const branch of dead) { + const start = ts.tokenStartOfNode(branch); + if (ranges.some(([lo, hi]) => start >= lo && start < hi)) continue; + ranges.push([start, branch.end]); + } + if (ranges.length > 0) { + const kept = edits.filter((e) => !ranges.some(([lo, hi]) => e.pos > lo && e.pos < hi)); + edits.length = 0; + edits.push(...kept); + const pkg = packageNameOf(sf.fileName); + const rel = sf.fileName.split("/node_modules/").pop() ?? sf.fileName; + for (const [lo, hi] of ranges) { + const line = sf.getLineAndCharacterOfPosition(lo).line + 1; + const message = + `scriptc internal error: shape inference proved the branch at ${rel}:${line} unreachable, but it ran. ` + + `This is a bug in scriptc's shape analysis, not in the program. Rebuild with --no-shape-overlay, ` + + `or set "scriptc": {"packages": {"${pkg}": {"shapes": false}}} in package.json.`; + edits.push({ pos: lo, end: hi, text: `{ throw new Error(${JSON.stringify(message)}); }` }); + deadBranches++; + } + } + // Debugging: SCRIPTC_SHAPE_OVERLAY_ONLY=a-b keeps the annotations + // numbered a..b (program order across files), to bisect a miscompile. + const only = process.env["SCRIPTC_SHAPE_OVERLAY_ONLY"]; + if (only !== undefined) { + const [lo, hi] = only.split("-").map(Number) as [number, number]; + edits.sort((a, b) => a.pos - b.pos); + const kept = edits.filter(() => { + const n = debugIndex++; + return n >= lo && n <= hi; + }); + edits.length = 0; + edits.push(...kept); + } + if (edits.length === 0) continue; + files.set(sf.fileName, neutralizeDocTags(applyEdits(sf.text, edits)) + typedefText.join("")); + } + return { files, annotations, deadBranches }; +} + +function packageNameOf(fileName: string): string { + const parts = (fileName.split("/node_modules/").pop() ?? "").split("/"); + return parts[0]!.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0]!; +} + +function literalInitializer(e: ts.Expression): boolean { + return ( + ts.isObjectLiteralExpression(e) || + (ts.isArrayLiteralExpression(e) && e.elements.nodes.length > 0) || + ts.isStringLiteral(e) || + ts.isNumericLiteral(e) || + ts.isNoSubstitutionTemplateLiteral(e) || + ts.isArrowFunction(e) || + ts.isFunctionExpression(e) || + e.kind === ts.SyntaxKind.TrueKeyword || + e.kind === ts.SyntaxKind.FalseKeyword + ); +} + +function applyEdits(text: string, edits: readonly Edit[]): string { + const sorted = [...edits].sort((a, b) => a.pos - b.pos); + let out = ""; + let at = 0; + for (const e of sorted) { + out += text.slice(at, e.pos) + e.text; + at = e.end ?? e.pos; + } + return out + text.slice(at); +} + +/** The package's JSDoc type tags become plain text: package JSDoc is a + * hint, not a declaration. The overlay's own comments carry MARK and are + * kept. Lengths are unchanged. */ +export function neutralizeDocTags(text: string): string { + return text.replace(/\/\*\*[\s\S]*?\*\//g, (comment) => { + if (comment.includes(MARK)) return comment; + return comment.replace( + /@(param|type|typedef|returns?|template|this|callback|property|prop|arg|argument|enum|implements|extends|augments|satisfies|overload|import)\b/g, + "-$1", + ); + }); +} + +/** A record site's keys: its static keys, or for a literal with spreads its + * own keys plus every key a spread source contributes (optional unless + * every value of that source has it). Null when a computed key or an + * untracked spread source makes the key set unknown. */ +function keysOfSite( + analysis: ShapeAnalysis, + v: SiteVerdict, +): { keys: string[]; optional: Set } | null { + const site = v.site; + if (site.keys !== null) return { keys: site.keys, optional: new Set() }; + if (site.hasComputedKey) return null; + const fields = analysis.fields.get(site.atom); + const any = fields?.get("*"); + if (any !== undefined && analysis.nodes[any]!.atoms.size > 0) return null; + const required = new Set(site.ownKeys); + for (const spread of site.spreads) { + const sets: Set[] = []; + for (const a of analysis.nodes[spread]!.atoms) { + if (a.kind === "prim" && (a.label === "undefined" || a.label === "null")) continue; + if (a.kind !== "object") return null; + const own = new Set(); + for (const k of analysis.fields.get(a)?.keys() ?? []) if (k !== "*") own.add(k); + sets.push(own); + } + const first = sets[0]; + if (first === undefined) continue; + for (const k of first) if (sets.every((set) => set.has(k))) required.add(k); + } + const all = [...site.ownKeys]; + for (const k of fields?.keys() ?? []) if (k !== "*" && !all.includes(k)) all.push(k); + return { keys: all, optional: new Set(all.filter((k) => !required.has(k))) }; +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d1c97cc..c0f0e0649 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,3 +1,4 @@ +import { shapeOverlayEnabled } from "./frontend/shape-inference/index.js"; import { optimizationField, type NativeOptimization } from "./backend/optimization.js"; import { targetCpuField, targetCpuTargetRefusal } from "./backend/target-cpu.js"; import { compilationTiming, type CompilationTiming, type TimingClock } from "./timing.js"; @@ -442,6 +443,10 @@ export async function compile( opts: CompileRequestOptions, ): Promise { clearCompileSessionCaches(); + // `shapeOverlay: false` reaches the frontend (and its child process) + // through the environment the overlay reads. + const overlayEnv = process.env["SCRIPTC_SHAPE_OVERLAY"]; + if (opts.shapeOverlay === false) process.env["SCRIPTC_SHAPE_OVERLAY"] = "0"; const frontendInputs = new FrontendInputTracker(); const warnings: CompileWarningSink = { warnings: [], sourceTexts: null }; // compileTracked starts it for a build with no earlier build of the same invocation. @@ -454,6 +459,10 @@ export async function compile( } finally { setLibDiagnosticsStore(null); frontendProcess?.release(); + if (opts.shapeOverlay === false) { + if (overlayEnv === undefined) delete process.env["SCRIPTC_SHAPE_OVERLAY"]; + else process.env["SCRIPTC_SHAPE_OVERLAY"] = overlayEnv; + } } } @@ -1190,6 +1199,8 @@ function configureLibDiagnostics(root: string | null): void { export async function runFrontendProcess(requestPath: string, resultPath: string): Promise { const request = JSON.parse(await readFile(requestPath, "utf8")) as FrontendProcessRequest; + // The process started before the build's options were known. + if (request.opts.shapeOverlay === false) process.env["SCRIPTC_SHAPE_OVERLAY"] = "0"; clearCompileSessionCaches(); setProvenanceSources(request.provenance); // The compile that started this process prepared the cache root. @@ -1520,6 +1531,7 @@ async function compileTracked( ...checkerField(), ...(youngNurseryRequested() ? { youngNursery: true as const } : {}), npmStatic: opts.npmStatic ?? null, + ...(shapeOverlayEnabled() ? { shapeOverlay: true as const } : {}), ffiProfile: opts.ffiProfilePath === undefined || ffiProfileBytes === null ? null diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index db872a525..6c13d3916 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -100,6 +100,7 @@ const EXPR_FIELDS: Classified = { unionIsTag: { unionId: "other" }, unionEq: { unionId: "other" }, jsonStringify: { indent: "other" }, + dynCheck: { inferredSite: "other" }, }; const STMT_FIELDS: Classified = { @@ -222,6 +223,8 @@ const MODULE_FIELDS: Record>, Role> = { "wasmUntranslated[].reason": "other", "threadSlotKeys[]": "other", "freezableArrays[]": "other", + "inferredFactFiles[].file": "other", + "inferredFactFiles[].pkg": "other", }; const loc = { file: "references.ts", start: 0, end: 0 }; diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 3818433f8..25fce8364 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1061,6 +1061,11 @@ export interface IrModule { * closures boxed for package code return records and arrays as live * capsules, so a value that comes back keeps its identity. */ declBoundary?: true; + /** Package files compiled from shape inference's typed overlay + * (frontend/shape-inference/overlay.ts), each with its package name: a + * checked conversion in them checks an analysis-proven fact, so its + * failure reports a compiler analysis bug instead of a type error. */ + inferredFactFiles?: { file: string; pkg: string }[]; /** The program constructs LifetimeRegion (@scriptc/experimental): every * ProgramLifetime instance lives in a counted region (the region.* * intrinsics; backend/llvm/regions.ts). */ @@ -3984,6 +3989,10 @@ export type IrExpr = preserveRefs?: true; adopt?: true; exact?: true; + /** Shape inference's overlay: this conversion checks an + * analysis-proven fact (" :"); a failure is a + * compiler analysis bug and reports itself as one. */ + inferredSite?: string; type: IrType; loc: SrcLoc; }; diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index d3997ebd9..5e31d2aa9 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1541,6 +1541,10 @@ void scr_dyn_check_fail(const ScrDynPath *path, const char *want, const ScrDyn * /* The Date object a checked-dynamic Date handle holds, +1, or NULL with * the check failure pending. */ ScrDate *scr_dyn_native_date_check(const ScrDyn *value, const ScrDynPath *path); +/* Names the analysis-proven fact the next checked conversion checks + * (shape inference's overlay), or NULL: its failure then reports a compiler + * analysis bug (" :"). */ +void scr_dyn_inferred_site(const char *site); /* Matchers and builders of recursive shapes (records and arrays whose type * reaches itself) track the dyn values they visit, so cyclic values match diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 45ba766a1..39e930cf1 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -10782,7 +10782,53 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) { return "unknown"; } +/* Shape inference's overlay (compiler frontend/shape-inference): a checked + * conversion of an analysis-proven fact names its site (" + * :") while it runs. A failure there is a compiler analysis + * bug, so it reports itself as one instead of as a type error. */ +static SCR_TL const char *scr_dyn_inferred_site_name; + +void scr_dyn_inferred_site(const char *site) { scr_dyn_inferred_site_name = site; } + +static void scr_dyn_inferred_fact_fail(const char *site, const ScrDynPath *path, const char *want, + const ScrDyn *got) { + const char *space = strchr(site, ' '); + size_t pkg_len = space ? (size_t)(space - site) : 0; + const char *where = space ? space + 1 : site; + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "scriptc internal error: an inferred type was contradicted at "); + scr_jb_puts(&b, where); + scr_jb_puts(&b, " (expected "); + scr_jb_puts(&b, want); + scr_jb_puts(&b, " at "); + scr_dyn_path_render(&b, path); + scr_jb_puts(&b, ", got "); + scr_jb_puts(&b, scr_dyn_kind_name(got)); + scr_jb_puts(&b, "). This is a bug in scriptc's shape analysis, not in the program's data. " + "Rebuild with --no-shape-overlay, or set \"scriptc\": {\"packages\": {\""); + for (size_t i = 0; i < pkg_len; i++) scr_jb_putc(&b, site[i]); + scr_jb_puts(&b, "\": {\"shapes\": false}}} in package.json, to compile the package without it."); + /* Validation mode: every hit is logged, also when the program catches it. */ + const char *log = getenv("SCRIPTC_SHAPE_CHECK_LOG"); + if (log && *log) { + FILE *f = fopen(log, "a"); + if (f) { + fwrite(b.data, 1, b.len, f); + fputc('\n', f); + fclose(f); + } + } + scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); +} + void scr_dyn_check_fail(const ScrDynPath *path, const char *want, const ScrDyn *got) { + const char *site = scr_dyn_inferred_site_name; + if (site) { + scr_dyn_inferred_site_name = NULL; + scr_dyn_inferred_fact_fail(site, path, want, got); + return; + } ScrJsonBuf b; scr_jb_init(&b); scr_jb_puts(&b, "expected "); diff --git a/tests/harness/shape-overlay.test.ts b/tests/harness/shape-overlay.test.ts new file mode 100644 index 000000000..ad1988238 --- /dev/null +++ b/tests/harness/shape-overlay.test.ts @@ -0,0 +1,62 @@ +/* The shape overlay (shape-inference/overlay.ts): with SCRIPTC_SHAPE_OVERLAY=1 + * the zod3-boundary npm case compiles zod's JavaScript with analysis-proven JSDoc + * types, reports what it typed and checks (SC6103), and prints what Node + * prints; the shapeOverlay: false option (the CLI's --no-shape-overlay) + * turns it off. */ +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const entry = join(repoRoot, "tests/fixtures/npm/cases/zod3-boundary/main.ts"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/shape-overlay"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +async function withOverlay(run: () => Promise): Promise { + const saved = process.env["SCRIPTC_SHAPE_OVERLAY"]; + process.env["SCRIPTC_SHAPE_OVERLAY"] = "1"; + try { + return await run(); + } finally { + if (saved === undefined) delete process.env["SCRIPTC_SHAPE_OVERLAY"]; + else process.env["SCRIPTC_SHAPE_OVERLAY"] = saved; + } +} + +test( + "the overlay types zod's JavaScript and keeps Node's output", + { timeout: 900_000 }, + async () => { + mkdirSync(outDir, { recursive: true }); + const result = await withOverlay(() => + compile(entry, { outPath: join(outDir, "on"), outDir, sanitize }), + ); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const report = (result.warnings ?? []).find((w) => w.code === "SC6103"); + expect(report?.message).toMatch( + /shape inference typed \d+ declarations in \d+ files? of 1 package \(zod\)/, + ); + const run = execFileAsync(result.binaryPath, [], { cwd: dirname(entry), encoding: "utf8" }); + run.child.stdin?.end(); + const node = await execFileAsync(process.execPath, [entry], { + cwd: dirname(entry), + encoding: "utf8", + }); + expect((await run).stdout).toBe(node.stdout); + }, +); + +test("shapeOverlay: false compiles without the overlay", { timeout: 900_000 }, async () => { + mkdirSync(outDir, { recursive: true }); + const result = await withOverlay(() => + compile(entry, { outPath: join(outDir, "off"), outDir, sanitize, shapeOverlay: false }), + ); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + expect((result.warnings ?? []).some((w) => w.code === "SC6103")).toBe(false); +}); From 33f63655a0ddd712253790c260657f6d51aba080 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:34:45 -0500 Subject: [PATCH 169/395] Infer untyped JavaScript collection types once, without re-entering The inference reads types while it runs: resolving a candidate's constructor through a destructured require (const { TextDecoder } = util) asks for the namespace's type, which asked for the inferred collection types again and restarted the inference until the stack overflowed. The lowerer now stores an empty table before inferring, so reads during the inference see no inferred types, and stores the result after. --- .../frontend/lowering/js-collection-types.ts | 10 +++++-- .../compiler/test/js-collection-types.test.ts | 29 ++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/js-collection-types.ts b/packages/compiler/src/frontend/lowering/js-collection-types.ts index a4c182523..2be4e0dbd 100644 --- a/packages/compiler/src/frontend/lowering/js-collection-types.ts +++ b/packages/compiler/src/frontend/lowering/js-collection-types.ts @@ -75,9 +75,15 @@ export function initializedCollectionType( /** The inferred collection type of an identifier that names a proven * binding, or null. Computed for every JavaScript file of the program on - * first use. */ + * first use. The inference itself reads types (a candidate's constructor + * through a destructured require, for example); while it runs, those reads + * see no inferred types rather than starting it again. */ export function inferredCollectionType(lowerer: Lowerer, node: ts.Identifier): ts.Type | null { - const types = (lowerer.inferredCollectionTypes ??= inferProgram(lowerer)); + if (lowerer.inferredCollectionTypes === undefined) { + lowerer.inferredCollectionTypes = new Map(); + lowerer.inferredCollectionTypes = inferProgram(lowerer); + } + const types = lowerer.inferredCollectionTypes; return types.size === 0 ? null : (types.get(node) ?? null); } diff --git a/packages/compiler/test/js-collection-types.test.ts b/packages/compiler/test/js-collection-types.test.ts index 6a6f65798..7a5ef967a 100644 --- a/packages/compiler/test/js-collection-types.test.ts +++ b/packages/compiler/test/js-collection-types.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; @@ -62,3 +62,30 @@ test("untyped JavaScript collections take the types their uses prove, and only t rmSync(dir, { recursive: true, force: true }); } }); + +// Resolving a candidate's constructor through a destructured require reads +// types while the inference runs; those reads must not start it again. +test("the inference runs once when a candidate's values come from a destructured require", async () => { + const dir = mkdtempSync(join(tmpdir(), "scriptc-js-collection-reentry-")); + try { + const entry = join(dir, "main.cjs"); + writeFileSync( + entry, + [ + 'const util = require("node:util");', + "const { TextDecoder } = util;", + "const decoder = new TextDecoder();", + 'const decoders = new Map([["utf-8", decoder]]);', + "const seen = new Set([decoder]);", + "console.log(decoders.size, seen.size);", + "", + ].join("\n"), + ); + const output = join(dir, "main.ir.json"); + const lowered = await compile(entry, { outputKind: "ir", outDir: dir, outPath: output }); + if (!lowered.ok) throw new Error(JSON.stringify(lowered.diagnostics)); + expect(validateModule(deserializeModule(readFileSync(output, "utf8")))).toEqual([]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); From f4eda0fb6c9fcc475e5b7f3eb4392d7af02063c2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:43:27 -0500 Subject: [PATCH 170/395] Number hoisted speculative leaves per function --- .../src/backend/llvm/emit-function.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 7 ++- tests/harness/atomics-emission.test.ts | 2 +- tests/harness/interface-dispatch.test.ts | 4 +- .../speculative-leaves-emission.test.ts | 63 +++++++++++++++++++ 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 tests/harness/speculative-leaves-emission.test.ts diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index b79e2406d..802a0c384 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -66,6 +66,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { emitter.usesLandingPads = false; emitter.aliasScopedVersions = 0; emitter.findInt32Allocations(fn); + emitter.speculativeLeaves = 0; emitter.currentTier = fn.tier; // Fiber switches restore the active exception cell before control // returns to a synchronous frame (emitPendingCheck). diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index d5964f1ba..7a49e2884 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1546,8 +1546,11 @@ export class LlEmitter { speculationResume: Map | null = null; /** The rewritten values of the writes `speculationResume` resumes at. */ speculationResumeValues: ReadonlyMap | null = null; - /** Hoisted speculative leaves numbered so far in the module. */ - private speculativeLeaves = 0; + /** Hoisted speculative leaves numbered so far in the function being + * emitted (emitFunction resets it): a function's local names must not + * depend on which functions the same emitter emitted before it, since + * function workers each emit a share of the module. */ + speculativeLeaves = 0; private readonly inlinableFunctions = new Map(); /** What speculative loop planning may assume here (SpeculationContext). */ diff --git a/tests/harness/atomics-emission.test.ts b/tests/harness/atomics-emission.test.ts index 2d708e382..9068ad59e 100644 --- a/tests/harness/atomics-emission.test.ts +++ b/tests/harness/atomics-emission.test.ts @@ -59,7 +59,7 @@ console.log(work(new Int32Array(buffer), new Uint8Array(buffer), new Int16Array( // Every runtime call is the cold path for an index out of range. const calls = work.match(/call double @scr_atomics_op\([^\n]*/g) ?? []; expect(calls).toHaveLength(9); - for (const call of calls) expect(call).toMatch(/\) cold$/); + for (const call of calls) expect(call).toMatch(/\)(?: nounwind)? cold$/); }); test("an unchanged local array is borrowed across Atomics operations", async () => { diff --git a/tests/harness/interface-dispatch.test.ts b/tests/harness/interface-dispatch.test.ts index 21e455e70..bf70ecafb 100644 --- a/tests/harness/interface-dispatch.test.ts +++ b/tests/harness/interface-dispatch.test.ts @@ -71,7 +71,7 @@ describe("interface method dispatch", () => { test("calls through an interface reach the implementing methods with typed values", async () => { const llvm = await emittedLlvm("direct", shapes("")); const run = bodies(llvm, "sc_bf_run"); - expect(run).toMatch(/call double @sc_bf__x25_dyn_class_protocol_\d+/); + expect(run).toMatch(/(?:call|invoke) double @sc_bf__x25_dyn_class_protocol_\d+/); expect(run).not.toContain("@scr_dyn_call"); expect(run).not.toContain("@sc_bf__x25_dyn_class_property_"); const dispatch = llvm.slice(llvm.indexOf("; %dyn.class.protocol.0")); @@ -90,7 +90,7 @@ describe("interface method dispatch", () => { console.log(sum([circle, new Rect(1)]));`), ); const sum = bodies(llvm, "sc_bf_sum"); - expect(sum).toMatch(/call double @sc_bf__x25_dyn_class_protocol_\d+/); + expect(sum).toMatch(/(?:call|invoke) double @sc_bf__x25_dyn_class_protocol_\d+/); expect(sum).not.toContain("@scr_dyn_iterator"); expect(sum).not.toContain("@sc_bf__x25_dyn_class_computed_"); }); diff --git a/tests/harness/speculative-leaves-emission.test.ts b/tests/harness/speculative-leaves-emission.test.ts new file mode 100644 index 000000000..16fd298d4 --- /dev/null +++ b/tests/harness/speculative-leaves-emission.test.ts @@ -0,0 +1,63 @@ +/* Speculative integer loops (behavior is in the differential corpus, 7757): + * the locals that hold hoisted calls are numbered per function, so a + * function's text does not depend on the functions emitted before it (the + * native compiler's function workers each emit a share of the module). */ +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const repoRoot = join(import.meta.dirname, "../.."); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); + +async function emittedLlvm(name: string, source: string): Promise { + const key = createHash("sha256").update(source).digest("hex").slice(0, 16); + const outDir = join(cacheDir, `spec-leaves-${key}`); + mkdirSync(outDir, { recursive: true }); + const file = join(outDir, `${name}.ts`); + writeFileSync(file, source); + const result = await compile(file, { + outPath: join(outDir, name), + outDir, + backend: "llvm", + outputKind: "llvm", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return readFileSync(result.artifact.path, "utf8"); +} + +function body(llvm: string, name: string): string { + const start = llvm.search(new RegExp(`^define internal [^\\n]*@${name}[.(]`, "m")); + expect(start).toBeGreaterThanOrEqual(0); + return llvm.slice(start, llvm.indexOf("\n}\n", start)); +} + +test("hoisted speculative leaves are numbered per function", async () => { + const llvm = await emittedLlvm( + "leaves", + `let calls = 0; +function noisy(x: number): number { + calls++; + return x % 5 === 0 ? x / 2 : x * 2; +} +function first(n: number): number { + let acc = 1; + for (let i = 0; i < n; i++) acc = (acc * 7 + noisy(i)) % 1000003; + return acc; +} +function second(n: number): number { + let acc = 3; + for (let i = 0; i < n; i++) acc = (acc * 5 + noisy(i)) % 1000003; + return acc; +} +console.log(first(20), second(20), calls); +`, + ); + for (const name of ["sc_f_first", "sc_f_second"]) { + const leaves = body(llvm, name).match(/%\w*spec_leaf_\d+ = alloca double/g) ?? []; + expect(leaves).toHaveLength(1); + expect(leaves[0]).toMatch(/_spec_leaf_0 = /); + } +}); From 688c1e03a15b69cf9dd0693a9199e45b08170d6d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:52:52 -0500 Subject: [PATCH 171/395] Keep undefined results, literal text and hoisted vars in the shape overlay - A function's results include undefined when a call can run off the end of its body or reach a bare return, so the overlay's return type keeps undefined and a caller's branch on it stays reachable. `new` of an ES5 constructor ignores primitive results, and functions called with `new` get no @returns tag. - Package JSDoc tags are neutralized only in comments: string, template and regular expression literals and JSX text keep their text. - A branch replaced as unreachable keeps the var names it declares that its scope declares nowhere else, so references outside it read undefined. A branch that declares a function in a block is kept. - The shape-overlay-facts npm case covers all three; four files of the overlay change are formatted. --- .../src/frontend/lowering/call-signatures.ts | 6 +- .../src/frontend/lowering/lower-classes.ts | 5 +- packages/compiler/src/frontend/pipeline.ts | 5 +- packages/compiler/src/frontend/program.ts | 6 +- .../src/frontend/shape-inference/analyze.ts | 64 +++++- .../frontend/shape-inference/overlay.test.ts | 32 +++ .../src/frontend/shape-inference/overlay.ts | 182 ++++++++++++++++-- .../cases/shape-overlay-facts/main.ts.json | 6 + .../npm/cases/shape-overlay-facts/main.ts | 10 + .../node_modules/shape-facts/index.d.ts | 4 + .../node_modules/shape-facts/index.js | 55 ++++++ .../node_modules/shape-facts/package.json | 1 + .../cases/shape-overlay-facts/package.json | 1 + tests/harness/shape-overlay.test.ts | 30 +++ 14 files changed, 384 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/src/frontend/shape-inference/overlay.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/shape-overlay-facts/main.ts.json create mode 100644 tests/fixtures/npm/cases/shape-overlay-facts/main.ts create mode 100644 tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.d.ts create mode 100644 tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.js create mode 100644 tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/package.json create mode 100644 tests/fixtures/npm/cases/shape-overlay-facts/package.json diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 9d8096c62..3b2a4bd39 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -182,7 +182,11 @@ export function checkedJsDocumentedParameter( lowerer: Lowerer, param: ts.ParameterDeclaration, ): boolean { - if (param.parent !== undefined && shapeOverlayAnnotated(param.parent) && documentedParameter(param)) + if ( + param.parent !== undefined && + shapeOverlayAnnotated(param.parent) && + documentedParameter(param) + ) return true; return lowerer.checkedJsFile(ts.getSourceFileOfNode(param)) && documentedParameter(param); } diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 573bb0c69..b2036dc8a 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1156,7 +1156,10 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E // Shape inference's overlay types a field as a record or array only when // the analysis proves its values never reach code that could observe a // copy, so those fields keep their native layout. - if ((type.kind === "array" || type.kind === "record") && !shapeOverlayAnnotated(expression.parent?.parent ?? expression)) + if ( + (type.kind === "array" || type.kind === "record") && + !shapeOverlayAnnotated(expression.parent?.parent ?? expression) + ) return DYN; // JavaScript documentation cannot guarantee that a call or an indexed // read returns a present number. Numeric fields receiving those values diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 92bafe54d..ad64fb8d5 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -700,7 +700,10 @@ function loadFrontend( }); // The declaration-typed boundary's notes (SC6102) join the // performance warnings; they never fail a build. - const notes = [...(finalLoad.declBoundaryNotes ?? []), ...shapeOverlayReport(finalLoad.entry.fileName)]; + const notes = [ + ...(finalLoad.declBoundaryNotes ?? []), + ...shapeOverlayReport(finalLoad.entry.fileName), + ]; if (notes.length === 0) return lowered; return { ...lowered, performance: [...notes, ...(lowered.performance ?? [])] }; }, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index daeb61ded..f44efb228 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1018,7 +1018,11 @@ function loadProgram7( for (const [file, text] of overlay) host.addVirtualFile(file, text); program = host.createProgram([...programRoots, overridesDtsPath()], options); if (tscWorld === undefined) - tscWorld = { program: before, diagnostics: ts.getPreEmitDiagnostics(before), projectWorld: null }; + tscWorld = { + program: before, + diagnostics: ts.getPreEmitDiagnostics(before), + projectWorld: null, + }; else if (tscWorld.program !== before) before.dispose(); } } diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index fe87a8c53..cbdfef081 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -131,6 +131,8 @@ export interface ShapeAnalysis { readonly escapedFunctions: ReadonlySet; /** ES5 constructors: each function's default prototype object. */ readonly protoOf: ReadonlyMap; + /** ES5 constructor instances: the function atom whose `new` made each. */ + readonly fnOfInstance: ReadonlyMap; /** Every value a declaration (parameter, variable, binding element) holds, * merged over the analysis contexts it was analyzed in. */ declValues(decl: Node): Set; @@ -249,6 +251,47 @@ function isLoop(node: Node): boolean { ); } +/** Whether control can reach the end of `stmts`. False only when every + * path ends in a return or throw, or in a `while (true)`, `do … while + * (true)` or `for (;;)` loop with no break inside; anything else is + * assumed to complete. */ +function canComplete(stmts: readonly Node[]): boolean { + return stmts.every(canCompleteStatement); +} + +function canCompleteStatement(s: Node): boolean { + if (ts.isReturnStatement(s) || ts.isThrowStatement(s)) return false; + if (ts.isBlock(s)) return canComplete(list(s.statements)); + if (ts.isIfStatement(s)) + return ( + s.elseStatement === undefined || + canCompleteStatement(s.thenStatement) || + canCompleteStatement(s.elseStatement) + ); + if (ts.isTryStatement(s)) { + if (s.finallyBlock !== undefined && !canCompleteStatement(s.finallyBlock)) return false; + return ( + canCompleteStatement(s.tryBlock) || + (s.catchClause !== undefined && canCompleteStatement(s.catchClause.block)) + ); + } + const endless = + ((ts.isWhileStatement(s) || ts.isDoStatement(s)) && + s.expression.kind === ts.SyntaxKind.TrueKeyword) || + (ts.isForStatement(s) && s.condition === undefined); + return !endless || containsBreak(s); +} + +/** Whether a break statement occurs in `node`'s body, outside nested + * functions. */ +function containsBreak(node: Node): boolean { + return ( + ts.forEachChild(node, (c) => + ts.isBreakStatement(c) || (!ts.isFunctionLike(c) && containsBreak(c)) ? true : undefined, + ) === true + ); +} + const LOOP_CALLBACK_METHODS: ReadonlySet = new Set([ "map", "forEach", @@ -369,6 +412,7 @@ class Analyzer { nodeLabel: this.nodeLabel, escapedFunctions: this.escapedFunctionCalls, protoOf: this.protoOf, + fnOfInstance: this.fnOfInstance, declValues: (decl) => this.declValues(decl), classChain: (decl) => this.classChain(decl), branchIfs: this.branchIfs, @@ -918,8 +962,11 @@ class Analyzer { } const body = fn.body; if (body !== undefined) { - if (ts.isBlock(body)) this.stmts(list(body.statements)); - else this.edge(this.expr(body as ts.Expression), info.ret); + if (ts.isBlock(body)) { + this.stmts(list(body.statements)); + // A call that runs off the end of the body returns undefined. + if (canComplete(list(body.statements))) this.add(info.ret, this.prim("undefined")); + } else this.edge(this.expr(body as ts.Expression), info.ret); } this.currentFn = saved; this.loopDepth = savedLoop; @@ -1253,8 +1300,9 @@ class Analyzer { return; } if (ts.isReturnStatement(s)) { - if (s.expression !== undefined && this.currentFn !== null) - this.edge(this.expr(s.expression), this.currentFn.ret); + if (this.currentFn === null) return; + if (s.expression !== undefined) this.edge(this.expr(s.expression), this.currentFn.ret); + else this.add(this.currentFn.ret, this.prim("undefined")); return; } if (ts.isFunctionDeclaration(s)) { @@ -2667,7 +2715,11 @@ class Analyzer { this.activate(info); this.add(info.thisNode, inst); this.bindArgs(info, args, spread); - this.edge(info.ret, dst); + // `new` ignores a primitive result (and the undefined of a + // body that runs off its end) and yields the instance. + this.on(info.ret, (r) => { + if (r.kind !== "prim") this.add(dst, r); + }); this.edgeFor(e, info); } return; @@ -3350,7 +3402,7 @@ function cjsExportName(decl: Node): string | null { ) return decl.left.name.text; return null; - } +} interface WalkEnv { readonly ctx: string; diff --git a/packages/compiler/src/frontend/shape-inference/overlay.test.ts b/packages/compiler/src/frontend/shape-inference/overlay.test.ts new file mode 100644 index 000000000..f0effae77 --- /dev/null +++ b/packages/compiler/src/frontend/shape-inference/overlay.test.ts @@ -0,0 +1,32 @@ +import { afterAll, expect, test } from "vitest"; +import { closeSourceParser, parseSourceFile } from "../ts7/source-parser-node.js"; +import { neutralizeDocTags, OVERLAY_MARK } from "./overlay.js"; + +afterAll(closeSourceParser); + +test("neutralizeDocTags rewrites JSDoc comments and keeps literals", () => { + const source = [ + "/** @param {number} a */", + "function f(a) {", + ' const s = "/** @type {number} */";', + " const t = `/** @returns {string} ${a} */`;", + " const r = /\\/\\*\\* @param \\*\\//;", + " // /** @type {string} */", + ` /** @type {string} ${OVERLAY_MARK} */ const u = s + t;`, + " return a / 2 /** @returns {number} */;", + "}", + ].join("\n"); + const out = neutralizeDocTags(parseSourceFile("overlay.js", source, "js")); + expect(out.length).toBe(source.length); + expect(out.split("\n")).toEqual([ + "/** -param {number} a */", + "function f(a) {", + ' const s = "/** @type {number} */";', + " const t = `/** @returns {string} ${a} */`;", + " const r = /\\/\\*\\* @param \\*\\//;", + " // /** @type {string} */", + ` /** @type {string} ${OVERLAY_MARK} */ const u = s + t;`, + " return a / 2 /** -returns {number} */;", + "}", + ]); +}); diff --git a/packages/compiler/src/frontend/shape-inference/overlay.ts b/packages/compiler/src/frontend/shape-inference/overlay.ts index 28b5336cf..7747fac20 100644 --- a/packages/compiler/src/frontend/shape-inference/overlay.ts +++ b/packages/compiler/src/frontend/shape-inference/overlay.ts @@ -268,6 +268,10 @@ export function planShapeOverlay( return name; }; + // Functions called with `new` yield their instance, whatever they return. + const constructed = new Set( + [...analysis.fnOfInstance.values()].map((a) => a.node), + ); // Return values merged over every analysis context of each function. const returns = new Map>(); for (const info of analysis.functions.values()) { @@ -382,7 +386,7 @@ export function planShapeOverlay( const paramTags = (fn: ts.FunctionLikeDeclaration): string | null => { const tags: string[] = []; - if (!ts.isConstructorDeclaration(fn)) { + if (!ts.isConstructorDeclaration(fn) && !constructed.has(fn)) { const ret = returnValues(fn); const t = ret !== undefined && ret.size > 0 ? typeOf(ret, false, 0) : null; const why = process.env["SCRIPTC_DEBUG_SHAPE_OVERLAY_WHY"]; @@ -498,9 +502,13 @@ export function planShapeOverlay( // Dead branches: outermost first; edits inside a replaced range drop. const dead = (deadByFile.get(sf) ?? []).sort((a, b) => a.pos - b.pos); const ranges: [number, number][] = []; + const hoisted = new Map(); for (const branch of dead) { const start = ts.tokenStartOfNode(branch); if (ranges.some(([lo, hi]) => start >= lo && start < hi)) continue; + const names = hoistedNames(branch); + if (names === null) continue; + hoisted.set(start, names); ranges.push([start, branch.end]); } if (ranges.length > 0) { @@ -515,7 +523,14 @@ export function planShapeOverlay( `scriptc internal error: shape inference proved the branch at ${rel}:${line} unreachable, but it ran. ` + `This is a bug in scriptc's shape analysis, not in the program. Rebuild with --no-shape-overlay, ` + `or set "scriptc": {"packages": {"${pkg}": {"shapes": false}}} in package.json.`; - edits.push({ pos: lo, end: hi, text: `{ throw new Error(${JSON.stringify(message)}); }` }); + // The branch's own var names stay declared in its scope, undefined. + const names = hoisted.get(lo)!; + const vars = names.length > 0 ? `var ${names.join(", ")}; ` : ""; + edits.push({ + pos: lo, + end: hi, + text: `{ ${vars}throw new Error(${JSON.stringify(message)}); }`, + }); deadBranches++; } } @@ -533,7 +548,7 @@ export function planShapeOverlay( edits.push(...kept); } if (edits.length === 0) continue; - files.set(sf.fileName, neutralizeDocTags(applyEdits(sf.text, edits)) + typedefText.join("")); + files.set(sf.fileName, applyEdits(neutralizeDocTags(sf), edits) + typedefText.join("")); } return { files, annotations, deadBranches }; } @@ -568,17 +583,158 @@ function applyEdits(text: string, edits: readonly Edit[]): string { return out + text.slice(at); } -/** The package's JSDoc type tags become plain text: package JSDoc is a - * hint, not a declaration. The overlay's own comments carry MARK and are - * kept. Lengths are unchanged. */ -export function neutralizeDocTags(text: string): string { - return text.replace(/\/\*\*[\s\S]*?\*\//g, (comment) => { - if (comment.includes(MARK)) return comment; - return comment.replace( - /@(param|type|typedef|returns?|template|this|callback|property|prop|arg|argument|enum|implements|extends|augments|satisfies|overload|import)\b/g, - "-$1", - ); +/** The file's text with the JSDoc type tags of its comments made plain + * text: package JSDoc is a hint, not a declaration. String, template and + * regular expression literals and JSX text are copied unchanged, so + * comment-like text in them keeps its runtime value. Lengths are unchanged, + * so edit positions in the source text stay valid. */ +export function neutralizeDocTags(sf: ts.SourceFile): string { + const text = sf.text; + // Literal token ranges, in source order. + const literals: [number, number][] = []; + const visit = (node: ts.Node): void => { + if (ts.isJsxText(node)) { + literals.push([node.pos, node.end]); + return; + } + if ( + ts.isStringLiteral(node) || + ts.isNoSubstitutionTemplateLiteral(node) || + ts.isTemplateHead(node) || + ts.isTemplateMiddle(node) || + ts.isTemplateTail(node) || + ts.isRegularExpressionLiteral(node) + ) { + literals.push([ts.tokenStartOfNode(node), node.end]); + return; + } + ts.forEachChild(node, (c) => { + visit(c); + return undefined; + }); + }; + visit(sf); + literals.sort((a, b) => a[0] - b[0]); + let out = ""; + let copied = 0; + let next = 0; + let pos = 0; + // Outside literals, `//` and `/*` only start comments. + while (pos < text.length) { + while (next < literals.length && literals[next]![1] <= pos) next++; + const literal = literals[next]; + if (literal !== undefined && literal[0] <= pos) { + pos = literal[1]; + continue; + } + if (text.charCodeAt(pos) !== 47) { + pos++; + continue; + } + const second = text.charCodeAt(pos + 1); + if (second === 47) { + while (pos < text.length && !lineBreak(text.charCodeAt(pos))) pos++; + continue; + } + if (second !== 42) { + pos++; + continue; + } + const close = text.indexOf("*/", pos + 2); + const end = close < 0 ? text.length : close + 2; + const comment = text.slice(pos, end); + if (comment.startsWith("/**") && !comment.includes(MARK)) { + out += text.slice(copied, pos) + neutralizeComment(comment); + copied = end; + } + pos = end; + } + return out + text.slice(copied); +} + +function lineBreak(code: number): boolean { + return code === 10 || code === 13 || code === 0x2028 || code === 0x2029; +} + +function neutralizeComment(comment: string): string { + return comment.replace( + /@(param|type|typedef|returns?|template|this|callback|property|prop|arg|argument|enum|implements|extends|augments|satisfies|overload|import)\b/g, + "-$1", + ); +} + +/** The `var` names a dead branch declares that its function (or file) + * declares nowhere else: replacing the branch must keep them bound, as + * undefined. Null when the branch declares a function in a block, whose + * binding outside the block depends on the file's strictness; such a + * branch is kept. */ +function hoistedNames(branch: ts.Node): readonly string[] | null { + let scope: ts.Node | undefined = branch.parent; + while (scope !== undefined && !ts.isSourceFile(scope) && !functionScope(scope)) + scope = scope.parent; + if (scope === undefined) return null; + const own = new Set(); + let blockFunction = false; + ts.walkPreorder(branch, (n) => { + if (n !== branch && ts.isFunctionDeclaration(n)) { + blockFunction = true; + return "skip"; + } + if (n !== branch && (functionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n))) + return "skip"; + if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) + for (const d of n.declarations.nodes) addBindingNames(d.name, own); + return undefined; }); + if (blockFunction) return null; + if (own.size === 0) return []; + // Names the scope binds outside the branch: parameters, a function + // expression's name, other var declarations and function declarations. + const elsewhere = new Set(); + if (!ts.isSourceFile(scope)) { + for (const p of list( + (scope as { parameters?: { readonly nodes: readonly ts.ParameterDeclaration[] } }).parameters, + )) + addBindingNames(p.name, elsewhere); + const name = (scope as { name?: ts.Node }).name; + if (name !== undefined && ts.isIdentifier(name)) elsewhere.add(name.text); + } + const body = ts.isSourceFile(scope) ? scope : (scope as { body?: ts.Node }).body; + if (body !== undefined) + ts.walkPreorder(body, (n) => { + if (n === branch) return "skip"; + if (n !== body && ts.isFunctionDeclaration(n) && n.name !== undefined) + elsewhere.add(n.name.text); + if (n !== body && (functionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n))) + return "skip"; + if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) + for (const d of n.declarations.nodes) addBindingNames(d.name, elsewhere); + return undefined; + }); + return [...own].filter((name) => !elsewhere.has(name)); +} + +function functionScope(n: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) || + ts.isClassStaticBlockDeclaration(n) + ); +} + +function addBindingNames(name: ts.BindingName, out: Set): void { + if (ts.isIdentifier(name)) { + out.add(name.text); + return; + } + for (const element of name.elements.nodes) + if (!ts.isOmittedExpression(element) && element.name !== undefined) + addBindingNames(element.name, out); } /** A record site's keys: its static keys, or for a literal with spreads its diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/shape-overlay-facts/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/shape-overlay-facts/main.ts.json new file mode 100644 index 000000000..f01de4ae5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/shape-overlay-facts/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/shape-overlay-facts/main.ts" + ], + "diags": [] +} diff --git a/tests/fixtures/npm/cases/shape-overlay-facts/main.ts b/tests/fixtures/npm/cases/shape-overlay-facts/main.ts new file mode 100644 index 000000000..3ed1cf5de --- /dev/null +++ b/tests/fixtures/npm/cases/shape-overlay-facts/main.ts @@ -0,0 +1,10 @@ +// The shape overlay's facts: results that include undefined, literals that +// hold comment-like text, and a var declared in a branch no call takes. +import { banner, hoisted, lookup, lookupOrReturn } from "shape-facts"; + +console.log(lookup("b")); +console.log(lookup("z")); +console.log(lookupOrReturn("a")); +console.log(lookupOrReturn("y")); +console.log(banner(7)); +console.log(hoisted("note:")); diff --git a/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.d.ts b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.d.ts new file mode 100644 index 000000000..b1865415d --- /dev/null +++ b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.d.ts @@ -0,0 +1,4 @@ +export declare function lookup(key: string): string; +export declare function lookupOrReturn(key: string): string; +export declare function banner(n: number): string; +export declare function hoisted(label: string): string; diff --git a/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.js b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.js new file mode 100644 index 000000000..505af75a0 --- /dev/null +++ b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/index.js @@ -0,0 +1,55 @@ +// Functions whose results include the undefined of a fall-through or a +// bare return, comment-like text in literals, and a branch no call takes +// that declares a var read outside it. + +const entries = [ + { key: "a", value: 1 }, + { key: "b", value: 2 }, +]; + +function find(key) { + for (const entry of entries) { + if (entry.key === key) return entry; + } +} + +function findOrReturn(key) { + for (const entry of entries) if (entry.key === key) return entry; + return; +} + +export function lookup(key) { + const hit = find(key); + if (hit) { + return "found " + hit.key + "=" + hit.value; + } else { + return "missing " + key; + } +} + +export function lookupOrReturn(key) { + const hit = findOrReturn(key); + if (hit) { + return "found " + hit.key + "=" + hit.value; + } else { + return "missing " + key; + } +} + +export function banner(n) { + const quoted = "/** @type {number} */"; + const template = `/** @returns {string} ${n} */`; + const pattern = /\/\*\* @param \*\//; + return quoted + " " + template + " " + pattern.source; +} + +function noteOf(extra) { + if (extra) { + var note = "set"; + } + return typeof note + " " + String(note); +} + +export function hoisted(label) { + return label + " " + noteOf(undefined); +} diff --git a/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/package.json b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/package.json new file mode 100644 index 000000000..317269e91 --- /dev/null +++ b/tests/fixtures/npm/cases/shape-overlay-facts/node_modules/shape-facts/package.json @@ -0,0 +1 @@ +{ "name": "shape-facts", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/shape-overlay-facts/package.json b/tests/fixtures/npm/cases/shape-overlay-facts/package.json new file mode 100644 index 000000000..d15818a37 --- /dev/null +++ b/tests/fixtures/npm/cases/shape-overlay-facts/package.json @@ -0,0 +1 @@ +{ "name": "shape-overlay-facts-case", "private": true, "type": "module" } diff --git a/tests/harness/shape-overlay.test.ts b/tests/harness/shape-overlay.test.ts index ad1988238..0eddc391d 100644 --- a/tests/harness/shape-overlay.test.ts +++ b/tests/harness/shape-overlay.test.ts @@ -60,3 +60,33 @@ test("shapeOverlay: false compiles without the overlay", { timeout: 900_000 }, a throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); expect((result.warnings ?? []).some((w) => w.code === "SC6103")).toBe(false); }); + +// shape-facts: results that include the undefined of a fall-through or a bare +// return keep the caller's falsy branch, comment-like text in literals keeps +// its value, and a replaced branch keeps its var declared. +const factsEntry = join(repoRoot, "tests/fixtures/npm/cases/shape-overlay-facts/main.ts"); + +test( + "the overlay keeps undefined results, literal text and hoisted vars", + { timeout: 900_000 }, + async () => { + mkdirSync(outDir, { recursive: true }); + const result = await withOverlay(() => + compile(factsEntry, { outPath: join(outDir, "facts"), outDir, sanitize }), + ); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const report = (result.warnings ?? []).find((w) => w.code === "SC6103"); + expect(report?.message).toMatch(/of 1 package \(shape-facts\)/); + expect(report?.message).toMatch(/replaces 1 branch it proved unreachable/); + const native = await execFileAsync(result.binaryPath, [], { + cwd: dirname(factsEntry), + encoding: "utf8", + }); + const node = await execFileAsync(process.execPath, [factsEntry], { + cwd: dirname(factsEntry), + encoding: "utf8", + }); + expect(native.stdout).toBe(node.stdout); + }, +); From add5f4869b12444003785a3f5da2045b8523722e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:53:15 -0500 Subject: [PATCH 172/395] Convert a loop's string receiver to UTF-16 at once, and publish it only when the scan holds another string --- .../src/backend/llvm/bytes-loop-versions.ts | 31 +++-- .../compiler/src/backend/llvm/string-reads.ts | 4 + .../test/string-scan-emission.test.ts | 4 +- .../test/string-unit-loop-emission.test.ts | 64 ++++++++++- .../corpus/8242-string-scan-republish.ts.json | 6 + packages/runtime/src/scr_string.c | 15 +++ packages/runtime/src/scr_string_methods.h | 5 + packages/runtime/test/test_string.c | 25 ++++ tests/corpus/8242-string-scan-republish.ts | 108 ++++++++++++++++++ 9 files changed, 249 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8242-string-scan-republish.ts.json create mode 100644 tests/corpus/8242-string-scan-republish.ts diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index 0fffacb25..de704ad42 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -23,7 +23,7 @@ import { f64Lit } from "./common.js"; import { emitStringAsciiFlag, emitStringByteLength, - STRING_CHAR_CODE_AT_DECL, + STRING_SCAN_UNITS_DECL, stringScanType, } from "./string-reads.js"; import { unitSafeLoop } from "./string-unit-versions.js"; @@ -1360,10 +1360,11 @@ export function emitBytesLoopVersionGuard( } /** The length of a version's units receiver `s` in UTF-16 units: its byte - * length when it is ASCII; otherwise, after one runtime read publishes it - * as the scan receiver, the published unit count (0 when the runtime does - * not hold its units). Also the units pointer, whether the loop is to - * read it, and whether the published receiver is `s`. */ + * length when it is ASCII; otherwise, once it is the scan receiver, the + * published unit count (0 when the runtime does not hold its units). When + * `s` is not already the scan receiver, the runtime converts it to units + * and publishes it (scr_str_scan_units). Also the units pointer, whether + * the loop is to read it, and whether the published receiver is `s`. */ function emitUnitsLength( host: LlvmEmitterContext, s: string, @@ -1378,16 +1379,28 @@ function emitUnitsLength( join = B.newLabel("bytes.version.length"); B.condBr(ascii, join, publish); B.startBlock(publish); - host.declare(STRING_CHAR_CODE_AT_DECL); - const read = B.tmp(), - receiver = B.tmp(), + // When `s` is already the scan receiver (an earlier loop over it published + // it), its units stay published until the runtime clears the scan: skip + // the call. + const held = B.tmp(), + holds = B.tmp(); + B.line(`${held} = load ptr, ptr @scr_str_scan`); + B.line(`${holds} = icmp eq ptr ${held}, ${s}`); + const call = B.newLabel("bytes.version.publish.units"), + load = B.newLabel("bytes.version.publish.load"); + B.condBr(holds, load, call); + B.startBlock(call); + host.declare(STRING_SCAN_UNITS_DECL); + const receiver = B.tmp(), same = B.tmp(), pointerSlot = B.tmp(), pointer = B.tmp(), countSlot = B.tmp(), count = B.tmp(), published = B.tmp(); - B.line(`${read} = call double @scr_str_char_code_at(ptr ${s}, double 0.0)`); + B.line(`call void @scr_str_scan_units(ptr ${s})`); + B.br(load); + B.startBlock(load); B.line(`${receiver} = load ptr, ptr @scr_str_scan`); B.line(`${same} = icmp eq ptr ${receiver}, ${s}`); B.line(`${pointerSlot} = getelementptr inbounds ${type}, ptr @scr_str_scan, i32 0, i32 1`); diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 08df79cf2..9fd2fa78c 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -24,6 +24,10 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js"; export const STRING_UTF16_LEN_DECL = "declare double @scr_str_utf16_len(ptr)"; export const STRING_CHAR_CODE_AT_DECL = "declare double @scr_str_char_code_at(ptr, double)"; +/** Converts a string to UTF-16 units and publishes it as the scan receiver + * (bytes-loop-versions.ts). Like the fallbacks below, it can evict an + * index entry, so it carries no memory restriction. */ +export const STRING_SCAN_UNITS_DECL = "declare void @scr_str_scan_units(ptr)"; /** The runtime fallbacks are cold: both common cases read inline. They * carry no * memory restriction: a fallback can evict an index entry, which resets diff --git a/packages/compiler/test/string-scan-emission.test.ts b/packages/compiler/test/string-scan-emission.test.ts index 427e2d543..76242e3d4 100644 --- a/packages/compiler/test/string-scan-emission.test.ts +++ b/packages/compiler/test/string-scan-emission.test.ts @@ -37,7 +37,9 @@ console.log(scan("é-é", "ü-ü", "ö-ö"), at("é", 0.5)); expect(llvm).toContain("@scr_str_scan = external global"); const fallbacks = llvm .split("\n") - .filter((line) => /call double @scr_str_(utf16_len|char_code_at)\(/.test(line)); + .filter((line) => + /call (double|void) @scr_str_(utf16_len|char_code_at|scan_units)\(/.test(line), + ); expect(fallbacks.length).toBeGreaterThanOrEqual(3); for (const line of fallbacks) expect(line).not.toMatch(/\bmemory\(/); } finally { diff --git a/packages/compiler/test/string-unit-loop-emission.test.ts b/packages/compiler/test/string-unit-loop-emission.test.ts index 32ee8411b..fa11af007 100644 --- a/packages/compiler/test/string-unit-loop-emission.test.ts +++ b/packages/compiler/test/string-unit-loop-emission.test.ts @@ -50,9 +50,9 @@ console.log(total, spaces("plain ascii text"), spaces("")); )![0]; const spaces = body("spaces"); - // A string that is not ASCII is published as the scan receiver once, - // before the loop, and its copy loads units. - expect(spaces).toMatch(/call double @scr_str_char_code_at\(ptr %\S+, double 0\.0\)/); + // A string that is not ASCII is converted to units and published as the + // scan receiver once, before the loop, and its copy loads units. + expect(spaces).toMatch(/call void @scr_str_scan_units\(ptr %\S+\)/); // The copies follow the checked one: bytes, then units. const bytesAt = spaces.search(/^bytes\.version\.fast\d+:/m); const unitsAt = spaces.search(/^bytes\.version\.fast\.units\d+:/m); @@ -92,3 +92,61 @@ console.log(total, spaces("plain ascii text"), spaces("")); await rm(dir, { recursive: true, force: true }); } }); + +test("loops that start at each token publish the scan receiver only when it changed", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unit-republish-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + ` +function words(s: string): number { + let n = 0, + pos = 0; + while (pos < s.length) { + while (pos < s.length && s.charCodeAt(pos) !== 32) pos++; + while (pos < s.length && s.charCodeAt(pos) === 32) pos++; + n++; + } + return n; +} +let text = ""; +for (let k = 0; k < 400; k++) text += k % 3 === 0 ? "café " : "naïve façade "; +console.log(words(text), words(text.replace(/a/g, "ä")), words("plain ascii")); +`, + ); + 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 words = ll.match( + /define internal [^\n]+ @sc_bf_words(?:\.i32)?\([^\n]*\) #0 \{[\s\S]*?\n\}/, + )![0]; + // Each inner loop's entry compares the scan receiver with `s` and calls + // the runtime to publish `s` only when they differ. + const guards = [ + ...words.matchAll( + /^bytes\.version\.publish\d+:[^\n]*\n\s+(%\S+) = load ptr, ptr @scr_str_scan\n\s+(%\S+) = icmp eq ptr \1, (%\S+)\n\s+br i1 \2, label %(bytes\.version\.publish\.load\d+), label %(bytes\.version\.publish\.units\d+)\n/gm, + ), + ]; + expect(guards.length).toBeGreaterThanOrEqual(2); + for (const [, , , s, load, convert] of guards) { + const block = words.slice(words.search(new RegExp(`^${convert}:`, "m"))); + expect(block).toMatch( + new RegExp( + `^${convert}:[^\\n]*\\n\\s+call void @scr_str_scan_units\\(ptr ${s}\\)\\n\\s+br label %${load}\\n`, + ), + ); + } + const calls = words.match(/call void @scr_str_scan_units\(ptr %\S+\)/g)!; + expect(calls.length).toBe(guards.length); + + const exe = join(dir, "main"); + const built = await compile(entry, { outputKind: "exe", outDir: dir, outPath: exe }); + if (!built.ok) throw new Error(JSON.stringify(built.diagnostics)); + expect(execFileSync(exe, { encoding: "utf8" })).toBe("666 666 2\n"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8242-string-scan-republish.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8242-string-scan-republish.ts.json new file mode 100644 index 000000000..78a4f52e6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8242-string-scan-republish.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8242-string-scan-republish.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 2d7f46d85..d2d397e25 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1889,6 +1889,21 @@ static bool scr_sidx_units(const ScrStr *s, ScrSidx *e) { return true; } +/* A loop about to read every unit of s (bytes-loop-versions.ts) converts it + * at once instead of after the threshold's reads, and publishes it as the + * scan's receiver. ASCII receivers and receivers past SCR_SIDX_UNITS_MAX + * publish nothing; the caller compares scr_str_scan.s with s. */ +void scr_str_scan_units(ScrStr *s) { + if (s->ascii || s->len > SCR_SIDX_UNITS_MAX) return; + ScrSidx *e = scr_sidx(s); + if (!e->units) e->units = scr_str_units(s, &e->nunits); + if (scr_str_scan.s != s) { + scr_str_scan.s = s; + scr_str_scan.units = e->units; + scr_str_scan.n = e->nunits; + } +} + typedef enum { SCR_UNIT_FIRST, SCR_UNIT_LAST, SCR_UNIT_AT } ScrUnitSearch; /* The UTF-16 index of needle in s: the first match at or after `from`, diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 1f25b8766..820b1c9e6 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -42,6 +42,11 @@ extern SCR_TL ScrStrScan scr_str_scan; * [0, length) — note charCodeAt(1.5) is index 1, matching Node. */ double scr_str_char_code_at(ScrStr *s, double i); +/* Converts a non-ASCII s up to the units cap to UTF-16 at once and + * publishes it in scr_str_scan, for a loop about to read all of it. Leaves + * scr_str_scan unchanged for any other s. */ +void scr_str_scan_units(ScrStr *s); + /* indexOf(needle, fromIndex): UTF-16 index of the first occurrence at or * after fromIndex (clamped to [0, length]), or -1. Empty needle returns the * clamped fromIndex per spec. */ diff --git a/packages/runtime/test/test_string.c b/packages/runtime/test/test_string.c index e49d8eb8b..3e14a5958 100644 --- a/packages/runtime/test/test_string.c +++ b/packages/runtime/test/test_string.c @@ -522,6 +522,30 @@ static void short_string_asserts(void) { scr_str_release(source); } +/* A loop's publish converts a non-ASCII receiver before any read, while a + * lone read still walks bytes. ASCII receivers publish nothing. */ +static void scan_units_asserts(void) { + ScrStr *s = scr_str_new("a\xC3\xA9\xF0\x9F\x98\x80" "b", 8); /* a é 😀 b */ + static const double codes[] = {97, 233, 0xD83D, 0xDE00, 98}; + if (scr_str_char_code_at(s, 0) != 97 || scr_str_scan.s == s) failed++; + scr_str_scan_units(s); + if (scr_str_scan.s != s || scr_str_scan.n != 5) failed++; + for (size_t i = 0; i < 5; i++) + if (scr_str_scan.n == 5 && (scr_str_scan.units[i] != codes[i] || + scr_str_char_code_at(s, (double)i) != codes[i])) failed++; + if (!isnan(scr_str_char_code_at(s, 5))) failed++; + scr_str_scan_units(s); /* republishing keeps the same units */ + const uint16_t *units = scr_str_scan.units; + scr_str_scan_units(s); + if (scr_str_scan.s != s || scr_str_scan.units != units) failed++; + ScrStr *ascii = scr_str_new("plain", 5); + scr_str_scan_units(ascii); + if (scr_str_scan.s != s) failed++; + scr_str_release(ascii); + scr_str_release(s); + if (scr_str_scan.s != NULL) failed++; + total++; +} #ifdef SCR_SIDX_TEST static void sidx_fail(const char *what) { failed++; @@ -1153,6 +1177,7 @@ int main(int argc, char **argv) { split_scratch_asserts(); accumulation_asserts(); short_string_asserts(); + scan_units_asserts(); #ifdef SCR_SIDX_TEST sparse_index_asserts(); sparse_ascii_prefix_asserts(); diff --git a/tests/corpus/8242-string-scan-republish.ts b/tests/corpus/8242-string-scan-republish.ts new file mode 100644 index 000000000..9800bb099 --- /dev/null +++ b/tests/corpus/8242-string-scan-republish.ts @@ -0,0 +1,108 @@ +// Token scans that alternate between two non-ASCII strings: each inner loop +// starts at a token and runs while the class holds, so every token enters a +// loop over one of the strings again. Also a string extended in place +// between scans, strings rebuilt with the same length and new content, and +// slices of a scanned string. + +function isWord(c: number): boolean { + return (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || c >= 0xc0; +} + +function isDigit(c: number): boolean { + return c >= 48 && c <= 57; +} + +// The kinds and lengths of s's tokens, hashed. +function lex(s: string): number { + let h = 0, + tokens = 0, + pos = 0; + const end = s.length; + while (pos < end) { + const c = s.charCodeAt(pos); + let start = pos; + if (isWord(c)) { + while (pos < end && isWord(s.charCodeAt(pos))) pos++; + h = (h * 31 + 1) | 0; + } else if (isDigit(c)) { + while (pos < end && isDigit(s.charCodeAt(pos))) pos++; + h = (h * 31 + 2) | 0; + } else if (c === 32) { + while (pos < end && s.charCodeAt(pos) === 32) pos++; + start = pos; + } else { + pos++; + h = (h * 31 + c) | 0; + } + h = (h * 31 + (pos - start)) | 0; + tokens++; + } + return h ^ tokens; +} + +// Two strings lexed token by token in turn: each token's loop runs over a +// different receiver than the one before it. +function interleave(a: string, b: string): number { + let i = 0, + j = 0, + h = 0; + while (i < a.length || j < b.length) { + const fromA = i < a.length; + while (i < a.length && a.charCodeAt(i) !== 32) { + h = (h * 31 + a.charCodeAt(i)) | 0; + i++; + } + i++; + while (j < b.length && b.charCodeAt(j) !== 32) { + h = (h * 37 + b.charCodeAt(j)) | 0; + j++; + } + j++; + if (fromA) h = (h + 1) | 0; + } + return h; +} + +// The sum of every unit of s. +function units(s: string): number { + let sum = 0; + for (let i = 0; i < s.length; i++) sum += s.charCodeAt(i); + return sum; +} + +function text(seed: number, words: number): string { + const parts = ["café", "naïve", "façade", "x", "😀", "Ωmega", "über", "ab"]; + let s = ""; + for (let k = 0; k < words; k++) { + s += parts[(seed + k * 7) % parts.length]!; + s += k % 5 === 0 ? " " + ((seed * k) % 1000) + " " : k % 3 === 0 ? "; " : " "; + } + return s; +} + +const first = text(1, 300), + second = text(4, 280); +for (let pass = 0; pass < 3; pass++) { + console.log(lex(first), lex(second), lex(first), units(second), units(first)); + console.log(interleave(first, second), interleave(second, first)); +} + +// Extended in place between scans: the second scan reads the new units. +let grown = text(2, 120); +console.log(lex(grown), units(grown), grown.length); +for (let round = 0; round < 4; round++) { + grown += round % 2 === 0 ? " é1 ü22 " : " 😀 tail "; + console.log(lex(grown), units(grown), grown.length, interleave(grown, first)); +} + +// Same length, different content, built after the previous one is dropped. +for (let round = 0; round < 6; round++) { + const s = round % 2 === 0 ? "é".repeat(200) + " a" : "ü".repeat(200) + " b"; + console.log(lex(s), units(s), units(s.slice(0, 100)), lex(second)); +} + +// Slices of a scanned string, scanned between scans of the whole. +for (const at of [0, 7, 101, first.length - 9]) { + const part = first.slice(at, at + 64); + console.log(lex(part), units(part), lex(first), units(part.slice(3))); +} From faa54f6443d35873d37b29ea9e4385707553744f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:58:41 -0500 Subject: [PATCH 173/395] Cache only number-key integer views that need no conversion A Map/Set lookup's integer key view was cached for the function by the key operand's name. When the view was a conversion emitted at a lookup in a branch, a later lookup of the same operand (a wide integer constant) reused it from a block the conversion does not dominate, and LLVM rejected the module. Only the value's own integer or uint32 view and integral literals are cached now. --- .../backend/llvm/map-number-lookup.test.ts | 49 +++++++++++++++++++ .../src/backend/llvm/map-number-lookup.ts | 16 ++++-- tests/corpus/6308-number-map-integer-keys.ts | 15 ++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts index 28b6a0552..7457db42d 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts @@ -206,6 +206,55 @@ test("a key with an integer view tests the direct index on the integer", () => { } }); +test("a key view converted in a branch is not reused by a later lookup of the same constant", () => { + // work(map, k, taken): if (taken) return map.has((k = 2 ** 40)); return map.has(2 ** 40). + const wide = 2 ** 40; + const mod = lookupFixture(); + const work = mod.functions[1]!; + const map = work.params[0]!.type; + const has = (key: IrExpr): IrExpr => ({ + kind: "mapIntrinsic", + method: "has", + receiver: ref("map", map), + args: [key], + type: BOOL, + loc, + }); + const literal: IrExpr = { kind: "numLit", value: wide, type: F64, loc }; + const taken = { localId: "taken", name: "taken", type: BOOL }; + work.params.push(taken); + work.locals.push({ id: "taken", name: "taken", type: BOOL, mutable: true }); + work.body = [ + { + kind: "if", + cond: ref("taken", BOOL), + then: [ + { + kind: "return", + value: has({ + kind: "assignExpr", + localId: "k", + value: literal, + type: F64, + loc, + }), + loc, + }, + ], + else_: null, + loc, + }, + { kind: "return", value: has(literal), loc }, + ]; + for (const bits of [32, 64] as const) { + const { work: text } = emitted(mod, bits); + // The lookup after the branch takes the literal, not a conversion made + // inside the branch. + expect(text).toMatch(/call ptr @sc_map_entry_f64_int\(ptr %\S+, i64 1099511627776, double /); + expect(text).not.toMatch(/@sc_map_entry_f64_int\(ptr %\S+, i64 %/); + } +}); + /** work(map): map.size, and keep(map): map returned as an owned value. */ function sizeFixture(): IrModule { const map: IrType = { kind: "map", key: F64, value: F64 }; diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.ts b/packages/compiler/src/backend/llvm/map-number-lookup.ts index 6cb7f1ea3..0038cbb96 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.ts @@ -34,12 +34,22 @@ import { exactInteger, widenInteger } from "./integer-values.js"; import { mapKeyAccess } from "./shapes.js"; export const NUMBER_MAP_ENTRY_DECL = "declare ptr @scr_map_entry_f64(ptr, double)"; -/** Record the integer view of a number Map/Set key operand, when the - * emitter has one (exactInteger), for the entry lookups of that key. */ +/** Record the integer view of a number Map/Set key operand for the entry + * lookups of that key, when the view needs no instruction of its own: the + * value's integer or uint32 view, defined with the value, or an integral + * literal. A view exactInteger converts at this lookup is not recorded: the + * key cache spans the function, and a later lookup of the same operand (a + * constant, say) can be in a block the conversion does not dominate. */ export function noteNumberMapKey(host: LlvmEmitterContext, key: LlValue, expr: IrExpr): void { if (key.type.kind !== "f64" || host.numberMapKeyIntegers.has(key.name)) return; + if (!key.integer && key.uint32 === undefined && expr.kind !== "numLit") return; const integer = exactInteger(host, key, expr); - if (integer) host.numberMapKeyIntegers.set(key.name, integer); + if (!integer) return; + const defined = + integer.name === key.integer?.name || + integer.name === key.uint32 || + /^-?\d+$/.test(integer.name); + if (defined) host.numberMapKeyIntegers.set(key.name, integer); } /** The entry for `key`, or null when absent. Valid until the next mutation. diff --git a/tests/corpus/6308-number-map-integer-keys.ts b/tests/corpus/6308-number-map-integer-keys.ts index 16110f80a..75efb3a72 100644 --- a/tests/corpus/6308-number-map-integer-keys.ts +++ b/tests/corpus/6308-number-map-integer-keys.ts @@ -60,3 +60,18 @@ for (let i = 0; i < 5; i++) { key = key * 2 - 1; } console.log([...wide.keys()].join(" "), wide.get(2 ** 52), wide.get(2 ** 53 - 1)); + +// A lookup whose key is an assignment of a wide integer constant, in a +// branch, then a lookup of the same constant after the branch. +const far = new Map(); +far.set(1099511627776, 7); +function probeFar(taken: boolean): number { + let k = 0; + let hits = 0; + if (taken) { + if (far.has((k = 1099511627776))) hits += 1; + } + hits += far.get(1099511627776) ?? -1; + return hits + k; +} +console.log(probeFar(true), probeFar(false)); From 6a299479988ccc7ee188e458786cdbcbf1259b47 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 22:59:02 -0500 Subject: [PATCH 174/395] Run the native compiler's nursery passes every 1024 candidates The native compiler now starts with the cycle collector's nursery trigger at 1024 new candidates instead of 256. scr_cyc_set_nursery_threshold sets the trigger that SCR_CYCLE_THRESHOLD sets, on the pattern of scr_cyc_set_growth_cap; the environment variable still takes precedence. The compiler sets it from a constructor in its host object, before its modules initialize. Set from main, the trigger leaves the build's collection work where it was: the cost depends on the collector's first passes. Programs keep the default trigger. A cold tsc-ts build takes 4% less wall time (12 paired runs) and its peak RSS goes from 1.41 GB to 1.31 GB. The program's IR is unchanged. --- packages/compiler/native/host.c | 11 +++++++++++ packages/runtime/src/scr_cycle.c | 19 +++++++++++++++---- packages/runtime/src/scr_rc.h | 6 ++++++ packages/runtime/test/test_cycle.c | 19 +++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index f54bdaa3f..4e1a0b754 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -135,3 +135,14 @@ void scr_cyc_set_growth_cap(long cap); void scriptc_native_bound_cycle_growth(void) { scr_cyc_set_growth_cap(2); } + +/* The runtime's nursery trigger (scr_rc.h). */ +void scr_cyc_set_nursery_threshold(long threshold); + +/* Runs nursery passes every 1024 new candidates instead of every 256. It + * has to be in place before the compiler's modules initialize: set from + * main it leaves the build's collection work as it is. SCR_CYCLE_THRESHOLD + * still overrides it. */ +__attribute__((constructor)) static void scriptc_native_widen_cycle_nursery(void) { + scr_cyc_set_nursery_threshold(1024); +} diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index dbd636306..d08f54a21 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -637,14 +637,25 @@ static SCR_TL bool scr_cyc_idle_points = false; static SCR_TL size_t scr_cyc_death_due_live = 0; #endif +/* The nursery trigger a program sets for itself + * (scr_cyc_set_nursery_threshold), 0 for the default; SCR_CYCLE_THRESHOLD + * takes precedence over it. */ +static long scr_cyc_nursery_threshold_set = 0; +static SCR_TL size_t scr_cyc_nursery_threshold_cached = 0; + static size_t scr_cyc_nursery_threshold(void) { - static SCR_TL size_t cached = 0; - if (cached == 0) { + if (scr_cyc_nursery_threshold_cached == 0) { const char *env = scr_getenv("SCR_CYCLE_THRESHOLD"); long v = env ? strtol(env, NULL, 10) : 0; - cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_CANDIDATES; + if (v <= 0) v = scr_cyc_nursery_threshold_set; + scr_cyc_nursery_threshold_cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_CANDIDATES; } - return cached; + return scr_cyc_nursery_threshold_cached; +} + +void scr_cyc_set_nursery_threshold(long threshold) { + scr_cyc_nursery_threshold_set = threshold; + scr_cyc_nursery_threshold_cached = 0; } /* The number of buffered candidates (across every generation) at which the diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index 4d8e059a9..de9f21f44 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -700,6 +700,12 @@ void scr_collect_cycles_phase(void); * that knows its own memory profile; the environment variable takes * precedence. Call it before the program allocates much. */ void scr_cyc_set_growth_cap(long cap); +/* Sets the nursery trigger SCR_CYCLE_THRESHOLD sets (scr_cycle.c): the + * number of new candidates at which the release path runs a pass, for a + * program that knows its own collection profile. The environment variable + * takes precedence; 0 restores the default. The trigger re-arms with it at + * the end of the next pass. Call it before the program allocates much. */ +void scr_cyc_set_nursery_threshold(long threshold); void scr_cyc_context_cleanup(void); /* One pass on the normal generational schedule, for callers that reach a diff --git a/packages/runtime/test/test_cycle.c b/packages/runtime/test/test_cycle.c index 160083d6b..0ab827e47 100644 --- a/packages/runtime/test/test_cycle.c +++ b/packages/runtime/test/test_cycle.c @@ -557,6 +557,24 @@ static void check_program_growth_cap(void) { scr_cyc_set_growth_cap(0); } +/* scr_cyc_set_nursery_threshold sets the trigger SCR_CYCLE_THRESHOLD sets, + * from the end of the next pass: with a trigger of 1024, dead rings released + * after a full pass wait until the 1024th, which collects them all. 0 + * restores the default. */ +static void check_program_nursery_threshold(void) { + enum { THRESHOLD = 1024 }; + if (getenv("SCR_CYCLE_THRESHOLD") != NULL) return; /* it takes precedence */ + scr_cyc_set_nursery_threshold(THRESHOLD); + scr_collect_cycles(); /* re-arm with the new trigger */ + size_t before = freed; + for (size_t i = 0; i < THRESHOLD - 1; i++) release_live(make_ring(1)); + check(freed == before, "a program's nursery trigger did not hold the pass back"); + release_live(make_ring(1)); + check(freed - before == THRESHOLD, "a program's nursery trigger did not run the pass"); + scr_cyc_set_nursery_threshold(0); + scr_collect_cycles(); +} + /* The phase sweep (scr_collect_cycles_phase) is for a caller that knows a * phase's data has just died: it sweeps, and the growth schedule starts * over. After passes that find nothing have widened the old level's @@ -1522,6 +1540,7 @@ int main(void) { check_unjudged_backlog_follows_growth(); check_acyclic_objects(); check_program_growth_cap(); + check_program_nursery_threshold(); printf("cycle collection checks passed: threshold=%zu growth_cap=%zu\n", configured_nursery_threshold(), configured_growth_cap()); return 0; From f716dec7567fc2deffc7a05dcdef5bfe69802ad3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:26:01 -0500 Subject: [PATCH 175/395] Convert Buffers to and from latin1 and hex text a word at a time - toString("latin1"): counts the high bytes eight at a time, copies words of ASCII bytes, and writes other bytes from a table of their UTF-8 pairs without branches. ASCII-only results are marked ASCII, and other results record their UTF-16 length (one unit per byte), so `.length` does not walk the string. - Buffer.from(text, "latin1" | "ascii" | "binary"): copies text known to be ASCII, copies words of ASCII bytes, and decodes eight bytes of one- and two-byte sequences in one word, storing each output byte without branches. Longer sequences take the previous path. - Buffer.from(text, "hex"): reads digit values from a table. - toString("hex") and toString("base64"/"base64url") mark their results ASCII, so a later decode, length or index read skips the scan for non-ASCII bytes. --- .../corpus/8005-buffer-encoding-paths.ts.json | 6 + packages/runtime/src/scr_bytes.c | 214 +++++++++++++++--- packages/runtime/src/scr_string.c | 13 ++ packages/runtime/src/scr_string.h | 3 + tests/corpus/8005-buffer-encoding-paths.ts | 105 +++++++++ .../8005-buffer-encoding-paths.ts.json | 1 + 6 files changed, 315 insertions(+), 27 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json create mode 100644 tests/corpus/8005-buffer-encoding-paths.ts create mode 100644 tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json new file mode 100644 index 000000000..7d6dac570 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8005-buffer-encoding-paths.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index e6df837c8..c1638596c 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -1577,6 +1577,7 @@ static ScrStr *scr_bytes_encode_b64(const uint8_t *in, size_t n, bool url) { if (!url) out[o++] = '='; } out[outlen] = '\0'; + scr_str_note_ascii(s); return s; } @@ -2497,6 +2498,106 @@ static ScrStr *scr_bytes_decode_utf16le(const uint8_t *in, size_t n) { return s; } +/* The UTF-8 bytes of U+0000..U+00FF: the byte itself below 0x80 (the + * second entry is overwritten by the next write), else a two-byte + * sequence. */ +static const uint8_t scr_latin1_utf8[256][2] = { + {0x00, 0x00}, {0x01, 0x00}, {0x02, 0x00}, {0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x06, 0x00}, {0x07, 0x00}, + {0x08, 0x00}, {0x09, 0x00}, {0x0a, 0x00}, {0x0b, 0x00}, {0x0c, 0x00}, {0x0d, 0x00}, {0x0e, 0x00}, {0x0f, 0x00}, + {0x10, 0x00}, {0x11, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x17, 0x00}, + {0x18, 0x00}, {0x19, 0x00}, {0x1a, 0x00}, {0x1b, 0x00}, {0x1c, 0x00}, {0x1d, 0x00}, {0x1e, 0x00}, {0x1f, 0x00}, + {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, + {0x28, 0x00}, {0x29, 0x00}, {0x2a, 0x00}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x00}, {0x2e, 0x00}, {0x2f, 0x00}, + {0x30, 0x00}, {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00}, {0x34, 0x00}, {0x35, 0x00}, {0x36, 0x00}, {0x37, 0x00}, + {0x38, 0x00}, {0x39, 0x00}, {0x3a, 0x00}, {0x3b, 0x00}, {0x3c, 0x00}, {0x3d, 0x00}, {0x3e, 0x00}, {0x3f, 0x00}, + {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x00}, {0x43, 0x00}, {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x47, 0x00}, + {0x48, 0x00}, {0x49, 0x00}, {0x4a, 0x00}, {0x4b, 0x00}, {0x4c, 0x00}, {0x4d, 0x00}, {0x4e, 0x00}, {0x4f, 0x00}, + {0x50, 0x00}, {0x51, 0x00}, {0x52, 0x00}, {0x53, 0x00}, {0x54, 0x00}, {0x55, 0x00}, {0x56, 0x00}, {0x57, 0x00}, + {0x58, 0x00}, {0x59, 0x00}, {0x5a, 0x00}, {0x5b, 0x00}, {0x5c, 0x00}, {0x5d, 0x00}, {0x5e, 0x00}, {0x5f, 0x00}, + {0x60, 0x00}, {0x61, 0x00}, {0x62, 0x00}, {0x63, 0x00}, {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x00}, {0x67, 0x00}, + {0x68, 0x00}, {0x69, 0x00}, {0x6a, 0x00}, {0x6b, 0x00}, {0x6c, 0x00}, {0x6d, 0x00}, {0x6e, 0x00}, {0x6f, 0x00}, + {0x70, 0x00}, {0x71, 0x00}, {0x72, 0x00}, {0x73, 0x00}, {0x74, 0x00}, {0x75, 0x00}, {0x76, 0x00}, {0x77, 0x00}, + {0x78, 0x00}, {0x79, 0x00}, {0x7a, 0x00}, {0x7b, 0x00}, {0x7c, 0x00}, {0x7d, 0x00}, {0x7e, 0x00}, {0x7f, 0x00}, + {0xc2, 0x80}, {0xc2, 0x81}, {0xc2, 0x82}, {0xc2, 0x83}, {0xc2, 0x84}, {0xc2, 0x85}, {0xc2, 0x86}, {0xc2, 0x87}, + {0xc2, 0x88}, {0xc2, 0x89}, {0xc2, 0x8a}, {0xc2, 0x8b}, {0xc2, 0x8c}, {0xc2, 0x8d}, {0xc2, 0x8e}, {0xc2, 0x8f}, + {0xc2, 0x90}, {0xc2, 0x91}, {0xc2, 0x92}, {0xc2, 0x93}, {0xc2, 0x94}, {0xc2, 0x95}, {0xc2, 0x96}, {0xc2, 0x97}, + {0xc2, 0x98}, {0xc2, 0x99}, {0xc2, 0x9a}, {0xc2, 0x9b}, {0xc2, 0x9c}, {0xc2, 0x9d}, {0xc2, 0x9e}, {0xc2, 0x9f}, + {0xc2, 0xa0}, {0xc2, 0xa1}, {0xc2, 0xa2}, {0xc2, 0xa3}, {0xc2, 0xa4}, {0xc2, 0xa5}, {0xc2, 0xa6}, {0xc2, 0xa7}, + {0xc2, 0xa8}, {0xc2, 0xa9}, {0xc2, 0xaa}, {0xc2, 0xab}, {0xc2, 0xac}, {0xc2, 0xad}, {0xc2, 0xae}, {0xc2, 0xaf}, + {0xc2, 0xb0}, {0xc2, 0xb1}, {0xc2, 0xb2}, {0xc2, 0xb3}, {0xc2, 0xb4}, {0xc2, 0xb5}, {0xc2, 0xb6}, {0xc2, 0xb7}, + {0xc2, 0xb8}, {0xc2, 0xb9}, {0xc2, 0xba}, {0xc2, 0xbb}, {0xc2, 0xbc}, {0xc2, 0xbd}, {0xc2, 0xbe}, {0xc2, 0xbf}, + {0xc3, 0x80}, {0xc3, 0x81}, {0xc3, 0x82}, {0xc3, 0x83}, {0xc3, 0x84}, {0xc3, 0x85}, {0xc3, 0x86}, {0xc3, 0x87}, + {0xc3, 0x88}, {0xc3, 0x89}, {0xc3, 0x8a}, {0xc3, 0x8b}, {0xc3, 0x8c}, {0xc3, 0x8d}, {0xc3, 0x8e}, {0xc3, 0x8f}, + {0xc3, 0x90}, {0xc3, 0x91}, {0xc3, 0x92}, {0xc3, 0x93}, {0xc3, 0x94}, {0xc3, 0x95}, {0xc3, 0x96}, {0xc3, 0x97}, + {0xc3, 0x98}, {0xc3, 0x99}, {0xc3, 0x9a}, {0xc3, 0x9b}, {0xc3, 0x9c}, {0xc3, 0x9d}, {0xc3, 0x9e}, {0xc3, 0x9f}, + {0xc3, 0xa0}, {0xc3, 0xa1}, {0xc3, 0xa2}, {0xc3, 0xa3}, {0xc3, 0xa4}, {0xc3, 0xa5}, {0xc3, 0xa6}, {0xc3, 0xa7}, + {0xc3, 0xa8}, {0xc3, 0xa9}, {0xc3, 0xaa}, {0xc3, 0xab}, {0xc3, 0xac}, {0xc3, 0xad}, {0xc3, 0xae}, {0xc3, 0xaf}, + {0xc3, 0xb0}, {0xc3, 0xb1}, {0xc3, 0xb2}, {0xc3, 0xb3}, {0xc3, 0xb4}, {0xc3, 0xb5}, {0xc3, 0xb6}, {0xc3, 0xb7}, + {0xc3, 0xb8}, {0xc3, 0xb9}, {0xc3, 0xba}, {0xc3, 0xbb}, {0xc3, 0xbc}, {0xc3, 0xbd}, {0xc3, 0xbe}, {0xc3, 0xbf}, +}; + +/* Each byte is U+00XX ("binary" normalizes here upstream): a byte below + * 0x80 is one UTF-8 byte and any other byte is two. Eight bytes without a + * high bit copy as one word; other bytes take their two table bytes and + * advance by their length, without branches (the terminator slot holds + * the last spare byte). */ +/* The number of bytes from 0x80: eight per step, each step adding its + * high bits into byte lanes that are summed every 255 steps. */ +static size_t scr_bytes_count_high(const uint8_t *in, size_t n) { + const uint64_t ones = UINT64_C(0x0101010101010101); + const uint64_t pairs = UINT64_C(0x00ff00ff00ff00ff); + size_t high = 0, i = 0; + while (n - i >= 8) { + size_t steps = (n - i) / 8 < 255 ? (n - i) / 8 : 255; + uint64_t lanes = 0; + for (size_t end = i + steps * 8; i < end; i += 8) { + uint64_t w; + memcpy(&w, in + i, 8); + lanes += (w >> 7) & ones; + } + lanes = (lanes & pairs) + ((lanes >> 8) & pairs); + high += (size_t)((lanes * UINT64_C(0x0001000100010001)) >> 48); + } + for (; i < n; i++) high += in[i] >> 7; + return high; +} + +static ScrStr *scr_bytes_latin1_text(const uint8_t *in, size_t n) { + size_t high = scr_bytes_count_high(in, n); + if (n > SIZE_MAX - high) scr_bytes_oom(); + ScrStr *s = scr_str_alloc_raw(n + high, n + high); + uint8_t *out = (uint8_t *)s->data; + if (high == 0) { + memcpy(out, in, n); + out[n] = '\0'; + scr_str_note_ascii(s); + return s; + } + const uint64_t top = UINT64_C(0x8080808080808080); + size_t i = 0, o = 0; + while (n - i >= 8) { + uint64_t w; + memcpy(&w, in + i, 8); + if (!(w & top)) { + memcpy(out + o, &w, 8); + i += 8; + o += 8; + continue; + } + for (size_t end = i + 8; i < end; i++) { + memcpy(out + o, scr_latin1_utf8[in[i]], 2); + o += 1 + (in[i] >> 7); + } + } + for (; i < n; i++) { + memcpy(out + o, scr_latin1_utf8[in[i]], 2); + o += 1 + (in[i] >> 7); + } + out[o] = '\0'; + scr_str_note_utf16_len(s, n); + return s; +} + ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc) { SCR_BYTES_SNAPSHOT(b); const uint8_t *in = b->data; @@ -2512,21 +2613,12 @@ ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc) { out[i * 2 + 1] = scr_hex_digits[in[i] & 0xf]; } out[n * 2] = '\0'; + scr_str_note_ascii(s); return s; } if (scr_enc_is(enc, "base64")) return scr_bytes_encode_b64(in, n, false); if (scr_enc_is(enc, "base64url")) return scr_bytes_encode_b64(in, n, true); - if (scr_enc_is(enc, "latin1")) { - /* Each byte is U+00XX ("binary" normalizes here upstream). */ - size_t high = 0; - for (size_t i = 0; i < n; i++) high += in[i] >> 7; - if (n > SIZE_MAX - high) scr_bytes_oom(); - ScrStr *s = scr_str_alloc_raw(n + high, n + high); - size_t o = 0; - for (size_t i = 0; i < n; i++) o = scr_bytes_put_cp(s->data, o, in[i]); - s->data[o] = '\0'; - return s; - } + if (scr_enc_is(enc, "latin1")) return scr_bytes_latin1_text(in, n); if (scr_enc_is(enc, "ascii")) { /* Node's ascii decode masks the high bit: byte & 0x7f. */ ScrStr *s = scr_str_alloc_raw(n, n); @@ -2621,12 +2713,25 @@ ScrStr *scr_bytes_to_str_checked_range(const ScrBytes *b, const ScrStr *enc, return out; } -static int scr_hex_val(uint8_t c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} +/* Hex digit values, -1 for any other byte. */ +static const int8_t scr_hex_values[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; /* Both alphabets are accepted by both Buffer base64 encoding names. */ static const int8_t scr_b64_values[256] = { @@ -2720,17 +2825,71 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const size_t n = s->len; if (scr_enc_is(enc, "latin1") || scr_enc_is(enc, "ascii")) { /* Node writes charCodeAt(i) & 0xFF for BOTH spellings — an astral - * code point contributes its two surrogates' low bytes. */ + * code point contributes its two surrogates' low bytes. ASCII text + * copies. Elsewhere eight ASCII bytes copy as one word. In eight bytes + * holding two or more high bytes and no sequence longer than two + * bytes, every continuation byte's output byte (the code point's low + * byte, from the continuation and the lead before it) is computed in + * one word; each byte is then stored without branches, a continuation + * replacing the byte its lead wrote. */ ScrBytes *b = scr_bytes_conversion_storage_in(sink, n); + if (scr_str_known_ascii(s)) { + memcpy(b->data, in, n); + b->len = n; + return b; + } + /* A local copy: a byte store could otherwise change b->data. */ + uint8_t *dst = b->data; + const uint64_t top = UINT64_C(0x8080808080808080); size_t o = 0, i = 0; while (i < n) { + if (n - i >= 8) { + uint64_t w; + memcpy(&w, in + i, 8); + uint64_t high = w & top; + if (!high) { + memcpy(dst + o, &w, 8); + i += 8; + o += 8; + continue; + } +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + /* Byte k of the input is bits 8k..8k+7 of w. */ + if (i > 0 && (high & (high - 1)) && !(w & (w << 1) & (w << 2) & top)) { + uint64_t prev = (w << 8) | in[i - 1]; + uint64_t cont = w & ~(w << 1) & top; + uint64_t low = ((prev & UINT64_C(0x0303030303030303)) << 6) | + (w & UINT64_C(0x3f3f3f3f3f3f3f3f)); + uint64_t mask = (cont >> 7) * 0xff; + uint64_t out = (w & ~mask) | (low & mask); + for (unsigned k = 0; k < 8; k++) { + o -= (size_t)(cont >> (8 * k + 7)) & 1; + dst[o++] = (uint8_t)(out >> (8 * k)); + } + i += 8; + continue; + } +#endif + } + unsigned c = in[i]; + if (c < 0x80) { + dst[o++] = (uint8_t)c; + i++; + continue; + } + if (c >> 6 == 2) { + /* The rest of a two-byte sequence whose lead ended a word. */ + dst[o - 1] = (uint8_t)(((in[i - 1] & 3u) << 6) | (c & 0x3f)); + i++; + continue; + } uint32_t cp = scr_bytes_next_cp(in, &i); if (cp > 0xffff) { cp -= 0x10000; - b->data[o++] = (uint8_t)(0xd800 + (cp >> 10)); - b->data[o++] = (uint8_t)(0xdc00 + (cp & 0x3ff)); + dst[o++] = (uint8_t)(0xd800 + (cp >> 10)); + dst[o++] = (uint8_t)(0xdc00 + (cp & 0x3ff)); } else { - b->data[o++] = (uint8_t)cp; + dst[o++] = (uint8_t)cp; } } b->len = o; /* never grows past n: multi-byte cps shrink */ @@ -2762,7 +2921,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const /* Node interprets encoded text as the low byte of each UTF-16 unit. * ASCII can borrow storage; non-ASCII must preserve that truncation. */ ScrBytes *narrowed = NULL; - if (scr_bytes_ascii_prefix(in, n) != n) { + if (!scr_str_known_ascii(s) && scr_bytes_ascii_prefix(in, n) != n) { ScrStr *latin1 = scr_str_new("latin1", 6); narrowed = scr_bytes_encode_str(NULL, s, latin1); scr_str_release(latin1); @@ -2773,11 +2932,12 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const if (hex) { /* Stop at the first invalid pair, discarding an odd tail. */ b = scr_bytes_conversion_storage_in(sink, n / 2); - size_t o = 0; - for (size_t i = 0; i + 1 < n; i += 2) { - int hi = scr_hex_val(in[i]), lo = scr_hex_val(in[i + 1]); - if (hi < 0 || lo < 0) break; - b->data[o++] = (uint8_t)((hi << 4) | lo); + uint8_t *dst = b->data; + size_t pairs = n / 2, o = 0; + for (; o < pairs; o++) { + int hi = scr_hex_values[in[2 * o]], lo = scr_hex_values[in[2 * o + 1]]; + if ((hi | lo) < 0) break; + dst[o] = (uint8_t)((hi << 4) | lo); } b->len = o; } else { diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 2d7f46d85..a5cc15ba8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1991,6 +1991,19 @@ double scr_str_utf16_len(ScrStr *s) { return (double)(int64_t)units; } +/* Records the UTF-16 length of a string whose producer counted it (latin1 + * text has one unit per byte), so scr_str_utf16_len does not walk it. Only + * strings that scr_str_utf16_len would otherwise record are kept. */ +void scr_str_note_utf16_len(ScrStr *s, size_t units) { + if (s->ascii || s->len <= 64 || s->rc == SIZE_MAX) return; + if (!scr_u16len_tab) { + scr_u16len_tab = calloc(SCR_U16LEN_N, sizeof *scr_u16len_tab); + if (!scr_u16len_tab) return; + scr_sidx_register_cleanup(); + } + *scr_u16len_slot(s) = (ScrU16Len){s, units}; +} + double scr_str_char_code_at(ScrStr *s, double i) { /* A sequential scan of a non-ASCII receiver stays inside the bounded * ASCII span cached at its last resolved position. Its unit offset is an diff --git a/packages/runtime/src/scr_string.h b/packages/runtime/src/scr_string.h index fc2f15562..e269498b1 100644 --- a/packages/runtime/src/scr_string.h +++ b/packages/runtime/src/scr_string.h @@ -83,6 +83,9 @@ static inline void scr_str_hash_forget(ScrStr *s) { /* True only when every byte is proven below 0x80 (see above). */ static inline bool scr_str_known_ascii(const ScrStr *s) { return s->ascii; } +/* Record the UTF-16 length a string's producer counted (scr_string.c). */ +void scr_str_note_utf16_len(ScrStr *s, size_t units); + /* Record a proven-ASCII fact. Immortal strings may live in read-only * memory and are shared across threads, so they are never written. */ static inline void scr_str_note_ascii(ScrStr *s) { diff --git a/tests/corpus/8005-buffer-encoding-paths.ts b/tests/corpus/8005-buffer-encoding-paths.ts new file mode 100644 index 000000000..014f22abf --- /dev/null +++ b/tests/corpus/8005-buffer-encoding-paths.ts @@ -0,0 +1,105 @@ +// Buffer latin1, ascii, hex and base64 conversions in both directions: +// ASCII, binary and mixed contents at sizes around eight-byte words and the +// pool threshold, strings with code points past U+00FF, invalid hex and +// base64 text, and the lengths and characters of the strings produced. + +let seed = 12345; +function next(): number { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed >>> 8; +} + +function fill(size: number, kind: string): Buffer { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) { + const r = next(); + if (kind === "ascii") buf[i] = 32 + (r % 95); + else if (kind === "binary") buf[i] = r & 255; + else if (kind === "high") buf[i] = 128 + (r % 128); + else buf[i] = r % 23 === 0 ? 160 + (r % 96) : 97 + (r % 26); + } + return buf; +} + +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; +} + +const sizes = [0, 1, 7, 8, 9, 15, 16, 17, 63, 64, 65, 4095, 4096, 4097, 70001]; +for (const kind of ["ascii", "binary", "high", "sparse"]) { + const rows: string[] = []; + for (const size of sizes) { + const buf = fill(size, kind); + const latin1 = buf.toString("latin1"); + const back = Buffer.from(latin1, "latin1"); + const ascii = Buffer.from(latin1, "ascii"); + const binary = Buffer.from(buf.toString("binary"), "binary"); + const hex = buf.toString("hex"); + const b64 = buf.toString("base64"); + const url = buf.toString("base64url"); + rows.push( + [ + size, + latin1.length, + digest(latin1), + back.equals(buf), + ascii.equals(buf), + binary.equals(buf), + Buffer.from(hex, "hex").equals(buf), + Buffer.from(b64, "base64").equals(buf), + Buffer.from(url, "base64url").equals(buf), + hex.length, + b64.length, + Buffer.byteLength(latin1, "latin1"), + size > 2 ? latin1.charCodeAt(size - 2) : -1, + size > 4 ? latin1.slice(1, 4).length : -1, + ].join(","), + ); + } + console.log(kind, rows.join(" ")); +} + +// Strings with code points past U+00FF keep the low byte of each UTF-16 +// unit, and an astral code point gives its two surrogates' low bytes. +const wide = [ + "abcéÿ", + "ĀŁ߿", + "xࠀ€￿y", + "\u{1F600}a\u{10FFFF}", + "é".repeat(9) + "€" + "è".repeat(9), + "aaaaaaaébbbbbbbb中cccccccc\u{1F600}", + "ÀÁÂÃÄÅÆÇࠀÈ", +]; +for (const text of wide) { + console.log( + Buffer.from(text, "latin1").toString("hex"), + Buffer.from(text, "ascii").toString("hex"), + Buffer.byteLength(text, "latin1"), + ); +} +const mixed = "é".repeat(5000) + "中" + "z".repeat(5000) + "ÿ".repeat(3000); +const mixedBytes = Buffer.from(mixed, "latin1"); +console.log(mixedBytes.length, mixedBytes[0], mixedBytes[5000], mixedBytes[5001], mixedBytes[13000]); + +// Hex stops at the first invalid pair and drops an odd last digit. +for (const text of ["0aFf", "0aF", "0a1g22", "zz00", "", "ABCDEF0123456789", "0aİ0b", "00a"]) { + console.log(JSON.stringify(text), Buffer.from(text, "hex").toString("hex"), Buffer.byteLength(text, "hex")); +} +const longHex = "0123456789abcdef".repeat(600) + "0x" + "ff".repeat(10); +console.log(Buffer.from(longHex, "hex").length); + +// base64 skips characters outside both alphabets and stops at padding. +for (const text of ["aGVsbG8=", "aGVsbG8", "aGV sbG8=", "aGV\nsbG8", "-_-_", "+/+/", "aGVsbG8=d29ybGQ=", "a", "ab", "abc", "aébc", "====", "YQ"]) { + console.log(JSON.stringify(text), Buffer.from(text, "base64").toString("hex"), Buffer.from(text, "base64url").toString("hex")); +} + +// Strings produced by hex and base64 behave as ASCII text afterwards. +const produced = fill(5000, "binary"); +const producedHex = produced.toString("hex"); +const joined = producedHex + "é" + produced.toString("base64"); +console.log(joined.length, joined.indexOf("é"), digest(joined.slice(9990, 10020))); +const latinText = fill(9000, "high").toString("latin1"); +const appended = latinText + "€"; +console.log(latinText.length, appended.length, appended.charCodeAt(8999), appended.charCodeAt(9000), appended.slice(8998).length); diff --git a/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json b/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json new file mode 100644 index 000000000..3aafbe296 --- /dev/null +++ b/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json @@ -0,0 +1 @@ +{ "plain": 2558, "san": 4160 } From 20041b4262e9be646fa30215f5efb3300925bdea Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:27:15 -0500 Subject: [PATCH 176/395] Keep speculative copies in typed-array loops over shared storage In a program that can start a worker, a typed-array loop version's guard requires thread-owned storage, so every view of a SharedArrayBuffer runs the version's checked copy. That copy gave its loops no speculative or int32 kind copies, on the assumption that it rarely runs, so loops over shared views, such as k-nucleotide's frequency count in every worker, ran only the double copy. A version whose guard requires thread-owned storage now keeps those copies in its checked copy. --- .../src/backend/llvm/emit-statements.ts | 9 ++++-- .../test/speculative-loop-emission.test.ts | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 378c82bee..63bae226e 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2061,7 +2061,8 @@ function emitCallbackVersionedLoop( /** The copy of a versioned loop that runs when its guard fails. Its loops * get no speculative or int32 kind copies, which would emit each loop again * for a path that rarely runs. Proven integer loops, which emit one copy, - * are kept. */ + * are kept. A typed-array version whose guard requires thread-owned + * storage does not use it (emitVersionedLoop). */ function emitSlowCopy(emitter: LlEmitter, s: IrStmt): void { const copies = emitter.loopCopiesEnabled; emitter.loopCopiesEnabled = false; @@ -2145,7 +2146,11 @@ function emitVersionedLoop( LOOPS.of(emitter).bytesLoopVersioning.add(s); try { B.startBlock(slow); - emitSlowCopy(emitter, s); + // A guard that requires thread-owned storage fails for every view of + // shared memory, which worker programs pass routinely: that checked + // copy keeps its loops' speculative and int32 kind copies. + if (version.ownedReceivers.length > 0) emitter.emitStmt(s); + else emitSlowCopy(emitter, s); B.br(done); if (version.owned.size > 0) emitter.bytesOwned = new Set([...originalOwned, ...version.owned]); if (version.ranges.size > 0) diff --git a/packages/compiler/test/speculative-loop-emission.test.ts b/packages/compiler/test/speculative-loop-emission.test.ts index 3f6aef158..748fd50c5 100644 --- a/packages/compiler/test/speculative-loop-emission.test.ts +++ b/packages/compiler/test/speculative-loop-emission.test.ts @@ -129,3 +129,33 @@ if (isMainThread) { expect(order.slice(slow, fast).some((l) => l.startsWith("integer.leave"))).toBe(true); expect(order.slice(fast).some((l) => l.startsWith("integer.leave"))).toBe(true); }); + +test("the shared-storage copy of a typed-array version keeps its speculative copy", async () => { + // In a program that can start a worker, the guard requires thread-owned + // storage, so every view of a SharedArrayBuffer runs the checked copy. + const llvm = await emitLlvm(` +import { Worker, isMainThread } from "node:worker_threads"; +function trim(value: number, bits: number): number { + return value - Math.floor(value / Math.pow(2, bits)) * Math.pow(2, bits); +} +function key(bytes: Uint8Array, bits: number): number { + let k = 0; + for (let i = 0; i < bytes.length; i++) k = trim(k, bits) * 4 + bytes[i]!; + return k; +} +if (isMainThread) { + if (process.argv[2] === "spawn") new Worker(new URL(import.meta.url)); + const shared = new Uint8Array(new SharedArrayBuffer(4)); + shared.set([1, 2, 3, 0]); + console.log(key(shared, 6), key(new Uint8Array([3, 2, 1, 0]), 6)); +} +`); + const fn = /^define internal double @sc_bf_\w*key[\w.]*\([^]*?^}/m.exec(llvm)?.[0] ?? ""; + const order = labels(fn); + const slow = order.findIndex((l) => l.startsWith("bytes.version.slow")); + const fast = order.findIndex((l) => l.startsWith("bytes.version.fast")); + expect(slow).toBeGreaterThanOrEqual(0); + expect(fast).toBeGreaterThan(slow); + expect(order.slice(slow, fast).some((l) => l.startsWith("spec.fast"))).toBe(true); + expect(order.slice(fast).some((l) => l.startsWith("spec.fast"))).toBe(true); +}); From f06f47aca1fbd4d2af6a354ce206750f324569e7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:27:46 -0500 Subject: [PATCH 177/395] Resolve promises with an argument in the rejectUnauthorized corpus program 8179-tls-reject-unauthorized-values.mjs called the resolve function of an untyped new Promise() with no argument. tsgo types that function as taking one argument and reported SC0001 for each call, while tsc-ts accepted them, so the checker-identity comparison failed. The calls now pass undefined. --- tests/corpus/8179-tls-reject-unauthorized-values.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/corpus/8179-tls-reject-unauthorized-values.mjs b/tests/corpus/8179-tls-reject-unauthorized-values.mjs index d0275ab4a..4e87099d4 100644 --- a/tests/corpus/8179-tls-reject-unauthorized-values.mjs +++ b/tests/corpus/8179-tls-reject-unauthorized-values.mjs @@ -31,10 +31,10 @@ for (const [label, value] of values) { c.on("secureConnect", () => events.push("client secureConnect")); c.on("data", (d) => events.push(`client data ${d}`)); c.on("error", (e) => events.push(`client error ${e.code}`)); - c.on("close", () => setTimeout(done, 50)); + c.on("close", () => setTimeout(() => done(undefined), 50)); }); console.log("rejectUnauthorized", label, "|", events.join(" ; ")); - await new Promise((res) => server.close(() => res())); + await new Promise((res) => server.close(() => res(undefined))); } { @@ -47,7 +47,7 @@ for (const [label, value] of values) { c.on("secureConnect", () => events.push(`secureConnect authorized=${c.authorized} error=${c.authorizationError}`)); c.on("data", (d) => events.push(`data ${d}`)); c.on("error", (e) => events.push(`error ${e.code}`)); - c.on("close", () => done()); + c.on("close", () => done(undefined)); }); console.log("tls.connect rejectUnauthorized", label, "|", events.join(" ; ")); } From 468ef7692b0af311dd9d4f3363757f40c83e3c04 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:35:56 -0500 Subject: [PATCH 178/395] Drop the refused never-promise instanceof case from the maybe-promise fences instanceof Promise on values that are never promises now compiles to false after the operand is evaluated, so the diagnostics fixture no longer lists it as refused. 8326-never-promise-instanceof covers records, arrays, dates, collections, regular expressions, byte arrays and unions with null, and a call operand that still runs. --- .../8326-never-promise-instanceof.ts.json | 6 +++++ tests/corpus/8326-never-promise-instanceof.ts | 26 +++++++++++++++++++ tests/diagnostics/maybe-promise-fences.ts | 4 --- .../__snapshots__/maybe-promise-fences.ts.txt | 9 +------ .../8326-never-promise-instanceof.ts.json | 1 + 5 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8326-never-promise-instanceof.ts.json create mode 100644 tests/corpus/8326-never-promise-instanceof.ts create mode 100644 tests/harness/corpus-costs/8326-never-promise-instanceof.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8326-never-promise-instanceof.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8326-never-promise-instanceof.ts.json new file mode 100644 index 000000000..d10bc2c18 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8326-never-promise-instanceof.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8326-never-promise-instanceof.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8326-never-promise-instanceof.ts b/tests/corpus/8326-never-promise-instanceof.ts new file mode 100644 index 000000000..6e01e3510 --- /dev/null +++ b/tests/corpus/8326-never-promise-instanceof.ts @@ -0,0 +1,26 @@ +// `instanceof Promise` on values that are never promises (records, arrays, +// dates, collections, regular expressions, byte arrays, null, and unions of +// them) is false, after the operand is evaluated. +let evaluated = 0; +function next(): { v: number } { + evaluated++; + return { v: evaluated }; +} + +const record = { v: 1 }; +console.log("record", record instanceof Promise); +console.log("call", next() instanceof Promise, evaluated); +console.log("array", [1, 2] instanceof Promise); +console.log("date", new Date(0) instanceof Promise); +console.log("map", new Map() instanceof Promise); +console.log("set", new Set() instanceof Promise); +console.log("regex", /a/ instanceof Promise); +console.log("bytes", new Uint8Array(2) instanceof Promise); + +function pick(i: number): { v: number } | number[] | null { + return i === 0 ? { v: 0 } : i === 1 ? [i] : null; +} +for (let i = 0; i < 3; i++) console.log("union", i, pick(i) instanceof Promise); + +const promised: unknown = Promise.resolve(1); +console.log("promise", promised instanceof Promise); diff --git a/tests/diagnostics/maybe-promise-fences.ts b/tests/diagnostics/maybe-promise-fences.ts index 1dcccaf08..e6fdf98fa 100644 --- a/tests/diagnostics/maybe-promise-fences.ts +++ b/tests/diagnostics/maybe-promise-fences.ts @@ -26,7 +26,3 @@ console.log(maybe(true)); // must be that arm's own promise type. const widened = Promise.resolve(maybe(false)); console.log(typeof widened); - -// `instanceof Promise` on a value that is never a promise. -const record = { v: 1 }; -console.log(record instanceof Promise); diff --git a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt index 159e88f76..ab325aaaa 100644 --- a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt +++ b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt @@ -48,11 +48,4 @@ maybe-promise-fences.ts:27:50 - error SC2020: 'Promise.resolve over 'Promise Date: Sat, 24 Oct 2026 23:41:28 -0500 Subject: [PATCH 179/395] Correct the native compiler's nursery trigger comment --- packages/compiler/native/host.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index 4e1a0b754..24d330cc0 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -139,10 +139,12 @@ void scriptc_native_bound_cycle_growth(void) { /* The runtime's nursery trigger (scr_rc.h). */ void scr_cyc_set_nursery_threshold(long threshold); -/* Runs nursery passes every 1024 new candidates instead of every 256. It - * has to be in place before the compiler's modules initialize: set from - * main it leaves the build's collection work as it is. SCR_CYCLE_THRESHOLD - * still overrides it. */ +/* Runs nursery passes every 1024 new candidates instead of every 256. Much + * of a build's cyclic garbage outlives 256 releases: in the shorter window + * it survives a nursery pass, is promoted, and only full passes over the + * whole live heap find it. A constructor sets it before the compiler's + * modules initialize, so every pass of a build uses it. + * SCR_CYCLE_THRESHOLD still overrides it. */ __attribute__((constructor)) static void scriptc_native_widen_cycle_nursery(void) { scr_cyc_set_nursery_threshold(1024); } From 3e115ce08c8aff475cf6f125e34f2282c4c52e3a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:47:41 -0500 Subject: [PATCH 180/395] Cache absent properties in the dynamic tier's property caches A read site's cache only remembered properties it found on the receiver or its first prototype. Reads of properties an object lacks, which validation libraries make constantly (zod reads ctx.skipChecks, payload.memo, x.aborted and _zod.optin on every check), walked the whole shape chain and then repeated the lookup on the generic path every time: 7.9 million such reads in one zod benchmark run. - Read sites call a new probe, scr_js_get_probe, that answers undefined for a key the receiver and its chain of plain shaped objects (at most one prototype, ending in the default prototype) lack, and caches that answer. The entry holds while the receiver's shape, its prototype link, and that prototype's shape are unchanged. Its tag and placeholder holder make the inline entry-0 test in compiled code miss, so the answer always comes from the probe. Method reads keep scr_js_load_probe, so a missing method still reaches the slow path and its "is not a function" error. Names of Object.prototype members are never cached as absent. - Receivers of one shape with different prototypes (zod's internals objects) each keep an entry instead of replacing one another. - A site whose four-way record is full consults a per-thread table keyed by shape and key before walking shapes, and fills it. --- packages/compiler/src/backend/llvm/js-ic.ts | 8 +- packages/compiler/test/js-ic-emission.test.ts | 4 +- .../corpus/absent-property-reads.mjs.json | 6 + packages/runtime/src/scr_js.h | 4 + packages/runtime/src/scr_js_object.c | 145 +++++++++++++++++- packages/runtime/src/scr_js_object.test.c | 120 +++++++++++++++ packages/runtime/src/scr_js_object.test.ts | 12 +- tests/corpus/absent-property-reads.mjs | 87 +++++++++++ .../absent-property-reads.mjs.json | 1 + 9 files changed, 370 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/absent-property-reads.mjs.json create mode 100644 tests/corpus/absent-property-reads.mjs create mode 100644 tests/harness/corpus-costs/absent-property-reads.mjs.json diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index b8be1ecb1..b69d3199d 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -6,7 +6,8 @@ * * load ordinary object whose shape is entry 0's own-property shape: * read entry `slot` inline unless it is an accessor; otherwise - * scr_js_load_probe (polymorphic, prototype hits, misses) + * scr_js_get_probe (polymorphic, prototype hits, absent keys, + * misses) * store ordinary object whose shape is entry 0's replace shape, outside * an intact parse region, writable data entry: replace inline; * otherwise scr_js_store_probe (adds, polymorphic, misses) @@ -402,9 +403,10 @@ function emitGet(host: Host, e: ExprOf<"call">, callee: IrFunction, key: string) B.startBlock(object); inlineLoad(host, receiver.name, ic, INLINE_WAYS, INLINE_PROTO, (cell) => cell, hits, hit, probe); B.startBlock(probe); - host.declare(`declare ptr @scr_js_load_probe(ptr, ptr, ptr)`); + // The read's probe also answers keys the receiver's plain chain lacks. + host.declare(`declare ptr @scr_js_get_probe(ptr, ptr, ptr)`); const probed = B.tmp(); - B.line(`${probed} = call ptr @scr_js_load_probe(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); + B.line(`${probed} = call ptr @scr_js_get_probe(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); const found = B.tmp(); B.line(`${found} = icmp ne ptr ${probed}, null`); const probeEnd = B.currentLabel(); diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 7a1d44f34..437ac34e4 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -48,7 +48,7 @@ console.log(read(o), at(dyn([5, 6]), 1), call(o), o.n); expect(llvm).toMatch( /@sc_js_ic_0 = internal (thread_local )?global \[21 x i64\] zeroinitializer/, ); - expect(body(llvm, "read")).toContain("@scr_js_load_probe("); + expect(body(llvm, "read")).toContain("@scr_js_get_probe("); expect(body(llvm, "write")).toContain("@scr_js_store_probe("); expect(body(llvm, "at")).toContain("@scr_js_keyed_probe("); expect(body(llvm, "call")).toContain("@scr_js_load_probe("); @@ -100,7 +100,7 @@ console.log(read(o), read(p), read(t), at(dyn([5, 6]), 1), at(p, "x"), at(t, "ta const mod = deserializeModule(await readFile(outPath, "utf8")); expect(validateModule(mod)).toEqual([]); const llvm = emitLlvmModule(mod); - expect(body(llvm, "read")).toContain("@scr_js_load_probe("); + expect(body(llvm, "read")).toContain("@scr_js_get_probe("); expect(body(llvm, "write")).toContain("@scr_js_store_probe("); expect(body(llvm, "at")).toContain("@scr_js_keyed_probe("); } finally { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/absent-property-reads.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/absent-property-reads.mjs.json new file mode 100644 index 000000000..3c0646fa9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/absent-property-reads.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/absent-property-reads.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_js.h b/packages/runtime/src/scr_js.h index 1d874b31e..3ff9becd3 100644 --- a/packages/runtime/src/scr_js.h +++ b/packages/runtime/src/scr_js.h @@ -231,6 +231,10 @@ typedef struct ScrJsIc { /* `receiver.key` for a plain data property: +1 value, or NULL. */ ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); +/* The same for a property read site, which also answers (+1) undefined for + * a key that the receiver and its plain prototype chain lack, and caches + * that answer. A method read uses scr_js_load_probe. */ +ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); /* `receiver.key = value` (value borrowed) when it replaces a writable own * data property or adds one the prototype chain lacks: true when done. */ bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJsIc *ic); diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index e09226fd1..e97651557 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -76,9 +76,21 @@ static SCR_TL size_t scr_js_filled_capacity; * which answer with a miss until the key set changes. */ static ScrJsShape scr_js_unshaped; +/* The second-level cache of load sites whose own record is full: one entry + * per (receiver shape, key bytes) slot, filled by the probe and checked like + * a site entry. It names shapes, so the shape reset clears it. */ +#define SCR_JS_SHARED_LOADS 2048 +typedef struct ScrJsSharedLoad { + const char *key; + ScrJsIcEntry entry; +} ScrJsSharedLoad; +static SCR_TL ScrJsSharedLoad *scr_js_shared_loads; + static void scr_js_shapes_reset(void) { for (size_t i = 0; i < scr_js_filled_count; i++) memset(scr_js_filled_ics[i], 0, sizeof *scr_js_filled_ics[i]); + free(scr_js_shared_loads); + scr_js_shared_loads = NULL; free(scr_js_filled_ics); scr_js_filled_ics = NULL; scr_js_filled_count = 0; @@ -244,38 +256,155 @@ static ScrDyn *scr_js_load_hit(ScrDyn *receiver, const ScrJsIcEntry *entry) { return found->accessor ? NULL : scr_dyn_retain(found->value); } -ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { +/* An absent entry: its target is this tag and its holder is a placeholder + * that is never a prototype, so the inline test of entry 0 in compiled code + * (a prototype hit needs the receiver's prototype to be the holder) always + * sends such a receiver to the probe. Its holder_shape and slot name the one + * prototype the read walked past (shape and object), or NULL and 0 when the + * receiver had no prototype. */ +static const ScrJsShape scr_js_absent; +static const ScrDyn scr_js_absent_holder; + +/* Keys whose read on a plain object is not just its [[Get]]: the members of + * Object.prototype, which reads of an absent own property answer through + * the inherited-member path, and the error constructor read. */ +static bool scr_js_absent_cacheable(const ScrStr *key) { +#define SCR_JS_INHERITED(name) {name, sizeof name - 1} + static const struct { + const char *name; + size_t len; + } inherited[] = { + SCR_JS_INHERITED("constructor"), SCR_JS_INHERITED("__proto__"), + SCR_JS_INHERITED("toString"), SCR_JS_INHERITED("toLocaleString"), + SCR_JS_INHERITED("valueOf"), SCR_JS_INHERITED("hasOwnProperty"), + SCR_JS_INHERITED("isPrototypeOf"), SCR_JS_INHERITED("propertyIsEnumerable"), + SCR_JS_INHERITED("__defineGetter__"), SCR_JS_INHERITED("__defineSetter__"), + SCR_JS_INHERITED("__lookupGetter__"), SCR_JS_INHERITED("__lookupSetter__"), + }; +#undef SCR_JS_INHERITED + for (size_t i = 0; i < sizeof inherited / sizeof inherited[0]; i++) + if (key->len == inherited[i].len && memcmp(key->data, inherited[i].name, key->len) == 0) + return false; + return true; +} + +static ScrJsSharedLoad *scr_js_shared_load(const ScrJsShape *shape, const ScrStr *key) { + if (!scr_js_shared_loads) { + scr_js_shared_loads = calloc(SCR_JS_SHARED_LOADS, sizeof *scr_js_shared_loads); + if (!scr_js_shared_loads) scr_trap("scriptc: out of memory\n"); + } + uint64_t mixed = scr_key_mix((uint64_t)(uintptr_t)shape ^ ((uint64_t)(uintptr_t)key->data << 1)); + return &scr_js_shared_loads[mixed & (SCR_JS_SHARED_LOADS - 1)]; +} + +/* Whether an absent entry still holds for `receiver`, whose shape matched: + * its own keys lack the key, so the walked prototype (if any) must be the + * same object with the same shape, and the chain must end there. */ +static bool scr_js_absent_hit(const ScrDyn *receiver, const ScrJsIcEntry *entry) { + const ScrDyn *prototype = receiver->prototype; + const ScrDyn *walked = (const ScrDyn *)entry->slot; + if (!walked) return prototype == NULL; + return prototype == walked && walked->kind == SCR_DYN_OBJ && + walked->v.obj.shape == entry->holder_shape && walked->prototype == NULL; +} + +/* A load site's probe. With `absent`, a read of a key that the receiver and + * its chain of plain shaped objects (ending in the default prototype) lack + * answers undefined and caches that answer. Method reads leave absent + * methods to the slow path, which names the missing function. */ +/* The answer of a matching entry for `receiver`, or NULL when the entry no + * longer holds. */ +static ScrDyn *scr_js_entry_answer(ScrDyn *receiver, const ScrJsIcEntry *entry) { + if (entry->target == &scr_js_absent) + return scr_js_absent_hit(receiver, entry) ? scr_dyn_retain(scr_dyn_undefined()) : NULL; + return scr_js_load_hit(receiver, entry); +} + +/* The object an entry's answer depends on besides the receiver: the + * prototype it read from or walked past, or NULL for an own property. */ +static const ScrDyn *scr_js_entry_prototype(const ScrJsIcEntry *entry) { + if (entry->target == &scr_js_absent) return (const ScrDyn *)entry->slot; + return entry->target ? entry->holder : NULL; +} + +/* Records `entry` in the site and, when the site's record is full, in the + * shared slot. A matching entry whose answer failed is replaced when it + * depends on the same prototype (that prototype changed); one that names + * another prototype stays, so receivers of one shape with different + * prototypes each keep an entry. */ +static void scr_js_load_fill(ScrJsIc *ic, uint32_t stale, ScrJsSharedLoad *shared, + const ScrStr *key, ScrJsIcEntry entry) { + if (stale < ic->count && + scr_js_entry_prototype(&ic->entry[stale]) != scr_js_entry_prototype(&entry)) + stale = UINT32_MAX; + if (shared) *shared = (ScrJsSharedLoad){key->data, entry}; + scr_js_ic_add(ic, stale, entry); +} + +/* A load site's probe. With `absent`, a read of a key that the receiver and + * its chain of plain shaped objects (ending in the default prototype) lack + * answers undefined and caches that answer. Method reads leave absent + * methods to the slow path, which names the missing function. */ +static ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic, bool absent) { const ScrJsShape *shape = scr_js_shape_of(receiver); if (!shape) return NULL; uint32_t stale = UINT32_MAX; for (uint32_t i = 0; i < ic->count; i++) { if (ic->entry[i].shape != shape) continue; - ScrDyn *value = scr_js_load_hit(receiver, &ic->entry[i]); + ScrDyn *value = scr_js_entry_answer(receiver, &ic->entry[i]); if (value) return value; - stale = i; - break; + if (stale == UINT32_MAX) stale = i; + } + ScrJsSharedLoad *shared = NULL; + if (ic->count == SCR_JS_IC_WAYS) { + shared = scr_js_shared_load(shape, key); + if (shared->key == key->data && shared->entry.shape == shape && + (absent || shared->entry.target != &scr_js_absent)) { + ScrDyn *value = scr_js_entry_answer(receiver, &shared->entry); + if (value) return value; + } } size_t slot = scr_js_shape_find(shape, key->data, key->len); if (slot != SIZE_MAX) { const ScrDynEntry *found = &receiver->v.obj.entries[slot]; if (found->accessor) return NULL; - scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, NULL, NULL, NULL, slot}); + scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, NULL, NULL, NULL, slot}); return scr_dyn_retain(found->value); } + absent = absent && scr_js_absent_cacheable(key); /* One prototype link: an ordinary object other than the builtin * Object.prototype (whose reads the generic path fences). */ ScrDyn *holder = receiver->prototype; - if (!holder) return NULL; + if (!holder) { + if (!absent) return NULL; + scr_js_load_fill(ic, stale, shared, key, + (ScrJsIcEntry){shape, &scr_js_absent, &scr_js_absent_holder, NULL, 0}); + return scr_dyn_retain(scr_dyn_undefined()); + } const ScrJsShape *holder_shape = scr_js_shape_of(holder); if (!holder_shape) return NULL; slot = scr_js_shape_find(holder_shape, key->data, key->len); - if (slot == SIZE_MAX) return NULL; + if (slot == SIZE_MAX) { + if (!absent || holder->prototype != NULL) return NULL; + scr_js_load_fill(ic, stale, shared, key, + (ScrJsIcEntry){shape, &scr_js_absent, &scr_js_absent_holder, holder_shape, + (size_t)holder}); + return scr_dyn_retain(scr_dyn_undefined()); + } const ScrDynEntry *found = &holder->v.obj.entries[slot]; if (found->accessor) return NULL; - scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, holder_shape, holder, NULL, slot}); + scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, holder_shape, holder, NULL, slot}); return scr_dyn_retain(found->value); } +ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + return scr_js_probe_load(receiver, key, ic, false); +} + +ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + return scr_js_probe_load(receiver, key, ic, true); +} + /* ── stores ───────────────────────────────────────────────────────────── */ /* The generic store keeps a native Error's fields in sync with these keys diff --git a/packages/runtime/src/scr_js_object.test.c b/packages/runtime/src/scr_js_object.test.c index 6b6934dea..06b804759 100644 --- a/packages/runtime/src/scr_js_object.test.c +++ b/packages/runtime/src/scr_js_object.test.c @@ -103,8 +103,128 @@ static void immortal_objects_stay_unshaped(void) { scr_str_release(key); } +/* A read site's probe answers a key that the receiver and its plain + * prototype chain lack with undefined, and caches that answer until the + * receiver's keys, its prototype, or the prototype's keys change. */ +static ScrJsIc get_ic; +static ScrJsIc other_ic; + +/* Each site reads one key, so each key below has its own record. */ +static bool get_reads_undefined_at(ScrJsIc *ic, ScrDyn *object, const ScrStr *key) { + ScrDyn *value = scr_js_get_probe(object, key, ic); + if (!value) return false; + assert(value->kind == SCR_DYN_UNDEF); + scr_dyn_release(value); + return true; +} + +static bool get_reads_undefined(ScrDyn *object, const ScrStr *key) { + ScrDyn *value = scr_js_get_probe(object, key, &get_ic); + if (!value) return false; + assert(value->kind == SCR_DYN_UNDEF); + scr_dyn_release(value); + return true; +} + +static bool get_reads(ScrDyn *object, const ScrStr *key, double n) { + ScrDyn *value = scr_js_get_probe(object, key, &get_ic); + if (!value) return false; + assert(value->kind == SCR_DYN_NUM && value->v.num == n); + scr_dyn_release(value); + return true; +} + +static void absent_reads(void) { + ScrStr *own = scr_str_new("own", 3); + ScrStr *missing = scr_str_new("missing", 7); + ScrStr *inherited = scr_str_new("toString", 8); + + /* No prototype: undefined, from the cache the second time. */ + ScrDyn *object = object_with(own, 1); + memset(&get_ic, 0, sizeof get_ic); + assert(get_reads_undefined(object, missing)); + assert(get_ic.count == 1); + assert(get_reads_undefined(object, missing)); + assert(get_ic.count == 1); + /* Method reads and Object.prototype member names keep the slow path. */ + memset(&load_ic, 0, sizeof load_ic); + assert(!scr_js_load_probe(object, missing, &load_ic)); + memset(&other_ic, 0, sizeof other_ic); + assert(!get_reads_undefined_at(&other_ic, object, inherited)); + /* The key added: the same record answers its value. */ + scr_dyn_obj_set(object, missing->data, missing->len, scr_dyn_new_num(2)); + assert(get_reads(object, missing, 2)); + + /* One plain prototype that lacks the key, then gains it. */ + ScrDyn *prototype = object_with(own, 5); + ScrDyn *child = object_with(own, 6); + child->prototype = scr_dyn_retain(prototype); + memset(&get_ic, 0, sizeof get_ic); + assert(get_reads_undefined(child, missing)); + assert(get_reads_undefined(child, missing)); + scr_dyn_obj_set(prototype, missing->data, missing->len, scr_dyn_new_num(3)); + assert(get_reads(child, missing, 3)); + + /* A receiver of the same shape with another prototype that lacks the + * key: both answers stay cached. */ + ScrDyn *other_prototype = object_with(own, 7); + ScrDyn *sibling = object_with(own, 8); + sibling->prototype = scr_dyn_retain(other_prototype); + assert(get_reads_undefined(sibling, missing)); + assert(get_reads(child, missing, 3)); + assert(get_reads_undefined(sibling, missing)); + + /* A longer chain is left to the generic read, even for a receiver whose + * absent answer was cached. */ + ScrDyn *grandparent = object_with(own, 9); + other_prototype->prototype = scr_dyn_retain(grandparent); + assert(!get_reads_undefined(sibling, missing)); + + /* A prototype swapped on a cached receiver: the entry no longer holds. */ + ScrDyn *plain = object_with(own, 10); + memset(&get_ic, 0, sizeof get_ic); + assert(get_reads_undefined(plain, missing)); + plain->prototype = scr_dyn_retain(prototype); + assert(get_reads(plain, missing, 3)); + + /* More shapes than the record holds: the shared cache answers the reads + * that the full record misses, absent keys included. */ + memset(&get_ic, 0, sizeof get_ic); + memset(&other_ic, 0, sizeof other_ic); + ScrDyn *shapes[SCR_JS_IC_WAYS + 3]; + char name[16]; + for (size_t i = 0; i < sizeof shapes / sizeof shapes[0]; i++) { + int n = snprintf(name, sizeof name, "k%zu", i); + shapes[i] = object_with(own, (double)i); + scr_dyn_obj_set(shapes[i], name, (size_t)n, scr_dyn_new_num((double)i)); + } + for (int round = 0; round < 3; round++) + for (size_t i = 0; i < sizeof shapes / sizeof shapes[0]; i++) { + assert(get_reads(shapes[i], own, (double)i)); + assert(get_reads_undefined_at(&other_ic, shapes[i], missing)); + } + for (size_t i = 0; i < sizeof shapes / sizeof shapes[0]; i++) scr_dyn_release(shapes[i]); + + scr_dyn_release(plain); + scr_dyn_release(sibling); + scr_dyn_release(other_prototype); + scr_dyn_release(grandparent); + scr_dyn_release(child); + scr_dyn_release(prototype); + scr_dyn_release(object); + scr_str_release(inherited); + scr_str_release(missing); + scr_str_release(own); +} + int main(int argc, char **argv) { scr_init(); + /* Shapes count against the budget, so these checks run on their own. */ + if (argc > 1 && strcmp(argv[1], "absent") == 0) { + absent_reads(); + puts("absent read checks passed"); + return 0; + } immortal_objects_stay_unshaped(); if (argc > 1 && strcmp(argv[1], "key-bytes") == 0) { /* 64 KiB keys spend the key-byte budget after 64 shapes. */ diff --git a/packages/runtime/src/scr_js_object.test.ts b/packages/runtime/src/scr_js_object.test.ts index 0a5aa1d5b..1fb90cdaa 100644 --- a/packages/runtime/src/scr_js_object.test.ts +++ b/packages/runtime/src/scr_js_object.test.ts @@ -7,7 +7,7 @@ import { expect, test } from "vitest"; const exec = promisify(execFile); -test("dynamic-tier shapes stay within the shape and key-byte budgets", async () => { +test("dynamic-tier shapes stay within the shape and key-byte budgets, and absent reads are cached", async () => { const scratch = await mkdtemp(join(tmpdir(), "scriptc-js-object-")); const src = import.meta.dirname; const binary = join(scratch, "js-object"); @@ -50,15 +50,19 @@ test("dynamic-tier shapes stay within the shape and key-byte budgets", async () ].map((file) => join(src, file)), ...(process.platform === "linux" ? ["-D_GNU_SOURCE", "-lm"] : []), ]); - for (const args of [[], ["key-bytes"]]) { - const result = await exec(binary, args, { + for (const [args, stdout] of [ + [[], "shape budget checks passed\n"], + [["key-bytes"], "shape budget checks passed\n"], + [["absent"], "absent read checks passed\n"], + ] as const) { + const result = await exec(binary, [...args], { env: { ...process.env, ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1", UBSAN_OPTIONS: "halt_on_error=1", }, }); - expect(result.stdout).toBe("shape budget checks passed\n"); + expect(result.stdout).toBe(stdout); expect(result.stderr).toBe(""); } } finally { diff --git a/tests/corpus/absent-property-reads.mjs b/tests/corpus/absent-property-reads.mjs new file mode 100644 index 000000000..cd568b11c --- /dev/null +++ b/tests/corpus/absent-property-reads.mjs @@ -0,0 +1,87 @@ +// Reads of properties an object lacks, from the same untyped sites many +// times: they answer undefined until the object, its prototype, or the +// prototype link changes, and Object.prototype members still answer. +function read(object) { + return object.flag; +} +function readName(object) { + return object.name; +} +function describe(object) { + return [object.toString === Object.prototype.toString, typeof object.constructor, typeof object.hasOwnProperty].join(); +} +function callIt(object) { + try { + return object.run(); + } catch (e) { + return `${e.name}: ${e.message}`; + } +} + +const seen = []; +const plain = { name: "plain" }; +for (let i = 0; i < 3; i++) seen.push(read(plain)); +plain.flag = "added"; +seen.push(read(plain)); +delete plain.flag; +seen.push(read(plain)); +console.log(seen.join()); + +// A prototype that gains, changes and loses the key. +const base = { kind: "base" }; +const child = Object.create(base); +child.name = "child"; +const fromProto = []; +for (let i = 0; i < 3; i++) fromProto.push(read(child)); +base.flag = "inherited"; +fromProto.push(read(child)); +base.flag = "changed"; +fromProto.push(read(child)); +delete base.flag; +fromProto.push(read(child)); +console.log(fromProto.join()); + +// Objects of one shape with different prototypes, read from one site. +const left = Object.create({ flag: "left" }); +const right = Object.create({ other: 1 }); +left.name = "l"; +right.name = "r"; +const mixed = []; +for (let i = 0; i < 3; i++) mixed.push(read(left), read(right)); +console.log(mixed.join()); + +// Changing the prototype link of an object whose absent read was cached. +const moved = { name: "moved" }; +for (let i = 0; i < 3; i++) read(moved); +Object.setPrototypeOf(moved, { flag: "after setPrototypeOf" }); +console.log(read(moved)); +Object.setPrototypeOf(moved, null); +console.log(read(moved), readName(moved)); + +// A longer prototype chain. +const grand = { flag: "grand" }; +const parent = Object.create(grand); +const deep = Object.create(parent); +console.log(read(deep), read(Object.create(Object.create({})))); + +// Object.prototype members and missing methods still answer as in Node. +const bare = { name: "bare" }; +for (let i = 0; i < 3; i++) describe(bare); +console.log(describe(bare), describe({ other: 2 })); +for (let i = 0; i < 3; i++) callIt(bare); +console.log(callIt(bare)); +bare.run = () => "ran"; +console.log(callIt(bare)); + +// Many shapes through one site. +const shapes = []; +for (let i = 0; i < 12; i++) { + const object = { name: `s${i}` }; + object[`k${i}`] = i; + if (i % 3 === 0) object.flag = i; + shapes.push(object); +} +let total = 0; +for (let round = 0; round < 4; round++) + for (const object of shapes) total += read(object) === undefined ? 1 : 100; +console.log(total, shapes.map(readName).join()); diff --git a/tests/harness/corpus-costs/absent-property-reads.mjs.json b/tests/harness/corpus-costs/absent-property-reads.mjs.json new file mode 100644 index 000000000..a9e46230d --- /dev/null +++ b/tests/harness/corpus-costs/absent-property-reads.mjs.json @@ -0,0 +1 @@ +{ "plain": 8432, "san": 13661 } From 944cae84a8b4cc4c2f7b0af2af0dcf8793e72591 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 00:06:13 -0500 Subject: [PATCH 181/395] Keep an unchanged executable without selecting the runtime pack An unchanged rebuild that kept its output still selected the runtime pack's artifacts first, reading the stored selection, and computed the identity of every selected artifact to open the executable cache. - The executable cache key names the pack by its package.json text, the manifest's identity (its text on darwin), and the link features and flavor. Together they fix which artifacts the selection names. - The cache selects the artifacts only when it needs their identities, in restore() and trace(). keep() checks the recorded entry, which lists them. - A build that does not keep or restore its executable selects the pack before code generation, as before. --- packages/compiler/src/native/compiler.ts | 46 ++++++------- .../src/native/executable-cache-open.test.ts | 21 ++++-- .../src/native/executable-cache.test.ts | 31 +++++++++ .../compiler/src/native/executable-cache.ts | 67 ++++++++++++++++--- 4 files changed, 125 insertions(+), 40 deletions(-) diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index e919090a9..64964d94f 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -30,6 +30,7 @@ import { verifyNativeHelper, } from "../backend/native-tools.js"; import { + type NativeRuntimeSelection, selectNativeRuntimePack, runtimePackManifest, stageNativeRuntimeSelection, @@ -400,34 +401,33 @@ export class NativeCompiler { outputKind === "exe" ? join(inputDirectory, "program" + target.outputSuffixes.obj) : stagedOutput; - const executablePack = - outputKind === "exe" - ? selectNativeRuntimePack( - toolchain.runtimePackRoot, - target, - toolchain.compilerVersion, - features, - optimization, - "executable", - this.runtimePackStore(), - ) - : null; + let executablePack: NativeRuntimeSelection | null = null; + const selectPack = (): NativeRuntimeSelection => + (executablePack ??= selectNativeRuntimePack( + toolchain.runtimePackRoot, + target, + toolchain.compilerVersion, + features, + optimization, + "executable", + this.runtimePackStore(), + )); const executableCache = - executablePack === null - ? null - : openNativeExecutableCache( - this.cache, - toolchain, - digestLlvm, - options, - ffi, - executablePack, - ); + outputKind === "exe" + ? openNativeExecutableCache(this.cache, toolchain, digestLlvm, options, ffi, { + root: toolchain.runtimePackRoot, + features, + flavor: optimization, + select: selectPack, + }) + : null; // An unchanged rebuild keeps the executable the last build left. const kept = executableCache?.keep(output) ?? false; const restored = kept || (executableCache?.restore(stagedOutput) ?? false); timing(restored ? "executable-cache-hit" : "executable-cache-miss"); if (!restored) { + // A missing or invalid runtime pack fails before code generation. + if (outputKind === "exe") selectPack(); let programObjects = [object]; if (options.sanitize) { runNativeTool(sanitizerDriver(toolchain), [ @@ -496,7 +496,7 @@ export class NativeCompiler { }, }; } - const pack = executablePack!; + const pack = selectPack(); const runtime = options.sanitize ? buildSanitizedRuntime(toolchain, pack, join(stage, "runtime"), "executable", this.cache) : stageNativeRuntimeSelection(pack, join(stage, "runtime"), this.runtimePackStore()); diff --git a/packages/compiler/src/native/executable-cache-open.test.ts b/packages/compiler/src/native/executable-cache-open.test.ts index 09a8b9cc9..0c3b2832f 100644 --- a/packages/compiler/src/native/executable-cache-open.test.ts +++ b/packages/compiler/src/native/executable-cache-open.test.ts @@ -2,9 +2,10 @@ import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:f import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, expect, test } from "vitest"; +import type { NativeLinkFeatures } from "../backend/native-link-info.js"; import type { NativeRuntimeSelection } from "../backend/runtime-pack-native.js"; import { NativeCache } from "./cache.js"; -import { openNativeExecutableCache } from "./executable-cache.js"; +import { type ExecutableRuntimePack, openNativeExecutableCache } from "./executable-cache.js"; import type { NativeToolchain } from "./toolchain.js"; const directories: string[] = []; @@ -50,14 +51,20 @@ function fixture(platform: string, driver: string) { linkerArgs: [], dsymutil: "dsymutil", } as unknown as NativeToolchain; - const pack = { + const pack: ExecutableRuntimePack = { root: runtime, - licenses: [], - packageText: "{}", - manifestText: "{}", - selected: { runtime: [], archives: [] }, + features: {} as NativeLinkFeatures, flavor: "release", - } as unknown as NativeRuntimeSelection; + select: () => + ({ + root: runtime, + licenses: [], + packageText: "{}", + manifestText: "{}", + selected: { runtime: [], archives: [] }, + flavor: "release", + }) as unknown as NativeRuntimeSelection, + }; const open = () => openNativeExecutableCache( new NativeCache(join(root, "cache")), diff --git a/packages/compiler/src/native/executable-cache.test.ts b/packages/compiler/src/native/executable-cache.test.ts index 97f9a3571..8bec9916e 100644 --- a/packages/compiler/src/native/executable-cache.test.ts +++ b/packages/compiler/src/native/executable-cache.test.ts @@ -300,3 +300,34 @@ test("a Linux build whose output changed links again, and then stores the execut expect(open().restore(f.destination)).toBe(true); expect(readFileSync(f.destination)).toEqual(readFileSync(f.output)); }); + +test("a Linux build that keeps its output does not read the deferred inputs", () => { + const f = fixture(); + const artifact = join(f.root, "inputs", "artifact.o"); + writeFileSync(artifact, "runtime pack artifact"); + const deferred = vi.fn(() => [artifact]); + const open = () => + new NativeExecutableCache(f.cache, f.key, "clang", false, [f.runtime], true, deferred); + const first = open(); + expect(first.trace(["-o", f.output], f.stage)).toBe(true); + first.recordOutput(f.output); + expect(deferred).toHaveBeenCalledTimes(1); + const recorded = ( + JSON.parse(f.cache.read("kept", f.key)!.toString()) as { inputs: { path: string }[] } + ).inputs.map((saved) => saved.path); + expect(recorded).toContain(artifact); + // The unchanged rebuild checks the recorded identities, the artifact's + // among them, without asking for the deferred inputs. + expect(open().keep(f.output)).toBe(true); + expect(deferred).toHaveBeenCalledTimes(1); + // A replaced artifact misses. + writeFileSync(artifact, "another runtime pack artifact"); + expect(open().keep(f.output)).toBe(false); + expect(deferred).toHaveBeenCalledTimes(1); + // restore() adds the deferred inputs before it checks an entry. + const second = open(); + expect(second.trace(["-o", f.output], f.stage)).toBe(true); + second.publish(f.output); + expect(open().restore(f.destination)).toBe(true); + expect(deferred).toHaveBeenCalledTimes(3); +}); diff --git a/packages/compiler/src/native/executable-cache.ts b/packages/compiler/src/native/executable-cache.ts index 0ce638d54..bafd2c020 100644 --- a/packages/compiler/src/native/executable-cache.ts +++ b/packages/compiler/src/native/executable-cache.ts @@ -9,6 +9,8 @@ import { toolchainEnvironmentFingerprint, } from "../backend/toolchain-environment.js"; import { runNativeTool } from "../backend/native-tools.js"; +import type { NativeLinkFeatures } from "../backend/native-link-info.js"; +import type { NativeOptimization } from "../backend/optimization.js"; import type { NativeRuntimeSelection } from "../backend/runtime-pack-native.js"; import type { NativeToolchain } from "./toolchain.js"; import { contentDigest, NativeCache } from "./cache.js"; @@ -204,6 +206,7 @@ export class NativeExecutableCache { private traced = false; /** The inputs of the entry restore() used. */ private restoredInputs: InputIdentity[] | null = null; + private deferredAdded = false; constructor( private readonly cache: NativeCache, @@ -214,10 +217,25 @@ export class NativeExecutableCache { /** Linux: trace with one verbose link, keep an unchanged output, and * store the executable from a key's second link on. */ private readonly linux = false, + /** Inputs that the key determines, such as the runtime pack's selected + * artifacts. keep() does not read them, because the entry it checks + * lists them; restore() and trace() add their identities first. */ + private readonly deferredPaths: () => string[] = () => [], ) { this.inputs = [...new Set(paths)].sort().map(identity); } + private addDeferredInputs(): void { + if (this.deferredAdded) return; + this.deferredAdded = true; + const listed = new Set(this.inputs.map((input) => input.path)); + for (const path of this.deferredPaths()) + if (!listed.has(path)) { + listed.add(path); + this.inputs.push(identity(path)); + } + } + /** Whether `recorded`, the inputs of an entry, still match and cover this * build's inputs. */ private covers(recorded: InputIdentity[]): boolean { @@ -249,6 +267,7 @@ export class NativeExecutableCache { try { const metadata = this.cache.read("executable", this.key); if (metadata === null) return false; + this.addDeferredInputs(); const entry: unknown = JSON.parse(metadata.toString("utf8")); if (!validEntry(entry) || !this.covers(entry.inputs)) return false; if (this.debug !== (entry.symbols !== null)) return false; @@ -275,6 +294,7 @@ export class NativeExecutableCache { trace(args: string[], stage: string): boolean { this.traced = false; try { + this.addDeferredInputs(); const roots = [resolve(stage), realpathSync(stage)]; let driverInputs = this.linux ? [] @@ -342,13 +362,24 @@ export class NativeExecutableCache { } } +/** The runtime pack an executable links: its directory, and the link features + * and flavor that select its artifacts. `select` reads and validates the + * manifest; the cache calls it only when it needs the artifacts' identities, + * so an unchanged rebuild that keeps its output never reads the manifest. */ +export interface ExecutableRuntimePack { + root: string; + features: NativeLinkFeatures; + flavor: NativeOptimization; + select: () => NativeRuntimeSelection; +} + export function openNativeExecutableCache( cache: NativeCache | null, toolchain: NativeToolchain, llvmDigest: () => string, options: CompileRequestOptions, ffi: FfiProfile | null, - pack: NativeRuntimeSelection, + pack: ExecutableRuntimePack, ): NativeExecutableCache | null { // Match the established complete-cache contract: the default Apple driver // or a Linux clang driver, known runtime inputs, and no wrapper or @@ -394,11 +425,19 @@ export function openNativeExecutableCache( join(toolchain.helperPackageRoot, "package.json"), join(toolchain.runtimePackRoot, "package.json"), join(toolchain.runtimePackRoot, "runtime-pack.json"), - ...[...pack.selected.runtime, ...pack.selected.archives, ...pack.licenses].map((artifact) => - join(pack.root, artifact.path), - ), ]; - if (platform === "linux") paths.push(join(pack.root, "runtime-pack.json")); + const manifest = join(pack.root, "runtime-pack.json"); + if (platform === "linux") paths.push(manifest); + // The manifest's identity, the features and the flavor in the key fix + // which artifacts the selection names. + const artifacts = (): string[] => { + const selection = pack.select(); + return [ + ...selection.selected.runtime, + ...selection.selected.archives, + ...selection.licenses, + ].map((artifact) => join(selection.root, artifact.path)); + }; if (debug) { const dsymutil = commandPath(toolchain.dsymutil); if (realpathSync(dsymutil) !== "/usr/bin/dsymutil") return null; @@ -410,13 +449,13 @@ export function openNativeExecutableCache( llvm: llvmDigest(), options, toolchain, - runtimeIdentity: pack.packageText, + runtimeIdentity: readFileSync(join(pack.root, "package.json"), "utf8"), // On Linux the manifest's identity names it: restore checks that // identity again, as it does every other input. runtimeManifest: - platform === "darwin" - ? pack.manifestText - : identity(join(pack.root, "runtime-pack.json")), + platform === "darwin" ? readFileSync(manifest, "utf8") : identity(manifest), + runtimeFeatures: pack.features, + runtimeFlavor: pack.flavor, linker: identity(linker), effective: identity(effective), environment: toolchainEnvironmentFingerprint(), @@ -424,7 +463,15 @@ export function openNativeExecutableCache( path: process.env["PATH"] ?? null, }), ); - return new NativeExecutableCache(cache, key, linker, debug, paths, platform === "linux"); + return new NativeExecutableCache( + cache, + key, + linker, + debug, + paths, + platform === "linux", + artifacts, + ); } catch { return null; } From be7fb3723568a2631d8788f2cbef9dd9dda171d7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 00:16:42 -0500 Subject: [PATCH 182/395] Expect the get probe at the native-array length site in the emission test The length read in the keyed and length emission test is a get site, which now calls scr_js_get_probe. --- packages/compiler/test/js-ic-emission.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 437ac34e4..15e8e92bc 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -170,7 +170,7 @@ console.log(lib.total([1, 2, 3])); expect(fn).toContain("@scr_js_keyed_probe("); // The length read answers from the native array's length. expect(fn).toContain("icg.rest"); - expect(fn).toContain("@scr_js_load_probe("); + expect(fn).toContain("@scr_js_get_probe("); } finally { await rm(dir, { recursive: true, force: true }); } From 478feb15cd860605215de22605ec4ee299bef182 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 00:26:55 -0500 Subject: [PATCH 183/395] Call the declared base method for super calls in JavaScript method values Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method whose `this` is the call-site receiver reflected the base class's whole prototype and read `m` from it. An instance's member lookup first tests its property bag, which inherits from its class's prototype data, so once the base's prototype was reflected, `child[k]` for a method the subclass overrides found the base's method there: `value[name](arg)` on a subclass instance called the base method. While no code reads a prototype method by name, `super.m()` now calls the declaring class's receiver-independent method value with the receiver, as the method's [[HomeObject]] lookup answers, without reflecting the base prototype. Programs that read prototype methods by name keep the reflected lookup. --- .../src/frontend/lowering/lower-classes.ts | 37 +++++++++++++------ .../main.ts.json | 7 ++++ .../8302-class-super-method-values/lib.cjs | 33 +++++++++++++++++ .../8302-class-super-method-values/main.ts | 26 +++++++++++++ .../package.json | 1 + .../main.ts.json | 1 + 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8302-class-super-method-values/main.ts.json create mode 100644 tests/corpus/8302-class-super-method-values/lib.cjs create mode 100644 tests/corpus/8302-class-super-method-values/main.ts create mode 100644 tests/corpus/8302-class-super-method-values/package.json create mode 100644 tests/harness/corpus-costs/8302-class-super-method-values/main.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 333e11ff8..b125734f7 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -194,7 +194,10 @@ import { classPrototypeData, reflectClassPrototype } from "./class-prototypes.js import { constructionEscape } from "./construction-escapes.js"; import { checkedClassConstruction, checkedClassConstructionPacked } from "./class-construction.js"; import { initializeRuntimeStatics, hasRuntimeStatics } from "./class-runtime-statics.js"; -import { reflectedClassStaticMethodValue } from "./class-method-values.js"; +import { + reflectedClassMethodValue, + reflectedClassStaticMethodValue, +} from "./class-method-values.js"; import { identifierReadSymbol } from "./surfaces.js"; export function returnsOnlyThis(member: ts.MethodDeclaration): boolean { @@ -9560,19 +9563,31 @@ export function lowerSuperMethodCall( // method up on the base class's complete prototype, as `super` reads // [[HomeObject]].[[Prototype]], and calls it with that receiver. const dynamicThis = thisRef.type.kind === "dyn"; + // While no code reads a prototype method by name (which could replace it), + // that method is the declaring class's own: its receiver-independent value + // is called directly. Reflecting the base's whole prototype instead would + // put its members on the prototype chain of every subclass instance's + // property bag, ahead of the subclasses' own compiled overrides. + const declared = + dynamicThis && !lowerer.prototypeMethodAccesses.has(access.name.text) + ? reflectedClassMethodValue(lowerer, found.declarer, access.name.text) + : null; if (dynamicThis || lowerer.prototypeMethodAccesses.has(access.name.text)) { - const prototype = - dynamicThis && cls.base.decl + const prototype = declared + ? null + : dynamicThis && cls.base.decl ? reflectClassPrototype(lowerer, cls.base, loc, classValueRef(lowerer, cls.base, access)) : classPrototypeData(lowerer, cls.base, loc); - if (prototype) { - const callee: IrExpr = { - kind: "dynKeyGet", - value: prototype, - key: { kind: "strLit", value: access.name.text, type: STRING, loc }, - type: DYN, - loc, - }; + if (declared || prototype) { + const callee: IrExpr = declared + ? lowerer.coerceToExpected(declared, DYN) + : { + kind: "dynKeyGet", + value: prototype!, + key: { kind: "strLit", value: access.name.text, type: STRING, loc }, + type: DYN, + loc, + }; // Spread arguments are spread into the dynamic call, as in a dynamic method call. const spreads: { arg: number; what: string }[] = []; const args = call.arguments.nodes.map((arg, index) => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8302-class-super-method-values/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8302-class-super-method-values/main.ts.json new file mode 100644 index 000000000..24fe2decc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8302-class-super-method-values/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/8302-class-super-method-values/lib.cjs", + "/tests/corpus/8302-class-super-method-values/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8302-class-super-method-values/lib.cjs b/tests/corpus/8302-class-super-method-values/lib.cjs new file mode 100644 index 000000000..4a8b1640a --- /dev/null +++ b/tests/corpus/8302-class-super-method-values/lib.cjs @@ -0,0 +1,33 @@ +"use strict"; +// Subclasses whose methods call super, read and called through computed and +// literal keys, as values called with call(), and directly. +class Base { + constructor(n) { this.n = n; } + scale(k) { return this.n * k; } + label() { return "base:" + this.n; } + plain(k) { return k; } +} +class Child extends Base { + scale(k) { return super.scale(k) * 10; } + label() { return "child(" + super.label() + ")"; } + plain(k) { return k * 100; } +} +class Grandchild extends Child { + scale(k) { return super.scale(k) + 1; } +} +class Sibling extends Base { + scale(k) { return -k; } + plain(k) { return super.plain(k) + 0.5; } +} +exports.make = function (i) { + switch (i % 4) { + case 0: return new Base(i); + case 1: return new Child(i); + case 2: return new Grandchild(i); + default: return new Sibling(i); + } +}; +exports.computed = function (value, name, arg) { return value[name](arg); }; +exports.viaCall = function (value, name, arg) { const f = value[name]; return f.call(value, arg); }; +exports.literal = function (value, arg) { return [value["scale"](arg), value["plain"](arg), value["label"]()].join(" "); }; +exports.direct = function (value, arg) { return [value.scale(arg), value.plain(arg), value.label()].join(" "); }; diff --git a/tests/corpus/8302-class-super-method-values/main.ts b/tests/corpus/8302-class-super-method-values/main.ts new file mode 100644 index 000000000..1a7edbb49 --- /dev/null +++ b/tests/corpus/8302-class-super-method-values/main.ts @@ -0,0 +1,26 @@ +// Methods of JavaScript subclasses that call super, read from instances +// through computed keys, literal keys and as values, and called directly. +import { createRequire } from "node:module"; + +interface Lib { + make(i: number): unknown; + computed(value: unknown, name: string, arg: number): unknown; + viaCall(value: unknown, name: string, arg: number): unknown; + literal(value: unknown, arg: number): string; + direct(value: unknown, arg: number): string; +} +const require = createRequire(import.meta.url); +const lib: Lib = require("./lib.cjs"); + +for (let i = 0; i < 8; i++) { + const value = lib.make(i); + console.log( + i, + lib.computed(value, "scale", 3), + lib.computed(value, "plain", 3), + lib.computed(value, "label", 0), + lib.viaCall(value, "scale", 2), + lib.viaCall(value, "label", 0), + ); + console.log(i, lib.literal(value, 5), "|", lib.direct(value, 5)); +} diff --git a/tests/corpus/8302-class-super-method-values/package.json b/tests/corpus/8302-class-super-method-values/package.json new file mode 100644 index 000000000..5ffd9800b --- /dev/null +++ b/tests/corpus/8302-class-super-method-values/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/tests/harness/corpus-costs/8302-class-super-method-values/main.ts.json b/tests/harness/corpus-costs/8302-class-super-method-values/main.ts.json new file mode 100644 index 000000000..30437efb4 --- /dev/null +++ b/tests/harness/corpus-costs/8302-class-super-method-values/main.ts.json @@ -0,0 +1 @@ +{ "plain": 4797 } From e5ba0734680922fe58128355f71582515d12467f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 00:44:25 -0500 Subject: [PATCH 184/395] Parse JSON into discriminated unions in one pass `JSON.parse(text) as T` for a discriminated union of records took the checked-dynamic route: it parsed the text into a dynamic tree, matched and copied that tree into records, then freed it. The one-pass schema parser now builds these unions directly, as record members, array elements and whole documents. For an object, a shallow scan of its members finds the discriminant's value, which selects the one record arm that holds that literal, and the object is parsed with that arm's schema. An object with a missing, non-string or unknown discriminant, or any member the arm rejects, declines, and the checked route then gives the result or the error as before. The compiler admits unions whose record arms each name their string literals, every literal naming one arm, plus null and undefined arms. --- packages/compiler/src/backend/llvm/dyn.ts | 86 ++++++++++-- .../test/json-schema-parse-emission.test.ts | 58 +++++++- ...43-json-parse-discriminated-unions.ts.json | 6 + packages/runtime/src/scr_json.c | 129 ++++++++++++++++-- packages/runtime/src/scr_json.h | 32 ++++- .../8243-json-parse-discriminated-unions.ts | 119 ++++++++++++++++ 6 files changed, 396 insertions(+), 34 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8243-json-parse-discriminated-unions.ts.json create mode 100644 tests/corpus/8243-json-parse-discriminated-unions.ts diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 9a91db0ca..54ce1bece 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -1480,8 +1480,9 @@ export class LlDyn { * fields, and at least one unless an index signature collects the * members), record members typed as unions of those leaves, records and * arrays, null and undefined (optional and nullable members; see - * jsonUnionSupported), and index signatures whose value type is not a - * union. Everything else keeps the checked-dynamic route alone. */ + * jsonUnionSupported), discriminated unions of records anywhere (see + * jsonDiscriminatedSupported), and index signatures whose value type is + * not a union. Everything else keeps the checked-dynamic route alone. */ private jsonSchemaSupported(t: IrType, visiting: Set, member = false): boolean { switch (t.kind) { case "f64": @@ -1508,13 +1509,42 @@ export class LlDyn { (!index || this.jsonSchemaSupported(index, visiting)) ); } - case "union": + case "union": { + const def = this.host.unionsById.get(t.unionId); + if (def?.discriminant) return this.jsonDiscriminatedSupported(def, visiting); return member && this.jsonUnionSupported(t, visiting); + } default: return false; } } + /** A discriminated union the one-pass parser writes directly, as a record + * member, an array element or the whole document: record arms that each + * name the string literals their discriminant member holds, every + * literal naming one arm, plus null and undefined arms. An object then + * selects the one arm whose literal its discriminant member holds, the + * only arm the checked route's literal tests leave to match + * (unionArmsByDiscriminant); an object holding no such literal declines. */ + private jsonDiscriminatedSupported(def: IrUnionDef, visiting: Set): boolean { + const discriminant = def.discriminant!; + const literals = new Set(); + let records = 0; + const supported = def.arms.every((arm, tag) => { + if (arm.kind === "nullT" || arm.kind === "undefinedT") return true; + if (arm.kind !== "record") return false; + records++; + const guard = discriminant.cases.find((candidate) => candidate.tag === tag); + if (!guard || guard.values.length === 0) return false; + for (const value of guard.values) { + if (typeof value !== "string" || literals.has(value)) return false; + literals.add(value); + } + return this.jsonSchemaSupported(arm, visiting); + }); + return supported && records >= 2; + } + /** A union record member the one-pass parser writes directly: every arm * a number, boolean, string, null, undefined, or a supported record or * array, with at most one record and one array arm and no discriminant. @@ -1619,9 +1649,15 @@ export class LlDyn { body = `${S} 3, ${S} ${entries.length}, ptr ${fields}, ptr ${overflow.elem}, ptr @${mangleRecordNew(t.shapeId)}, ptr null, ptr ${vAdapters(host, t).release}, ptr null, ${S} ${overflow.offset}`; break; } - case "union": - body = `${S} 5, ${S} 0, ptr null, ptr null, ptr null, ptr null, ptr null, ptr @${this.jsonUnionSchema(t, name)}, ${S} 0`; + case "union": { + // A discriminated union can be a whole document, whose value the + // parser releases when text follows it. + const release = host.unionsById.get(t.unionId)?.discriminant + ? vAdapters(host, t).release + : "null"; + body = `${S} 5, ${S} 0, ptr null, ptr null, ptr null, ptr null, ptr ${release}, ptr @${this.jsonUnionSchema(t, name)}, ${S} 0`; break; + } default: throw new InternalCompilerError(`llvm emitter bug: JSON schema of ${t.kind}`); } @@ -1636,7 +1672,11 @@ export class LlDyn { /** The ScrJsonUnion descriptor of a union member (scr_json.h layout): * its slot representation, the arm each JSON value kind selects, and the * values the runtime stores for null and for a missing member. Boxed - * unions build their boxes through `_box`. */ + * unions build their boxes through `_box`. A discriminated union + * also lists its object arms by discriminant literal (`_cases`), + * and, because it can be an array element or a whole document, whose + * values are boxes unless the union is a pointer union, it keeps its null + * value and box whatever its field representation. */ private jsonUnionSchema(t: IrType & { kind: "union" }, name: string): string { const host = this.host; const S = host.sizeType; @@ -1655,10 +1695,11 @@ export class LlDyn { ); const nullTag = tags[0]!; const absentTag = tags[6]!; - const nullValue = !inline && nullTag >= 0 ? host.unitInstanceRef(t.unionId, nullTag) : "null"; - const absentValue = !inline && absentTag >= 0 ? host.absentInstanceRef(t.unionId) : "null"; + const values = !inline || def.discriminant !== undefined; + const nullValue = values && nullTag >= 0 ? host.unitInstanceRef(t.unionId, nullTag) : "null"; + const absentValue = values && absentTag >= 0 ? host.absentInstanceRef(t.unionId) : "null"; let box = "null"; - if (repr === 2) { + if (repr === 2 || (def.discriminant && !nullable)) { box = `@${name}_box`; const lines = [`define internal ptr ${box}(i32 %tag, i64 %bits) ${FN_ATTRS} {`, `entry:`]; const cases: string[] = []; @@ -1699,9 +1740,27 @@ export class LlDyn { ``, ); } + let cases = "null"; + if (def.discriminant) { + const discriminant = def.discriminant; + const caseTy = `{ ptr, ${S}, ${S}, ptr }`; + const entries = discriminant.cases.flatMap((guard) => + guard.values.map( + (value) => + `${caseTy} { ptr ${host.cstr(String(value))}, ${S} ${Buffer.byteLength(String(value), "utf8")}, ${S} ${guard.tag}, ptr @${this.jsonSchema(def.arms[guard.tag]!)} }`, + ), + ); + const table = `@${name}_case_table`; + cases = `@${name}_cases`; + this.defs.push( + `${table} = internal constant [${entries.length} x ${caseTy}] [${entries.join(", ")}]`, + `${cases} = internal constant { ptr, ${S}, ${S}, ptr } { ptr ${host.cstr(discriminant.field)}, ${S} ${Buffer.byteLength(discriminant.field, "utf8")}, ${S} ${entries.length}, ptr ${table} }`, + ``, + ); + } const descriptor = `${name}_union`; this.defs.push( - `@${descriptor} = internal constant { ${S}, [7 x ${S}], [7 x ptr], ptr, ptr, ptr } { ${S} ${repr}, [7 x ${S}] [${tags.map((tag) => `${S} ${tag}`).join(", ")}], [7 x ptr] [${arms.join(", ")}], ptr ${nullValue}, ptr ${absentValue}, ptr ${box} }`, + `@${descriptor} = internal constant { ${S}, [7 x ${S}], [7 x ptr], ptr, ptr, ptr, ptr } { ${S} ${repr}, [7 x ${S}] [${tags.map((tag) => `${S} ${tag}`).join(", ")}], [7 x ptr] [${arms.join(", ")}], ptr ${nullValue}, ptr ${absentValue}, ptr ${box}, ptr ${cases} }`, ``, ); return descriptor; @@ -1751,7 +1810,12 @@ export class LlDyn { * dynCheck builder — which reports the exact error. Returns +1, or null * with the pending flag set. */ jsonParseHelper(t: IrType): string | null { - if (t.kind !== "record" && t.kind !== "array") return null; + if ( + t.kind !== "record" && + t.kind !== "array" && + !(t.kind === "union" && this.host.unionsById.get(t.unionId)?.discriminant) + ) + return null; const key = typeKey(t); const existing = this.jsonParsers.get(key); if (existing) return existing; diff --git a/packages/compiler/test/json-schema-parse-emission.test.ts b/packages/compiler/test/json-schema-parse-emission.test.ts index b96c9a032..dd6bf52ae 100644 --- a/packages/compiler/test/json-schema-parse-emission.test.ts +++ b/packages/compiler/test/json-schema-parse-emission.test.ts @@ -58,13 +58,13 @@ console.log(orders.length, typed.sku, loose.name, mixed.length, pair[0], either. // record pointer (0), the inline scalar pair (1), and a tagged box built // through the schema's box function (2), with arm tags by JSON kind. expect(llvm).toMatch( - /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr \} \{ i64 0, /, + /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr, ptr \} \{ i64 0, /, ); expect(llvm).toMatch( - /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr \} \{ i64 1, /, + /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr, ptr \} \{ i64 1, /, ); expect(llvm).toMatch( - /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr \} \{ i64 2, .*ptr @sc_jsch_\d+_box \}/, + /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr, ptr \} \{ i64 2, .*ptr @sc_jsch_\d+_box, ptr null \}/, ); expect(llvm).toMatch(/^define internal ptr @sc_jsch_\d+_box\(i32 %tag, i64 %bits\)/m); // A pure dictionary has no declared fields: its index value schema and @@ -78,9 +78,59 @@ console.log(orders.length, typed.sku, loose.name, mixed.length, pair[0], either. const wasm = emitLlvmModule(mod, { pointerBits: 32, wasi: true }); expect(wasm).toContain("= internal constant { i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, i32 }"); - expect(wasm).toContain("= internal constant { i32, [7 x i32], [7 x ptr], ptr, ptr, ptr }"); + expect(wasm).toContain("= internal constant { i32, [7 x i32], [7 x ptr], ptr, ptr, ptr, ptr }"); expect(wasm).toMatch(/\{ ptr, i32, i32, ptr \} \{ ptr @sc_cs_\d+, i32 3, i32 ptrtoint/); } finally { await rm(dir, { recursive: true, force: true }); } }); + +test("JSON.parse casts to discriminated unions fuse into the schema parser", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-json-schema-union-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile( + entry, + ` +type Event = + | { kind: "click"; x: number; y: number } + | { kind: "focus" | "blur"; target: string } + | { kind: "load"; ms: number; next: Event | null }; +type Expr = { kind: "num"; value: number } | { kind: "add"; items: Expr[] }; +type Loose = { kind: string; a: number } | { kind: string; b: string }; +const text = process.argv[2] ?? "[]"; +const events = JSON.parse(text) as Event[]; +const tree = JSON.parse(text) as Expr; +const loose = JSON.parse(text) as Loose[]; +console.log(events.length, tree.kind, loose.length); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const llvm = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + // Event[] and the whole-document Expr take the fused path; a union + // without literal discriminants keeps the checked-dynamic route alone. + expect(llvm.match(/^define internal ptr @sc_jp_\d+(?:\.i32)?\(ptr %text\)/gm)).toHaveLength(2); + expect(llvm.match(/call ptr @scr_json_parse\(ptr /g)).toHaveLength(3); + // Each literal names its arm's tag and record schema; an arm with two + // literals is listed under both. Event, `Event | null` and Expr each + // have a table. + const cases = [...llvm.matchAll(/^@sc_jsch_\d+_case_table = internal constant \[(\d+) x /gm)]; + expect(cases.map((m) => Number(m[1])).sort()).toEqual([2, 4, 4]); + expect(llvm).toMatch( + /^@sc_jsch_\d+_cases = internal constant \{ ptr, i64, i64, ptr \} \{ ptr @sc_cs_\d+, i64 4, i64 4, ptr @sc_jsch_\d+_case_table \}/m, + ); + // The union descriptors point at their cases, and a union schema that + // can be a whole document carries its release. + expect(llvm).toMatch( + /@sc_jsch_\d+_union = internal constant \{ i64, \[7 x i64\], \[7 x ptr\], ptr, ptr, ptr, ptr \} \{ .*, ptr @sc_jsch_\d+_cases \}/, + ); + expect(llvm).toMatch( + /= internal constant \{ i64, i64, ptr, ptr, ptr, ptr, ptr, ptr, i64 \} \{ i64 5, i64 0, ptr null, ptr null, ptr null, ptr null, ptr @\S+, ptr @sc_jsch_\d+_union, i64 0 \}/, + ); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8243-json-parse-discriminated-unions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8243-json-parse-discriminated-unions.ts.json new file mode 100644 index 000000000..96ef2084c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8243-json-parse-discriminated-unions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8243-json-parse-discriminated-unions.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cfe3e8389..7539b3a1b 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -12304,7 +12304,8 @@ static bool scr_jschema_read(ScrJsonP *p, const ScrJsonSchema *s, ScrJsonSlot *o static void scr_jschema_drop(const ScrJsonSchema *s, ScrJsonSlot *v) { if (s->kind == SCR_JSCHEMA_STR) scr_str_release(v->ptr); - else if (s->kind == SCR_JSCHEMA_REC || s->kind == SCR_JSCHEMA_ARR) s->release(v->ptr); + else if (s->kind == SCR_JSCHEMA_REC || s->kind == SCR_JSCHEMA_ARR || s->kind == SCR_JSCHEMA_UNION) + s->release(v->ptr); } static bool scr_jschema_string_skip(ScrJsonP *p) { @@ -12418,14 +12419,79 @@ static void scr_jschema_inline_store(char *slot, size_t tag, uint64_t bits) { memcpy(slot, &pair, sizeof pair); } -/* A union member value written into its field slot. Each JSON value kind - * selects at most one arm (the compiler admits no two arms one kind could - * both match), so the checked route's first full match is that arm, and - * any other value declines. */ -static __attribute__((noinline)) bool scr_jschema_union(ScrJsonP *p, const ScrJsonUnion *u, - char *slot) { - scr_json_ws(p); - if (p->pos >= p->len) return false; +/* The string at p->pos (a quote), as a span into the text or, when it + * holds escapes, decoded into *decoded (released by the caller). */ +static bool scr_jschema_string_at(ScrJsonP *p, const char **span, size_t *span_len, + ScrStr **decoded) { + *decoded = NULL; + int r = scr_json_string_span(p, span, span_len); + if (r > 0) return true; + if (r < 0) return false; + *decoded = scr_json_string_slow(p, *span_len); + if (!*decoded) return false; + *span = (*decoded)->data; + *span_len = (*decoded)->len; + return true; +} + +/* The case of discriminated union `d` that the object at p->pos (its + * opening brace) selects: the one whose literal the object's discriminant + * member holds. A shallow scan of the members up to that one finds it and + * restores p->pos. NULL when the member is missing or not a string, when + * no case has its value, or when the text before it is malformed: the + * checked route then decides. A repeated discriminant member selects by + * its first value; the arm's record then declines the repeat. */ +static __attribute__((noinline)) const ScrJsonCase *scr_jschema_case(ScrJsonP *p, + const ScrJsonDiscriminant *d) { + const size_t start = p->pos, depth = p->depth; + const ScrJsonCase *found = NULL; + if (++p->depth > SCR_JSON_MAX_DEPTH) goto done; + p->pos++; /* '{' */ + for (;;) { + scr_json_ws(p); + if (p->pos >= p->len || p->s[p->pos] != '"') goto done; + const char *key; + size_t key_len; + ScrStr *decoded; + if (!scr_jschema_string_at(p, &key, &key_len, &decoded)) goto done; + bool discriminant = key_len == d->field_len && scr_key_equal(key, d->field, key_len); + scr_str_release(decoded); + scr_json_ws(p); + if (p->pos >= p->len || p->s[p->pos] != ':') goto done; + p->pos++; + if (discriminant) { + scr_json_ws(p); + if (p->pos >= p->len || p->s[p->pos] != '"') goto done; + const char *value; + size_t value_len; + if (!scr_jschema_string_at(p, &value, &value_len, &decoded)) goto done; + for (size_t i = 0; i < d->ncases; i++) { + const ScrJsonCase *c = &d->cases[i]; + if (c->value_len == value_len && memcmp(c->value, value, value_len) == 0) { + found = c; + break; + } + } + scr_str_release(decoded); + goto done; + } + if (!scr_jschema_skip(p)) goto done; + scr_json_ws(p); + if (p->pos >= p->len || p->s[p->pos] != ',') goto done; + p->pos++; + } +done: + p->pos = start; + p->depth = depth; + return found; +} + +/* A union value's arm tag and payload at p->pos (whitespace skipped): the + * arm its JSON value kind selects or, for an object of a discriminated + * union, its case's record. A null value reads no payload (*arm_out is + * SCR_JARM_NULL). False when the value declines. */ +static bool scr_jschema_union_payload(ScrJsonP *p, const ScrJsonUnion *u, size_t *tag, + size_t *arm_out, ScrJsonSlot *v) { char c = p->s[p->pos]; size_t arm; if (c == 'n') arm = SCR_JARM_NULL; @@ -12435,16 +12501,36 @@ static __attribute__((noinline)) bool scr_jschema_union(ScrJsonP *p, const ScrJs else if (c == 't' || c == 'f') arm = SCR_JARM_BOOL; else if (c == '-' || (c >= '0' && c <= '9')) arm = SCR_JARM_NUM; else return false; - size_t tag = u->tags[arm]; - if (tag == SCR_JSCHEMA_NO_ARM) return false; + *arm_out = arm; + if (arm == SCR_JARM_OBJ && u->disc) { + const ScrJsonCase *selected = scr_jschema_case(p, u->disc); + if (!selected) return false; + *tag = selected->tag; + return scr_jschema_read(p, selected->record, v); + } + *tag = u->tags[arm]; + if (*tag == SCR_JSCHEMA_NO_ARM) return false; + if (arm == SCR_JARM_NULL) return scr_json_lit(p, "null", 4); + return scr_jschema_read(p, u->arms[arm], v); +} + +/* A union member value written into its field slot. Each JSON value kind + * selects at most one arm, and an object of a discriminated union selects + * its case's arm (the compiler admits no two arms one value could both + * match), so the checked route's first full match is that arm, and any + * other value declines. */ +static __attribute__((noinline)) bool scr_jschema_union(ScrJsonP *p, const ScrJsonUnion *u, + char *slot) { + scr_json_ws(p); + if (p->pos >= p->len) return false; + size_t tag, arm; + ScrJsonSlot v; + if (!scr_jschema_union_payload(p, u, &tag, &arm, &v)) return false; if (arm == SCR_JARM_NULL) { - if (!scr_json_lit(p, "null", 4)) return false; if (u->repr == SCR_JUNION_INLINE) scr_jschema_inline_store(slot, tag, 0); else memcpy(slot, &u->null_value, sizeof u->null_value); /* immortal */ return true; } - ScrJsonSlot v; - if (!scr_jschema_read(p, u->arms[arm], &v)) return false; uint64_t bits = 0; if (arm == SCR_JARM_NUM) memcpy(&bits, &v.f, sizeof bits); else if (arm == SCR_JARM_BOOL) bits = v.b ? 1 : 0; @@ -12460,6 +12546,19 @@ static __attribute__((noinline)) bool scr_jschema_union(ScrJsonP *p, const ScrJs return true; } +/* A discriminated union value outside a record field: an array element or + * the whole document. A pointer union's value is the case's record itself; + * any other union's is a box. */ +static bool scr_jschema_union_value(ScrJsonP *p, const ScrJsonUnion *u, ScrJsonSlot *out) { + size_t tag, arm; + ScrJsonSlot v; + if (!scr_jschema_union_payload(p, u, &tag, &arm, &v)) return false; + if (arm == SCR_JARM_NULL) out->ptr = u->null_value; /* immortal */ + else if (u->repr == SCR_JUNION_NULLABLE) out->ptr = v.ptr; + else out->ptr = u->box((uint32_t)tag, (uint64_t)(uintptr_t)v.ptr); /* the record moves in */ + return true; +} + /* A declared member value written into its (zeroed) slot of `rec`. */ static inline __attribute__((always_inline)) bool scr_jschema_field(ScrJsonP *p, const ScrJsonSchemaField *f, @@ -12805,6 +12904,8 @@ static bool scr_jschema_read(ScrJsonP *p, const ScrJsonSchema *s, ScrJsonSlot *o p->depth--; return ok; } + case SCR_JSCHEMA_UNION: + return scr_jschema_union_value(p, s->uni, out); } return false; } diff --git a/packages/runtime/src/scr_json.h b/packages/runtime/src/scr_json.h index 8a4ecc62d..a8a59b60c 100644 --- a/packages/runtime/src/scr_json.h +++ b/packages/runtime/src/scr_json.h @@ -13,11 +13,14 @@ ScrDyn *scr_json_parse(ScrStr *text); /* Compiler-emitted description of a statically known JSON.parse target * (`JSON.parse(text) as T`). Constant data in the program image; the LLVM * backend mirrors this layout ({size, size, ptr, ptr, ptr, ptr, ptr, ptr, size}, - * {ptr, size, size, ptr} and {size, [7 x size], [7 x ptr], ptr, ptr, ptr}). - * Records list their declared fields (at most 64), each with its byte - * offset in the record struct; bool fields are one byte, f64 fields - * doubles, inline union fields a {uint32 tag, uint64 bits} pair, every - * other kind a +1 pointer. Unions appear only as record field types. A + * {ptr, size, size, ptr}, {size, [7 x size], [7 x ptr], ptr, ptr, ptr, ptr}, + * and for discriminated unions {ptr, size, size, ptr} with {ptr, size, size, + * ptr} cases). Records list their declared fields (at most 64), each with + * its byte offset in the record struct; bool fields are one byte, f64 + * fields doubles, inline union fields a {uint32 tag, uint64 bits} pair, + * every other kind a +1 pointer. Unions appear as record field types; + * discriminated unions also as array elements and whole documents, whose + * values are the union's box (or its pointer, for a pointer union). A * record with an index signature names its index value type in `elem` and * its overflow map's offset in `ovf_offset`. */ enum { @@ -57,6 +60,22 @@ typedef struct { size_t offset; const ScrJsonSchema *type; } ScrJsonSchemaField; +/* One string literal of a discriminated union's discriminant member and + * the record arm (canonical tag and schema) that holds it. */ +typedef struct { + const char *value; + size_t value_len; + size_t tag; + const ScrJsonSchema *record; +} ScrJsonCase; +/* A discriminated union's object arms by discriminant literal: every + * literal names one arm. */ +typedef struct { + const char *field; + size_t field_len; + size_t ncases; + const ScrJsonCase *cases; +} ScrJsonDiscriminant; typedef struct { size_t repr; size_t tags[SCR_JARM_COUNT]; /* canonical arm tag, or SCR_JSCHEMA_NO_ARM */ @@ -66,6 +85,9 @@ typedef struct { /* boxed: a +1 box of the value arm `tag`; bits hold its double, its * bool, or its +1 payload pointer (moved in). */ void *(*box)(uint32_t tag, uint64_t bits); + /* discriminated unions: the object arms; NULL for other unions, whose + * object arm is arms[SCR_JARM_OBJ] */ + const ScrJsonDiscriminant *disc; } ScrJsonUnion; struct ScrJsonSchema { size_t kind; diff --git a/tests/corpus/8243-json-parse-discriminated-unions.ts b/tests/corpus/8243-json-parse-discriminated-unions.ts new file mode 100644 index 000000000..df3fd6dd8 --- /dev/null +++ b/tests/corpus/8243-json-parse-discriminated-unions.ts @@ -0,0 +1,119 @@ +// JSON.parse cast to discriminated unions of records: arrays of them, +// whole documents, members (with null and optional arms) and recursive +// trees. The discriminant appears first, last or between other members, +// escaped, repeated, and beside undeclared members; values hold escapes +// and non-ASCII text. + +type Event = + | { kind: "click"; x: number; y: number } + | { kind: "focus" | "blur"; target: string } + | { key: string; kind: "key"; repeat: boolean } + | { kind: "load"; ms: number; next: Event | null; note?: string }; + +type Expr = + | { kind: "num"; value: number } + | { kind: "neg"; operand: Expr } + | { items: Expr[]; kind: "max" } + | { body: Expr; kind: "let"; name: string; value: Expr }; + +function describe(e: Event): string { + switch (e.kind) { + case "click": + return `click ${e.x},${e.y}`; + case "focus": + case "blur": + return `${e.kind} ${e.target}`; + case "key": + return `key ${e.key}${e.repeat ? " (repeat)" : ""}`; + case "load": + return `load ${e.ms}${e.note === undefined ? "" : ` "${e.note}"`}${e.next === null ? "" : ` then ${describe(e.next)}`}`; + } +} + +function evaluate(e: Expr, env: Map): number { + switch (e.kind) { + case "num": + return e.value; + case "neg": + return -evaluate(e.operand, env); + case "max": { + let best = -Infinity; + for (const item of e.items) best = Math.max(best, evaluate(item, env)); + return best; + } + case "let": { + const inner = new Map(env); + inner.set(e.name, evaluate(e.value, env)); + return evaluate(e.body, inner) + (inner.get(e.name) ?? 0); + } + } +} + +const events = JSON.parse(`[ + {"kind":"click","x":1,"y":2}, + {"x":3,"y":4,"kind":"click"}, + {"y":6,"kind":"click","x":5}, + {"kind":"focus","target":"input"}, + {"target":"button","kind":"blur"}, + {"key":"Enter","kind":"key","repeat":false}, + {"repeat":true,"key":"\\u00e9","kind":"key"}, + {"kind":"load","ms":12,"next":null}, + {"kind":"load","ms":7,"note":"caf\\u00e9 \\ud83d\\ude00","next":{"kind":"blur","target":"tab"}}, + {"next":{"kind":"load","ms":1,"next":{"kind":"click","x":0,"y":-1}},"ms":30,"kind":"load"}, + {"k\\u0069nd":"focus","target":"escaped key"}, + {"kind":"bl\\u0075r","target":"escaped value"}, + {"kind":"click","kind":"focus","target":"repeated"}, + {"kind":"click","x":8,"y":9,"extra":{"kind":"not an event","list":[1,[2]]}}, + { "kind" : "key" , "key" : "space" , "repeat" : true } +]`) as Event[]; +console.log(events.length); +for (const e of events) console.log(describe(e)); +console.log(JSON.stringify(events[12]), JSON.stringify(events[13])); + +const empty = JSON.parse("[]") as Event[]; +console.log(empty.length); + +const single = JSON.parse('{"target":"whole document","kind":"focus"}') as Event; +console.log(describe(single)); + +const tree = JSON.parse(`{ + "body": {"items": [{"kind":"num","value":4}, {"kind":"neg","operand":{"kind":"num","value":-9}}, {"items":[],"kind":"max"}], "kind": "max"}, + "kind": "let", + "name": "x", + "value": {"kind":"neg","operand":{"kind":"neg","operand":{"kind":"num","value":2.5}}} +}`) as Expr; +console.log(evaluate(tree, new Map())); + +const forest = JSON.parse( + `[${Array.from({ length: 50 }, (_, i) => + i % 3 === 0 + ? `{"kind":"num","value":${i}}` + : i % 3 === 1 + ? `{"operand":{"kind":"num","value":${i}},"kind":"neg"}` + : `{"items":[{"kind":"num","value":${i}},{"kind":"num","value":${50 - i}}],"kind":"max"}`, + ).join(",")}]`, +) as Expr[]; +let total = 0; +for (const t of forest) total += evaluate(t, new Map()); +console.log(forest.length, total); + +// Parsing the same text again builds separate values. +const again = JSON.parse('[{"kind":"num","value":1},{"kind":"num","value":2}]') as Expr[]; +const twice = JSON.parse('[{"kind":"num","value":1},{"kind":"num","value":2}]') as Expr[]; +console.log(again.length + twice.length, again[0] === twice[0]); + +// Malformed text throws Node's SyntaxError, including text that ends +// inside the members before the discriminant or inside its value. +for (const text of [ + '[{"kind":"click","x":1,"y":2},', + '[{"x":1,"kind"', + '[{"x":[1,2,"kind":"click"}]', + '[{"kind":"cl', + '[{"target":"a" "kind":"focus"}]', +]) { + try { + console.log((JSON.parse(text) as Event[]).length); + } catch (error) { + console.log(error instanceof SyntaxError, (error as Error).message); + } +} From a54e0811d0c22d6fb019b8aa776b15922bf37e3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:04:31 -0500 Subject: [PATCH 185/395] Journal the Promise.all tuple and AsyncLocalStorage callback thunks --- .../compiler/src/backend/llvm/lib-dispatch.ts | 52 ++++++++++++------- .../compiler/src/backend/llvm/lib-process.ts | 7 +++ .../compiler/test/chunked-emission.test.ts | 35 ++++++++++++- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index e1f038093..8a98fb154 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -25,6 +25,7 @@ import { DYN_KIND } from "./dyn.js"; import { caBytes } from "./lib-abi.js"; import { emitInlineAtomicsOp } from "./expr-bytes.js"; import { regexLiteralCaptures } from "./regex-matchers.js"; +import { registerReplay } from "./emit-journal.js"; /** ScrDyn.v.typed_ref.type_key_len: the low 27 bits of its bit-field word. */ const TYPE_KEY_LEN_MASK = (1 << 27) - 1; @@ -1130,6 +1131,36 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { } } +/** The settlement adapter of Promise.all's tuple form for the builder + * `fnName`, which fulfills the result with the builder's tuple. */ +function allTupleAdapterFor(host: LlvmEmitterContext, fnName: string, tuple: IrType): string { + const key = `allTuple:${fnName}`; + let adapter = host.resolveThunks.get(key); + if (adapter !== undefined) return adapter; + adapter = `sc_all_tuple_${host.resolveThunks.size}`; + host.resolveThunks.set(key, adapter); + host.journal?.allocated(adapter, "allTupleAdapterFor", [fnName, tuple]); + const rc = vAdapters(host.shapeHost, tuple); + host.declare(`declare ptr @scr_promise_payload_ref(ptr)`); + host.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); + host.resolveThunkDefs.push( + `define internal void @${adapter}(ptr %dst, ptr %src) ${FN_ATTRS} { ; Promise.all tuple`, + `entry:`, + ` %entries = call ptr @scr_promise_payload_ref(ptr %src) ; +1, moves into the builder`, + ` %tuple = call ptr @${host.callTarget(fnName)}(ptr %entries)`, + ` call void @scr_promise_fulfill_ref(ptr %dst, ptr %tuple, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, tuple)})`, + ` ret void`, + `}`, + ``, + ); + host.journal?.built(); + return adapter; +} + +registerReplay("allTupleAdapterFor", (host, args) => + allTupleAdapterFor(host, args[0] as string, args[1] as IrType), +); + /** Promise.all's tuple form (promise.allTuple): the combinator over the * entries stores each fulfilled entry promise, and a settlement adapter on * the combinator calls the builder with them and fulfills the result with @@ -1142,26 +1173,7 @@ function emitPromiseAllTuple(host: LlvmEmitterContext, e: LibCallExpr): LlValue throw new InternalCompilerError("llvm emitter bug: promise.allTuple shape"); const entriesT = e.args[0]!.type; const entries = host.emitExpr(e.args[0]!); - const tuple = e.type.inner; - const key = `allTuple:${build.fnName}`; - let adapter = host.resolveThunks.get(key); - if (adapter === undefined) { - adapter = `sc_all_tuple_${host.resolveThunks.size}`; - host.resolveThunks.set(key, adapter); - const rc = vAdapters(host.shapeHost, tuple); - host.declare(`declare ptr @scr_promise_payload_ref(ptr)`); - host.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); - host.resolveThunkDefs.push( - `define internal void @${adapter}(ptr %dst, ptr %src) ${FN_ATTRS} { ; Promise.all tuple`, - `entry:`, - ` %entries = call ptr @scr_promise_payload_ref(ptr %src) ; +1, moves into the builder`, - ` %tuple = call ptr @${host.callTarget(build.fnName)}(ptr %entries)`, - ` call void @scr_promise_fulfill_ref(ptr %dst, ptr %tuple, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, tuple)})`, - ` ret void`, - `}`, - ``, - ); - } + const adapter = allTupleAdapterFor(host, build.fnName, e.type.inner); host.declare(`declare ptr @scr_promise_all(ptr, ptr, ptr)`); host.declare(`declare void @scr_promise_all_store_promise(ptr, double, ptr)`); host.declare(`declare ptr @scr_promise_new()`); diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 74ab34971..75d8038fc 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -6,6 +6,7 @@ import type { LlvmEmitterContext, LibCallExpr, LlValue } from "./expr-context.js import { f64Lit } from "./common.js"; import { mockTimerSym } from "./lib-shared.js"; import { FN_ATTRS } from "./shapes.js"; +import { registerReplay } from "./emit-journal.js"; export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { const B = host.B; @@ -498,6 +499,7 @@ function boundCallbackThunk(host: LlvmEmitterContext, cbT: IrType & { kind: "fun if (sym) return sym; sym = `sc_alsbound_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); + host.journal?.allocated(sym, "boundCallbackThunk", [cbT]); host.declare("declare ptr @scr_als_bound_target(ptr)"); host.declare("declare ptr @scr_als_bound_enter(ptr)"); host.declare("declare void @scr_als_bound_leave(ptr, ptr)"); @@ -517,9 +519,14 @@ function boundCallbackThunk(host: LlvmEmitterContext, cbT: IrType & { kind: "fun `}`, ``, ); + host.journal?.built(); return sym; } +registerReplay("boundCallbackThunk", (host, args) => + boundCallbackThunk(host, args[0] as IrType & { kind: "func" }), +); + export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { const B = host.B; if (e.fn === "process.forkTarget") { diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 2aec1e49b..f4977f035 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; @@ -25,6 +25,7 @@ const PROGRAMS = [ "6305-json-discriminant-dispatch.ts", "7321-speculative-loop-bail-sites.ts", "7360-worker-typed-records.ts", + "7110-als-completion-callbacks.ts", "node24-text-utilities-coercion.js", ]; @@ -70,6 +71,38 @@ test("function bodies emitted in chunks merge into the text one emitter gives", } }, 600_000); +// Thunks a range creates are journaled, so the merging emitter defines +// them: here Promise.all's tuple adapter, requested by a function that is +// not async (async functions are emitted again when the merging emitter +// selects stackless bodies). +test("a Promise.all tuple adapter requested in a range is defined", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-tuple-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + [ + "function both(a: Promise, b: Promise): Promise<[number, string]> {", + " return Promise.all([a, b]);", + "}", + 'both(Promise.resolve(1), Promise.resolve("x")).then((v) => console.log(v[0], v[1]));', + "", + ].join("\n"), + ); + const outPath = join(dir, "main.ir.json"); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ir = await readFile(outPath, "utf8"); + const serial = text(emitLlvmModuleSource(deserializeModule(ir), {})); + expect(serial).toContain("define internal void @sc_all_tuple_0("); + const chunked = text(emitLlvmModuleSource(deserializeModule(ir), { functionChunks: 2 })); + expect(chunked === serial).toBe(true); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}, 60_000); + // Debug metadata is numbered per emitter (debug-info.ts) and not journaled: // a debug build emits its function bodies in one emitter. test("debug builds emit function bodies serially, with every metadata reference defined", async () => { From 9d7f0182e5d670bb603e80cf23720b5c78d742a3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:04:55 -0500 Subject: [PATCH 186/395] Keep CommonJS wrapper names and redeclared exports out of namespace rewrites - A guarded declaration of a name the CommonJS wrapper defines (`var exports = exports || {}`, and `module`, `require`, `__filename`, `__dirname`, `arguments`) reads the wrapper's value, not undefined, so it keeps its text instead of becoming a fresh object. - The write scan counts destructuring assignments, and destructuring for-in/for-of targets, as writes, so a hoisted function that sets the namespace before its guarded declaration keeps the declaration as it is. - An exported `var` that a later declaration of the same name sets again is not settled at the export: importers would read the redeclared value, where Node copies the first one into module.exports. It stays refused (SC1090), as before. The npm case var-export-redeclared pins it. --- .../src/frontend/lowering/lower-stmts.ts | 8 +++-- .../npm-static-namespace-defaults.test.ts | 17 ++++++++++ .../frontend/npm-static-namespace-defaults.ts | 31 ++++++++++++++++--- .../cases/var-export-redeclared/main.ts.json | 6 ++++ .../npm/cases/var-export-redeclared/main.ts | 3 ++ .../node_modules/redeclared-ns/index.d.ts | 1 + .../node_modules/redeclared-ns/index.js | 5 +++ .../node_modules/redeclared-ns/package.json | 1 + .../cases/var-export-redeclared/package.json | 1 + tests/harness/npm.test.ts | 2 ++ 10 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/var-export-redeclared/main.ts.json create mode 100644 tests/fixtures/npm/cases/var-export-redeclared/main.ts create mode 100644 tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.d.ts create mode 100644 tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.js create mode 100644 tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/package.json create mode 100644 tests/fixtures/npm/cases/var-export-redeclared/package.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 851cae4c9..4366a04b0 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -12708,13 +12708,15 @@ function isAssertionInitializer(node: ts.Expression): boolean { return ts.isAsExpression(e) || ts.isTypeAssertion(e); } -/** A `var` (or `let`) whose initialized declaration precedes the export - * statement in the module's top-level statements and that nothing ever - * reassigns: exporting it by reference reads the value Node copies. The +/** A `var` (or `let`) whose one initialized declaration precedes the + * export statement in the module's top-level statements and that nothing + * ever reassigns or redeclares: exporting it by reference reads the value Node copies. The * legacy `var ns = ns || {}; …; module.exports = { ns }` shape. */ function settledBeforeExport(lowerer: Lowerer, sym: ts.Symbol, exportNode: ts.Node): boolean { const decl = sym.valueDeclaration; if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return false; + // A redeclaration (`var ns = other` after the export) writes it too. + if (sym.declarations.some((d) => d !== decl)) return false; const stmt = decl.parent?.parent; if (!stmt || !ts.isVariableStatement(stmt) || !ts.isSourceFile(stmt.parent)) return false; let exportStmt: ts.Node = exportNode; diff --git a/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts b/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts index 93a776f7e..0fe7dc9a1 100644 --- a/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts +++ b/packages/compiler/src/frontend/npm-static-namespace-defaults.test.ts @@ -60,4 +60,21 @@ test("any other write, declaration or earlier mention keeps the text", () => { expect(rewrite("let ns = ns || {};\n")).toBeNull(); // Not the declared name on the left. expect(rewrite("var ns = other || {};\n")).toBeNull(); + // Written through a destructuring assignment, here by a hoisted function + // that runs before the declaration. + expect( + rewrite("init();\nvar ns = ns || {};\nfunction init() { [ns] = [{ a: 1 }]; }\n"), + ).toBeNull(); + expect( + rewrite("init();\nvar ns = ns || {};\nfunction init() { ({ ns } = { ns: { a: 1 } }); }\n"), + ).toBeNull(); + expect(rewrite("var ns = ns || {};\nfor ([ns] of [[{}]]);\n")).toBeNull(); +}); + +test("a name the CommonJS wrapper defines keeps the text", () => { + // `exports` is the module's export object here, not undefined. + const source = "var exports = exports || {};\nexports.answer = 42;\n"; + expect(rewrite(source)).toBeNull(); + expect(rewrite("var module = module || {};\n")).toBeNull(); + expect(rewrite("var require = require || null;\n")).toBeNull(); }); diff --git a/packages/compiler/src/frontend/npm-static-namespace-defaults.ts b/packages/compiler/src/frontend/npm-static-namespace-defaults.ts index 2e4bf046c..26a8e4cd0 100644 --- a/packages/compiler/src/frontend/npm-static-namespace-defaults.ts +++ b/packages/compiler/src/frontend/npm-static-namespace-defaults.ts @@ -6,8 +6,9 @@ * read in its own initializer sees `undefined` unless something writes the * variable first. When nothing else in the file ever writes it (no other * declaration, assignment or update of that name anywhere, no function or - * class of that name) and no earlier top-level statement mentions it, the - * initializer is its right operand: `var ns = {};`. Inference then types + * class of that name), no earlier top-level statement mentions it, and it + * is not a name a CommonJS module's wrapper defines (`exports`, `module`, + * ...), the initializer is its right operand: `var ns = {};`. Inference then types * the namespace from that literal and the members later statements add, * instead of `any` (the self-reference makes the checker give up). `??` * is the same. Anything else keeps the original text. */ @@ -15,6 +16,18 @@ import * as ts from "./ts7/syntax.js"; import { tokenStartOfNode } from "./ts7/node-functions.js"; import { applyEdits, unparen, type Edit } from "./npm-static-bundled-chunks.js"; +/** Names a CommonJS module's wrapper function defines before its first + * statement: its parameters and its arguments object. `var exports = + * exports || {}` keeps the module's export object, not a fresh one. */ +const COMMONJS_WRAPPER_NAMES = new Set([ + "exports", + "require", + "module", + "__filename", + "__dirname", + "arguments", +]); + /** A cheap probe: some `var X = X ||` or `?? ` may be present. */ export function mayHoldNamespaceDefault(source: string): boolean { return /\bvar\s+([A-Za-z_$][\w$]*)\s*=\s*\(?\s*\1\s*(\|\||\?\?)/.test(source); @@ -42,6 +55,7 @@ export function rewriteNamespaceDefaults(sf: ts.SourceFile): string | null { continue; const left = unparen(init.left); if (!ts.isIdentifier(left) || left.text !== name) continue; + if (COMMONJS_WRAPPER_NAMES.has(name)) continue; if (mentionedBefore(stmts, i, name) || writtenElsewhere(sf, name, decl)) continue; const right = init.right; edits.push({ @@ -82,7 +96,7 @@ function writtenElsewhere(sf: ts.SourceFile, name: string, decl: ts.VariableDecl if (written) return "stop"; if (n === decl.name) return undefined; if (ts.isBinaryExpression(n) && isAssignmentOperator(n.operatorToken.kind)) { - if (isName(n.left, name)) written = true; + if (assignsName(n.left, name)) written = true; } else if ( (ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) && (n.operator === ts.SyntaxKind.PlusPlusToken || @@ -108,7 +122,7 @@ function writtenElsewhere(sf: ts.SourceFile, name: string, decl: ts.VariableDecl } else if ( (ts.isForInStatement(n) || ts.isForOfStatement(n)) && !ts.isVariableDeclarationList(n.initializer) && - isName(n.initializer, name) + assignsName(n.initializer, name) ) { written = true; } @@ -128,6 +142,15 @@ function bindsName(binding: ts.Node, name: string): boolean { return found; } +/** Whether an assignment target may write `name`: the name itself, or a + * destructuring pattern that mentions it anywhere (over-approximated: a + * property key of that name counts too). */ +function assignsName(target: ts.Node, name: string): boolean { + const t = unparen(target as ts.Expression); + if (ts.isArrayLiteralExpression(t) || ts.isObjectLiteralExpression(t)) return bindsName(t, name); + return isName(t, name); +} + function isName(node: ts.Node, name: string): boolean { const n = unparen(node as ts.Expression); return ts.isIdentifier(n) && n.text === name; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/var-export-redeclared/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/var-export-redeclared/main.ts.json new file mode 100644 index 000000000..ccc1a3653 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/var-export-redeclared/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/var-export-redeclared/main.ts" + ], + "diags": [] +} diff --git a/tests/fixtures/npm/cases/var-export-redeclared/main.ts b/tests/fixtures/npm/cases/var-export-redeclared/main.ts new file mode 100644 index 000000000..23f014d29 --- /dev/null +++ b/tests/fixtures/npm/cases/var-export-redeclared/main.ts @@ -0,0 +1,3 @@ +// An importer sees the value module.exports holds, not the redeclared binding. +import { ns } from "redeclared-ns"; +console.log(ns.version); diff --git a/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.d.ts b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.d.ts new file mode 100644 index 000000000..1db3d1444 --- /dev/null +++ b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.d.ts @@ -0,0 +1 @@ +export declare const ns: { version: number }; diff --git a/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.js b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.js new file mode 100644 index 000000000..45f29a690 --- /dev/null +++ b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/index.js @@ -0,0 +1,5 @@ +// A namespace exported by reference, then redeclared with a new value: +// Node copies the first object into module.exports. +var ns = { version: 1 }; +module.exports = { ns }; +var ns = { version: 2 }; diff --git a/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/package.json b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/package.json new file mode 100644 index 000000000..c18908fd8 --- /dev/null +++ b/tests/fixtures/npm/cases/var-export-redeclared/node_modules/redeclared-ns/package.json @@ -0,0 +1 @@ +{ "name": "redeclared-ns", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/var-export-redeclared/package.json b/tests/fixtures/npm/cases/var-export-redeclared/package.json new file mode 100644 index 000000000..023d0b3d9 --- /dev/null +++ b/tests/fixtures/npm/cases/var-export-redeclared/package.json @@ -0,0 +1 @@ +{ "name": "var-export-redeclared-case", "private": true, "type": "module" } diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 27b27b8e3..203e75a41 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -104,6 +104,8 @@ const KNOWN: Record = { "self-name": { build: "SC2013" }, "stream-shims": { run: "SC2020" }, "util-shims": { run: "SC2020" }, + // The package redeclares the var it exported: Node copies the first value. + "var-export-redeclared": { run: "SC1090" }, "web-streams": { run: "SC1090" }, "workspace-copied": { build: "SC2013" }, "workspace-unclean": { build: "SC2013" }, From 6b27ffbf08660c5b4cc1b8b69bea657664b9a436 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:25:03 -0500 Subject: [PATCH 187/395] Fetch only the syntax trees lowering reads --- .../compiler/src/frontend/lowering/argument-counts.ts | 3 +-- .../compiler/src/frontend/lowering/js-collection-types.ts | 4 ++-- packages/compiler/src/frontend/lowering/lower-classes.ts | 4 ++-- packages/compiler/src/frontend/lowering/lower-server.ts | 8 ++++---- packages/compiler/src/frontend/lowering/lowerer.ts | 4 +--- packages/compiler/src/frontend/lowering/symbol-fields.ts | 4 ++-- packages/compiler/src/frontend/program.ts | 5 ++--- packages/compiler/src/frontend/shape-inference/analyze.ts | 3 +-- 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/argument-counts.ts b/packages/compiler/src/frontend/lowering/argument-counts.ts index ec1753160..d79e7b43f 100644 --- a/packages/compiler/src/frontend/lowering/argument-counts.ts +++ b/packages/compiler/src/frontend/lowering/argument-counts.ts @@ -193,8 +193,7 @@ export function usedAsValue( const symbol = symbolAt(name); if (!symbol) return false; let found = false; - for (const sf of lowerer.program.getSourceFiles()) { - if (sf.isDeclarationFile) continue; + for (const sf of lowerer.program.getImplementationSourceFiles()) { ts.walkPreorder(sf, (n) => { if (found) return "stop"; if (ts.isTypeNode(n)) return "skip"; diff --git a/packages/compiler/src/frontend/lowering/js-collection-types.ts b/packages/compiler/src/frontend/lowering/js-collection-types.ts index 2be4e0dbd..1e3219276 100644 --- a/packages/compiler/src/frontend/lowering/js-collection-types.ts +++ b/packages/compiler/src/frontend/lowering/js-collection-types.ts @@ -91,8 +91,8 @@ function inferProgram(lowerer: Lowerer): Map { const out = new Map(); const carriers = carrierTypes(lowerer); if (carriers === null) return out; - for (const file of lowerer.program.getSourceFiles()) { - if (file.isDeclarationFile || !isJsSourceFile(file)) continue; + for (const file of lowerer.program.getImplementationSourceFiles()) { + if (!isJsSourceFile(file)) continue; for (const candidate of inferFile(lowerer, file)) { const name = candidate.kind === "map" diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index bb1f1714f..5f1f6a03d 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -7019,8 +7019,8 @@ function capturesEnclosingScope( * reflection: the member a runtime lookup finds can then differ. */ function memberAssigned(lowerer: Lowerer, member: ts.MethodDeclaration, name: string): boolean { let found = false; - for (const sf of lowerer.program.getSourceFiles()) { - if (sf.isDeclarationFile || found) continue; + for (const sf of lowerer.program.getImplementationSourceFiles()) { + if (found) continue; if (!sf.text.includes(name)) continue; ts.walkPreorder(sf, (node) => { if (found) return "skip"; diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 4121d13b4..7359b56a8 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -2317,8 +2317,8 @@ function lowerNetServerMethodCall( function socketEncodingReachable(lowerer: Lowerer): boolean { if (lowerer.socketEncodingReachable !== null) return lowerer.socketEncodingReachable; let reachable = false; - for (const file of lowerer.program.getSourceFiles()) { - if (file.isDeclarationFile || !file.text.includes("setEncoding")) continue; + for (const file of lowerer.program.getImplementationSourceFiles()) { + if (!file.text.includes("setEncoding")) continue; ts.walkPreorder(file, (node) => { if (reachable) return "skip"; if (!ts.isCallExpression(node)) return undefined; @@ -2349,8 +2349,8 @@ function socketEncodingReachable(lowerer: Lowerer): boolean { function unixListenReachable(lowerer: Lowerer): boolean { if (lowerer.unixListenReachable !== null) return lowerer.unixListenReachable; let reachable = false; - for (const file of lowerer.program.getSourceFiles()) { - if (file.isDeclarationFile || !file.text.includes("listen")) continue; + for (const file of lowerer.program.getImplementationSourceFiles()) { + if (!file.text.includes("listen")) continue; ts.walkPreorder(file, (node) => { if (reachable) return "skip"; if (!ts.isCallExpression(node) || node.arguments.nodes.length === 0) return undefined; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 15c0ef993..6b54b17bf 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3743,9 +3743,7 @@ export class Lowerer { * that comes back through a boundary cast keeps its identity. */ private declBoundaryMemo: boolean | null = null; get declBoundary(): boolean { - this.declBoundaryMemo ??= this.program - .getSourceFiles() - .some((sf) => isDeclBoundaryFile(sf.fileName)); + this.declBoundaryMemo ??= this.program.getSourceFileNames().some(isDeclBoundaryFile); return this.declBoundaryMemo; } /** Tracked classes' bag initializers over a typed `this` diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index cdfb9e1b8..958af5963 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -394,9 +394,9 @@ function tablePropertyWritten( } return false; }; - for (const sf of lowerer.program.getSourceFiles()) { + for (const sf of lowerer.program.getImplementationSourceFiles()) { if (written) break; - if (sf.isDeclarationFile || !sf.text.includes(name)) continue; + if (!sf.text.includes(name)) continue; ts.walkPreorder(sf, (node) => { if (written) return "stop"; let target: ts.Node | undefined; diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index aa6ddbfa7..00afcd696 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1291,10 +1291,9 @@ export function loadProgram( * packages whose declarations the boundary projects from. */ function declBoundaryUserFiles(program: ts.Program, packages: readonly string[]): string[] { const out: string[] = []; - for (const sf of program.getSourceFiles()) { + for (const sf of program.getImplementationSourceFiles()) { const name = sf.fileName.split("\\").join("/"); - if (sf.isDeclarationFile || name.includes("/node_modules/") || !/\.[mc]?tsx?$/.test(name)) - continue; + if (name.includes("/node_modules/") || !/\.[mc]?tsx?$/.test(name)) continue; if (name.endsWith(".tsx")) continue; // JSX files: not projected yet const text = sf.text; if ( diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 19d3cac22..c403c6ba8 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -325,8 +325,7 @@ class Analyzer { } run(): ShapeAnalysis { - for (const sf of this.program.getSourceFiles()) { - if (sf.isDeclarationFile) continue; + for (const sf of this.program.getImplementationSourceFiles()) { const name = sf.fileName.split("\\").join("/"); const inPackage = isPackageJsFile(sf); if (!inPackage && name.includes("/node_modules/")) continue; From db2cb5c99e767d1f3f3841965649f16a73a7efd8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:35:28 -0500 Subject: [PATCH 188/395] Split readline input in one scan and copy ASCII strings into buffers whole readline split redirected input byte by byte, twice per chunk (once to find whether the chunk ends a line, once to split it). Before splitting it copied each chunk out of its Buffer and into a scratch buffer, decoded it into a new string, and copied the unfinished line and the whole chunk into a joined buffer. Now: - one scan per chunk: memchr for a CR once, then LF to LF with memchr when the chunk has none, and the LF, CR and CRLF split otherwise; - the unfinished line joins only the first line of the chunk; - an ASCII chunk with no partial UTF-8 sequence held from the previous one is split where it is, and a chunk that is not shared memory is read from its own storage. Lines are still copied into their strings before the first is delivered. Buffer.from(string, 'latin1' | 'ascii') encoded character by character; an ASCII prefix, all of an ASCII string, is now copied with memcpy. Line events, their order and the CRLF delay are unchanged. Corpus 8344 covers lines joined across chunks, a line longer than a chunk, empty lines, mixed line endings and UTF-8 split across chunks. The new line-reader workload reads a 67 MB log through readline over a file stream. --- benchmarks/runtime/README.md | 1 + benchmarks/runtime/workloads/line-reader.json | 7 ++ benchmarks/runtime/workloads/line-reader.ts | 34 +++++++++ .../8344-readline-chunk-splitting.ts.json | 6 ++ packages/runtime/src/scr_bytes.c | 4 +- packages/runtime/src/scr_readline.c | 71 +++++++++++-------- scripts/bench-runtime.mjs | 10 +++ tests/corpus/8344-readline-chunk-splitting.ts | 41 +++++++++++ 8 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 benchmarks/runtime/workloads/line-reader.json create mode 100644 benchmarks/runtime/workloads/line-reader.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8344-readline-chunk-splitting.ts.json create mode 100644 tests/corpus/8344-readline-chunk-splitting.ts diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 83cfcb831..520964cf3 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -20,6 +20,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `json-records` | cpu | `JSON.stringify`, typed `JSON.parse`, Map aggregation | | `json-unions` | cpu | `JSON.parse` into discriminated unions: an event log with eight kinds and nested expression trees with nine, evaluated after the cast | | `keyed-option-writes` | cpu | class methods whose names are also keys of option records written through element accesses: `enabled[name] = true` over a union of literal names and `totals["count"] = n` | +| `line-reader` | cpu | `readline` over a file stream: 'line' events for a 67 MB access log (every 50th line not ASCII), with a status count, a path tally and byte totals | | `log-lines` | cpu | 200k immediate `console.log`/`process.stdout.write` lines into the runner's pipe | | `log-summary`, `inventory-report` | cpu | the multi-module applications from `benchmarks/builds`, on 300k-line inputs | | `number-maps` | cpu | number-keyed Map/Set: a dense-id table, sparse ids in many small maps, get/has/set mixes | diff --git a/benchmarks/runtime/workloads/line-reader.json b/benchmarks/runtime/workloads/line-reader.json new file mode 100644 index 000000000..85194f70c --- /dev/null +++ b/benchmarks/runtime/workloads/line-reader.json @@ -0,0 +1,7 @@ +{ + "entry": "workloads/line-reader.ts", + "input": "line-reader", + "args": [ + "{input}" + ] +} diff --git a/benchmarks/runtime/workloads/line-reader.ts b/benchmarks/runtime/workloads/line-reader.ts new file mode 100644 index 000000000..cbdf384bb --- /dev/null +++ b/benchmarks/runtime/workloads/line-reader.ts @@ -0,0 +1,34 @@ +// Line-by-line processing of a log file with readline over a file stream: +// 'line' events for about 67 MB of access-log lines, with light work per +// line (a status count, a path tally, byte totals). +import { createReadStream } from "node:fs"; +import { createInterface } from "node:readline"; + +const path = process.argv[2]!; +const statuses = new Map(); +const paths = new Map(); +let lines = 0; +let chars = 0; +let bytes = 0; +let longest = 0; + +const input = createInterface({ input: createReadStream(path), crlfDelay: Infinity }); +input.on("line", (line: string) => { + lines++; + chars += line.length; + if (line.length > longest) longest = line.length; + const quote = line.indexOf('"'); + const space = line.indexOf(" ", quote); + const path = line.slice(space + 1, line.indexOf("/", line.indexOf("/", space) + 1)); + paths.set(path, (paths.get(path) ?? 0) + 1); + const close = line.indexOf('"', space); + const status = line.slice(close + 2, close + 5); + statuses.set(status, (statuses.get(status) ?? 0) + 1); + bytes += Number(line.slice(close + 6, line.indexOf(" ", close + 6))); +}); +input.on("close", () => { + console.log(`${lines} lines, ${chars} characters, longest ${longest}`); + console.log(`bytes ${bytes}`); + for (const status of [...statuses.keys()].sort()) console.log(`status ${status}: ${statuses.get(status)}`); + for (const prefix of [...paths.keys()].sort()) console.log(`path ${prefix}: ${paths.get(prefix)}`); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8344-readline-chunk-splitting.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8344-readline-chunk-splitting.ts.json new file mode 100644 index 000000000..8c01e6b47 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8344-readline-chunk-splitting.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8344-readline-chunk-splitting.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index e6df837c8..6b8594c08 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2722,7 +2722,9 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const /* Node writes charCodeAt(i) & 0xFF for BOTH spellings — an astral * code point contributes its two surrogates' low bytes. */ ScrBytes *b = scr_bytes_conversion_storage_in(sink, n); - size_t o = 0, i = 0; + /* An ASCII prefix (all of an ASCII string) is its own encoding. */ + size_t o = s->ascii ? n : scr_bytes_ascii_prefix(in, n), i = o; + memcpy(b->data, in, o); while (i < n) { uint32_t cp = scr_bytes_next_cp(in, &i); if (cp > 0xffff) { diff --git a/packages/runtime/src/scr_readline.c b/packages/runtime/src/scr_readline.c index 1bc48bbe7..178d620c6 100644 --- a/packages/runtime/src/scr_readline.c +++ b/packages/runtime/src/scr_readline.c @@ -283,7 +283,23 @@ static void scr_rl_buf_append(ScrRl *rl, const char *p, size_t n) { rl->line_len += n; } -/* kNormalWrite over decoded UTF-8 text. */ +/* The end of the line starting at `i` in text without a CR: the next LF, + * or n when the text ends first. */ +static size_t scr_rl_next_lf(const char *p, size_t i, size_t n) { + const char *lf = memchr(p + i, '\n', n - i); + return lf ? (size_t)(lf - p) : n; +} + +/* The end of the line starting at `i`: the next LF or CR, or n. */ +static size_t scr_rl_next_break(const char *p, size_t i, size_t n) { + while (i < n && p[i] != '\n' && p[i] != '\r') i++; + return i; +} + +/* kNormalWrite over decoded UTF-8 text. The text is scanned once: LF-only + * text (no CR anywhere in it) moves from LF to LF with memchr, other text + * splits at LF, CR and CRLF. The unfinished line before the text only + * joins the first line, so the rest of the text is never copied whole. */ static void scr_rl_feed_text(ScrRl *rl, const char *p, size_t n) { if (rl->tty) { scr_rl_tty_ops->feed(rl, p, n); @@ -296,43 +312,31 @@ static void scr_rl_feed_text(ScrRl *rl, const char *p, size_t n) { } rl->saw_return_at = 0; } - bool ending = false; - for (size_t i = 0; i < n && !ending; i++) ending = p[i] == '\n' || p[i] == '\r'; - if (!ending) { + bool cr = n > 0 && memchr(p, '\r', n) != NULL; + size_t end = n == 0 ? 0 : cr ? scr_rl_next_break(p, 0, n) : scr_rl_next_lf(p, 0, n); + if (end == n) { if (n > 0) scr_rl_buf_append(rl, p, n); return; } - /* The unfinished line joins the new text; the scan runs over both. */ - char *text = p ? (char *)p : (char *)""; - size_t len = n; - char *joined = NULL; - if (rl->line_len > 0) { - joined = malloc(rl->line_len + n); - if (!joined) scr_rl_oom(); - memcpy(joined, rl->line_buf, rl->line_len); - memcpy(joined + rl->line_len, p, n); - text = joined; - len = rl->line_len + n; - } - rl->line_len = 0; - rl->saw_return_at = len > 0 && text[len - 1] == '\r' ? scr_now_ms() : 0; + rl->saw_return_at = p[n - 1] == '\r' ? scr_now_ms() : 0; /* Split first (the remainder becomes the new unfinished line before any * line is delivered, as in Node), then deliver in order. */ size_t n_lines = 0, cap_lines = 0; ScrStr **lines = NULL; size_t start = 0; - for (size_t i = 0; i < len; i++) { - size_t adv; - if (text[i] == '\n') adv = 1; - else if (text[i] == '\r') adv = i + 1 < len && text[i + 1] == '\n' ? 2 : 1; - else continue; + while (end < n) { lines = scr_rl_grow(lines, &cap_lines, n_lines + 1, sizeof *lines); - lines[n_lines++] = scr_str_new(text + start, i - start); - i += adv - 1; - start = i + 1; + if (n_lines == 0 && rl->line_len > 0) { + scr_rl_buf_append(rl, p, end); + lines[n_lines++] = scr_str_new(rl->line_buf, rl->line_len); + } else { + lines[n_lines++] = scr_str_new(p + start, end - start); + } + start = end + (p[end] == '\r' && end + 1 < n && p[end + 1] == '\n' ? 2 : 1); + end = start >= n ? n : cr ? scr_rl_next_break(p, start, n) : scr_rl_next_lf(p, start, n); } - if (start < len) scr_rl_buf_append(rl, text + start, len - start); - free(joined); + rl->line_len = 0; + if (start < n) scr_rl_buf_append(rl, p + start, n - start); for (size_t i = 0; i < n_lines; i++) { if (rl->closed || scr_exc_pending()) { scr_str_release(lines[i]); @@ -366,6 +370,11 @@ static void scr_rl_feed_bytes(ScrRl *rl, const char *p, size_t n) { rl->parked_len += n; return; } + /* ASCII with no held bytes decodes to itself: split it in place. */ + if (rl->n_held == 0 && scr_bytes_ascii_span(p, n) == n) { + scr_rl_feed_text(rl, p, n); + return; + } size_t total = rl->n_held + n; unsigned char *all = malloc(total ? total : 1); if (!all) scr_rl_oom(); @@ -401,6 +410,12 @@ static void scr_rl_feed_end(ScrRl *rl) { void scr_rl_input_bytes(double id, ScrBytes *chunk) { ScrRl *rl = scr_rl_find(id); if (!rl || rl->closed) return; + /* The splitter copies every line it keeps before it delivers one, so it + * can read a chunk's own storage; shared memory is copied out first. */ + if (!chunk->shared) { + scr_rl_feed_bytes(rl, (const char *)chunk->data, chunk->len); + return; + } char *p = malloc(chunk->len ? chunk->len : 1); if (!p) scr_rl_oom(); scr_bytes_read(chunk, 0, p, chunk->len); diff --git a/scripts/bench-runtime.mjs b/scripts/bench-runtime.mjs index b45e3ebdf..d89a4e759 100644 --- a/scripts/bench-runtime.mjs +++ b/scripts/bench-runtime.mjs @@ -180,6 +180,16 @@ function generateInput(kind) { ["sku-" + i, "category-" + (i % 17), "product " + i, i % 40, 199 + (i % 5000)].join("\t"), ).join("\n") + "\n", ); + } else if (kind === "line-reader") { + // About 67 MB of access-log lines; every 50th path is not ASCII. + writeFileSync( + path, + Array.from( + { length: 600_000 }, + (_, i) => + `10.${i % 251}.${i % 7}.${i % 13} - - [24/Oct/2026:${String(i % 24).padStart(2, "0")}:${String(i % 60).padStart(2, "0")}:00 +0000] "${i % 9 === 0 ? "POST" : "GET"} /${i % 50 === 0 ? "caf\u00e9" : "api"}/items/${i % 997}?page=${i % 31} HTTP/1.1" ${i % 23 === 0 ? 404 : 200} ${(i * 7919) % 50000} "scriptc-bench/1.0"`, + ).join("\n") + "\n", + ); } else if (kind === "cli-config") { writeFileSync(path, JSON.stringify(deployManifest(), null, 2) + "\n"); } else throw new Error(`unknown input kind ${kind}`); diff --git a/tests/corpus/8344-readline-chunk-splitting.ts b/tests/corpus/8344-readline-chunk-splitting.ts new file mode 100644 index 000000000..85eff8cdc --- /dev/null +++ b/tests/corpus/8344-readline-chunk-splitting.ts @@ -0,0 +1,41 @@ +// Line splitting over stream chunks: an unfinished line joined across many +// chunks, a line longer than a chunk, empty lines, LF-only +// chunks next to chunks with CR and CRLF, an ASCII chunk after a chunk that +// ends inside a UTF-8 sequence, and text written with rl.write(). Each line +// event shows the line's length and its first and last characters. +import { createInterface } from "node:readline"; +import { Readable } from "node:stream"; + +function show(tag: string, line: string): void { + console.log(tag, line.length, JSON.stringify(line.slice(0, 12)), JSON.stringify(line.slice(-6))); +} + +function feed(tag: string, chunks: (string | Buffer)[]): Promise { + return new Promise((resolve) => { + const src = new Readable({ read() {} }); + const rl = createInterface({ input: src, crlfDelay: Infinity }); + rl.on("line", (line: string) => show(tag, line)); + rl.on("close", () => { + console.log(tag, "closed"); + resolve(); + }); + for (const chunk of chunks) src.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk); + src.push(null); + }); +} + +const long = "x".repeat(70000) + "y"; +await feed("joined", ["par", "tial ", "line", " continues", "\nnext\n", "tail"]); +await feed("long", [long.slice(0, 30000), long.slice(30000, 65536), long.slice(65536) + "\nshort\n"]); +await feed("empty", ["\n\n", "a\n\n", "\n", "b"]); +await feed("mixed", ["lf only\nstill lf\n", "cr\rcrlf\r\nlf\n", "end\r", "\nafter\r", "\r\n", "done"]); +const euro = Buffer.from("€uro\n", "utf8"); +await feed("utf8", [euro.subarray(0, 2), Buffer.concat([euro.subarray(2), Buffer.from("plain ascii\nmore")])]); +await feed("utf8-lines", ["café one\nnaïve two\n", "ascii three\n", "中文 four"]); + +const writer = createInterface({ input: new Readable({ read() {} }), crlfDelay: Infinity }); +writer.on("line", (line: string) => show("write", line)); +writer.write("first part "); +writer.write("second part\nthird\r\nfourth\rfifth"); +writer.write("\n"); +writer.close(); From 3ef34770bd7a652169db35bc0d21b8934c43feef Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:41:31 -0500 Subject: [PATCH 189/395] Read optional elements of bindings that hold undefined as undefined `a?.[i]` on a binding whose type admits undefined, such as a `string[] | undefined` parameter, threw "undefined is not representable in the target union" when the binding held undefined. Element access replaced an identifier receiver whose storage has an undefined arm with a checked narrow to its present arm before the optional chain ran, so the chain saw a receiver that is never nullish and lowered `?.[` as a plain element read. Element access now leaves the receiver to the optional chain when the access carries `?.`; the chain tests the undefined arm and lowers the access again inside its guard. This applies to arrays, tuples and record keys. Property access (`a?.length`, `a?.at(0)`) already lowered through the chain. Corpus 8330 matches Node's output. --- .../compiler/src/frontend/lowering/lowerer.ts | 4 + ...tional-element-undefined-receivers.ts.json | 6 ++ ...30-optional-element-undefined-receivers.ts | 75 +++++++++++++++++++ ...tional-element-undefined-receivers.ts.json | 1 + 4 files changed, 86 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8330-optional-element-undefined-receivers.ts.json create mode 100644 tests/corpus/8330-optional-element-undefined-receivers.ts create mode 100644 tests/harness/corpus-costs/8330-optional-element-undefined-receivers.ts.json diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 15c0ef993..9065e691e 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -13145,6 +13145,10 @@ export class Lowerer { ) { return lowerElementAccess(this, expr); } + // `a?.[i]`: the optional chain tests the receiver's undefined arm and + // lowers this access again inside its guard. + if (expr.questionDotToken && !this.chainHandled.has(expr)) + return lowerElementAccess(this, expr); const receiver = this.runtimeOptionalIdentifierValue(expr.expression); const receiverValue: IrExpr | null = receiver && (receiver.present.kind === "array" || receiver.present.kind === "record") diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8330-optional-element-undefined-receivers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8330-optional-element-undefined-receivers.ts.json new file mode 100644 index 000000000..ef78d4391 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8330-optional-element-undefined-receivers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8330-optional-element-undefined-receivers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8330-optional-element-undefined-receivers.ts b/tests/corpus/8330-optional-element-undefined-receivers.ts new file mode 100644 index 000000000..b9433e66c --- /dev/null +++ b/tests/corpus/8330-optional-element-undefined-receivers.ts @@ -0,0 +1,75 @@ +// Optional element access on bindings whose type admits undefined: an +// undefined receiver short-circuits to undefined without evaluating the +// key, and a present receiver reads the element as a plain access does. + +function first(kinds: string[] | undefined, i: number): string { + const kind = kinds?.[i]; + return kind === undefined ? "none" : String(kind); +} +console.log(first(undefined, 0)); +console.log(first(["a", "b"], 1)); +console.log(first(["a"], 5)); + +function orDefault(values: number[] | undefined, i: number): number { + return values?.[i] ?? -1; +} +console.log(orDefault(undefined, 0), orDefault([4, 5], 1), orDefault([4], 3)); + +function isMissing(flags: boolean[] | undefined, i: number): boolean { + return flags?.[i] === undefined; +} +console.log(isMissing(undefined, 0), isMissing([true], 0), isMissing([false], 2)); + +function head(pair: [string, number] | undefined): string { + return pair?.[0] ?? "empty"; +} +console.log(head(undefined), head(["x", 1])); + +function lookup(table: Record | undefined, key: string): number | undefined { + return table?.[key]; +} +console.log(lookup(undefined, "a"), lookup({ a: 1 }, "a"), lookup({ a: 1 }, "b")); + +function width(words: string[] | undefined, i: number): number { + return words?.[i]?.length ?? 0; +} +console.log(width(undefined, 0), width(["abc"], 0), width(["abc"], 1)); + +// The key is not evaluated when the receiver is undefined. +let keys = 0; +function nextKey(): number { + keys++; + return 0; +} +function counted(items: string[] | undefined): string | undefined { + return items?.[nextKey()]; +} +console.log(counted(undefined), keys); +console.log(counted(["k"]), keys); + +// Local bindings, including one reassigned between reads. +function maybe(present: boolean): number[] | undefined { + return present ? [7, 8] : undefined; +} +let current = maybe(false); +console.log(current?.[0]); +current = maybe(true); +console.log(current?.[1]); +current = maybe(false); +console.log(current?.[1]); + +function local(present: boolean): string { + const items: string[] | undefined = present ? ["p", "q"] : undefined; + const second = items?.[1]; + return second ?? "absent"; +} +console.log(local(true), local(false)); + +// A sparse array reads its holes as undefined. +const sparse: number[] = [1]; +sparse[3] = 4; +function at(values: number[] | undefined, i: number): string { + const value = values?.[i]; + return value === undefined ? "hole" : String(value); +} +console.log(at(sparse, 1), at(sparse, 3), at(undefined, 3)); diff --git a/tests/harness/corpus-costs/8330-optional-element-undefined-receivers.ts.json b/tests/harness/corpus-costs/8330-optional-element-undefined-receivers.ts.json new file mode 100644 index 000000000..662eb397f --- /dev/null +++ b/tests/harness/corpus-costs/8330-optional-element-undefined-receivers.ts.json @@ -0,0 +1 @@ +{ "plain": 2108 } From 8b0921243f87dd17f096eb030c52648b35a49c96 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 01:59:32 -0500 Subject: [PATCH 190/395] Compile large sanitized dev programs in shards --- packages/compiler/src/backend/library-pack.ts | 2 +- .../compiler/src/backend/llvm/split.test.ts | 132 ++++++- packages/compiler/src/backend/llvm/split.ts | 335 ++++++++++++++---- .../src/backend/native-toolchain.test.ts | 22 +- .../compiler/src/backend/native/contracts.ts | 4 +- .../compiler/src/backend/native/executable.ts | 18 +- .../compiler/src/backend/native/library.ts | 4 +- .../src/backend/native/program-shards.ts | 6 +- packages/compiler/src/index.ts | 11 +- packages/compiler/src/native/compiler.ts | 2 +- tests/harness/three.test.ts | 6 +- 11 files changed, 431 insertions(+), 111 deletions(-) diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index e7fa7a782..521f1d53e 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -153,7 +153,7 @@ export async function compilePackedLibrary( ...(await Promise.all( shards .slice(start, start + 4) - .map((shard, offset) => emit(shard.source, `shard-${start + offset}.o`)), + .map((shard, offset) => emit(shard.source(), `shard-${start + offset}.o`)), )), ); } diff --git a/packages/compiler/src/backend/llvm/split.test.ts b/packages/compiler/src/backend/llvm/split.test.ts index a49a11545..2e0cfa5a5 100644 --- a/packages/compiler/src/backend/llvm/split.test.ts +++ b/packages/compiler/src/backend/llvm/split.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, expect, test } from "vitest"; -import { splitLlvmLibraryProgram, splitLlvmProgram } from "./split.js"; +import { splitLlvmLibraryProgram, splitLlvmProgram, splitLlvmProgramFile } from "./split.js"; import { LlvmDebugInfo } from "./debug-info.js"; import { VOID } from "../../ir/ir.js"; @@ -61,17 +61,17 @@ test("splits generated LLVM functions and promotes only private cross-shard defi expect(actual!.promotedSymbols).not.toContain("public_entry"); expect(actual!.publicSymbols).toEqual(["public_value", "public_entry"]); expect(actual!.shards[0]!.name).toBe("program-globals.ll"); - expect(actual!.shards[0]!.source).toContain("@hidden_value = hidden global i64 7"); + expect(actual!.shards[0]!.source()).toContain("@hidden_value = hidden global i64 7"); expect( actual!.shards .slice(1) - .every((shard) => shard.source.includes("@hidden_value = external hidden global i64")), + .every((shard) => shard.source().includes("@hidden_value = external hidden global i64")), ).toBe(true); expect( actual!.shards.some( (shard) => - !shard.source.includes("define hidden i64 @right() #0") && - shard.source.includes("declare hidden i64 @right() #0"), + !shard.source().includes("define hidden i64 @right() #0") && + shard.source().includes("declare hidden i64 @right() #0"), ), ).toBe(true); }); @@ -82,9 +82,115 @@ test("thread-local definitions and declarations preserve their storage model acr "@hidden_value = internal thread_local global i64 7", ); const split = splitLlvmProgram(tls, { minimumBytes: 0, targetBytes: 64 * 1024 })!; - expect(split.shards[0]!.source).toContain("@hidden_value = hidden thread_local global i64 7"); + expect(split.shards[0]!.source()).toContain("@hidden_value = hidden thread_local global i64 7"); for (const shard of split.shards.slice(1)) { - expect(shard.source).toContain("@hidden_value = external hidden thread_local global i64"); + expect(shard.source()).toContain("@hidden_value = external hidden thread_local global i64"); + } +}); + +const PADDING = Array.from( + { length: 80 }, + (_, i) => + `define internal i64 @pad_${i}() #0 {\nentry:\n ; ${"x".repeat(2048)}\n ret i64 ${i}\n}\n`, +).join("\n"); + +/** SAMPLE with more globals after @public_value and enough functions to + * split into several shards. */ +function withGlobals(globals: string, tail = ""): string { + return `${SAMPLE.replace( + "@public_value = constant i64 11", + `@public_value = constant i64 11\n${globals}`, + ).replace("define i64 @public_entry", `${PADDING}\ndefine i64 @public_entry`)}${tail}`; +} + +/** Writes each shard and compiles it with clang. */ +async function compileShards( + shards: readonly { name: string; source: () => string }[], +): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-llvm-split-shards-")); + scratch.push(dir); + for (const shard of shards) { + const source = join(dir, shard.name); + await writeFile(source, shard.source()); + execFileSync("clang", ["-Wno-override-module", "-c", source, "-o", `${source}.o`]); + } +} + +test("private unnamed_addr lookup tables are promoted like internal globals", async () => { + const split = splitLlvmProgram( + withGlobals('@table = private unnamed_addr constant [2 x i8] c"\\01\\02"'), + { minimumBytes: 0, targetBytes: 64 * 1024 }, + )!; + expect(split).not.toBeNull(); + const [globals, ...functions] = split.shards; + expect(globals!.source()).toContain('@table = hidden unnamed_addr constant [2 x i8] c"\\01\\02"'); + for (const shard of functions) + expect(shard.source()).toContain("@table = external hidden constant [2 x i8]"); + expect(split.promotedSymbols).toContain("table"); + await compileShards(split.shards); +}); + +test("aggregate initializers over several lines stay whole in the globals shard", async () => { + // The regex global's comment holds unbalanced brackets. + const split = splitLlvmProgram( + withGlobals(`@regex = internal global { ptr, i64 } { ptr @left, i64 0 } ; /(a|[b/ +@vt = internal constant [2 x { i64, ptr }] [ + { i64, ptr } { i64 1, ptr @left }, + { i64, ptr } { i64 2, ptr @right } +]`), + { minimumBytes: 0, targetBytes: 64 * 1024 }, + )!; + expect(split).not.toBeNull(); + const [globals, ...functions] = split.shards; + expect(globals!.source()).toContain( + "@vt = hidden constant [2 x { i64, ptr }] [\n { i64, ptr } { i64 1, ptr @left },\n { i64, ptr } { i64 2, ptr @right }\n]", + ); + for (const shard of functions) { + expect(shard.source()).toContain("@vt = external hidden constant [2 x { i64, ptr }]"); + expect(shard.source()).not.toContain("{ i64, ptr } { i64 1"); + } + expect(split.promotedSymbols).toEqual(expect.arrayContaining(["regex", "vt"])); + await compileShards(split.shards); +}); + +test("globals after the attributes and metadata join the globals shard", async () => { + const split = splitLlvmProgram(withGlobals("", "@flag = internal constant i1 true\n"), { + minimumBytes: 0, + targetBytes: 64 * 1024, + })!; + expect(split).not.toBeNull(); + const [globals, ...functions] = split.shards; + expect(globals!.source()).toContain("@flag = hidden constant i1 true"); + for (const shard of functions) + expect(shard.source()).toContain("@flag = external hidden constant i1"); + await compileShards(split.shards); +}); + +test("a module split from its file has the shards of the same module split in memory", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-llvm-split-file-")); + scratch.push(dir); + const options = { minimumBytes: 0, targetBytes: 64 * 1024 }; + const storage = withGlobals( + `@table = private unnamed_addr constant [2 x i8] c"\\01\\02" +@vt = internal constant [2 x { i64, ptr }] [ + { i64, ptr } { i64 1, ptr @left }, + { i64, ptr } { i64 2, ptr @right } +]`, + "@flag = internal constant i1 true\n", + ); + for (const [name, source] of [ + ["sample.ll", withGlobals("")], + ["storage.ll", storage], + ] as const) { + const path = join(dir, name); + await writeFile(path, source); + const fromFile = splitLlvmProgramFile(path, options)!; + expect(fromFile).not.toBeNull(); + const materialize = (split: NonNullable) => ({ + ...split, + shards: split.shards.map((shard) => ({ name: shard.name, source: shard.source() })), + }); + expect(materialize(fromFile)).toEqual(materialize(splitLlvmProgram(source, options)!)); } }); @@ -127,7 +233,7 @@ entry: for (const shard of split.shards) { const path = join(dir, shard.name); const object = `${path}.o`; - await writeFile(path, shard.source); + await writeFile(path, shard.source()); execFileSync("clang", ["-Wno-override-module", "-O2", "-c", path, "-o", object]); objects.push(object); } @@ -169,10 +275,10 @@ test("shared metadata bounds shard growth while preserving every definition", () expect(split).not.toBeNull(); expect(split!.shards.length).toBeGreaterThan(1); expect( - split!.shards.reduce((bytes, shard) => bytes + Buffer.byteLength(shard.source), 0), + split!.shards.reduce((bytes, shard) => bytes + Buffer.byteLength(shard.source()), 0), ).toBeLessThanOrEqual(2 * Buffer.byteLength(source)); const definitions = split!.shards.flatMap((shard) => - [...shard.source.matchAll(/^define (?:hidden )?i64 @(\w+)\(/gm)].map((match) => match[1]), + [...shard.source().matchAll(/^define (?:hidden )?i64 @(\w+)\(/gm)].map((match) => match[1]), ); expect(definitions.sort()).toEqual([...names, "left", "right", "public_entry"].sort()); expect(split!.publicSymbols).toEqual(["public_value", "public_entry"]); @@ -207,9 +313,9 @@ test("debug metadata stays on definitions when splitting LLVM modules", async () const dir = await mkdtemp(join(tmpdir(), "scriptc-debug-split-")); scratch.push(dir); for (const shard of split!.shards) { - expect(shard.source).not.toMatch(/^declare.*!dbg/m); + expect(shard.source()).not.toMatch(/^declare.*!dbg/m); const path = join(dir, shard.name); - await writeFile(path, shard.source); + await writeFile(path, shard.source()); const result = spawnSync("clang", ["-Wno-override-module", "-c", path, "-o", `${path}.o`], { encoding: "utf8", }); @@ -250,7 +356,7 @@ test("every shard compiles and its merged object exposes only canonical public d for (const shard of split.shards) { const source = join(dir, shard.name); const object = `${source}.o`; - await writeFile(source, shard.source); + await writeFile(source, shard.source()); execFileSync("clang", ["-Wno-override-module", "-c", source, "-o", object]); objects.push(object); } diff --git a/packages/compiler/src/backend/llvm/split.ts b/packages/compiler/src/backend/llvm/split.ts index 3fb41ce6b..aecef8df8 100644 --- a/packages/compiler/src/backend/llvm/split.ts +++ b/packages/compiler/src/backend/llvm/split.ts @@ -19,10 +19,15 @@ * canonical TU normally — cache optimization must never become correctness. */ +import { closeSync, openSync, readSync, statSync } from "node:fs"; + export interface LlvmProgramShard { /** Stable source identity used in object cache keys and diagnostics. */ name: string; - source: string; + /** Assembles the shard's LLVM text. A module split from a file reads its + * function bodies back from there on each call, so only the shards being + * written are in memory. */ + source: () => string; } export interface LlvmProgramSplit { @@ -54,12 +59,20 @@ const LIBRARY_TARGET_BYTES = 768 * 1024; const LIBRARY_MINIMUM_BYTES = 2 * 1024 * 1024; interface FunctionDef { - source: string; + /** UTF-8 length of the definition's text. */ + bytes: number; + /** The definition's text: its header (promoted to hidden linkage when + * internal), body and closing brace. A definition left in a file reads + * its body through `read`. */ + text: (read: RangeReader) => string; declaration: string; symbol: string; promoted: boolean; } +/** Reads the text between two byte offsets of the module file. */ +type RangeReader = (start: number, end: number) => string; + interface GlobalDef { line: string; declaration: string | null; @@ -72,50 +85,68 @@ function symbolOf(spelling: string): string | null { return match?.[1] ?? null; } -function functionDefAt( - lines: readonly string[], - start: number, -): { def: FunctionDef; end: number } | null { - const header = lines[start]!; +/** A definition's header line: the line its shard compiles, the + * declaration every other shard carries, and its linkage. */ +function functionHeader( + header: string, +): { line: string; declaration: string; symbol: string; promoted: boolean } | null { const match = /^define\s+(internal\s+)?(.+?)\s+(@[-$._A-Za-z][-$.\w]*)\((.*)\)(.*)\{(?:\s*;.*)?$/.exec( header, ); if (!match) return null; - let end = start + 1; - while (end < lines.length && lines[end] !== "}") end++; - if (end >= lines.length) return null; const symbol = symbolOf(match[3]!); if (symbol === null) return null; const promoted = match[1] !== undefined; const linkage = promoted ? "hidden " : ""; - const definitionHeader = promoted - ? header.replace(/^define internal /, "define hidden ") - : header; - const source = [definitionHeader, ...lines.slice(start + 1, end + 1)].join("\n"); + const line = promoted ? header.replace(/^define internal /, "define hidden ") : header; // Parameter names are legal on declarations. Preserve the emitted text so // attributes, zeroext, and varargs stay byte-exact. // A definition's DISubprogram cannot be attached to a declaration in a // different shard. Its owning definition retains the original metadata. const attrs = match[5]!.replace(/\s+!dbg !\d+/g, ""); const declaration = `declare ${linkage}${match[2]} ${match[3]}(${match[4]})${attrs}`.trimEnd(); - return { def: { source, declaration, symbol, promoted }, end }; + return { line, declaration, symbol, promoted }; +} + +function functionDefAt( + lines: readonly string[], + start: number, +): { def: FunctionDef; end: number } | null { + const header = functionHeader(lines[start]!); + if (header === null) return null; + let end = start + 1; + while (end < lines.length && lines[end] !== "}") end++; + if (end >= lines.length) return null; + const source = [header.line, ...lines.slice(start + 1, end + 1)].join("\n"); + return { + def: { + bytes: Buffer.byteLength(source), + text: () => source, + declaration: header.declaration, + symbol: header.symbol, + promoted: header.promoted, + }, + end, + }; } function globalDef(line: string): GlobalDef | null { const match = - /^(@[-$._A-Za-z][-$.\w]*)\s+=\s+(internal\s+)?(thread_local(?:\([^)]*\))?\s+)?(global|constant)\s+(.+)$/.exec( + /^(@[-$._A-Za-z][-$.\w]*)\s+=\s+((?:internal|private)\s+)?(thread_local(?:\([^)]*\))?\s+)?((?:local_)?unnamed_addr\s+)?(global|constant)\s+([\s\S]+)$/.exec( line, ); if (!match) return null; const symbol = symbolOf(match[1]!); if (symbol === null) return null; + // Internal and private definitions (such as private unnamed_addr lookup + // tables) are local to their module: shards reach them as hidden ones. const promoted = match[2] !== undefined; const threadLocal = match[3] === undefined ? "" : `${match[3].trim()} `; // The declaration needs only the complete type. Emitted initializers begin // after it; the generated LLVM subset uses first-class scalars/pointers or // balanced aggregate types, so one small scanner is sufficient. - const rest = match[5]!; + const rest = match[6]!; let depth = 0; let quote = false; let typeEnd = -1; @@ -133,13 +164,30 @@ function globalDef(line: string): GlobalDef | null { if (typeEnd < 0) return null; const type = rest.slice(0, typeEnd); return { - line: promoted ? line.replace(`${match[1]} = internal `, `${match[1]} = hidden `) : line, - declaration: `${match[1]} = external ${promoted ? "hidden " : ""}${threadLocal}${match[4]} ${type}`, + line: promoted ? line.replace(`${match[1]} = ${match[2]}`, `${match[1]} = hidden `) : line, + declaration: `${match[1]} = external ${promoted ? "hidden " : ""}${threadLocal}${match[5]} ${type}`, symbol, promoted, }; } +/** The change in aggregate nesting depth over one line of LLVM text, + * outside string constants and comments (a regex global's comment spells + * its source). */ +function bracketDepth(line: string): number { + let depth = 0; + let quote = false; + for (let i = 0; i < line.length; i++) { + const ch = line[i]!; + if (ch === '"') quote = !quote; + if (quote) continue; + if (ch === ";") break; + if (ch === "{" || ch === "[" || ch === "<" || ch === "(") depth++; + else if (ch === "}" || ch === "]" || ch === ">" || ch === ")") depth--; + } + return depth; +} + function stableBucket(symbol: string, count: number): number { let hash = 0x811c9dc5; for (let i = 0; i < symbol.length; i++) { @@ -154,7 +202,7 @@ function chunksOf( targetBytes: number, maximumBuckets: number, ): { bucket: number; functions: FunctionDef[] }[] { - const totalBytes = functions.reduce((sum, fn) => sum + Buffer.byteLength(fn.source) + 2, 0); + const totalBytes = functions.reduce((sum, fn) => sum + fn.bytes + 2, 0); // A power-of-two bucket count changes only when the program crosses a wide // size band. Ordinary edits therefore keep every function in the same // symbol-hash bucket; ceil(total/target) would reshuffle the whole program @@ -168,6 +216,74 @@ function chunksOf( ); } +/** A module's top-level forms, as the splitter classifies them. */ +interface ModuleParts { + preamble: string[]; + globals: GlobalDef[]; + functions: FunctionDef[]; + trailer: string[]; +} + +/** Classifies a module one line at a time. `define` lines go to + * `onFunction`, which takes the following lines through the definition's + * closing brace; every other line is offered to `line`. */ +class ModuleReader { + readonly parts: ModuleParts = { preamble: [], globals: [], functions: [], trailer: [] }; + private sawDefinition = false; + private inTrailer = false; + /** A global definition whose aggregate initializer continues over the + * following lines (vtable arrays list one element per line). */ + private pendingGlobal: { text: string; depth: number } | null = null; + failed = false; + + /** Whether a definition may start here (none may follow the trailer). */ + startFunction(): boolean { + if (this.inTrailer || this.pendingGlobal !== null) this.failed = true; + this.sawDefinition = true; + return !this.failed; + } + + line(line: string): void { + if (this.pendingGlobal !== null) { + this.pendingGlobal.text += "\n" + line; + this.pendingGlobal.depth += bracketDepth(line); + if (this.pendingGlobal.depth <= 0) { + const { text } = this.pendingGlobal; + this.pendingGlobal = null; + this.global(text); + } + return; + } + if (line.startsWith("@") && line.includes(" = ")) { + const depth = bracketDepth(line); + if (depth > 0) this.pendingGlobal = { text: line, depth }; + else this.global(line); + return; + } + if (line.startsWith("attributes ") || line.startsWith("!")) this.inTrailer = true; + if (this.inTrailer) this.parts.trailer.push(line); + else if (!this.sawDefinition || line.trim() !== "") this.parts.preamble.push(line); + } + + /** Globals may follow the attributes and metadata (the feature flags the + * link-features step appends); they are module storage like any other. */ + private global(definition: string): void { + const parsed = globalDef(definition); + if (parsed === null) { + // External declarations are already valid in every shard. + if (/^@.+\s+=\s+external\s+/.test(definition)) this.parts.preamble.push(definition); + else this.failed = true; + } else { + this.parts.globals.push(parsed); + this.sawDefinition = true; + } + } + + finish(): ModuleParts | null { + return this.failed || this.pendingGlobal !== null ? null : this.parts; + } +} + export function splitLlvmProgram( source: string, options: LlvmProgramSplitOptions = {}, @@ -175,42 +291,129 @@ export function splitLlvmProgram( const minimumBytes = options.minimumBytes ?? DEFAULT_MINIMUM_BYTES; const sourceBytes = Buffer.byteLength(source); if (sourceBytes < minimumBytes) return null; - const targetBytes = Math.max(64 * 1024, options.targetBytes ?? DEFAULT_TARGET_BYTES); const lines = source.split("\n"); - const preamble: string[] = []; - const globals: GlobalDef[] = []; - const functions: FunctionDef[] = []; - const trailer: string[] = []; - let sawDefinition = false; - let inTrailer = false; - for (let i = 0; i < lines.length; i++) { + const reader = new ModuleReader(); + for (let i = 0; i < lines.length && !reader.failed; i++) { const line = lines[i]!; if (line.startsWith("define ")) { - if (inTrailer) return null; + if (!reader.startFunction()) break; const parsed = functionDefAt(lines, i); if (parsed === null) return null; - functions.push(parsed.def); - sawDefinition = true; + reader.parts.functions.push(parsed.def); i = parsed.end; - continue; - } - if (line.startsWith("@") && line.includes(" = ")) { - if (inTrailer) return null; - const parsed = globalDef(line); - if (parsed === null) { - // External declarations are already valid in every shard. - if (/^@.+\s+=\s+external\s+/.test(line)) preamble.push(line); - else return null; - } else { - globals.push(parsed); - sawDefinition = true; - } - continue; - } - if (line.startsWith("attributes ") || line.startsWith("!")) inTrailer = true; - if (inTrailer) trailer.push(line); - else if (!sawDefinition || line.trim() !== "") preamble.push(line); + } else reader.line(line); } + const parts = reader.finish(); + return parts === null ? null : shardModule(parts, sourceBytes, options, null); +} + +/** The read size of splitLlvmProgramFile's scan. */ +const FILE_CHUNK_BYTES = 16 * 1024 * 1024; + +/** splitLlvmProgram over LLVM text already written to `path`, for modules + * too large to hold in memory more than once (or past the longest string the + * engine allows). Function bodies stay in the file: each shard's source + * reads its bodies when it is read, so only the shards being written at a + * time are in memory. */ +export function splitLlvmProgramFile( + path: string, + options: LlvmProgramSplitOptions = {}, +): LlvmProgramSplit | null { + const minimumBytes = options.minimumBytes ?? DEFAULT_MINIMUM_BYTES; + const sourceBytes = statSync(path).size; + if (sourceBytes < minimumBytes) return null; + const reader = new ModuleReader(); + // The definition being read: its header and where its body starts. + let current: { header: NonNullable>; body: number } | null = + null; + const onLine = (bytes: Buffer, start: number, end: number, offset: number): void => { + if (current !== null) { + // Only the closing brace ends a definition; its body is not decoded. + if (end - start === 1 && bytes[start] === 0x7d) { + const { header, body } = current; + const bodyEnd = offset + 1; + reader.parts.functions.push({ + bytes: Buffer.byteLength(header.line) + 1 + (bodyEnd - body), + text: (read) => `${header.line}\n${read(body, bodyEnd)}`, + declaration: header.declaration, + symbol: header.symbol, + promoted: header.promoted, + }); + current = null; + } + return; + } + const line = bytes.toString("utf8", start, end); + if (line.startsWith("define ")) { + const header = functionHeader(line); + if (header === null || !reader.startFunction()) reader.failed = true; + else current = { header, body: offset + (end - start) + 1 }; + } else reader.line(line); + }; + const fd = openSync(path, "r"); + try { + let carry: Buffer = Buffer.alloc(0); + let carryOffset = 0; + for (let position = 0; position < sourceBytes && !reader.failed;) { + const chunk = Buffer.allocUnsafe(Math.min(FILE_CHUNK_BYTES, sourceBytes - position)); + const read = readSync(fd, chunk, 0, chunk.length, position); + if (read === 0) break; + const data = + carry.length === 0 + ? chunk.subarray(0, read) + : Buffer.concat([carry, chunk.subarray(0, read)]); + const dataOffset = carry.length === 0 ? position : carryOffset; + position += read; + let lineStart = 0; + for (let newline = data.indexOf(10); newline >= 0; newline = data.indexOf(10, lineStart)) { + onLine(data, lineStart, newline, dataOffset + lineStart); + lineStart = newline + 1; + } + carry = Buffer.from(data.subarray(lineStart)); + carryOffset = dataOffset + lineStart; + } + // The text after the last newline is a line too (empty when the file + // ends with one), as String.prototype.split answers it. + if (!reader.failed) onLine(carry, 0, carry.length, carryOffset); + } finally { + closeSync(fd); + } + if (current !== null) return null; + const parts = reader.finish(); + return parts === null ? null : shardModule(parts, sourceBytes, options, path); +} + +/** Runs `use` with a reader of `path`'s byte ranges (or of nothing, for a + * module held in memory). */ +function withRangeReader(path: string | null, use: (read: RangeReader) => T): T { + if (path === null) + return use(() => { + throw new Error("an in-memory LLVM module has no file to read"); + }); + const fd = openSync(path, "r"); + try { + return use((start, end) => { + const bytes = Buffer.allocUnsafe(end - start); + for (let done = 0; done < bytes.length;) { + const read = readSync(fd, bytes, done, bytes.length - done, start + done); + if (read === 0) throw new Error(`${path} ended before byte ${end}`); + done += read; + } + return bytes.toString("utf8"); + }); + } finally { + closeSync(fd); + } +} + +function shardModule( + parts: ModuleParts, + sourceBytes: number, + options: LlvmProgramSplitOptions, + path: string | null, +): LlvmProgramSplit | null { + const { preamble, globals, functions, trailer } = parts; + const targetBytes = Math.max(64 * 1024, options.targetBytes ?? DEFAULT_TARGET_BYTES); if (functions.length < 2) return null; const globalDecls = globals.flatMap((global) => global.declaration === null ? [] : [global.declaration], @@ -230,16 +433,22 @@ export function splitLlvmProgram( const chunks = chunksOf(functions, targetBytes, 2 ** Math.floor(Math.log2(bucketLimit))); if (chunks.length < 2) return null; const tail = trailer.length === 0 ? "" : `\n${trailer.join("\n")}`; - const functionShards = chunks.map(({ bucket, functions: chunk }) => { + // A shard's source is assembled when it is read; a module split from a + // file reads its function bodies from there each time. + const functionShards = chunks.map(({ bucket, functions: chunk }): LlvmProgramShard => { const owned = new Set(chunk); - const declarations = functions.filter((fn) => !owned.has(fn)).map((fn) => fn.declaration); return { name: `program-f${bucket.toString().padStart(3, "0")}.ll`, - source: [ - [...preamble, ...globalDecls, ...declarations, ""].join("\n"), - ...chunk.map((fn) => fn.source), - tail, - ].join("\n"), + source: () => { + const declarations = functions.filter((fn) => !owned.has(fn)).map((fn) => fn.declaration); + return withRangeReader(path, (read) => + [ + [...preamble, ...globalDecls, ...declarations, ""].join("\n"), + ...chunk.map((fn) => fn.text(read)), + tail, + ].join("\n"), + ); + }, }; }); // Global initializer changes are common (literal text, generated tables) @@ -247,15 +456,15 @@ export function splitLlvmProgram( // all module storage in one dedicated shard that declares every function. const globalShard: LlvmProgramShard = { name: "program-globals.ll", - source: [ - [...preamble, ...functions.map((fn) => fn.declaration), ""].join("\n"), - ...globals.map((global) => global.line), - tail, - ].join("\n"), + source: () => + [ + [...preamble, ...functions.map((fn) => fn.declaration), ""].join("\n"), + ...globals.map((global) => global.line), + tail, + ].join("\n"), }; - const shards = [globalShard, ...functionShards]; return { - shards, + shards: [globalShard, ...functionShards], promotedSymbols: [ ...globals.filter((global) => global.promoted).map((global) => global.symbol), ...functions.filter((fn) => fn.promoted).map((fn) => fn.symbol), diff --git a/packages/compiler/src/backend/native-toolchain.test.ts b/packages/compiler/src/backend/native-toolchain.test.ts index fd7a5325f..f1bcde404 100644 --- a/packages/compiler/src/backend/native-toolchain.test.ts +++ b/packages/compiler/src/backend/native-toolchain.test.ts @@ -3983,12 +3983,12 @@ test.skipIf( programShards: [ { name: "program-f000.ll", - source: + source: () => "define i32 @main() {\nentry:\n ret i32 7\n}\ndeclare hidden i64 @other_value()\n", }, { name: "program-f001.ll", - source: + source: () => "declare i32 @main()\ndefine hidden i64 @other_value() {\nentry:\n ret i64 9\n}\n", }, ], @@ -4038,11 +4038,11 @@ test.skipIf(!nativeShardMergeAvailable)( const programShards = [ { name: "program-f000.ll", - source: "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n", + source: () => "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n", }, { name: "program-f001.ll", - source: "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n", + source: () => "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n", }, ]; try { @@ -4082,7 +4082,7 @@ test.skipIf(!nativeShardMergeAvailable)( // The canonical TU and ABI stay fixed, but the exact shard projection // changes. The merged-object tier must not reuse the previous layout. await build("bar", join(dir, "bar-commented.lib.a"), [ - { ...programShards[0]!, source: `; projection edit\n${programShards[0]!.source}` }, + { ...programShards[0]!, source: () => `; projection edit\n${programShards[0]!.source()}` }, programShards[1]!, ]); expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(3); @@ -4131,11 +4131,11 @@ test.skipIf( const programShards = [ { name: "program-f000.ll", - source: "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n", + source: () => "define i64 @foo() {\nentry:\n ret i64 1\n}\ndeclare i64 @bar()\n", }, { name: "program-f001.ll", - source: "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n", + source: () => "declare i64 @foo()\ndefine i64 @bar() {\nentry:\n ret i64 2\n}\n", }, ]; const ldWrapper = (generation: string): string => `#!/bin/sh @@ -4227,8 +4227,8 @@ test.skipIf( // If compilation reaches either shard, this build fails. Missing ld // (and objcopy on Linux) must select the valid canonical TU instead. programShards: [ - { name: "program-f000.ll", source: "not valid LLVM IR\n" }, - { name: "program-f001.ll", source: "also not valid LLVM IR\n" }, + { name: "program-f000.ll", source: () => "not valid LLVM IR\n" }, + { name: "program-f001.ll", source: () => "also not valid LLVM IR\n" }, ], programPublicSymbols: ["public_value"], outPath, @@ -4293,12 +4293,12 @@ test.skipIf( programShards: [ { name: "program-f000.ll", - source: + source: () => "define i64 @public_value() {\nentry:\n ret i64 7\n}\ndeclare i64 @other_value()\n", }, { name: "program-f001.ll", - source: + source: () => "declare i64 @public_value()\ndefine i64 @other_value() {\nentry:\n ret i64 9\n}\n", }, ], diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index ff1c7a3f6..e3f97710f 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -29,7 +29,7 @@ export interface CcOptions { windowsSubsystem?: WindowsSubsystem; /** Optional equivalent LLVM modules for dev compilation. Unsupported * targets or merge failures fall back to the canonical cPath TU. */ - programShards?: readonly { name: string; source: string }[]; + programShards?: readonly { name: string; source: () => string }[]; /** Canonical externally visible definitions retained while shard merging * demotes generated cross-shard linkage back to local symbols. */ programPublicSymbols?: readonly string[]; @@ -306,7 +306,7 @@ export interface LibArchiveOptions { * `programSource` remains the canonical TU/cache identity; these stable * shards compile independently and are relocatably merged into one program * object before archive assembly. */ - programShards?: readonly { name: string; source: string }[]; + programShards?: readonly { name: string; source: () => string }[]; /** Canonical externally visible definitions retained while the shard merge * demotes generated cross-shard linkage back to local symbols. */ programPublicSymbols?: readonly string[]; diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 71bec40e8..7f989a5e4 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -436,7 +436,7 @@ export async function compileCInternal( ? ["-gline-tables-only", ...(opts.cPath.endsWith(".ll") ? [] : ["-gno-column-info"])] : []; const { programShards, programPublicSymbols, programShardMergeIdentity } = - await selectProgramShards(driver, opts, optimization === "dev" && !sanitize); + await selectProgramShards(driver, opts, optimization === "dev"); // Mobile triples produce library archives, never standalone executables: // the executable-lane runtime (event loop, sockets, child processes) is // not verified on those device classes. compile() reports the SC3002 @@ -890,9 +890,15 @@ export async function compileCInternal( }; const buildExecutable = async ( runtimeInput: (path: string) => string, - build: { programPath?: string; outPath?: string; compilerVisibleSource?: string } = {}, + build: { + programPath?: string; + outPath?: string; + compilerVisibleSource?: string; + /** programPath is an object already (the merged program shards). */ + programObject?: boolean; + } = {}, ): Promise => { - if (!separateSanitizedProgram) { + if (!separateSanitizedProgram || build.programObject === true) { await runClang(buildArgs(runtimeInput, build)); return; } @@ -1471,7 +1477,7 @@ export async function compileCInternal( .update("\0") .update(shard.name) .update("\0") - .update(shard.source) + .update(shard.source()) .digest("hex"); return { ...shard, @@ -1485,7 +1491,7 @@ export async function compileCInternal( for (let i = 0; i < entries.length; i += shardWidth) { await Promise.all( entries.slice(i, i + shardWidth).map(async (entry) => { - await writeFile(entry.sourcePath, entry.source); + await writeFile(entry.sourcePath, entry.source()); if (await copyValidCachedFile(entry.cachePath, entry.staged)) return; entry.missed = true; await runClang([ @@ -1572,7 +1578,7 @@ export async function compileCInternal( (p) => objects?.get(p) ?? p, shardedProgramObject === null ? { programPath, outPath: privateOut, compilerVisibleSource: opts.cPath } - : { programPath: shardedProgramObject, outPath: privateOut }, + : { programPath: shardedProgramObject, outPath: privateOut, programObject: true }, ); if (darwinDebugSymbols) { // clang invokes dsymutil when compiling a source TU while linking. diff --git a/packages/compiler/src/backend/native/library.ts b/packages/compiler/src/backend/native/library.ts index cca706896..f0577f3ca 100644 --- a/packages/compiler/src/backend/native/library.ts +++ b/packages/compiler/src/backend/native/library.ts @@ -485,7 +485,7 @@ export async function compileLibArchive(opts: LibArchiveOptions): Promise .update("\0") .update(shard.name) .update("\0") - .update(shard.source) + .update(shard.source()) .digest("hex"); cachePath = join(persistentCache.root, "program-shard", key); } @@ -495,7 +495,7 @@ export async function compileLibArchive(opts: LibArchiveOptions): Promise for (let i = 0; i < shardEntries.length; i += shardWidth) { await Promise.all( shardEntries.slice(i, i + shardWidth).map(async (entry) => { - await writeFile(entry.sourcePath, entry.source); + await writeFile(entry.sourcePath, entry.source()); if ( entry.cachePath !== null && (await copyValidCachedFile(entry.cachePath, entry.staged)) diff --git a/packages/compiler/src/backend/native/program-shards.ts b/packages/compiler/src/backend/native/program-shards.ts index 978be3a97..1afd87f7b 100644 --- a/packages/compiler/src/backend/native/program-shards.ts +++ b/packages/compiler/src/backend/native/program-shards.ts @@ -7,7 +7,7 @@ import { rememberFingerprintDependencies } from "./dependency-files.js"; export function updateProgramShardCacheIdentity( hash: ReturnType, - shards: readonly { name: string; source: string }[] | undefined, + shards: readonly { name: string; source: () => string }[] | undefined, publicSymbols: readonly string[] | undefined, mergeIdentity: string | undefined, ): void { @@ -17,7 +17,7 @@ export function updateProgramShardCacheIdentity( } else { hash.update("\0"); for (const shard of shards) { - hash.update(shard.name).update("\0").update(shard.source).update("\0"); + hash.update(shard.name).update("\0").update(shard.source()).update("\0"); } } hash.update("\0program-public-symbols\0"); @@ -78,7 +78,7 @@ export async function resolveProgramShardMergeIdentity(driver: CcDriver): Promis } /** Library builds admit shards in both optimization modes; executable builds - * only admit unsanitized dev LLVM. Both use the same validated names, required + * admit dev LLVM, sanitized or not. Both use the same validated names, required * public-symbol projection, target support, and canonical-TU fallback. */ export async function selectProgramShards( driver: CcDriver, diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 74a61d892..17087d542 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -103,7 +103,11 @@ import { platformLinkerSupportsPersistentCache, resolvePlatformLinker, } from "./backend/linker.js"; -import { splitLlvmLibraryProgram, splitLlvmProgram } from "./backend/llvm/split.js"; +import { + splitLlvmLibraryProgram, + splitLlvmProgram, + splitLlvmProgramFile, +} from "./backend/llvm/split.js"; import { emitLibraryIdentityLines, replaceLibraryIdentity, @@ -566,8 +570,8 @@ async function compileExecutableNative( } const effectiveProgramSplit = programSplit ?? - (!programIsObject && features.optimization === "dev" && features.backend === "llvm" && !sanitize - ? splitLlvmProgram(await readFile(llvmPath, "utf8")) + (!programIsObject && features.optimization === "dev" && features.backend === "llvm" + ? splitLlvmProgramFile(llvmPath) : null); if (programIsObject) throw new InternalCompilerError("program objects must link against a precompiled runtime pack"); @@ -912,7 +916,6 @@ async function prepareExecutableInput( !useRuntimePack && backend === "llvm" && (opts.optimization ?? "release") === "dev" && - !(opts.sanitize ?? false) && typeof llvmSource === "string" ? splitLlvmProgram(llvmSource) : null; diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index 64964d94f..8710d31b6 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -295,7 +295,7 @@ export class NativeCompiler { for (const [index, shard] of split.shards.entries()) { const shardInput = join(stage, `shard-${index}.ll`); const shardOutput = join(stage, `shard-${index}` + this.toolchain.target.outputSuffixes.obj); - writeFileSync(shardInput, shard.source); + writeFileSync(shardInput, shard.source()); this.emitObject(shardInput, shardOutput, source + "." + shard.name, optimization, "obj"); objects.push(shardOutput); } diff --git a/tests/harness/three.test.ts b/tests/harness/three.test.ts index 967c8654a..27fb86d1f 100644 --- a/tests/harness/three.test.ts +++ b/tests/harness/three.test.ts @@ -72,11 +72,7 @@ test.for(cases)( const previousTarget = process.env["SCRIPTC_TARGET"]; const previousCc = process.env["SCRIPTC_CC"]; const wasm = target === "wasm32-wasi"; - // The sanitized nodes program is one 1.14 GB LLVM module, whose clang - // compile needs about 16 GB, more than the CI runners have. It runs - // unsanitized until sanitized dev builds compile large programs in - // shards; every other fixture keeps the sanitizer. - const sanitize = !wasm && fixture !== "nodes" && process.env["SCRIPTC_SAN"] === "1"; + const sanitize = !wasm && process.env["SCRIPTC_SAN"] === "1"; try { if (wasm) { process.env["SCRIPTC_TARGET"] = target; From 0af32aff5a22a364b694bc6440edbf687ed6e1df Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 02:15:04 -0500 Subject: [PATCH 191/395] Compute dev builds' int32 slot analysis on a forked process --- .../compiler/src/backend/llvm/emit-module.ts | 8 +- packages/compiler/src/backend/llvm/emitter.ts | 73 ++++++++++++--- .../src/backend/llvm/forked-analysis.ts | 45 ++++++++++ packages/compiler/src/ir/int32-slots.ts | 76 ++++++++++++++++ packages/compiler/src/ir/node-index.ts | 30 +++++++ .../compiler/test/chunked-emission.test.ts | 6 +- .../test/int32-slots-transfer.test.ts | 88 +++++++++++++++++++ 7 files changed, 306 insertions(+), 20 deletions(-) create mode 100644 packages/compiler/src/backend/llvm/forked-analysis.ts create mode 100644 packages/compiler/test/int32-slots-transfer.test.ts diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index c6d3dbff4..dfdd95451 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -102,10 +102,10 @@ function emitFunctionsInChunks(emitter: LlEmitter, chunks: number): string[] { return fnDefs; } -/** Modules with fewer functions emit in one process: forking and merging - * would cost more than the workers save (SCRIPTC_EMIT_WORKER_FUNCTIONS - * lowers it, for tests). */ -function workerMinFunctions(): number { +/** Modules with fewer functions emit in one process, and compute their + * analyses in it: forking and merging would cost more than the workers save + * (SCRIPTC_EMIT_WORKER_FUNCTIONS lowers it, for tests). */ +export function workerMinFunctions(): number { const value = Number(process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] ?? "2000"); return Number.isFinite(value) && value > 0 ? value : 2000; } diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index a8c5d2c39..8049f8624 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -104,7 +104,7 @@ import { analyzeInt32Slots, dynamicFieldStores, type DynamicFieldStores, - type Int32Slots, + Int32Slots, } from "../../ir/int32-slots.js"; import { analyzeFinalFields, type FinalFields } from "../../ir/final-fields.js"; import { foldConstantGlobals } from "../../ir/constant-globals.js"; @@ -373,7 +373,9 @@ import { emitModuleParts, emitParts, helperDefs, + workerMinFunctions, } from "./emit-module.js"; +import { forkedAnalysis } from "./forked-analysis.js"; import { emitLoopCopies, emitSpeculativeWrite, @@ -1483,7 +1485,13 @@ export class LlEmitter { streamingSplitsEnabled = false; readonly initializerBindings: ReturnType; /** Whole-program int32 storage and parameter/result facts. */ - readonly int32Slots: Int32Slots; + get int32Slots(): Int32Slots { + return (this.int32SlotsValue ??= this.joinInt32Slots()); + } + private int32SlotsValue: Int32Slots | undefined; + /** The join of the int32 slot analysis a forked process computes + * (startInt32Slots). */ + private int32SlotsJoin: (() => Int32Slots) | undefined; /** The program has no runtime store into a checked-dynamic value * (dynamicFieldStores), so live views of classes and records never * commit back into their objects. Library builds keep their commits: @@ -1736,6 +1744,15 @@ export class LlEmitter { // One walk of every body, which the analyses below read instead of // walking the bodies themselves. this.nodeIndex = int32Analysis?.nodeIndex ?? new IrNodeIndex(mod); + this.initializerBindings = + int32Analysis?.initializerBindings ?? findInitializerBindings(mod, this.nodeIndex); + // One walk finds the dynamic field stores every analysis below consults. + const dynamicStores = + int32Analysis?.dynamicStores ?? dynamicFieldStores(mod, undefined, undefined, this.nodeIndex); + // Started before the other analyses, which may run while a forked + // process computes it (startInt32Slots). + if (int32Analysis !== null) this.int32SlotsValue = int32Analysis.slots; + else this.startInt32Slots(dynamicStores); this.stackTraces = observesStacks(mod, this.nodeIndex); this.nullableUnions = new NullableUnions( mod, @@ -1754,18 +1771,6 @@ export class LlEmitter { this.nullableUnions.armTypes(), ); this.constantNumericTables = findConstantNumericTables(mod); - this.initializerBindings = - int32Analysis?.initializerBindings ?? findInitializerBindings(mod, this.nodeIndex); - // One walk finds the dynamic field stores every analysis below consults. - const dynamicStores = - int32Analysis?.dynamicStores ?? dynamicFieldStores(mod, undefined, undefined, this.nodeIndex); - this.int32Slots = - int32Analysis?.slots ?? - analyzeInt32Slots(mod, { - dynamicStores, - initializerBindings: this.initializerBindings, - nodes: this.nodeIndex, - }); this.untypedIntegrity = mod.untypedIntegrity === true; this.liveViewsReadOnly = mod.lib === undefined && @@ -2211,6 +2216,46 @@ export class LlEmitter { ); // After the vtable slots: borrowing slots exclude their implementations. if (this.debug === null) this.analyzeBorrowedReturns(); + this.int32SlotsValue ??= this.joinInt32Slots(); + } + + /** Computes the int32 slot analysis, or starts it on a forked process. + * Dev builds read its result only near the end of the constructor + * (int32Abis is empty with debug info), so for a large module the + * process computes it while the constructor runs the other analyses. */ + private startInt32Slots(dynamicStores: DynamicFieldStores): void { + const mod = this.mod; + const nodes = this.nodeIndex; + const analyze = (): Int32Slots => + analyzeInt32Slots(mod, { + dynamicStores, + initializerBindings: this.initializerBindings, + nodes, + }); + const fork = this.targetOptions.forkPrimitives; + if ( + fork === undefined || + this.targetOptions.debugSources === undefined || + (this.targetOptions.functionWorkers ?? 1) < 2 || + mod.functions.length < workerMinFunctions() + ) { + this.int32SlotsValue = analyze(); + return; + } + this.int32SlotsJoin = forkedAnalysis( + fork, + analyze, + (slots) => slots.encode(nodes), + (text) => Int32Slots.decode(text, mod, nodes), + ); + } + + private joinInt32Slots(): Int32Slots { + const join = this.int32SlotsJoin; + if (join === undefined) + throw new InternalCompilerError("llvm emitter bug: int32 slots read before analysis"); + this.int32SlotsJoin = undefined; + return join(); } /** The borrowed parameters of a hierarchy's vtable slot (empty: owned). */ diff --git a/packages/compiler/src/backend/llvm/forked-analysis.ts b/packages/compiler/src/backend/llvm/forked-analysis.ts new file mode 100644 index 000000000..80ba35fac --- /dev/null +++ b/packages/compiler/src/backend/llvm/forked-analysis.ts @@ -0,0 +1,45 @@ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { ForkPrimitives } from "./emitter.js"; + +/** Starts `compute` on a forked child process, which inherits this process + * as it is at the fork, and returns the join that answers its result. The + * child writes `encode`'s text to a file and exits; the join waits for it + * and decodes the text. When the fork fails, the child fails, or `encode` + * answers null, the join runs `compute` here instead, so the result does + * not depend on the child. Join once. */ +export function forkedAnalysis( + fork: ForkPrimitives, + compute: () => T, + encode: (value: T) => string | null, + decode: (text: string) => T, +): () => T { + const dir = mkdtempSync(join(tmpdir(), "scriptc-analysis-")); + const file = join(dir, "result.json"); + const pid = fork.fork(); + if (pid === 0) { + let code = 1; + try { + const text = encode(compute()); + if (text !== null) { + writeFileSync(file, text); + code = 0; + } + } catch { + // The parent computes the result itself. + } + fork.exit(code); + } + return () => { + let text: string | null = null; + try { + if (pid > 0 && fork.wait(pid) === 0) text = readFileSync(file, "utf8"); + } catch { + // The result is computed here. + } finally { + rmSync(dir, { recursive: true, force: true }); + } + return text === null ? compute() : decode(text); + }; +} diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index 1616a2a77..35207f79b 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -352,6 +352,82 @@ export class Int32Slots { for (const params of this.paramFacts.values()) count += params.size; return count; } + + /** The analysis as JSON, for a process that shares this module and its + * node index (a forked one). Proven writes are numbered by their position + * in `nodes`. Null when a proven write is not in the index. */ + encode(nodes: IrNodeIndex): string | null { + const proven = nodes.expressionPositions(this.provenWrites); + if (proven === null) return null; + const data: Int32SlotsData = { + fields: [...this.fields], + familyOf: [...this.familyOf], + paramFacts: [...this.paramFacts].map(([fn, params]) => [fn, [...params]]), + returns: [...this.returns], + returnRanges: [...this.returnRanges], + boxes: [...this.boxes], + boxGroupOf: [...this.boxGroupOf], + provenWrites: proven, + called: [...this.called], + reasons: [...this.reasons], + pinned: [...this.pinned], + int32Locals: [...this.int32Locals], + int32Elements: [...this.int32Elements], + pinViolations: this.pinViolations, + nonNegative: [...this.nonNegative], + narrowedReads: this.narrowedReads, + int32Arrays: [...this.int32Arrays], + checkedInt32Arrays: [...this.checkedInt32Arrays], + }; + return JSON.stringify(data); + } + + /** The analysis `encode` wrote for `mod`, whose node index is `nodes`. */ + static decode(text: string, mod: IrModule, nodes: IrNodeIndex): Int32Slots { + const data = JSON.parse(text) as Int32SlotsData; + const slots = new Int32Slots(new Map((mod.classes ?? []).map((c) => [c.name, c]))); + for (const family of data.fields) slots.fields.add(family); + for (const [key, family] of data.familyOf) slots.familyOf.set(key, family); + for (const [fn, params] of data.paramFacts) slots.paramFacts.set(fn, new Map(params)); + for (const fn of data.returns) slots.returns.add(fn); + for (const [fn, range] of data.returnRanges) slots.returnRanges.set(fn, range); + for (const group of data.boxes) slots.boxes.add(group); + for (const [member, group] of data.boxGroupOf) slots.boxGroupOf.set(member, group); + for (const node of nodes.expressionsAt(data.provenWrites)) slots.provenWrites.add(node); + for (const fn of data.called) slots.called.add(fn); + for (const [key, why] of data.reasons) slots.reasons.set(key, why); + for (const key of data.pinned) slots.pinned.add(key); + for (const key of data.int32Locals) slots.int32Locals.add(key); + for (const key of data.int32Elements) slots.int32Elements.add(key); + slots.pinViolations.push(...data.pinViolations); + for (const key of data.nonNegative) slots.nonNegative.add(key); + slots.narrowedReads = data.narrowedReads; + for (const key of data.int32Arrays) slots.int32Arrays.add(key); + for (const key of data.checkedInt32Arrays) slots.checkedInt32Arrays.add(key); + return slots; + } +} + +/** Int32Slots as plain data (Int32Slots.encode). */ +interface Int32SlotsData { + fields: string[]; + familyOf: [string, string | null][]; + paramFacts: [string, [string, IntegerRange][]][]; + returns: string[]; + returnRanges: [string, IntegerRange][]; + boxes: string[]; + boxGroupOf: [string, string][]; + provenWrites: number[]; + called: string[]; + reasons: [string, string][]; + pinned: string[]; + int32Locals: string[]; + int32Elements: string[]; + pinViolations: string[]; + nonNegative: string[]; + narrowedReads: boolean; + int32Arrays: string[]; + checkedInt32Arrays: string[]; } const DYN_KINDS = new Set(["dyn", "jsval", "caught"]); diff --git a/packages/compiler/src/ir/node-index.ts b/packages/compiler/src/ir/node-index.ts index 2aac7df92..be1fa506a 100644 --- a/packages/compiler/src/ir/node-index.ts +++ b/packages/compiler/src/ir/node-index.ts @@ -122,6 +122,36 @@ export class IrNodeIndex { if (statementKinds.has(kind)) this.statements[code] = 1; } + /** The positions of the expressions `wanted` in the index, which an + * index of the same module in another process resolves with + * expressionsAt. Null when an expression is not recorded, or is recorded + * more than once. */ + expressionPositions(wanted: ReadonlySet): number[] | null { + const positions: number[] = []; + if (wanted.size === 0) return positions; + const found = new Set(); + const { nodes, codes, statements } = this; + for (let i = 0; i < nodes.length; i++) { + if (statements[codes[i]!] !== 0) continue; + const node = nodes[i]! as IrExpr; + if (!wanted.has(node)) continue; + if (found.has(node)) return null; + found.add(node); + positions.push(i); + } + return found.size === wanted.size ? positions : null; + } + + /** The expressions at `positions` (expressionPositions). */ + expressionsAt(positions: readonly number[]): IrExpr[] { + return positions.map((position) => { + const code = this.codes[position]; + if (code === undefined || this.statements[code] !== 0) + throw new InternalCompilerError(`no IR expression at index position ${position}`); + return this.nodes[position]! as IrExpr; + }); + } + /** A scan for the nodes of `kinds`, for eachNodeOfKinds. Pass a * constant list: scans are kept by the list's identity. */ scan(kinds: readonly IrNodeKind[]): IrKindScan { diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index f4977f035..6d2b2e06a 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -104,7 +104,9 @@ test("a Promise.all tuple adapter requested in a range is defined", async () => }, 60_000); // Debug metadata is numbered per emitter (debug-info.ts) and not journaled: -// a debug build emits its function bodies in one emitter. +// a debug build emits its function bodies in one emitter. Its one fork +// computes the int32 slot analysis; when the fork fails, the emitter +// computes it itself. test("debug builds emit function bodies serially, with every metadata reference defined", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-")); const minFunctions = process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; @@ -148,7 +150,7 @@ test("debug builds emit function bodies serially, with every metadata reference const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1])); expect([...referenced].filter((id) => !defined.has(id))).toEqual([]); } - expect(forks).toBe(0); + expect(forks).toBe(1); } finally { if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions; diff --git a/packages/compiler/test/int32-slots-transfer.test.ts b/packages/compiler/test/int32-slots-transfer.test.ts new file mode 100644 index 000000000..3ea4f6a6a --- /dev/null +++ b/packages/compiler/test/int32-slots-transfer.test.ts @@ -0,0 +1,88 @@ +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import { findInitializerBindings } from "../src/ir/initializer-bindings.js"; +import { analyzeInt32Slots, dynamicFieldStores, Int32Slots } from "../src/ir/int32-slots.js"; +import type { IrExpr, IrModule } from "../src/ir/ir.js"; +import { IrNodeIndex } from "../src/ir/node-index.js"; +import { unshareNodes } from "../src/ir/unshare.js"; + +// Programs with int32 fields, parameters, results, captured boxes, contract +// pins, int32 arrays and disqualified candidates. +const PROGRAMS = [ + "4451-int32-field-storage.ts", + "4452-int32-field-dynamic-writes.ts", + "4453-int32-closed-locals.ts", + "4630-contract-int32.ts", + "4970-int32-call-abi.ts", + "5323-int32-nonnegative-slot-facts.ts", + "5332-int32-narrowed-slot-facts.ts", + "5930-contract-int32-arrays.ts", + "7326-int32-element-arrays.ts", + "7580-int32-arrays-across-calls.ts", +]; + +async function lower(program: string, dir: string): Promise { + const entry = join(import.meta.dirname, "../../../tests/corpus", program); + const outPath = join(dir, `${program}.ir.json`); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return readFile(outPath, "utf8"); +} + +/** The analysis LlEmitter makes of `mod`, with its node index. */ +function analyze(mod: IrModule): { nodes: IrNodeIndex; slots: Int32Slots } { + const nodes = new IrNodeIndex(mod); + const slots = analyzeInt32Slots(mod, { + nodes, + dynamicStores: dynamicFieldStores(mod, undefined, undefined, nodes), + initializerBindings: findInitializerBindings(mod, nodes), + }); + return { nodes, slots }; +} + +// A forked process holds its own copy of the module: the encoded analysis +// names nodes by index position, which resolve in any copy of the module. +test("the int32 slot analysis decodes against another copy of its module", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-int32-transfer-")); + try { + let provenWrites = 0; + for (const program of PROGRAMS) { + const ir = await lower(program, dir); + const first = analyze(unshareNodes(deserializeModule(ir))); + const copy = unshareNodes(deserializeModule(ir)); + const second = analyze(copy); + const text = first.slots.encode(first.nodes); + expect(text, program).not.toBeNull(); + const decoded = Int32Slots.decode(text!, copy, second.nodes); + expect(decoded, program).toEqual(second.slots); + for (const node of decoded.provenWrites) + expect(second.slots.provenWrites.has(node), program).toBe(true); + provenWrites += decoded.provenWrites.size; + } + expect(provenWrites).toBeGreaterThan(0); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}, 120_000); + +test("an analysis whose proven writes the index does not record does not encode", () => { + const loc = { file: "test.ts", start: 0, end: 0 }; + const mod: IrModule = { + irVersion: 17, + sourceFile: "test.ts", + entry: "%main", + functions: [ + { loc, name: "%main", params: [], locals: [], returnType: { kind: "void" }, body: [] }, + ], + }; + const { nodes, slots } = analyze(mod); + expect(slots.encode(nodes)).not.toBeNull(); + const elsewhere: IrExpr = { loc, kind: "numLit", value: 1, type: { kind: "f64" } }; + slots.provenWrites.add(elsewhere); + expect(nodes.expressionPositions(slots.provenWrites)).toBeNull(); + expect(slots.encode(nodes)).toBeNull(); +}); From ad26bed418b3e976d4b6168cc75049e278bea26b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 02:29:47 -0500 Subject: [PATCH 192/395] List line-reader among the benchmark workloads that link the dynamic tier The workload reads its file through readline over fs.createReadStream. The stream and readline runtime units define dynamic-tier symbols on their own: a program that only reads a file with createReadStream links the same 32, as log-summary and inventory-report do. --- scripts/zero-cost-compare.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index b23f61a0a..7ae33d64a 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -418,6 +418,7 @@ export const DYNAMIC_WORKLOADS = new Set([ "bench/typed-array-scan-workers", "bench/log-summary", "bench/inventory-report", + "bench/line-reader", "bench/json-unions", "bench/throw-chains", ]); From f176e868bf3f27b0056191d703ed2ee9f7e007d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 02:32:44 -0500 Subject: [PATCH 193/395] Prove int32 array candidates in one analysis that revises dropped ones selectInt32Arrays proposed int32 array candidates, analyzed the marked module, and when a candidate dropped, marked the module again without it and analyzed the result from scratch, until a round kept every candidate it marked. On tsc-ts the first analysis drops 6 of 8 candidates, and a second full analysis confirms the other 2. The analysis now revises the module as candidates drop: a candidate whose element stores fail (and that keeps no checked stores) has its functions replaced by the module's functions marked without it, which call the element readers again, and the fixpoint goes on over the revised functions. The selection takes the module of the functions the analysis ended with when marking the module for the candidates it kept proposes exactly those candidates, in those functions, and the emitter takes over the analysis with that module's node index, initializer bindings and dynamic stores. A revision that changes anything else in a function, such as a call of a function no other call reaches, ends the analysis, and selection falls back to rounds. SCRIPTC_INT32_VERIFY=1 also runs the rounds and throws unless the selected module and every fact of its analysis are the same. --- packages/compiler/src/backend/llvm/emitter.ts | 236 ++++++++++++++---- packages/compiler/src/ir/int32-arrays.ts | 169 +++++++++++-- packages/compiler/src/ir/int32-slots.ts | 196 ++++++++++++++- .../test/int32-element-array-emission.test.ts | 58 ++++- 4 files changed, 581 insertions(+), 78 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index a8c5d2c39..329ec4771 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -17,6 +17,7 @@ import { import { youngNurseryRequested } from "../young-nursery.js"; import { llvmBytes as llBytes, wtf8Bytes } from "../literals.js"; import { InternalCompilerError } from "../../errors.js"; +import { serializeModuleParts } from "../../ir/serialize.js"; import { createHash } from "node:crypto"; import { endsWithJump, @@ -88,6 +89,8 @@ import { fillAllocationLength, int32ArrayKey, selectInt32Arrays, + type Int32ArrayProof, + type Int32ArrayRevision, } from "../../ir/int32-arrays.js"; import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js"; import { @@ -103,6 +106,7 @@ import { resolveSelfCaptures } from "../../ir/self-captures.js"; import { analyzeInt32Slots, dynamicFieldStores, + Int32RevisionConflict, type DynamicFieldStores, type Int32Slots, } from "../../ir/int32-slots.js"; @@ -552,15 +556,60 @@ export interface Int32SlotAnalysis { readonly slots: Int32Slots; } -/** The int32 slot analysis of `mod`, with the inputs LlEmitter gives it. */ -function analyzeModuleInt32Slots(mod: IrModule): Int32SlotAnalysis { +/** The int32 slot analysis of `mod`, with the inputs LlEmitter gives it. + * With `revise`, the analysis revises the functions of candidates that drop + * (Int32SlotInputs.revise), and `mod` is the module it started from. */ +function analyzeModuleInt32Slots( + mod: IrModule, + revise?: (fnName: string, dropped: ReadonlySet) => IrFunction, +): Int32SlotAnalysis { const nodeIndex = new IrNodeIndex(mod); const initializerBindings = findInitializerBindings(mod, nodeIndex); const dynamicStores = dynamicFieldStores(mod, undefined, undefined, nodeIndex); - const slots = analyzeInt32Slots(mod, { dynamicStores, initializerBindings, nodes: nodeIndex }); + const slots = analyzeInt32Slots(mod, { + dynamicStores, + initializerBindings, + nodes: nodeIndex, + ...(revise === undefined ? {} : { revise }), + }); return { mod, nodeIndex, initializerBindings, dynamicStores, slots }; } +/** The candidates an analysis keeps: int32 arrays and checked ones. */ +function keptInt32Arrays(analysis: Int32SlotAnalysis): ReadonlySet { + return new Set([...analysis.slots.int32Arrays, ...analysis.slots.checkedInt32Arrays]); +} + +/** An analysis that revised functions (analyzeModuleInt32Slots' `revise`) + * as the analysis of `mod`, the module of the functions it ended with: the + * node index of `mod`, and its initializer bindings and dynamic stores, + * which must be the ones the analysis read. Null when they differ. */ +function revisedAnalysis(analysis: Int32SlotAnalysis, mod: IrModule): Int32SlotAnalysis | null { + const nodeIndex = new IrNodeIndex(mod); + const initializerBindings = findInitializerBindings(mod, nodeIndex); + const dynamicStores = dynamicFieldStores(mod, undefined, undefined, nodeIndex); + const read = analysis.initializerBindings; + if (initializerBindings.size !== read.size) return null; + for (const [name, globals] of initializerBindings) { + const other = read.get(name); + if (other?.length !== globals.length || globals.some((g, i) => other[i] !== g)) return null; + } + const stores = analysis.dynamicStores; + if ( + dynamicStores.all !== stores.all || + dynamicStores.names.size !== stores.names.size || + [...dynamicStores.names].some((name) => !stores.names.has(name)) + ) + return null; + return { + mod, + nodeIndex, + initializerBindings, + dynamicStores, + slots: analysis.slots, + }; +} + /** The functions whose calls the backend may emit as calls of their * result-slot entries (record-results.ts). */ function resultEntryTargets(mod: IrModule): ReadonlySet { @@ -575,57 +624,127 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { // Facts are keyed by node identity: emit the same module whether it was // lowered in memory or read back from its serialized form. const mod = unshareNodes(input); - // Each round of selectInt32Arrays analyzes the module it marks; the - // emitter of the module it returns reuses that round's analysis. - const rounds: { last: Int32SlotAnalysis | null } = { last: null }; - const proven = (marked: IrModule): ReadonlySet => { - const analysis = analyzeModuleInt32Slots(marked); - rounds.last = analysis; - return new Set([...analysis.slots.int32Arrays, ...analysis.slots.checkedInt32Arrays]); + // Each proof selectInt32Arrays asks for analyzes a marked module; the + // emitter of the module it returns reuses the last one. A proof that + // revised functions analyzed the module of the functions it ended with. + const rounds: { + last: Int32SlotAnalysis | null; + functions: readonly IrFunction[] | null; + revising: boolean; + } = { last: null, functions: null, revising: true }; + const proven = (marked: IrModule, revise: Int32ArrayRevision | null): Int32ArrayProof | null => { + rounds.functions = null; + if (revise === null || !rounds.revising) { + rounds.last = analyzeModuleInt32Slots(marked); + return { kept: keptInt32Arrays(rounds.last), functions: null }; + } + const latest = new Map(); + try { + rounds.last = analyzeModuleInt32Slots(marked, (fnName, dropped) => { + const fn = revise(fnName, dropped); + latest.set(fnName, fn); + return fn; + }); + } catch (error) { + if (!(error instanceof Int32RevisionConflict)) throw error; + rounds.last = null; + return null; + } + if (latest.size === 0) return { kept: keptInt32Arrays(rounds.last), functions: null }; + rounds.functions = marked.functions.map((fn) => latest.get(fn.name) ?? fn); + return { kept: keptInt32Arrays(rounds.last), functions: rounds.functions }; }; // Keep source storage intact for debugger inspection in dev builds. - const emitted = - options.debugSources === undefined - ? selectInt32Arrays( - versionParameters( - addRecordResultEntries( - scalarizeLiteralResults( - scalarizeNumericRecords( - specializeCallbackCalls( - specializeNumericCalls( - pairRowSums( - inlineExpressionFunctions( - foldConstantGlobals( - copyStableCaptures( - resolveSelfCaptures( - hoistFinalFieldReads( - freezeInitOnlyGlobals( - holdCharLocalsAsUnits( - dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), - ), - ), - ), - ), + if (options.debugSources !== undefined) return new LlEmitter(mod, options, null, null); + const passed = versionParameters( + addRecordResultEntries( + scalarizeLiteralResults( + scalarizeNumericRecords( + specializeCallbackCalls( + specializeNumericCalls( + pairRowSums( + inlineExpressionFunctions( + foldConstantGlobals( + copyStableCaptures( + resolveSelfCaptures( + hoistFinalFieldReads( + freezeInitOnlyGlobals( + holdCharLocalsAsUnits( + dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), ), ), ), ), - mangleFunction, ), - mangleFunction, ), ), ), + mangleFunction, ), + mangleFunction, ), - proven, - resultEntryTargets, - ) - : mod; - const prepared = rounds.last; + ), + ), + ), + ); + let emitted = selectInt32Arrays(passed, proven, resultEntryTargets); + let prepared = rounds.last; + // A proof that revised functions analyzed the module of the functions it + // ended with, which is the selected module unless selection fell back to + // rounds: then the selected module was proved by its own round. + if (prepared !== null && rounds.functions !== null && prepared.mod !== emitted) { + const functions = rounds.functions; + prepared = + emitted !== passed && + emitted.functions.length === functions.length && + emitted.functions.every((fn, i) => fn === functions[i]) + ? revisedAnalysis(prepared, emitted) + : null; + if (prepared === null && emitted !== passed) { + rounds.revising = false; + emitted = selectInt32Arrays(passed, proven, resultEntryTargets); + prepared = rounds.last; + } + } + if (process.env["SCRIPTC_INT32_VERIFY"] === "1") + verifyInt32Selection(passed, emitted, prepared?.mod === emitted ? prepared : null); return new LlEmitter(emitted, options, null, prepared?.mod === emitted ? prepared : null); } +/** SCRIPTC_INT32_VERIFY=1: throws unless the selection with revisions + * (`emitted`, and the analysis the emitter takes over) is the one rounds + * without revisions give: the same module, and the same facts. */ +function verifyInt32Selection( + passed: IrModule, + emitted: IrModule, + analysis: Int32SlotAnalysis | null, +): void { + let last: Int32SlotAnalysis | null = null; + const reference = selectInt32Arrays( + passed, + (marked) => { + last = analyzeModuleInt32Slots(marked); + return { kept: keptInt32Arrays(last), functions: null }; + }, + resultEntryTargets, + ); + const text = (mod: IrModule): string => serializeModuleParts(mod).join(""); + if (text(reference) !== text(emitted)) + throw new InternalCompilerError("int32 selection: the revised module differs from the rounds'"); + const facts = (mod: IrModule, prepared: Int32SlotAnalysis | null): string => + (prepared?.mod === mod ? prepared : analyzeModuleInt32Slots(mod)).slots.summary(mod); + const expected = facts(reference, last); + const actual = facts(emitted, analysis); + if (expected !== actual) { + const want = expected.split("\n"); + const got = actual.split("\n"); + const line = want.findIndex((entry, i) => entry !== got[i]); + throw new InternalCompilerError( + `int32 selection: the revised analysis differs from the rounds': ${want[line]?.slice(0, 400)} != ${got[line]?.slice(0, 400)}`, + ); + } +} + /** Emits the module's parts, emitting it again with the functions an * UnwindAbiViolation names on the pending-flag ABI. */ function emitModuleText(input: IrModule, options: LlvmTargetOptions): string[] { @@ -1438,7 +1557,12 @@ export class LlEmitter { /** Preorder intervals of the runtime error classes under THIS module's * class-forest numbering (main() stamps scr_error_vts with them, exactly * like the runtime ABI’s errorVtStampLines). */ - readonly errorIntervals: { kind: number; pre: number; post: number; lib: string }[] = []; + readonly errorIntervals: { + kind: number; + pre: number; + post: number; + lib: string; + }[] = []; /** The runtime emitter vtable's preorder interval, when the program * touches node:events (the class def rides the module exactly then) — * main() stamps scr_emitter_vt with it (emitterVtStampLines, ported). */ @@ -1447,7 +1571,12 @@ export class LlEmitter { * ported): the defs ride every emitter-touching module (the frontend * collects the whole emitter-rooted tree), and scr_stream.c links on * the same predicate, so the stamps always have their globals. */ - readonly streamIntervals: { vt: string; pre: number; post: number; lib: string }[] = []; + readonly streamIntervals: { + vt: string; + pre: number; + post: number; + lib: string; + }[] = []; // ── per-function state (reset in emitFunction) ───────────────────────── B = new BlockBuilder(); @@ -1514,7 +1643,12 @@ export class LlEmitter { /** Active induction bindings retain their width independently of size_t. */ integerLoopBindings = new Map< string, - { slot: string; type: "i32" | "i64"; signed?: boolean; range?: { min: number; max: number } } + { + slot: string; + type: "i32" | "i64"; + signed?: boolean; + range?: { min: number; max: number }; + } >(); /** Constant number locals whose latest emitted declaration stored an * exact integer beside the double, such as `const j = i * 4` in a @@ -1597,7 +1731,11 @@ export class LlEmitter { * (the fast copy) or as double arrays (an ordinary copy entered only * after they widened), as `function\0local` keys, and where each of the * fast copy's writes resumes in the third copy. */ - int32Kinds: { int32: Set; wide: Set; resume: Map } | null = null; + int32Kinds: { + int32: Set; + wide: Set; + resume: Map; + } | null = null; /** The resume label of the fast-copy write being emitted. */ int32KindResume: string | null = null; /** The function being emitted, for speculative globals. */ @@ -1671,7 +1809,12 @@ export class LlEmitter { * releases frames/scopes down to the recorded depths and branches to * `label` (the catch, or the exception-path finally) instead of * returning out of the function. Entering a try emits no code. */ - tryStack: { label: string; used: boolean; frameDepth: number; scopeDepth: number }[] = []; + tryStack: { + label: string; + used: boolean; + frameDepth: number; + scopeDepth: number; + }[] = []; /** Return type of the function being emitted — the unwind path returns * a dummy of this type (never read: callers check the flag first). */ currentReturnType: IrType = VOID; @@ -7150,7 +7293,10 @@ export class LlEmitter { return childDataDynThunkFor(this, chunk); } - emitterFixedAdapter(cbT: IrType & { kind: "func" }): { fn: string; shim: string } { + emitterFixedAdapter(cbT: IrType & { kind: "func" }): { + fn: string; + shim: string; + } { return emitterFixedAdapter(this, cbT); } diff --git a/packages/compiler/src/ir/int32-arrays.ts b/packages/compiler/src/ir/int32-arrays.ts index 353755c05..0677b2ad1 100644 --- a/packages/compiler/src/ir/int32-arrays.ts +++ b/packages/compiler/src/ir/int32-arrays.ts @@ -805,6 +805,41 @@ function rewriteCandidates( return { ...fn, body, locals: locals.filter((local) => !popLocals.has(local.id)) }; } +/** A module marked for its int32 array candidates: the candidates of + * each function (int32ArrayCandidates) and the element readers the marking + * inlines. */ +interface Int32ArrayMarking { + readonly mod: IrModule; + readonly readers: ReadonlyMap; + readonly candidates: ReadonlyMap>; +} + +/** markInt32ArrayCandidates with the candidates it marked; null when it + * marks none. */ +function markCandidates( + mod: IrModule, + keep: ReadonlySet | null, + redirected: ReadonlySet, +): Int32ArrayMarking | null { + if (mod.lib !== undefined) return null; + const readers = new Map(); + for (const fn of mod.functions) if (isElementReader(fn)) readers.set(fn.name, fn); + const all = moduleInt32ArrayCandidates(mod, new Set(readers.keys()), keep, redirected); + if (all.size === 0) return null; + const keys = new Set(); + const functions = mod.functions.map((fn) => { + const candidates = all.get(fn.name); + if (!candidates) return fn; + for (const id of candidates) keys.add(int32ArrayKey(fn.name, id)); + return rewriteCandidates(fn, candidates, readers); + }); + return { + mod: { ...mod, functions, int32ArrayCandidates: [...keys] }, + readers, + candidates: all, + }; +} + /** Propose the module's int32 array candidates (int32ArrayKey keys in * `int32ArrayCandidates`), only those in `keep` when given, and rewrite * their functions. */ @@ -813,42 +848,126 @@ export function markInt32ArrayCandidates( keep: ReadonlySet | null = null, redirected: ReadonlySet = new Set(), ): IrModule { - if (mod.lib !== undefined) return mod; - const readers = new Map(); - for (const fn of mod.functions) if (isElementReader(fn)) readers.set(fn.name, fn); - const all = moduleInt32ArrayCandidates(mod, new Set(readers.keys()), keep, redirected); - if (all.size === 0) return mod; - const keys = new Set(); - const functions = mod.functions.map((fn) => { - const candidates = all.get(fn.name); - if (!candidates) return fn; - for (const id of candidates) keys.add(int32ArrayKey(fn.name, id)); - return rewriteCandidates(fn, candidates, readers); - }); - return { ...mod, functions, int32ArrayCandidates: [...keys] }; + return markCandidates(mod, keep, redirected)?.mod ?? mod; +} + +/** Revises a function of a module marked for int32 array candidates when + * some of them drop: the function marked for its candidates outside + * `dropped` (int32ArrayKey keys), which is the unmarked function when none + * is left. */ +export type Int32ArrayRevision = (fnName: string, dropped: ReadonlySet) => IrFunction; + +/** What a proof of a marked module (selectInt32Arrays' `prove`) keeps. */ +export interface Int32ArrayProof { + /** The candidates it keeps. */ + readonly kept: ReadonlySet; + /** The functions of the module it proved, in module order, when it + * revised some through the Int32ArrayRevision it was given; otherwise + * null, for the marked module's own. */ + readonly functions: readonly IrFunction[] | null; } /** The module the backend emits: rewritten for exactly the candidates the - * int32 slot analysis (`prove`, returning its int32Arrays) keeps, so a - * module without int32 arrays is `mod` itself. A dropped candidate's - * rewrite is undone by marking again without it, until every candidate - * marked is kept. `redirected` names the functions some of whose calls the + * int32 slot analysis (`prove`) keeps, so a module without int32 arrays is + * `mod` itself. `redirected` names the functions some of whose calls the * backend emits as calls of another function (a result-slot entry): their - * parameters keep doubles. */ + * parameters keep doubles. + * + * The proof of the marked module gets an Int32ArrayRevision: as candidates + * drop, it revises their functions and goes on proving the revised module, + * so one analysis ends with the module marked for the candidates it keeps. + * That module is the answer when marking for those candidates again proposes + * exactly them, in the functions the proof revised. A proof that cannot + * follow a revision answers null; then each round marks the module, proves + * it without revisions, and marks again without the dropped candidates, + * until a round keeps every candidate it marks (four rounds at most). */ export function selectInt32Arrays( mod: IrModule, - prove: (marked: IrModule) => ReadonlySet, + prove: (marked: IrModule, revise: Int32ArrayRevision | null) => Int32ArrayProof | null, redirected: (mod: IrModule) => ReadonlySet = () => new Set(), ): IrModule { - let keep: ReadonlySet | null = null; const others = redirected(mod); - for (let round = 0; round < 4; round++) { + const marking = markCandidates(mod, null, others); + if (marking === null) return mod; + const original = new Map(mod.functions.map((fn) => [fn.name, fn])); + const markedFunctions = new Map(marking.mod.functions.map((fn) => [fn.name, fn])); + // Each function's latest revision and the candidates it is marked for. + const revisions = new Map }>(); + const revise: Int32ArrayRevision = (fnName, dropped) => { + const candidates = marking.candidates.get(fnName) ?? new Set(); + const ids = new Set(); + for (const id of candidates) if (!dropped.has(int32ArrayKey(fnName, id))) ids.add(id); + const latest = revisions.get(fnName); + if (latest !== undefined && latest.ids.size === ids.size) return latest.fn; + if (latest === undefined && ids.size === candidates.size) return markedFunctions.get(fnName)!; + const fn = + ids.size === 0 + ? original.get(fnName)! + : rewriteCandidates(original.get(fnName)!, ids, marking.readers); + revisions.set(fnName, { fn, ids }); + return fn; + }; + const proof = prove(marking.mod, revise); + const proposed = marking.mod.int32ArrayCandidates ?? []; + let keep: ReadonlySet | null = null; + let round = 0; + if (proof !== null && proof.functions === null) { + // The marked module itself was proved: the first round. + if (proposed.every((key) => proof.kept.has(key))) return marking.mod; + keep = new Set(proposed.filter((key) => proof.kept.has(key))); + round = 1; + } else if (proof !== null) { + const settled = settleRevisions(mod, marking, proof, revisions, others); + if (settled !== null) return settled; + } + for (; round < 4; round++) { const marked = markInt32ArrayCandidates(mod, keep, others); if (marked === mod) return mod; - const kept = prove(marked); - const proposed = marked.int32ArrayCandidates ?? []; - if (proposed.every((key) => kept.has(key))) return marked; - keep = new Set(proposed.filter((key) => kept.has(key))); + const kept = prove(marked, null)!.kept; + const roundProposed = marked.int32ArrayCandidates ?? []; + if (roundProposed.every((key) => kept.has(key))) return marked; + keep = new Set(roundProposed.filter((key) => kept.has(key))); } return mod; } + +/** The module marked for the candidates a revising proof kept, built from + * the functions it proved: null unless marking `mod` for those candidates + * proposes, in each function, exactly the candidates its proved function is + * marked for. `mod` itself when it proposes none. */ +function settleRevisions( + mod: IrModule, + marking: Int32ArrayMarking, + proof: Int32ArrayProof, + revisions: ReadonlyMap }>, + redirected: ReadonlySet, +): IrModule | null { + const kept = proof.kept; + const functions = proof.functions!; + if (functions.length !== mod.functions.length) return null; + const all = moduleInt32ArrayCandidates( + mod, + new Set(marking.readers.keys()), + new Set([...(marking.mod.int32ArrayCandidates ?? [])].filter((key) => kept.has(key))), + redirected, + ); + const keys = new Set(); + for (let i = 0; i < mod.functions.length; i++) { + const fn = mod.functions[i]!; + const proved = functions[i]!; + const candidates = all.get(fn.name); + const revision = revisions.get(fn.name); + const marked = revision?.ids ?? marking.candidates.get(fn.name) ?? new Set(); + if ((candidates?.size ?? 0) !== marked.size) return null; + if (candidates !== undefined) + for (const id of candidates) { + if (!marked.has(id)) return null; + keys.add(int32ArrayKey(fn.name, id)); + } + const expected = + revision?.fn ?? (marking.candidates.has(fn.name) ? marking.mod.functions[i]! : fn); + if (proved !== expected) return null; + } + if (all.size === 0) return mod; + return { ...mod, functions: [...functions], int32ArrayCandidates: [...keys] }; +} diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index 1616a2a77..af1ba36ac 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -27,7 +27,7 @@ import { int32ArraySlot, int32ArrayStores, } from "./int32-arrays.js"; -import { everyExprChild, everyStmtChild, everyTypeChild } from "./traverse.js"; +import { everyExprChild, everyStmtChild, everyStmtList, everyTypeChild } from "./traverse.js"; /** The storage slot an array-valued expression reads, as an * IrContracts.int32Elements key: a local or parameter @@ -352,6 +352,47 @@ export class Int32Slots { for (const params of this.paramFacts.values()) count += params.size; return count; } + + /** Every fact, in a canonical order, with each proven write as its + * position in `mod` (function index, then walk order): two analyses of + * equal modules hold the same facts when their summaries are equal. */ + summary(mod: IrModule): string { + const sorted = (values: Iterable): string => [...values].sort().join(","); + const lines = [ + `fields ${sorted(this.fields)}`, + `params ${sorted([...this.paramFacts].flatMap(([fn, params]) => [...params].map(([id, r]) => `${fn}\0${id}=${r.min}:${r.max}`)))}`, + `returns ${sorted(this.returns)}`, + `returnRanges ${sorted([...this.returnRanges].map(([fn, r]) => `${fn}=${r.min}:${r.max}`))}`, + `boxes ${sorted(this.boxes)}`, + `boxGroups ${sorted([...this.boxGroupOf].map(([member, group]) => `${member}=${group}`))}`, + `called ${sorted(this.called)}`, + `pinned ${sorted(this.pinned)}`, + `int32Locals ${sorted(this.int32Locals)}`, + `int32Elements ${sorted(this.int32Elements)}`, + `nonNegative ${sorted(this.nonNegative)}`, + `pinViolations ${sorted(this.pinViolations)}`, + `narrowedReads ${this.narrowedReads}`, + `int32Arrays ${sorted(this.int32Arrays)}`, + `checkedInt32Arrays ${sorted(this.checkedInt32Arrays)}`, + ]; + const positions: string[] = []; + mod.functions.forEach((fn, f) => { + let at = 0; + everyStmtList(fn.body, { + expr: (e) => { + if (this.provenWrites.has(e)) positions.push(`${f}:${at}`); + at++; + return true; + }, + stmt: () => { + at++; + return true; + }, + }); + }); + lines.push(`provenWrites ${this.provenWrites.size} ${positions.join(",")}`); + return lines.join("\n"); + } } const DYN_KINDS = new Set(["dyn", "jsval", "caught"]); @@ -833,6 +874,95 @@ export interface Int32SlotInputs { initializerBindings?: ReadonlyMap; /** The module's node index, read instead of walking the bodies. */ nodes?: IrNodeIndex; + /** Revises a function of a module marked for int32 array candidates when + * candidates drop (int32-arrays.ts Int32ArrayRevision): the function + * marked without the `dropped` candidates. With it, a candidate that drops + * (and keeps no checked stores) takes its sites back to the element-reader + * calls the marking inlined, and the fixpoint goes on over the revised + * functions, so its result is the analysis of the module marked for the + * candidates it keeps. A revision that changes more of a function than + * those sites throws Int32RevisionConflict. */ + revise?: (fnName: string, dropped: ReadonlySet) => IrFunction; +} + +/** A revision (Int32SlotInputs.revise) changed a function in a way the + * fixpoint cannot follow: a call of a function no other call reaches, or + * any change besides element-reader calls taking the place of their + * inlined reads. The module needs an analysis of its own. */ +export class Int32RevisionConflict extends Error {} + +/** What the analysis's setup reads from a function body, besides direct + * calls: a revision must leave all of it unchanged. */ +function revisionStructure(fn: IrFunction): { + calls: { callee: string; args: number }[]; + rest: string; +} { + const calls: { callee: string; args: number }[] = []; + const rest: string[] = []; + const boxed = new Set(fn.locals.filter((local) => local.boxed).map((local) => local.id)); + const expr = (e: IrExpr): boolean => { + const referenced = exprFunctionReference(e); + if (referenced !== undefined) rest.push(`ref:${referenced}`); + switch (e.kind) { + case "call": + calls.push({ callee: e.callee, args: e.args.length }); + break; + case "new": + rest.push(`new:${e.className}:${e.args.length}`); + break; + case "varRef": + if (boxed.has(e.localId)) rest.push(`read:${e.localId}`); + break; + case "assignExpr": + case "incDec": + rest.push(`${e.kind}:${e.localId}`); + break; + case "fieldGet": + case "fieldIncDec": + rest.push(`${e.kind}:${e.className}\0${e.field}`); + break; + case "closure": + rest.push(`closure:${e.fnName}:${e.captures.join(",")}`); + break; + case "classRef": + rest.push(`classRef:${e.className}:${(e.captures ?? []).join(",")}`); + break; + case "virtualCall": + rest.push(`virtualCall:${e.className}.${e.method}`); + break; + case "instanceOfValue": + rest.push(`instanceOf:${e.classValue.kind}`); + break; + } + if (BOUNDARY_KINDS.has(e.kind)) rest.push(`boundary:${e.kind}`); + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + switch (s.kind) { + case "forOf": + rest.push(`forOf:${s.localId}`); + break; + case "tryCatch": + if (s.catchLocalId) rest.push(`catch:${s.catchLocalId}`); + break; + case "assign": + case "varDecl": + if (boxed.has(s.localId)) rest.push(`${s.kind}:${s.localId}`); + break; + case "throw": + rest.push("throw"); + break; + } + return everyStmtChild(s, expr, stmt); + }; + for (const s of fn.body) stmt(s); + rest.sort(); + for (const local of fn.locals) + if (local.boxed || local.tdz) + rest.push(`local:${local.id}:${local.boxed === true}:${local.tdz === true}`); + for (const capture of fn.captures ?? []) rest.push(`capture:${capture.localId}`); + for (const capture of fn.classCaptures ?? []) rest.push(`classCapture:${capture.localId}`); + return { calls, rest: rest.join("\n") }; } export function analyzeInt32Slots(mod: IrModule, inputs: Int32SlotInputs = {}): Int32Slots { @@ -1204,6 +1334,36 @@ export function analyzeInt32Slots(mod: IrModule, inputs: Int32SlotInputs = {}): }); return found; }; + // Candidates dropped without checked stores, and the revision of the + // functions that hold them (Int32SlotInputs.revise). + const dropped = new Set(); + const revise = (names: ReadonlySet): void => { + for (const name of names) { + const previous = functions.get(name)!; + const next = inputs.revise!(name, dropped); + if (next === previous) continue; + const before = revisionStructure(previous); + const after = revisionStructure(next); + if (before.rest !== after.rest) throw new Int32RevisionConflict(name); + // The revision adds the element-reader calls back: each must be of a + // function other calls already reach, with its arity. + const counts = new Map(); + for (const { callee } of before.calls) counts.set(callee, (counts.get(callee) ?? 0) + 1); + for (const { callee, args } of after.calls) { + const left = counts.get(callee) ?? 0; + if (left > 0) { + counts.set(callee, left - 1); + continue; + } + const target = functions.get(callee); + if (!called.has(callee) || target === undefined || target.params.length !== args) + throw new Int32RevisionConflict(name); + reads(`ret:${callee}`, name); + } + for (const left of counts.values()) if (left > 0) throw new Int32RevisionConflict(name); + functions.set(name, next); + } + }; const provenByFunction = new Map(); // Each function's parameter and result writes under its latest analysis: // at the fixpoint, the ranges of every write under the final facts. @@ -1248,6 +1408,8 @@ export function analyzeInt32Slots(mod: IrModule, inputs: Int32SlotInputs = {}): for (const id of globalArrays) both.add(id); arrays = both; } + // Functions whose candidates this visit drops, revised after it. + const revised = new Set(); if (arrays && arrayGroups) { const requeue = (name: string): void => { if (queued.has(name)) return; @@ -1282,9 +1444,39 @@ export function analyzeInt32Slots(mod: IrModule, inputs: Int32SlotInputs = {}): checkableInt32Allocations(fn, id) ) slots.checkedInt32Arrays.add(key); - else for (const member of group) reason(`array:${member}`, "an element store is not int32"); + else { + for (const member of group) reason(`array:${member}`, "an element store is not int32"); + if (inputs.revise !== undefined) { + // The revised module no longer marks the group: its functions + // leave the candidate sets the visits below consult. + for (const member of group) { + dropped.add(member); + if (member.startsWith("\0")) { + globalArrays.delete(member.slice(1)); + const names = arrayGroups.globalReaders.get(member); + if (names) for (const name of names) revised.add(name); + } else { + const cut = member.indexOf("\0"); + const owner = member.slice(0, cut); + const ids = arrayCandidates.get(owner); + ids?.delete(member.slice(cut + 1)); + if (ids?.size === 0) arrayCandidates.delete(owner); + revised.add(owner); + } + } + globalArrayFunctions.clear(); + for (const [member, names] of arrayGroups.globalReaders) + if (!dropped.has(member)) for (const name of names) globalArrayFunctions.add(name); + } + } } } + if (revised.size > 0) { + revise(revised); + // A revised body is analyzed again (it was queued with its readers); + // the writes of the body it replaces no longer count. + if (functions.get(fn.name) !== fn) continue; + } // An int32 written into a slot still assumed non-negative: widen the // assumption when the value may be negative, and reanalyze its readers. const settle = (slot: string, e: IrExpr): void => { diff --git a/packages/compiler/test/int32-element-array-emission.test.ts b/packages/compiler/test/int32-element-array-emission.test.ts index 605f64e04..fccf06973 100644 --- a/packages/compiler/test/int32-element-array-emission.test.ts +++ b/packages/compiler/test/int32-element-array-emission.test.ts @@ -3,8 +3,10 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { compile, deserializeModule } from "../src/index.js"; -import type { IrModule } from "../src/ir/ir.js"; +import type { IrFunction, IrModule } from "../src/ir/ir.js"; import { markInt32ArrayCandidates, selectInt32Arrays } from "../src/ir/int32-arrays.js"; +import { analyzeInt32Slots, type Int32Slots } from "../src/ir/int32-slots.js"; +import { serializeModule } from "../src/ir/serialize.js"; async function emitLlvm(source: string): Promise { const dir = await mkdtemp(join(tmpdir(), "scriptc-int32-elements-")); @@ -210,12 +212,12 @@ test("only the candidates the proof keeps are rewritten", async () => { // An array that reaches `join` is never proposed. expect(proposed.some((key) => key.startsWith("joined\0"))).toBe(false); // Without a kept candidate, the module is the input itself. - expect(selectInt32Arrays(mod, () => new Set())).toBe(mod); + expect(selectInt32Arrays(mod, () => ({ kept: new Set(), functions: null }))).toBe(mod); const kept = new Set(["compose\0p.0", "compose\0q.0"]); - const selected = selectInt32Arrays( - mod, - (marked) => new Set((marked.int32ArrayCandidates ?? []).filter((key) => kept.has(key))), - ); + const selected = selectInt32Arrays(mod, (marked) => ({ + kept: new Set((marked.int32ArrayCandidates ?? []).filter((key) => kept.has(key))), + functions: null, + })); expect(selected.int32ArrayCandidates).toEqual([...kept]); for (const fn of selected.functions) if (fn.name !== "compose") expect(fn).toBe(mod.functions.find((f) => f.name === fn.name)); @@ -259,3 +261,47 @@ test("arrays passed to helpers and held in module globals get 4-byte slots", asy expect(functions(llvm, "mean")).not.toMatch(/getelementptr inbounds i32, ptr/); expect(llvm).toMatch(/@scr_arr_new(_small)?\(i32 7/); }); + +/** The module selectInt32Arrays selects, the analysis of its last proof, + * and how many proofs it asked for: with `revising`, each proof revises + * the functions of candidates that drop. */ +function selection( + mod: IrModule, + revising: boolean, +): { selected: IrModule; slots: Int32Slots; proofs: number } { + let slots: Int32Slots | null = null; + let proofs = 0; + const selected = selectInt32Arrays(mod, (marked, revise) => { + proofs++; + const latest = new Map(); + slots = analyzeInt32Slots( + marked, + revising && revise !== null + ? { + revise: (fnName, dropped) => { + const fn = revise(fnName, dropped); + latest.set(fnName, fn); + return fn; + }, + } + : {}, + ); + const kept = new Set([...slots.int32Arrays, ...slots.checkedInt32Arrays]); + if (latest.size === 0) return { kept, functions: null }; + return { kept, functions: marked.functions.map((fn) => latest.get(fn.name) ?? fn) }; + }); + return { selected, slots: slots!, proofs }; +} + +test("one proof that revises dropped candidates selects what rounds of proofs select", async () => { + for (const program of [source, sharedSource]) { + const mod = await lowerIr(program); + const rounds = selection(mod, false); + const revised = selection(mod, true); + // The rounds drop a candidate and prove the module again. + expect(rounds.proofs).toBeGreaterThan(1); + expect(revised.proofs).toBe(1); + expect(serializeModule(revised.selected, true)).toBe(serializeModule(rounds.selected, true)); + expect(revised.slots.summary(revised.selected)).toBe(rounds.slots.summary(rounds.selected)); + } +}); From 12bbb9bde6069414b66a363696bad7d2bfc986b3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:00:30 -0500 Subject: [PATCH 194/395] Keep a throttler's pending-call storage within its live calls A queue that never emptied kept the slots of calls already taken, so its storage grew with lifetime traffic even when maxPending bounded the live calls. Before growing, the queue now moves its live calls to the front. --- packages/runtime/src/scr_util_timing.c | 7 +++++++ tests/corpus/7711-util-throttle.cjs | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/runtime/src/scr_util_timing.c b/packages/runtime/src/scr_util_timing.c index a8a700d47..13651f5ff 100644 --- a/packages/runtime/src/scr_util_timing.c +++ b/packages/runtime/src/scr_util_timing.c @@ -303,6 +303,13 @@ static size_t ut_pending_count(const UtState *s) { } static void ut_push(UtState *s, ScrPromise *promise /* moves in */, ScrDyn **args /* moves in */, size_t argc) { + /* Calls already taken free their slots, so a queue that never empties + * keeps its storage within twice its live calls. */ + if (s->len == s->cap && s->head > 0) { + s->len -= s->head; + memmove(s->calls, s->calls + s->head, s->len * sizeof *s->calls); + s->head = 0; + } if (s->len == s->cap) { size_t cap = s->cap ? s->cap * 2 : 4; UtCall *grown = realloc(s->calls, cap * sizeof *grown); diff --git a/tests/corpus/7711-util-throttle.cjs b/tests/corpus/7711-util-throttle.cjs index d786535ef..f776e7174 100644 --- a/tests/corpus/7711-util-throttle.cjs +++ b/tests/corpus/7711-util-throttle.cjs @@ -35,6 +35,18 @@ async function main() { console.log(c.activeCount, c.pendingCount); console.log(await ca, await cb, order, c.activeCount); + // A queue that never empties while calls are taken and others arrive. + const q = util.throttle(async (x) => { await null; return x; }, 1000, 1000, { concurrency: 1 }); + const queued = []; + for (let i = 0; i < 5; i++) queued.push(q(i)); + const results = []; + for (let i = 5; i < 25; i++) { + results.push(await queued[i - 5]); + queued.push(q(i)); + } + for (let i = 20; i < 25; i++) results.push(await queued[i]); + console.log(results.join(","), q.pendingCount, q.activeCount); + // A throwing fn rejects that call only. const e = util.throttle((x) => { if (x === 2) throw new RangeError("two"); return x; }, 5, 10); await settle("e1", e(1)); From 975270cef5a59ffe33821c8abe2be257dc141b24 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:02:53 -0500 Subject: [PATCH 195/395] Decode base64 eight characters at a time Buffer.from(text, "base64" | "base64url") decoded each four-character group with four lookups and a sign test, then stored its three bytes one at a time. The decoder now first takes complete groups two at a time: each character's value comes from a table that holds it already shifted into place within its group's 24 bits, a single bit test covers all eight characters, and each group's three bytes go out in one four-byte store whose last byte the next store overwrites. A group with any other character (padding, whitespace, an invalid character) takes the previous path, and the two-group step resumes after it, so line-wrapped text keeps most of the gain. The destination already holds two spare bytes, which cover the extra store byte. --- .../corpus/8006-buffer-base64-decode.ts.json | 6 ++ packages/runtime/src/scr_bytes.c | 66 ++++++++++++++++--- tests/corpus/8006-buffer-base64-decode.ts | 66 +++++++++++++++++++ .../8006-buffer-base64-decode.ts.json | 1 + 4 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8006-buffer-base64-decode.ts.json create mode 100644 tests/corpus/8006-buffer-base64-decode.ts create mode 100644 tests/harness/corpus-costs/8006-buffer-base64-decode.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8006-buffer-base64-decode.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8006-buffer-base64-decode.ts.json new file mode 100644 index 000000000..135fe4af6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8006-buffer-base64-decode.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8006-buffer-base64-decode.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 6b8594c08..31716604e 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2672,20 +2672,66 @@ static ScrBytes *scr_bytes_conversion_storage(size_t capacity) { return scr_bytes_conversion_storage_in(NULL, capacity); } +/* scr_b64_values, each value shifted into its place in a four-character + * group's 24 bits; a character outside both alphabets sets bit 24. */ +#define SCR_B64_VALUE(c) \ + ((c) >= 'A' && (c) <= 'Z' ? (c) - 'A' \ + : (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 26 \ + : (c) >= '0' && (c) <= '9' ? (c) - '0' + 52 \ + : (c) == '+' || (c) == '-' ? 62 \ + : (c) == '/' || (c) == '_' ? 63 \ + : -1) +#define SCR_B64_AT(c, shift) \ + (SCR_B64_VALUE(c) < 0 ? UINT32_C(0x1000000) : (uint32_t)SCR_B64_VALUE(c) << (shift)) +#define SCR_B64_4(c, s) SCR_B64_AT(c, s), SCR_B64_AT(c + 1, s), SCR_B64_AT(c + 2, s), SCR_B64_AT(c + 3, s) +#define SCR_B64_16(c, s) SCR_B64_4(c, s), SCR_B64_4(c + 4, s), SCR_B64_4(c + 8, s), SCR_B64_4(c + 12, s) +#define SCR_B64_64(c, s) SCR_B64_16(c, s), SCR_B64_16(c + 16, s), SCR_B64_16(c + 32, s), SCR_B64_16(c + 48, s) +#define SCR_B64_256(s) SCR_B64_64(0, s), SCR_B64_64(64, s), SCR_B64_64(128, s), SCR_B64_64(192, s) +static const uint32_t scr_b64_shifted[4][256] = { + {SCR_B64_256(18)}, {SCR_B64_256(12)}, {SCR_B64_256(6)}, {SCR_B64_256(0)}}; + +/* One group's 24 bits as three bytes, in a four-byte store whose last byte + * the next store overwrites (the destination holds two spare bytes). */ +static inline void scr_b64_put_group(uint8_t *out, uint32_t bits) { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint32_t word = __builtin_bswap32(bits << 8); +#else + uint32_t word = bits << 8; +#endif + memcpy(out, &word, 4); +} + static ScrBytes *scr_bytes_decode_b64(ScrBytesSink *sink, const uint8_t *in, size_t n) { ScrBytes *b = scr_bytes_conversion_storage_in(sink, n / 4 * 3 + 2); + /* A local copy: a byte store could otherwise change b->data. */ + uint8_t *out = b->data; + const uint32_t(*t)[256] = scr_b64_shifted; size_t i = 0, o = 0; unsigned acc = 0, have = 0; while (i < n) { + /* Complete groups, two per step: each character's shifted value comes + * from a table and one bit test covers all eight. The second store + * ends at o + 6, within the destination while eight characters remain. */ + if (have == 0) { + while (n - i >= 8) { + uint32_t first = t[0][in[i]] | t[1][in[i + 1]] | t[2][in[i + 2]] | t[3][in[i + 3]]; + uint32_t second = t[0][in[i + 4]] | t[1][in[i + 5]] | t[2][in[i + 6]] | t[3][in[i + 7]]; + if ((first | second) & UINT32_C(0x1000000)) break; + scr_b64_put_group(out + o, first); + scr_b64_put_group(out + o + 3, second); + o += 6; + i += 8; + } + } /* Common complete groups need neither per-character branches nor state. */ if (have == 0 && n - i >= 4) { int a = scr_b64_values[in[i]], c = scr_b64_values[in[i + 1]]; int d = scr_b64_values[in[i + 2]], e = scr_b64_values[in[i + 3]]; if ((a | c | d | e) >= 0) { unsigned v = ((unsigned)a << 18) | ((unsigned)c << 12) | ((unsigned)d << 6) | (unsigned)e; - b->data[o++] = (uint8_t)(v >> 16); - b->data[o++] = (uint8_t)(v >> 8); - b->data[o++] = (uint8_t)v; + out[o++] = (uint8_t)(v >> 16); + out[o++] = (uint8_t)(v >> 8); + out[o++] = (uint8_t)v; i += 4; continue; } @@ -2696,17 +2742,17 @@ static ScrBytes *scr_bytes_decode_b64(ScrBytesSink *sink, const uint8_t *in, siz if (v < 0) continue; acc = (acc << 6) | (unsigned)v; if (++have == 4) { - b->data[o++] = (uint8_t)(acc >> 16); - b->data[o++] = (uint8_t)(acc >> 8); - b->data[o++] = (uint8_t)acc; + out[o++] = (uint8_t)(acc >> 16); + out[o++] = (uint8_t)(acc >> 8); + out[o++] = (uint8_t)acc; acc = 0; have = 0; } } - if (have == 2) b->data[o++] = (uint8_t)(acc >> 4); + if (have == 2) out[o++] = (uint8_t)(acc >> 4); else if (have == 3) { - b->data[o++] = (uint8_t)(acc >> 10); - b->data[o++] = (uint8_t)(acc >> 2); + out[o++] = (uint8_t)(acc >> 10); + out[o++] = (uint8_t)(acc >> 2); } b->len = o; return b; @@ -2764,7 +2810,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const /* Node interprets encoded text as the low byte of each UTF-16 unit. * ASCII can borrow storage; non-ASCII must preserve that truncation. */ ScrBytes *narrowed = NULL; - if (scr_bytes_ascii_prefix(in, n) != n) { + if (!scr_str_known_ascii(s) && scr_bytes_ascii_prefix(in, n) != n) { ScrStr *latin1 = scr_str_new("latin1", 6); narrowed = scr_bytes_encode_str(NULL, s, latin1); scr_str_release(latin1); diff --git a/tests/corpus/8006-buffer-base64-decode.ts b/tests/corpus/8006-buffer-base64-decode.ts new file mode 100644 index 000000000..3d7b20303 --- /dev/null +++ b/tests/corpus/8006-buffer-base64-decode.ts @@ -0,0 +1,66 @@ +// Buffer.from(text, "base64" | "base64url") over complete eight-character +// blocks and the text around them: every length from 0 to 40, an invalid +// character at each position of a block, padding and text after it, both +// alphabets, line breaks every 76 characters, sizes around the pool +// threshold, and characters past U+00FF. + +let seed = 7; +function next(): number { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed >>> 8; +} +function bytes(size: number): Buffer { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) buf[i] = next() & 255; + return buf; +} + +const rows: string[] = []; +for (let size = 0; size <= 30; size++) { + const buf = bytes(size); + const text = buf.toString("base64"); + const url = buf.toString("base64url"); + rows.push( + `${size}:${text.length}:${Buffer.from(text, "base64").equals(buf)}:${Buffer.from(url, "base64url").equals(buf)}:${Buffer.from(url, "base64").equals(buf)}`, + ); +} +console.log(rows.join(" ")); + +const block = Buffer.from("0123456789abcdefghijklmnopqrstuv").toString("base64"); +for (let at = 0; at < 16; at++) { + for (const bad of [" ", "\n", "*", "=", "é", "中"]) { + const text = block.slice(0, at) + bad + block.slice(at); + console.log(at, JSON.stringify(bad), Buffer.from(text, "base64").toString("hex")); + } +} + +for (const text of [ + "QUJDREVGR0g=", + "QUJDREVGR0", + "QUJDREVGR0g=QUJD", + "QUJD=REVGR0hJ", + "QUJDREVGR0hJSktM", + "QUJDREVGR0hJSktMTQ", + "QUJDREVGR0hJSktMTQ==", + "--__--__", + "++//++//", + "+-/_+-/_+-/_", + "Q U J D R E V G R 0 h J", + "QUJD\tREVG\r\nR0hJ", + "=QUJDREVGR0hJ", + "", +]) { + console.log(JSON.stringify(text), Buffer.from(text, "base64").toString("hex"), Buffer.from(text, "base64url").length); +} + +for (const size of [4095, 4096, 4097, 6144, 6145, 70000]) { + const buf = bytes(size); + const text = buf.toString("base64"); + const wrapped = text.replace(/.{76}/g, "$&\n"); + const decoded = Buffer.from(text, "base64"); + const unwrapped = Buffer.from(wrapped, "base64"); + console.log(size, decoded.length, decoded.equals(buf), unwrapped.equals(buf), Buffer.byteLength(text, "base64")); +} + +const wide = "QUJDREVGR0hJİSktM"; +console.log(Buffer.from(wide, "base64").toString("hex"), Buffer.from("ŁłQUJD", "base64").toString("hex")); diff --git a/tests/harness/corpus-costs/8006-buffer-base64-decode.ts.json b/tests/harness/corpus-costs/8006-buffer-base64-decode.ts.json new file mode 100644 index 000000000..88f85d0aa --- /dev/null +++ b/tests/harness/corpus-costs/8006-buffer-base64-decode.ts.json @@ -0,0 +1 @@ +{ "plain": 3995, "san": 2236 } From 493785a12166c34e1a85343de0cd6804ed4d7788 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:20:44 -0500 Subject: [PATCH 196/395] Test switch labels in copied callbacks that return early A lone array callback whose body has an early return is copied into an exit block, and its statements are renamed by a separate path from the single-return case. The new test covers labeled switches, a nested labeled switch and breaks to both labels in map and some callbacks that return early. Both calls run as loops, and the IR validator accepts every renamed break. --- .../test/array-pipeline-fusion.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index ba814105f..e41e378a3 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -213,3 +213,39 @@ console.log(out.join(",")); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); }); + +test("a copied callback with early returns keeps its switch labels", async () => { + const mod = await lowerSource(`${prelude} +const out = rows.map((r) => { + let n = 0; + outer: switch (r.id % 4) { + case 0: + return -1; + case 1: + n = 10; + break outer; + default: + inner: switch (r.value % 2) { + case 0: + n = r.value; + break outer; + default: + break inner; + } + n = 1; + } + return n; +}); +const hit = rows.some((r) => { + pick: switch (r.id) { + case 3: + break pick; + default: + return false; + } + return true; +}); +console.log(out.join(","), hit); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop", "%arr.loop"]); +}); From 2b11f14a041c49b64e304db16abdd9e1d830f7d8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:21:25 -0500 Subject: [PATCH 197/395] Test that typed-array element descriptors are read through their getters Object.defineProperty on a typed-array index reads its descriptor with ToPropertyDescriptor (scr_dyn_descriptor_fields_read): every field once, in specification order, through its getter, and a primitive descriptor throws a TypeError. 7753 now also checks that the stored value is the value getter's result, read once; that a getter-produced writable: false is refused after the fields are read in order; and that null, booleans, BigInts and symbols are refused as descriptors. --- .../corpus/7753-typed-array-named-properties.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js index 449e78ac7..e37181a5e 100644 --- a/tests/corpus/7753-typed-array-named-properties.js +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -27,6 +27,23 @@ function expandos(TA) { const got = []; Object.defineProperty(a, "1", { get value() { got.push("value"); return 6; }, get enumerable() { got.push("enumerable"); return true; } }); console.log(a[1], got); + // Each field is read once, through its getter; the stored value is the + // getter's result, and a getter-produced `writable: false` is refused. + let valueReads = 0; + Object.defineProperty(a, "1", { get value() { valueReads++; return 40 + valueReads; } }); + console.log(a[1], valueReads); + const order = []; + try { + Object.defineProperty(a, "1", { + get writable() { order.push("writable"); return false; }, + get value() { order.push("value"); return 2; }, + get configurable() { order.push("configurable"); return true; }, + }); + } catch (e) { console.log(e.constructor.name, e.message); } + console.log(a[1], order); + for (const bad of [null, true, 1n, Symbol("d")]) { + try { Object.defineProperty(a, "1", bad); } catch (e) { console.log(e.constructor.name); } + } for (const bad of [5, "x", undefined]) { try { Object.defineProperty(a, "0", bad); } catch (e) { console.log(e.constructor.name); } } From 342b770cf20a8b1ae124c5053b174ab18281177f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:25:37 -0500 Subject: [PATCH 198/395] Keep a Map.get result owned when its key can replace the map's holder The receiver of a borrowed Map.get is read before its key. When the key's evaluation replaces the field or local that holds the map (`this.m.get(this.key())` where key() assigns this.m), the read runs on the old map, which only the statement still holds, and the borrowed value outlives it. A borrow now also requires the read itself, receiver and key included, to preserve reference edges; otherwise the read keeps its own reference. Corpus 8181 and the emission test cover a key method that replaces the field. --- .../src/backend/llvm/map-read-lifetimes.ts | 7 +++++-- .../test/map-get-borrow-emission.test.ts | 17 ++++++++++++++++- tests/corpus/8181-map-get-borrows.ts | 19 ++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index 3371a7f70..d5d286674 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -186,8 +186,10 @@ function heldReceiver(expr: IrExpr): boolean { * branches and joins them; any other statement that names the local is * judged whole. A later pass of an enclosing loop declares the local again, * so only the rest of its own statement list matters. The map must be held - * by a local or a field (heldReceiver), and locals declared from the local - * count as its uses. An update such as + * by a local or a field (heldReceiver), the read itself must preserve + * reference edges (a key whose evaluation replaces the receiver's field + * leaves the read on a map the statement releases), and locals declared + * from the local count as its uses. An update such as * `const c = m.get(k); if (c === undefined) m.set(k, v); else c.n++` keeps * the borrow: the write runs only on the branch that does not read `c`. */ export function findMapReadBorrows( @@ -264,6 +266,7 @@ export function findMapReadBorrows( stmt.kind === "varDecl" && stmt.init?.kind === "mapIntrinsic" && heldReceiver(stmt.init.receiver) && + effects.preserves(stmt.init) && candidate(stmt) && walk(list.slice(index + 1), new Set([stmt.localId]), false) !== null ) diff --git a/packages/compiler/test/map-get-borrow-emission.test.ts b/packages/compiler/test/map-get-borrow-emission.test.ts index eba430453..4e3c91766 100644 --- a/packages/compiler/test/map-get-borrow-emission.test.ts +++ b/packages/compiler/test/map-get-borrow-emission.test.ts @@ -49,9 +49,20 @@ function temporary(keys: number[]): number { } return total; } +class Holder { + m = new Map([[1, new Counter()]]); + key(): number { + this.m = new Map(); + return 1; + } + read(): number { + const held = this.m.get(this.key()); + return held === undefined ? 0 : held.num; + } +} const numbers = new Map(); count(numbers, [1, 2, 1]); -console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers), aliased(numbers), temporary([1, 2])); +console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers), aliased(numbers), temporary([1, 2]), new Holder().read()); `, ); const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); @@ -72,6 +83,10 @@ console.log(named(new Map([["a", new Counter()]]), "a"), replaced(numbers), alia // does a read from a map that is released after the declaration. for (const name of ["replaced", "aliased", "temporary"]) expect(fn(name)).toContain("@sc_retain_Counter"); + // A key whose evaluation replaces the receiver's field leaves the read + // on the old map, which the statement releases: the read keeps its own + // reference. + expect(fn("_x25_Holder_read")).toContain("@sc_retain_Counter"); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/tests/corpus/8181-map-get-borrows.ts b/tests/corpus/8181-map-get-borrows.ts index 5df7ad226..1a6c71667 100644 --- a/tests/corpus/8181-map-get-borrows.ts +++ b/tests/corpus/8181-map-get-borrows.ts @@ -2,7 +2,8 @@ // release the entry's value: an update on the absent branch, a write, delete // or clear before a later read, a call that may change the map, loops, // string keys, values that are stored, returned or compared, a local declared -// from the read, and a read from a temporary map. +// from the read, a read from a temporary map, and a key that replaces the +// field holding the map. class Counter { num: number; label: string; @@ -153,3 +154,19 @@ function fromTemporary(): string { return held === undefined ? "none" : `${held.num}:${held.label}:${filler.length}`; } console.log("temporary", fromTemporary()); + +// A key whose evaluation replaces the field that holds the map: the read +// uses the old map, which only the statement still holds. +class Holder { + m = new Map([[1, new Counter(9, "nine")]]); + key(): number { + this.m = new Map(); + return 1; + } + read(): string { + const held = this.m.get(this.key()); + const filler = [new Counter(10, "filler"), new Counter(11, "filler")]; + return held === undefined ? "none" : `${held.num}:${held.label}:${filler.length}:${this.m.size}`; + } +} +console.log("replaced by key", new Holder().read()); From 9cb9750f92c6bd3daeb4d1c97ff888367073d9d9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:34:33 -0500 Subject: [PATCH 199/395] Set a trusted build aside while a getter runs outside a visit session A getter or proxy trap that runs during a trusted build outside any visit session now runs with the build's state set aside, as inside a session. A conversion the getter starts, such as a recursive union that opens and ends its own session, no longer clears the mark that keeps the rest of the outer build from trusting its earlier match. --- packages/runtime/src/scr_json.c | 22 ++++++++---------- tests/harness/dyncheck.test.ts | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index b0f4c99c4..9f10368cc 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -10932,9 +10932,9 @@ static void scr_dyn_visit_exit(void) { } /* Getters and proxy traps run user code, which may start conversions of - * its own and catch their failures. It runs with the session set aside, - * so its conversions use their own tables and leave the session's - * entries alone. */ + * its own and catch their failures. It runs with the session and any + * trusted build set aside, so its conversions use their own tables and + * leave the session's entries and the trusted build's state alone. */ typedef struct { size_t depth; ScrDynVisitSet matching, built, viewed; @@ -10977,12 +10977,10 @@ static void scr_dyn_visit_resume(const ScrDynVisitState *saved) { scr_dyn_session_ran_code = true; } -/* Calls a getter for a member read, outside any visit session. */ +/* Calls a getter for a member read, outside any visit session or trusted + * build. */ static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) { - if (scr_dyn_visit_depth == 0) { - if (scr_dyn_trusted) scr_dyn_session_ran_code = true; - return scr_dyn_call(getter, NULL, 0, "getter"); - } + if (scr_dyn_visit_depth == 0 && !scr_dyn_trusted) return scr_dyn_call(getter, NULL, 0, "getter"); ScrDynVisitState saved; scr_dyn_visit_suspend(&saved); ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter"); @@ -10990,12 +10988,10 @@ static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) { return result; } -/* A proxy's get trap for a member read, outside any visit session. */ +/* A proxy's get trap for a member read, outside any visit session or + * trusted build. */ static ScrDyn *scr_dyn_visit_proxy_get(const ScrDyn *d, ScrStr *name) { - if (scr_dyn_visit_depth == 0) { - if (scr_dyn_trusted) scr_dyn_session_ran_code = true; - return scr_dyn_proxy_get(d, name); - } + if (scr_dyn_visit_depth == 0 && !scr_dyn_trusted) return scr_dyn_proxy_get(d, name); ScrDynVisitState saved; scr_dyn_visit_suspend(&saved); ScrDyn *result = scr_dyn_proxy_get(d, name); diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index f38d8237d..8dde55167 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -686,6 +686,46 @@ console.log(JSON.stringify(plain)); ]); }); + test("nested unions: a getter that converts a recursive union keeps a non-recursive build untrusted", async () => { + // The getter converts a recursive union, which opens and ends a visit + // session of its own, before it answers. The outer build still matches + // the getter's second answer in full, and the failure names that union. + const r = await compileAndRun( + "flat-discriminated-getter-session", + `type Tree = { kind: "leaf"; n: number } | { kind: "node"; left: Tree; right: Tree }; +type Inner = { tag: "x"; n: number } | { tag: "y"; s: string }; +type Outer = { kind: "a"; inner: Inner } | { kind: "b"; other: number }; +const v = JSON.parse('{"kind":"a"}') as unknown; +let calls = 0; +let leaves = 0; +Object.defineProperty(v, "inner", { + enumerable: true, + get() { + calls++; + const t = JSON.parse('{"kind":"node","left":{"kind":"leaf","n":1},"right":{"kind":"leaf","n":2}}') as Tree; + if (t.kind === "node") leaves += 2; + return calls === 1 ? JSON.parse('{"tag":"x","n":1}') : JSON.parse('{"tag":"x","n":"bad"}'); + }, +}); +try { + const o = v as Outer; + console.log("ok", JSON.stringify(o)); +} catch (err) { + console.log((err as Error).message); +} +console.log("getter calls", calls, "leaves", leaves); +const plain = JSON.parse('{"kind":"a","inner":{"tag":"y","s":"z"}}') as Outer; +console.log(JSON.stringify(plain)); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout.trimEnd().split("\n")).toEqual([ + "expected object | object at $.inner, got object", + "getter calls 2 leaves 4", + '{"kind":"a","inner":{"tag":"y","s":"z"}}', + ]); + }); + test("array where a record is expected names the mismatch at the root", async () => { const r = await compileAndRun( "root-kind", From 1e799cb60abe10a04db4b2395bbeef8abe40ef55 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:40:30 -0500 Subject: [PATCH 200/395] Answer net.Socket write() with Node's backpressure boolean, and fire 'drain' A TCP socket's write() answered nothing in typed code and true through untyped values, and a 'drain' listener never fired. Node 24 answers false when the bytes the write leaves unsent reach the socket's 65536-byte high-water mark (writeOrBuffer computes it after the socket writes), sets needDrain, and emits 'drain' when the write buffer empties; writes to an ended or destroyed socket answer false. The runtime's write entry points now return that answer, record needDrain, and fire the socket's 'drain' listeners from the flush that empties the buffer, unless the write half is ending or the socket is gone. writableLength reads the unsent bytes and writableNeedDrain the flag. on('drain') and once('drain') register on the typed path and through untyped values, and socket.write() may be used as a value. The fallback node:net declarations type write() as boolean, add the 'drain' event and both properties. RUNTIME_ABI_VERSION goes from 43 to 44: the socket write entry points return a boolean. --- packages/cli/test/native-link-info.test.ts | 4 +- .../ambient/scriptc-node-fallback.d.ts | 19 +++- .../compiler/src/backend/llvm/lib-network.ts | 2 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-server.ts | 59 +++++++++--- .../compiler/src/ir/builtin-signatures.ts | 20 +++- .../test/native-codegen-integration.test.ts | 4 +- .../7515-net-socket-backpressure.ts.json | 6 ++ .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_net.c | 94 ++++++++++++++++--- packages/runtime/src/scr_net.h | 12 ++- tests/corpus/7515-net-socket-backpressure.ts | 47 ++++++++++ .../7515-net-socket-backpressure.ts.json | 1 + .../self-hosting-native-toolchain.test.ts | 2 +- 20 files changed, 238 insertions(+), 50 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json create mode 100644 tests/corpus/7515-net-socket-backpressure.ts create mode 100644 tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index b4b11fa95..ef07c838d 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v43"); + expect(error?.stderr).toContain("scr_runtime_abi_v44"); }); }); diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 1924baece..463808a1a 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -5322,10 +5322,10 @@ declare module "node:zlib" { * with the real port discoverable as address().port (the composed read * is the lowered form — bare address() has no lowering); close([cb]) * firing once connections drain; socket write/end/destroy/pipe and the - * data/end/close/error/connect events (on and once — listeners take at - * most the declared parameter). write/end return void here (Node returns - * the backpressure boolean / the socket); on/once return void (chaining - * is fenced). connect's host is a numeric IP or "localhost" (pinned to + * data/end/close/error/connect/drain events (on and once — listeners + * take at most the declared parameter). write returns Node's + * backpressure boolean; end returns void here (Node returns the socket); + * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, * and an 'error' with no listener exits 1 like an unhandled EventEmitter * 'error'. */ @@ -5357,7 +5357,13 @@ declare module "net" { readonly bytesWritten: number; /* write's callback runs once the data is flushed, with null; end's * once the socket finishes. */ - write(data: string | Uint8Array, callback?: (err?: Error | null) => void): void; + /* false when the bytes this write left unsent reach the 65536-byte + * high-water mark, as in Node; 'drain' fires when they are all sent. */ + write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + /* the bytes written and not yet sent */ + readonly writableLength: number; + /* a write answered false and 'drain' has not fired yet */ + readonly writableNeedDrain: boolean; end(callback?: () => void): void; end(data: string | Uint8Array, callback?: () => void): void; destroy(): void; @@ -5405,6 +5411,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" @@ -5429,6 +5436,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" @@ -5452,6 +5460,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index d49bbde14..35eb885cd 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -309,6 +309,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) e.fn === "net.sockOnClose" || e.fn === "net.sockOnConnect" || e.fn === "net.sockOnTimeout" || + e.fn === "net.sockOnDrain" || e.fn === "net.sockOnReadable" || e.fn === "net.sockOnReady" || e.fn === "http.reqOnEnd" || @@ -329,6 +330,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) "net.sockOnClose": "scr_net_sock_on_close", "net.sockOnConnect": "scr_net_sock_on_connect", "net.sockOnTimeout": "scr_net_sock_on_timeout", + "net.sockOnDrain": "scr_net_sock_on_drain", "net.sockOnReadable": "scr_net_sock_on_readable", "net.sockOnReady": "scr_net_sock_on_ready", "http.reqOnEnd": "scr_http_req_on_end", diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index def2dd6bf..2548b75d5 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1337,6 +1337,8 @@ export const LIB_FN_SYMS: Record = { "net.sockResetAndDestroy": "scr_net_sock_reset_and_destroy", "net.sockAddress": "scr_net_sock_address", "net.sockBytesWritten": "scr_net_sock_bytes_written", + "net.sockWritableLength": "scr_net_sock_writable_length", + "net.sockWritableNeedDrain": "scr_net_sock_writable_need_drain", "net.sockAddrField": "scr_net_sock_addr_field", "net.sockBytesRead": "scr_net_sock_bytes_read", "net.sockConnecting": "scr_net_sock_connecting", diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index d8e256349..7ac0877eb 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 43 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v43" as const; +export const RUNTIME_ABI_VERSION = 44 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v44" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 5da2b8e59..650e7c543 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index a87fabd2e..443c22d9e 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -129,7 +129,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 7359b56a8..614d6a206 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -2425,7 +2425,9 @@ function lowerNetSocketMethodCall( }; } if (name === "write" || name === "end") { - requireStatementPosition(lowerer, call, `socket.${name}(...)`); + // write() answers Node's backpressure boolean; end() stays a statement + if (name === "end") requireStatementPosition(lowerer, call, `socket.${name}(...)`); + const resultT = name === "write" ? BOOL : VOID; // write(chunk, encoding) — the two-argument encoding form: 'buffer' // beside a string chunk is Writable's encoding check (a synchronous // ERR_UNKNOWN_ENCODING TypeError, "Unknown encoding: buffer" — the @@ -2451,7 +2453,7 @@ function lowerNetSocketMethodCall( const socket = coerceToHandle(lowerer, access.expression, NETSOCKET_T); const chunk = lowerer.lowerExpr(args[0]!); const fn: IrLibFn = chunk.type.kind === "string" ? "net.sockWrite" : "net.sockWriteBytes"; - return { kind: "libCall", fn, args: [socket, chunk], type: VOID, loc }; + return { kind: "libCall", fn, args: [socket, chunk], type: BOOL, loc }; } } } @@ -2510,7 +2512,7 @@ function lowerNetSocketMethodCall( NARROW_DATA_HINT, ); } - main = { kind: "libCall", fn, args: [receiver(), data], type: VOID, loc }; + main = { kind: "libCall", fn, args: [receiver(), data], type: resultT, loc }; } const cb = lowerer.lowerExpr(last); let register: IrExpr; @@ -2531,8 +2533,24 @@ function lowerNetSocketMethodCall( loc, }; } - stmts.push({ kind: "exprStmt", expr: main, loc }); - return { kind: "seqExpr", stmts, result: register, type: VOID, loc }; + if (resultT.kind === "void") { + stmts.push({ kind: "exprStmt", expr: main, loc }); + return { kind: "seqExpr", stmts, result: register, type: VOID, loc }; + } + // write(data, cb): the data is written, the callback registered, and + // the write's answer is the value + const okLocal = lowerer.declareHiddenLocal("%sockWriteOk", BOOL); + stmts.push( + { kind: "varDecl", localId: okLocal.id, init: main, loc }, + { kind: "exprStmt", expr: register, loc }, + ); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: okLocal.id, type: BOOL, loc }, + type: BOOL, + loc, + }; } const maxArgs = name === "write" ? 1 : 1; const minArgs = name === "write" ? 1 : 0; @@ -2552,17 +2570,17 @@ function lowerNetSocketMethodCall( const data = lowerer.lowerExpr(args[0]!); if (data.type.kind === "string") { const fn: IrLibFn = name === "write" ? "net.sockWrite" : "net.sockEndStr"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } if (data.type.kind === "bytes" && data.type.elem === "u8") { const fn: IrLibFn = name === "write" ? "net.sockWriteBytes" : "net.sockEndBytes"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } if (data.type.kind === "dyn") { // An untyped JS payload (a helper's parameter): the runtime // dispatches STR/BYTES and throws Node's chunk TypeError otherwise. const fn: IrLibFn = name === "write" ? "net.sockWriteDyn" : "net.sockEndDyn"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } lowerer.noLowering( `${name} of '${lowerer.fmt(data.type)}' data`, @@ -2944,6 +2962,7 @@ function lowerNetSocketMethodCall( event === "connect" || event === "ready" || event === "timeout" || + event === "drain" || event === "readable" ) { const { cb } = lowerCallbackArg( @@ -2962,11 +2981,13 @@ function lowerNetSocketMethodCall( ? "net.sockOnClose" : event === "timeout" ? "net.sockOnTimeout" - : event === "readable" - ? "net.sockOnReadable" - : event === "ready" - ? "net.sockOnReady" - : "net.sockOnConnect"; + : event === "drain" + ? "net.sockOnDrain" + : event === "readable" + ? "net.sockOnReadable" + : event === "ready" + ? "net.sockOnReady" + : "net.sockOnConnect"; return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } if (event === "lookup") { @@ -3957,6 +3978,18 @@ export function lowerServerProperty( const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); return { kind: "libCall", fn: "net.sockBytesWritten", args: [receiver], type: F64, loc }; } + if ( + recvKind === "netSocket" && + lowerer.isStdlibMember(expr) && + (expr.name.text === "writableLength" || expr.name.text === "writableNeedDrain") + ) { + // The write buffer's unsent bytes, and whether a write answered false + // and 'drain' has not fired yet. + const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); + return expr.name.text === "writableLength" + ? { kind: "libCall", fn: "net.sockWritableLength", args: [receiver], type: F64, loc } + : { kind: "libCall", fn: "net.sockWritableNeedDrain", args: [receiver], type: BOOL, loc }; + } if (recvKind === "netSocket" && lowerer.isStdlibMember(expr) && expr.name.text === "readable") { // true until the read half is done (peer FIN / destroy). const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e768f257f..7183b72a1 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1543,15 +1543,18 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * true } and a callback taking an address string or list, then dials * the answers in order. MAY THROW (host validation, a lookup throw). */ "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** socket.write: Node's Writable answer — false when the bytes the write + * left unsent reach the 65536-byte high-water mark ('drain' follows), + * and on an ended or closed socket. */ + "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: BOOL }, + "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: BOOL }, "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a * typed socket): the runtime dispatches STR/BYTES and throws Node's * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ - "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: BOOL }, "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, @@ -2009,6 +2012,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID, }, + /** on('drain') / once('drain'): fires after a write answered false, once + * the write buffer empties. */ + "net.sockOnDrain": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, /** socket.encrypted — `boolean | undefined`: the true arm iff the * socket carries a TLS transport (Node types `encrypted: true` on @@ -2135,6 +2144,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** socket.address(): { address, family, port } or {} as a dyn. */ "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, + /** socket.writableLength: the bytes written and not yet sent. */ + "net.sockWritableLength": { argTypes: [NETSOCKET_T], result: F64 }, + /** socket.writableNeedDrain: a write answered false and 'drain' has not + * fired, with the write half open. */ + "net.sockWritableNeedDrain": { argTypes: [NETSOCKET_T], result: BOOL }, /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), * localFamily (4): dyn values, undefined when unknown. */ "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index 8e8439749..dc3f94e6c 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v43"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v44"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v43"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v44"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json new file mode 100644 index 000000000..16f6edb8d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7515-net-socket-backpressure.ts" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index 84c3b83b6..7858c9782 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -366,7 +366,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 545c6651b..b770d81b6 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -469,7 +469,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 2f7c91b7d..ad49e4f6f 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v43(void) {} +void scr_runtime_abi_v44(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index 5d6d80778..8e50ec0ca 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v43(void); +void scr_runtime_abi_v44(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index bd1233978..91e8461b7 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -565,6 +565,11 @@ struct ScrNetSocket { ScrNetLs lookup_ls; struct ScrNetLookupEv *lookup_evs; ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls; + /* Writable backpressure: a user write answered false (the bytes it left + * unsent reached SCR_NET_WRITE_HWM), so 'drain' fires once the write + * buffer empties */ + ScrNetLs drain_ls; + bool need_drain; /* Flow control (pause()/resume()): user_paused holds reads OFF even * with consumers (kernel/TCP backpressure is the buffer); flowing * counts as a consumer even with no 'data' listener @@ -747,6 +752,7 @@ void scr_net_sock_release(ScrNetSocket *s) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->drain_ls); scr_net_ls_drop(&s->finish_ls); scr_net_ls_drop(&s->wcb_ls); scr_net_ls_drop(&s->ready_ls); @@ -1103,6 +1109,40 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { s->wlen += len; } +/* Node's net.Socket writableHighWaterMark (the Writable default, + * getDefaultHighWaterMark()). */ +#define SCR_NET_WRITE_HWM 65536 + +/* 'drain' once a false write's bytes are all out: Node's afterWrite emits + * it when needDrain is set and nothing is pending, unless the stream is + * ending or destroyed. */ +static void scr_net_sock_maybe_drain(ScrNetSocket *s) { + if (!s->need_drain || s->wlen > s->whead) return; + if (s->wr_ending || s->close_emitted || s->fd < 0) return; + s->need_drain = false; + scr_net_fire0_this(&s->drain_ls, s, SCR_DYNH_NET_SOCKET); +} + +static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len); + +/* A user write: Writable.write's answer, as Node 24's writeOrBuffer + * computes it after the socket writes — the bytes the write path left + * unsent below the high-water mark — with needDrain set when it is false. + * A write the kernel takes at once answers true whatever its size; the + * buffered bytes of a false one drain from the flush. A write to an ended + * or closed socket answers false. */ +static bool scr_net_sock_user_write(ScrNetSocket *s, const char *data, size_t len) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + bool pre_dial = w->fd < 0 && (w->dial_deferred || w->lookup_wait) && !w->wr_ending && + !w->close_emitted; + bool live = pre_dial || !(w->fd < 0 || w->wr_ending || w->close_emitted); + scr_net_sock_write_raw(s, data, len); + if (!live) return false; + bool ok = w->wlen - w->whead < SCR_NET_WRITE_HWM; + if (!ok) w->need_drain = true; + return ok; +} + /* The write path shared by write()/end(data)/pipe delivery. Ignored on a * dead or ended write half (Node fires 'error' there; this slice drops — * SEMANTICS.md documents the bound). */ @@ -1252,6 +1292,9 @@ static void scr_net_sock_flush(ScrNetSocket *s) { s->whead = s->wlen = 0; scr_net_sock_update_write(s); scr_net_sock_maybe_finish_write(s); + /* Node's afterWrite for a write the socket completed later: 'drain' + * now, unless the write half is ending or the socket is gone */ + scr_net_sock_maybe_drain(s); } } @@ -3150,16 +3193,40 @@ void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowe /* ── the socket surface ──────────────────────────────────────────────── */ -void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) { +bool scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) { char *owned; const char *bytes = scr_str_utf8(data, &owned); - scr_net_sock_write_raw(s, bytes, data->len); + bool ok = scr_net_sock_user_write(s, bytes, data->len); scr_str_utf8_end(owned); + return ok; } -void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { +bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { SCR_BYTES_SNAPSHOT(data); - scr_net_sock_write_raw(s, (const char *)data->data, (data->len * scr_bytes_elem_size(data->elem))); + return scr_net_sock_user_write(s, (const char *)data->data, + (data->len * scr_bytes_elem_size(data->elem))); +} + +/* socket.writableLength: the bytes written and not yet sent. */ +double scr_net_sock_writable_length(ScrNetSocket *s) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + return (double)(w->wlen - w->whead); +} + +/* socket.writableNeedDrain: a write answered false and 'drain' has not + * fired, on a socket whose write half is open. */ +bool scr_net_sock_writable_need_drain(ScrNetSocket *s) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + return w->need_drain && !w->wr_ending && !w->close_emitted && w->fd >= 0; +} + +/* on('drain') / once('drain'). */ +void scr_net_sock_on_drain(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) { + if (s->close_emitted) { + scr_closure_release(cb); + return; + } + scr_net_ls_add(&s->drain_ls, cb, NULL, once); } void scr_net_sock_end(ScrNetSocket *s) { @@ -5860,6 +5927,7 @@ static void scr_net_cleanup_atexit(void) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->drain_ls); scr_net_ls_drop(&s->lookup_ls); /* Tear down the protocol layer's parser NOW: an in-flight http * exchange (a handler that threw before res.end()) holds req/res, @@ -5984,8 +6052,7 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho /* fires once when the FIN goes out — once either way */ scr_net_sock_on_finish(s, scr_dyn_listener_closure0(cb)); } else if (scr_net_dynh_name_is(name, "drain")) { - /* This surface never backpressures (write answers true) — an - * accepted, never-fired registration is the consistent answer. */ + scr_net_sock_on_drain(s, scr_dyn_listener_closure0(cb), once); } else if (scr_net_dynh_tls != NULL && name->kind == SCR_DYN_STR && scr_net_dynh_tls->on(s, name->v.str->data, cb, once)) { /* a TLS event name ('secureConnect'), handled by the TLS unit */ @@ -6008,13 +6075,13 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk); return NULL; } - if (chunk->kind == SCR_DYN_STR) scr_net_sock_write_str(s, chunk->v.str); - else scr_net_sock_write_bytes(s, chunk->v.bytes); + bool ok = chunk->kind == SCR_DYN_STR ? scr_net_sock_write_str(s, chunk->v.str) + : scr_net_sock_write_bytes(s, chunk->v.bytes); if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) { /* write(chunk, cb): fires when the buffer drains (sweep-deferred) */ scr_net_sock_on_write_flush(s, scr_dyn_listener_closure0(args[1])); } - return scr_dyn_new_bool(true); /* backpressure is not modeled (SEMANTICS.md) */ + return scr_dyn_new_bool(ok); } if (strcmp(method, "end") == 0) { const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined(); @@ -6500,10 +6567,11 @@ void scr_net_dyn_install(void) { * STR/BYTES dispatch onto the typed entries; undefined/null end()s plain * (Node accepts them); anything else throws Node's ERR_INVALID_ARG_TYPE * chunk TypeError. Both borrowed. */ -void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d) { - if (d->kind == SCR_DYN_STR) scr_net_sock_write_str(s, d->v.str); - else if (d->kind == SCR_DYN_BYTES) scr_net_sock_write_bytes(s, d->v.bytes); - else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d); +bool scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d) { + if (d->kind == SCR_DYN_STR) return scr_net_sock_write_str(s, d->v.str); + if (d->kind == SCR_DYN_BYTES) return scr_net_sock_write_bytes(s, d->v.bytes); + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d); + return false; } void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d) { diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index 95490aaa5..6a41e26d8 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -227,15 +227,21 @@ ScrNetSocket *scr_net_connect_lookup(double port, ScrStr *host /*borrowed*/, ScrClosure *lookup /*moves*/, void *answer_fn); /* +1 */ void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowed, nullable*/, ScrArr *ips /*borrowed, nullable*/); -void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); -void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); +/* write(): Node's Writable answer — false when the bytes the write left + * unsent reach the 65536-byte high-water mark, after which 'drain' fires + * when the write buffer empties; false on an ended or closed socket. */ +bool scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); +bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); +double scr_net_sock_writable_length(ScrNetSocket *s); +bool scr_net_sock_writable_need_drain(ScrNetSocket *s); +void scr_net_sock_on_drain(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once); void scr_net_sock_end(ScrNetSocket *s); void scr_net_sock_end_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); /* Checked-dynamic chunk forms (an untyped JS payload into a typed * socket): STR/BYTES dispatch; end() takes undefined/null as no chunk; * anything else throws Node's ERR_INVALID_ARG_TYPE chunk TypeError. */ -void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); +bool scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); void scr_net_sock_destroy(ScrNetSocket *s); /* Flow control (pause/resume — see the struct's flag comments), the diff --git a/tests/corpus/7515-net-socket-backpressure.ts b/tests/corpus/7515-net-socket-backpressure.ts new file mode 100644 index 000000000..a265d3f77 --- /dev/null +++ b/tests/corpus/7515-net-socket-backpressure.ts @@ -0,0 +1,47 @@ +// net.Socket write() backpressure, as in Node 24: write answers false when +// the bytes it leaves unsent reach the 65536-byte high-water mark, which a +// write made before the connection does, writableNeedDrain stays true +// until 'drain', and 'drain' fires once the buffered bytes are sent. An +// echo client that waits for 'drain' when write answers false moves every +// byte. +import { connect, createServer, Socket } from "node:net"; + +const CHUNK = 65536; +const TOTAL = 64 * CHUNK; +const chunk = Buffer.alloc(CHUNK, 120); + +const server = createServer((socket: Socket) => { + socket.pipe(socket); +}); + +server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1"); + console.log("small write before connect", client.write("hi"), client.writableLength); + console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); + console.log("need drain", client.writableNeedDrain); + let sent = 70002; + let received = 0; + const pump = (): void => { + while (sent < TOTAL) { + sent += CHUNK; + if (!client.write(chunk)) { + client.once("drain", pump); + return; + } + } + client.end(); + }; + client.once("drain", () => { + console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); + pump(); + }); + client.on("data", (data: Buffer) => { + received += data.length; + }); + client.on("end", () => { + console.log("received", received, "of", sent); + server.close(); + }); +}); diff --git a/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json b/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json new file mode 100644 index 000000000..feabbe802 --- /dev/null +++ b/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 4200 } diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index 8470ce04e..f8d901ee8 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -110,7 +110,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From e38f7c329d4119ef3629e5eed802cbe6f8176d57 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:56:48 -0500 Subject: [PATCH 201/395] Speculate floor-mods and products by powers of two as masks and shifts In a speculative integer loop, `v - Math.floor(v / 2^n) * 2^n` computed the floor division as an arithmetic shift, multiplied it back by 2^n with overflow and -0 checks, and subtracted with range checks. For a safe integer v and n in [0, 52] the result is the floor-mod, which is v's low n bits: the fast copy now computes it as `v & (2^n - 1)`, which lies in [0, 2^n) and needs no check. A product with a power of two (a literal or `Math.pow(2, n)`) becomes a shift, with one test that the other factor's magnitude is at most (2^53 - 1) >> n; its zero result is +0. --- .../src/backend/llvm/speculative-integers.ts | 62 +++++++++++++ .../test/speculative-loop-emission.test.ts | 24 +++++ .../8244-speculative-floor-mod-powers.ts.json | 6 ++ .../8244-speculative-floor-mod-powers.ts | 88 +++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8244-speculative-floor-mod-powers.ts.json create mode 100644 tests/corpus/8244-speculative-floor-mod-powers.ts diff --git a/packages/compiler/src/backend/llvm/speculative-integers.ts b/packages/compiler/src/backend/llvm/speculative-integers.ts index 07e5e3721..971bd00cb 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.ts @@ -187,6 +187,37 @@ function exactPowerOfTwo(value: number): number | null { return k >= 0 && k <= 52 && 2 ** k === value ? k : null; } +/** The same speculated operand written twice: a local, a literal, or a + * power of two of the same exponent. */ +function sameOperand(a: IrExpr, b: IrExpr): boolean { + if (a.kind === "varRef") return b.kind === "varRef" && a.localId === b.localId; + if (a.kind === "numLit") return b.kind === "numLit" && Object.is(a.value, b.value); + return ( + a.kind === "libCall" && + b.kind === "libCall" && + a.fn === "math.pow" && + b.fn === "math.pow" && + powerOfTwo(a) && + powerOfTwo(b) && + sameOperand(a.args[1]!, b.args[1]!) + ); +} + +/** `a - Math.floor(a / p) * p` (either factor order) with p a power of two + * and `a` a local or literal: the floor-mod of `a` by p. */ +function floorModPowerOfTwo(e: IrExpr): { value: IrExpr; power: IrExpr } | null { + if (e.kind !== "bin" || e.op !== "-" || e.right.kind !== "bin" || e.right.op !== "*") return null; + const { left, right } = e.right; + const [floor, power] = + left.kind === "libCall" && left.fn === "math.floor" ? [left, right] : [right, left]; + if (floor.kind !== "libCall" || floor.fn !== "math.floor" || floor.args.length !== 1) return null; + const quotient = floor.args[0]!; + if (quotient.kind !== "bin" || quotient.op !== "/") return null; + if (!powerOfTwo(power) || !sameOperand(quotient.right, power)) return null; + if (e.left.kind !== "varRef" && e.left.kind !== "numLit") return null; + return sameOperand(quotient.left, e.left) ? { value: e.left, power } : null; +} + /** Whether `e` is a power of two speculation can shift by: * `Math.pow(2, n)` or a literal 2^k with k in [0, 52]. */ function powerOfTwo(e: IrExpr): boolean { @@ -950,6 +981,37 @@ export function emitSpeculativeInteger( } case "bin": { if (e.op !== "+" && e.op !== "-" && e.op !== "*" && e.op !== "%") break; + // The floor-mod of a safe integer by 2^n (n in [0, 52]) is its low n + // bits, also for a negative value: in [0, 2^n), and never -0. + const mod = floorModPowerOfTwo(e); + if (mod) { + const value = emitSpeculativeInteger(host, mod.value, bail, reads, integral); + const n = exponentOf(host, mod.power, bail, reads, integral); + const mask = B.tmp(), + low = B.tmp(); + B.line(`${mask} = sub i64 ${shiftOne(host, n)}, 1`); + B.line(`${low} = and i64 ${value}, ${mask}`); + return low; + } + // A product with 2^n shifts: it is safe iff the other factor's + // magnitude is at most (2^53 - 1) >> n, and a zero product of a safe + // integer and a positive power is +0. + if (e.op === "*" && (powerOfTwo(e.right) || powerOfTwo(e.left))) { + const [factor, power] = powerOfTwo(e.right) ? [e.left, e.right] : [e.right, e.left]; + const value = emitSpeculativeInteger(host, factor, bail, reads, integral); + const n = exponentOf(host, power, bail, reads, integral); + const limit = B.tmp(), + magnitude = B.tmp(), + outside = B.tmp(), + product = B.tmp(); + B.line(`${limit} = ashr i64 ${SAFE}, ${n}`); + host.declare("declare i64 @llvm.abs.i64(i64, i1)"); + B.line(`${magnitude} = call i64 @llvm.abs.i64(i64 ${value}, i1 true)`); + B.line(`${outside} = icmp ugt i64 ${magnitude}, ${limit}`); + bail(outside); + B.line(`${product} = shl nsw i64 ${value}, ${n}`); + return product; + } const a = emitSpeculativeInteger(host, e.left, bail, reads, integral); const b = emitSpeculativeInteger(host, e.right, bail, reads, integral); const result = B.tmp(); diff --git a/packages/compiler/test/speculative-loop-emission.test.ts b/packages/compiler/test/speculative-loop-emission.test.ts index 748fd50c5..d26a0c96c 100644 --- a/packages/compiler/test/speculative-loop-emission.test.ts +++ b/packages/compiler/test/speculative-loop-emission.test.ts @@ -159,3 +159,27 @@ if (isMainThread) { expect(order.slice(slow, fast).some((l) => l.startsWith("spec.fast"))).toBe(true); expect(order.slice(fast).some((l) => l.startsWith("spec.fast"))).toBe(true); }); + +test("a floor-mod by a power of two speculates as a mask and a product by one as a shift", async () => { + const llvm = await emitLlvm(` +function trim(value: number, bits: number): number { + return value - Math.floor(value / Math.pow(2, bits)) * Math.pow(2, bits); +} +function key(bytes: Uint8Array, bits: number): number { + let k = 0; + for (let i = 0; i < bytes.length; i++) k = trim(k, bits) * 4 + bytes[i]!; + return k; +} +console.log(key(new Uint8Array([1, 2, 3, 0]), 6)); +`); + const fn = /^define internal double @sc_bf_\w*key[\w.]*\([^]*?^}/m.exec(llvm)?.[0] ?? ""; + const fast = fn.slice(fn.search(/^spec\.fast\d+:/m)); + expect(fast.length).toBeLessThan(fn.length); + // trim(k, bits): the low `bits` bits; * 4: a shift by two. + expect(fast).toMatch(/= sub i64 %\S+, 1\n\s+%\S+ = and i64 %\S+, %\S+\n/); + expect(fast).toMatch(/= shl nsw i64 %\S+, 2\n/); + // No floor division of k (an arithmetic shift of it) and no checked + // multiply remain. + expect(fast).not.toMatch(/ashr i64 %/); + expect(fast).not.toContain("@llvm.smul.with.overflow.i64"); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8244-speculative-floor-mod-powers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8244-speculative-floor-mod-powers.ts.json new file mode 100644 index 000000000..65e7ccb38 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8244-speculative-floor-mod-powers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8244-speculative-floor-mod-powers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8244-speculative-floor-mod-powers.ts b/tests/corpus/8244-speculative-floor-mod-powers.ts new file mode 100644 index 000000000..db28dcde0 --- /dev/null +++ b/tests/corpus/8244-speculative-floor-mod-powers.ts @@ -0,0 +1,88 @@ +// Integer loops whose writes take a floor-mod by a power of two +// (`v - Math.floor(v / 2^n) * 2^n`) or multiply by one: negative and zero +// values, exponents 0, 52 and above, literal powers, both factor orders, +// fractional values and results past 2^53, which leave the integer copy. + +function trim(value: number, bits: number): number { + return value - Math.floor(value / Math.pow(2, bits)) * Math.pow(2, bits); +} + +function low3(value: number): number { + return value - Math.floor(value / 8) * 8; +} + +function rolling(bytes: Uint8Array, bits: number): number { + let key = 0; + let sum = 0; + for (let i = 0; i < bytes.length; i++) { + key = trim(key, bits) * 4 + bytes[i]!; + sum = (sum + key) % 1000003; + } + return key * 7 + sum; +} + +function stepped(start: number, step: number, bits: number, count: number): number { + let v = start; + let h = 0; + for (let i = 0; i < count; i++) { + v = trim(v, bits) * 3 - step; + h = (h * 31 + v) % 999983; + } + return h * 1000 + v; +} + +function literal(start: number, count: number): number { + let v = start; + let h = 0; + for (let i = 0; i < count; i++) { + v = low3(v) * 16 - i; + h = (h * 17 + v) % 65521; + } + return h * 100 + v; +} + +function scaled(start: number, bits: number, count: number): number { + let v = start; + let h = 0; + for (let i = 0; i < count; i++) { + v = Math.pow(2, bits) * v + 1; + h = (h * 7 + v) % 10007; + } + return h + v; +} + +function shifted(start: number, count: number): number { + let v = start; + let h = 0; + for (let i = 0; i < count; i++) { + v = v * 1024 - i; + h = (h * 3 + v) % 101; + } + return h + v; +} + +const bytes = new Uint8Array(4000); +for (let i = 0; i < bytes.length; i++) bytes[i] = (i * 7 + (i >> 3)) & 3; + +for (const bits of [0, 1, 2, 10, 22, 34, 50, 52, 53, 60]) console.log("rolling", bits, rolling(bytes, bits)); +for (const [start, step, bits] of [ + [0, 1, 3], + [-5, 3, 2], + [-1, -1, 0], + [123456789, 7, 20], + [-9007199254740991, 1, 52], + [2.5, 1, 4], + [7, 0.5, 3], +] as const) + console.log("stepped", start, step, bits, stepped(start, step, bits, 50)); +for (const start of [0, 13, -13, 4096, -7.25]) console.log("literal", start, literal(start, 40)); +for (const [start, bits] of [ + [1, 3], + [-2, 5], + [0, 52], + [3, 20], + [-1, 0], +] as const) + console.log("scaled", start, bits, scaled(start, bits, 6)); +for (const start of [0, 1, -1, 3, 1e6]) console.log("shifted", start, shifted(start, 7)); +console.log(Object.is(stepped(0, 0, 3, 5), 0), Object.is(scaled(0, 2, 3), 21 + 1)); From 45171ea02cb01c5d8b2683a7195623464d8d8bcc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:59:10 -0500 Subject: [PATCH 202/395] Use a built-in typed-array constructor as its own species before the species symbol exists A typed array whose constructor property is set to another built-in typed-array constructor now creates that kind from map, filter, slice and subarray when the program has not evaluated Symbol.species. --- packages/runtime/src/scr_json.c | 7 ++++-- .../8321-typed-array-constructor-kind.js | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/corpus/8321-typed-array-constructor-kind.js diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index f06080ef6..6e0ca43c0 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5345,9 +5345,12 @@ static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { *failed = true; return NULL; } - ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + /* Before the program creates the species symbol, no constructor can have + * a Symbol.species property of its own. */ + ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) + : scr_dyn_retain(scr_dyn_undefined()); if (species && species->kind == SCR_DYN_UNDEF && scr_bytes_ctor_elem(constructor) >= 0 && - !scr_bytes_ctor_own_species(constructor)) { + (!scr_species_symbol || !scr_bytes_ctor_own_species(constructor))) { /* A built-in typed-array constructor inherits %TypedArray%'s species * getter, which returns the constructor itself. */ scr_dyn_release(species); diff --git a/tests/corpus/8321-typed-array-constructor-kind.js b/tests/corpus/8321-typed-array-constructor-kind.js new file mode 100644 index 000000000..277128a93 --- /dev/null +++ b/tests/corpus/8321-typed-array-constructor-kind.js @@ -0,0 +1,23 @@ +// A built-in typed-array constructor set as a view's `constructor` is the +// species of map, filter, slice and subarray when the program never names +// the species symbol. +function kinds(TA, Other) { + const a = new TA([1, 2, 3, 4]); + a.constructor = Other; + const m = a.map((v) => v * 1.5), f = a.filter((v) => v > 1), s = a.slice(1, 3); + console.log(m, f, s, m.constructor === Other, f.constructor === Other, s.constructor === Other); + try { + const sub = a.subarray(0, 2); + console.log(sub, sub.constructor === Other); + } catch (err) { + console.log(err.constructor.name); + } + a.constructor = TA; + console.log(a.map((v) => v / 2).constructor === TA); + a.constructor = {}; + console.log(a.slice().constructor === TA); +} +kinds(Uint8Array, Float64Array); +kinds(Int16Array, Uint8Array); +kinds(Float32Array, Int32Array); +kinds(Float64Array, Uint8ClampedArray); From 0d4df6ff6202ae01094be725890c38c3493ee8d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:14:58 -0500 Subject: [PATCH 203/395] Skip fork discovery in programs that never name node:child_process forkTargetPaths asked the checker for the symbol of every identifier callee in every implementation file, on each root-discovery pass and twice more in preflight, to find calls of child_process.fork. A call is a fork only through a binding whose import or alias chain names the module in a module specifier, or through a '#' specifier a package.json "imports" field maps. namesChildProcess reads every program file once per program, outside Node's type declarations and scriptc's own declaration files, which declare the module and alias it only as node:child_process. Implementation files are read from their syntax trees, which discovery fetches anyway; other files as the host serves them to the server (the new Ts7Program.getSourceText), without fetching their syntax trees. When no file holds "child_process" in its text, an escape or line continuation that spells it, or an "imports" entry that names it, forkTargetPaths answers no targets without querying the checker. The discovered fork targets are unchanged. --- .../compiler/src/frontend/fork-target.test.ts | 53 +++++++++++++ packages/compiler/src/frontend/fork-target.ts | 77 ++++++++++++++++++- .../compiler/src/frontend/program-analysis.ts | 3 + packages/compiler/src/frontend/resolve.ts | 6 ++ .../compiler/src/frontend/ts7/program-host.ts | 52 ++++++++----- 5 files changed, 172 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index 5e56cea5d..ffe01ee47 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -65,3 +65,56 @@ test("fork discovery batches only candidate bindings and preserves aliases and s rmSync(directory, { recursive: true, force: true }); } }); + +/** forkTargetPaths over the entry of a program made of `files`, and + * whether it asked the checker for any binding. */ +function discover(files: Record): { targets: string[]; queried: boolean } { + const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-gate-")); + for (const [name, text] of Object.entries(files)) writeFileSync(join(directory, name), text); + const entry = join(directory, "main.ts"); + const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); + const program = host.createProgram([entry, fallbackDtsPath()], { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }); + const batch = vi.spyOn(program.getTypeChecker(), "prefetchSymbolNodesExact"); + try { + const targets = forkTargetPaths(program, [program.getSourceFile(entry)!]).map((path) => + path.slice(directory.length + 1), + ); + return { targets, queried: batch.mock.calls.length > 0 }; + } finally { + batch.mockRestore(); + program.dispose(); + host.close(); + rmSync(directory, { recursive: true, force: true }); + } +} + +test("fork discovery asks the checker nothing when no file names node:child_process", () => { + expect( + discover({ + "main.ts": + 'function fork(url: URL) { return url; }\nfork(new URL("./child.ts", import.meta.url));\n', + }), + ).toEqual({ targets: [], queried: false }); +}); + +test("fork discovery reads a module specifier spelled with escapes", () => { + expect( + discover({ + "main.ts": + 'import { fork } from "node:child\\u005fprocess";\nfork(new URL("./child.ts", import.meta.url));\n', + }), + ).toEqual({ targets: ["child.ts"], queried: true }); +}); + +test("fork discovery reads a package imports entry that maps node:child_process", () => { + const result = discover({ + "package.json": JSON.stringify({ type: "module", imports: { "#cp": "node:child_process" } }), + "main.ts": 'import { fork } from "#cp";\nfork(new URL("./child.ts", import.meta.url));\n', + }); + expect(result.queried).toBe(true); +}); diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 38c8f5f07..93fc7c244 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -1,7 +1,14 @@ import { resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import * as ts from "./ts7/adapter.js"; -import { fallbackDtsPath, isNodeTypesPath, tsgoPath } from "./dts-paths.js"; +import { + ambientDtsPath, + fallbackDtsPath, + isNodeTypesPath, + overridesDtsPath, + tsgoPath, +} from "./dts-paths.js"; +import { nearestPackageImports } from "./resolve.js"; import { canonicalBuiltinModule } from "./builtin-modules.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; @@ -292,9 +299,77 @@ export function forkCallModulePath(program: ts.Program, call: ts.CallExpression) return staticForkModulePath(program, call.arguments.nodes[0]); } +/** The ways a string or template literal can spell one character of a + * module name: the character, \xHH, \uHHHH, \u{H…}, a legacy octal escape, + * or the character after a backslash where that is not another escape. */ +function spelledCharacter(ch: string): string { + const code = ch.charCodeAt(0); + const hex = code.toString(16); + const forms = [ + ch, + "\\\\x" + hex.padStart(2, "0"), + "\\\\u" + hex.padStart(4, "0"), + "\\\\u\\{0*" + hex + "\\}", + "\\\\" + code.toString(8), + ]; + if (!"bfnrtvux0123456789".includes(ch)) forms.push("\\\\" + ch); + return "(?:" + forms.join("|") + ")"; +} + +/** "child_process" with escapes and line continuations in any of its + * characters (case-insensitive, a superset). */ +const SPELLED_CHILD_PROCESS = new RegExp( + [..."child_process"].map(spelledCharacter).join("(?:\\\\(?:\\r\\n|[\\n\\r\\u2028\\u2029]))*"), + "i", +); + +/** Whether a call in the program can reach node:child_process's fork. A + * fork binding is an import or alias whose chain names the module in a + * module specifier (a string), or a '#' specifier the nearest package.json + * "imports" field maps. Node's type declarations and scriptc's own declare + * the module, and alias it only as node:child_process, so they are not + * read; any other file that names the module in a string holds + * "child_process" in its text, or an escape that spells it. */ +function namesChildProcess(program: ts.Program): boolean { + const known = program.analysis.namesChildProcess; + if (known !== null) return known; + const own = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + // Implementation files are already fetched; any other file is read as + // the host serves it, without fetching its syntax tree. + const texts = new Map(); + for (const sourceFile of program.getImplementationSourceFiles()) + texts.set(tsgoPath(sourceFile.fileName), sourceFile.text); + const scopes = new Set(); + let names = false; + for (const fileName of program.getSourceFileNames()) { + const path = tsgoPath(fileName); + if (isNodeTypesPath(fileName) || own.has(path)) continue; + const text = texts.get(path) ?? program.getSourceText(fileName) ?? ""; + if ( + text.includes("child_process") || + (text.includes("\\") && SPELLED_CHILD_PROCESS.test(text)) + ) { + names = true; + break; + } + const imports = nearestPackageImports(fileName); + if (imports === undefined || scopes.has(imports)) continue; + scopes.add(imports); + if (JSON.stringify(imports).includes("child_process")) { + names = true; + break; + } + } + program.analysis.namesChildProcess = names; + return names; +} + /** Source-order static fork targets in the supplied files, deduplicated by * resolved path. */ export function forkTargetPaths(program: ts.Program, files: readonly ts.SourceFile[]): string[] { + // Without a binding of node:child_process no call is a fork, and the + // checker queries below are skipped. + if (!namesChildProcess(program)) return []; const targets: string[] = []; const seen = new Set(); for (const sourceFile of files) { diff --git a/packages/compiler/src/frontend/program-analysis.ts b/packages/compiler/src/frontend/program-analysis.ts index d4ec9fa98..475942908 100644 --- a/packages/compiler/src/frontend/program-analysis.ts +++ b/packages/compiler/src/frontend/program-analysis.ts @@ -25,6 +25,8 @@ export class ProgramAnalysis { /** The modules of every import cycle the exact initialization rule admitted. */ readonly exactCycleMembers = new Set(); cycleEarlyBindings: EarlyBindingsMemo | null = null; + /** Whether some program file can name node:child_process (namesChildProcess). */ + namesChildProcess: boolean | null = null; moduleEarlyBindings: EarlyBindingsMemo | null = null; /** Top-level require declarations: the first reference to their bindings * that code running before the require can reach (requireEarlyReference). */ @@ -46,6 +48,7 @@ export class ProgramAnalysis { this.moduleSourceCandidates.clear(); this.exactCycleMembers.clear(); this.cycleEarlyBindings = null; + this.namesChildProcess = null; this.moduleEarlyBindings = null; this.requireEarlyReferences.clear(); this.requireFirstRunnable.clear(); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 3b32dad3a..5b035b6cb 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -562,6 +562,12 @@ export function nearestPkgJsonPath(fromFile: string): string | null { return dir === null ? null : join(dir, "package.json"); } +/** The nearest package scope's "imports" field (undefined without one). */ +export function nearestPackageImports(fromFile: string): unknown { + const dir = nearestPkgDir(dirname(resolve(fromFile))); + return dir === null ? undefined : pkgJsonOf(dir)?.imports; +} + /** The target the nearest package scope's "imports" field maps a '#' * specifier to under Node's ESM runtime conditions: a './' path inside the * package or another package's specifier. Null when no entry maps it. */ diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index e66d598d9..84cbe2474 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -152,33 +152,37 @@ export class Ts7Host { private readonly virtualChanges = new Map(); private readonly closingProjects = new Set(); private readonly api: Ts7Api; + /** The text the server reads for a file (its readFile hook). */ + private readonly readSource: (fileName: string) => string | null; private closed = false; constructor(createApi: Ts7ApiFactory, options?: Ts7HostOptions) { const virtualFiles = this.virtualFiles; const shadow = options?.fsShadow ?? null; + // A virtual file, a shadow's replacement, a package shim's declaration + // entry or the file on disk; null when shadowed out of existence or + // missing. + this.readSource = (fileName) => { + const virtual = virtualFiles.get(tsgoPath(fileName)); + if (virtual !== undefined) return stripSourceBom(virtual); + if (shadow !== null) { + if (shadow.hideFile(fileName)) return null; + const replacement = shadow.readFile(fileName); + if (replacement !== undefined) return stripSourceBom(replacement); + } + // A package scriptc implements itself (package-shims.ts): its + // installed declaration entry re-exports the shim module, in every + // program the host creates. + const shim = packageShimDeclarationText(fileName); + if (shim !== undefined) return shim; + const source = trackedReadFile(fileName); + return source === null ? null : stripSourceBom(source); + }; this.api = createApi({ cwd: options?.cwd ?? process.cwd(), collectTiming: options?.collectTiming ?? false, fs: { - // string => virtual (or shadowed) hit; null => shadowed out of - // existence; undefined => real-FS fallthrough. - readFile: (fileName) => { - const virtual = virtualFiles.get(tsgoPath(fileName)); - if (virtual !== undefined) return stripSourceBom(virtual); - if (shadow !== null) { - if (shadow.hideFile(fileName)) return null; - const replacement = shadow.readFile(fileName); - if (replacement !== undefined) return stripSourceBom(replacement); - } - // A package scriptc implements itself (package-shims.ts): its - // installed declaration entry re-exports the shim module, in every - // program the host creates. - const shim = packageShimDeclarationText(fileName); - if (shim !== undefined) return shim; - const source = trackedReadFile(fileName); - return source === null ? null : stripSourceBom(source); - }, + readFile: this.readSource, fileExists: (fileName) => { if (virtualFiles.has(tsgoPath(fileName))) return true; if (shadow !== null && shadow.hideFile(fileName)) return false; @@ -193,6 +197,12 @@ export class Ts7Host { }); } + /** The text this host serves for a file (null when it serves none), + * without asking the server for its syntax tree. */ + sourceText(fileName: string): string | null { + return this.readSource(fileName); + } + /** Registers an in-memory file served to tsgo by the virtual-FS hooks. */ addVirtualFile(path: string, content: string): void { this.ensureOpen(); @@ -315,6 +325,12 @@ export class Ts7Program { return this.project.program.getSourceFile(resolve(fileName)); } + /** A file's text as the host serves it, without fetching its syntax + * tree (Ts7Host.sourceText). */ + getSourceText(fileName: string): string | null { + return this.host.sourceText(fileName); + } + getSourceFileNames(): readonly string[] { this.snapshot.ensureActive(); return (this.sourceFileNamesCache ??= this.project.program.getSourceFileNames()); From 080fe5e5e0f3e2660c58a8dd6b64a3ede63827d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:16:32 -0500 Subject: [PATCH 204/395] Record the TypeScript order baseline for the typed-array constructor kind fixture --- .../tests/corpus/8321-typed-array-constructor-kind.js.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json new file mode 100644 index 000000000..6da151987 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8321-typed-array-constructor-kind.js" + ], + "diags": [] +} From 534c08dd2f30fa2ce5611fdbfcce20598a8f8f77 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:34:24 -0500 Subject: [PATCH 205/395] Pack spread tuple fields together with other spreads into optional-element rests A call that spread a fixed tuple and also an array, a Set or another iterable into a `(T | undefined)[]` rest parameter took the general rest path, which converts every value to T, so an undefined tuple field threw ("undefined is not representable in the target union"). Such calls now pack every item in argument order through lowerArraySpreadItems, which takes values already lowered (the tuple's fields) beside argument nodes and keeps undefined values as present undefined elements. A tuple element typed only `undefined`, as in `...["b", undefined]`, has a `null | undefined` field. Stored into an array that keeps undefined elements it is now evaluated and stored as undefined, where it was an internal compiler error; the checker admits it there only as undefined. --- .../src/frontend/lowering/call-arguments.ts | 16 ++++-- .../lowering/containers/array-construction.ts | 53 +++++++++++++++++-- .../8004-optional-element-conversions.ts | 9 ++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index a139bfc64..79e891d4f 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -407,10 +407,18 @@ export function completeArgs( // A `(T | undefined)[]` rest (stored as T elements) packs undefined // arguments as present undefined elements. const restNodes = sources.slice(restAt); - if (restType.optionalElements === true && !restNodes.some(isIr)) { - out.push( - lowerArraySpreadItems(lowerer, restNodes as ts.Expression[], restType.elem, restType, loc), - ); + // Plain arguments and spreads, also together with spread tuple fields + // (pre-lowered): each item, in argument order. + if ( + restType.optionalElements === true && + (!restNodes.some(isIr) || restNodes.some((a) => !isIr(a) && ts.isSpreadElement(a))) + ) { + const items: (ts.Expression | IrExpr)[] = []; + for (const a of restNodes) { + if (isIr(a)) items.push(a.ir); + else items.push(a); + } + out.push(lowerArraySpreadItems(lowerer, items, restType.elem, restType, loc)); return out; } // Tuple-spread values (pre-lowered) and plain arguments pack the same diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 25a98c8a6..63e2e6e07 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -4,6 +4,7 @@ import { generatorDrain } from "../iterator-adapters.js"; import { identityPreservingWidening } from "../coercions/identity.js"; import { liftedFreshSpread } from "../coercions/fresh-spread.js"; import { checkedIterableSpread } from "../checked-iterable-spread.js"; +import { AstNode } from "../../ts7/ast-node.js"; import { InternalCompilerError } from "../../../errors.js"; import { dynUndefinedExpr, @@ -27,7 +28,9 @@ import { type IrType, STRING, type SrcLoc, + UNDEFINED_T, arrayOf, + isUnitType, canConvertToDyn, funcOf, isRefCounted, @@ -57,6 +60,34 @@ import { } from "./callback-arguments.js"; import { classGeneratorIterable } from "../class-iterables.js"; +/** A value whose type admits only null and undefined, stored into an array + * that keeps undefined elements: the checker admits it there only when its + * type is `undefined` (a tuple element typed `undefined` maps to such a + * union), so it is evaluated and stored as undefined. */ +function undefinedForNullish( + lowerer: Lowerer, + value: IrExpr, + arrType: IrType & { kind: "array" }, +): IrExpr { + if (arrType.optionalElements !== true || value.type.kind !== "union") return value; + const arms = lowerer.unions.get(value.type.unionId)?.arms ?? []; + if (arms.length === 0 || !arms.every(isUnitType)) return value; + const loc = value.loc; + const optional: IrType = { + kind: "union", + unionId: lowerer.unions.intern([arrType.elem, UNDEFINED_T]), + }; + const missing = lowerer.wrappedUndefined(optional, loc); + if (!missing) return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: missing, + type: optional, + loc, + }; +} + /** Build the argument array for a mutating/copying operation while keeping * the receiver's scalar payload ABI. A missing read is represented as the * present UNDEFINED state in this temporary, so push/unshift and toSpliced @@ -92,7 +123,8 @@ export function lowerArrayValueItems( loc, }, ]; - for (const value of values) { + for (const item of values) { + const value = undefinedForNullish(lowerer, item, arrType); const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null || optionalElementValue(lowerer, value, elem) @@ -103,16 +135,21 @@ export function lowerArrayValueItems( return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc }; } +/** The items of an array built from argument nodes (a spread copies its + * elements in) and values already lowered (a spread tuple's fields), in + * order. Each value is stored when its turn comes. */ export function lowerArraySpreadItems( lowerer: Lowerer, - nodes: readonly ts.Expression[], + nodes: readonly (ts.Expression | IrExpr)[], elem: IrType, arrType: IrType & { kind: "array" }, loc: SrcLoc, ): IrExpr { const out = lowerer.declareHiddenLocal("%arrayItems", arrType); const outRef = varRef(out.id, arrType, loc); - const plainItems = nodes.filter((node) => !ts.isSpreadElement(node)).length; + const plainItems = nodes.filter( + (node) => !(node instanceof AstNode) || !ts.isSpreadElement(node), + ).length; const length = (): IrExpr => ({ kind: "arrIntrinsic", method: "length", @@ -136,7 +173,15 @@ export function lowerArraySpreadItems( }, ]; for (const node of nodes) { - if (ts.isSpreadElement(node)) { + if (!(node instanceof AstNode)) { + const value = undefinedForNullish(lowerer, node, arrType); + const stored = + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) + ? value + : lowerer.coerceToExpected(value, elem); + body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); + } else if (ts.isSpreadElement(node)) { let source = lowerer.lowerExpr(node.expression); if ( source.type.kind === "set" && diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 07178e9b0..9a4ac55ac 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -49,6 +49,15 @@ function swap(pair: [string, string | undefined]): string { return "v"; } console.log(label("z", ...swapped, swap(swapped), ...swapped)); +// Tuple spreads together with array and Set spreads keep undefined +// elements, and every item is read in argument order. +const mixedPair: [boolean, boolean | undefined] = [true, undefined]; +const mixedMore: (boolean | undefined)[] = [false, undefined]; +console.log(flags(...mixedPair, ...mixedMore), flags(...mixedMore, ...mixedPair), flags(true, ...mixedPair, ...mixedMore, undefined)); +const mixedChanging: [boolean, boolean | undefined] = [true, undefined]; +console.log(flags(...mixedChanging, ...mixedMore, flip(mixedChanging, true), ...mixedChanging)); +const mixedWords: [string, string | undefined] = ["a", undefined]; +console.log(label("z", ...["b", undefined, "c"], ...mixedWords), label("y", ...mixedWords, ...new Set(["s", undefined]))); function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From 50c46bb9de2dfe5ee43604ba5bf0ced4f7bdb7b0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:50:20 -0500 Subject: [PATCH 206/395] Count socket writes as Node's state.length, and read write state from untyped code A socket's write() answer and writableLength counted the bytes left unsent. Node counts each write that is not completely sent in full, so a large write the kernel takes in part still counts its whole length. The socket now keeps its buffered writes in order with their lengths and completes each one when its last byte is sent; write() answers false when those reach the 65536-byte high-water mark. write() now checks whether the write half is open after the send, so a send that fails with EPIPE or ECONNRESET and closes the socket answers false. writableNeedDrain also reads true while a custom lookup or a deferred dial is pending, since writes then buffer until the socket connects. Untyped code reads writableLength and writableNeedDrain, and writableHighWaterMark and readableHighWaterMark read 65536, Node's default, instead of 16384. --- packages/runtime/src/scr_net.c | 103 +++++++++--- tests/corpus/7515-net-socket-backpressure.ts | 157 +++++++++++++++---- 2 files changed, 201 insertions(+), 59 deletions(-) diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 91e8461b7..0fe06cedc 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -516,6 +516,16 @@ struct ScrNetSocket { /* write buffer: [whead, wlen) of wbuf is unsent */ char *wbuf; size_t whead, wlen, wcap; + /* Node's writable state.length: each write with bytes in the buffer + * counts in full until its last byte is sent (a partly sent write + * included). wq[wq_head, wq_n) holds those writes in order, each with + * the offset where its bytes end, counted from wbase, the offset of + * wbuf[0] in everything buffered since the buffer last emptied. */ + struct ScrNetPendingWrite { + size_t end, len; + } *wq; + size_t wq_head, wq_n, wq_cap; + size_t wbase, wpending; /* receive buffer: [rhead, rlen) of rbuf is arrived-but-unconsumed — * fed by paused-mode reads ('readable' consumers) and unshift; drained * FIRST by every consumer (data listeners, the parser attach, the TLS @@ -770,6 +780,7 @@ void scr_net_sock_release(ScrNetSocket *s) { if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s); scr_net_tunnel_free(s->tunnel); free(s->wbuf); + free(s->wq); free(s->rbuf); if (s->pipe_dst) scr_net_sock_release(s->pipe_dst); if (s->conn_pending) scr_net_server_release(s->conn_pending); @@ -1088,13 +1099,34 @@ static void scr_net_sock_maybe_finish_write(ScrNetSocket *s) { else scr_net_sock_update_read(s); /* the drain-to-EOF mode arms here */ } +/* The write buffer emptied (or its bytes moved elsewhere): every pending + * write is complete. */ +static void scr_net_sock_wq_clear(ScrNetSocket *s) { + s->whead = s->wlen = 0; + s->wq_head = s->wq_n = 0; + s->wbase = 0; + s->wpending = 0; +} + +/* Completes the pending writes whose last byte the flush has sent. */ +static void scr_net_sock_wq_settle(ScrNetSocket *s) { + size_t sent_to = s->wbase + s->whead; + while (s->wq_head < s->wq_n && s->wq[s->wq_head].end <= sent_to) { + s->wpending -= s->wq[s->wq_head].len; + s->wq_head++; + } + if (s->wq_head == s->wq_n) s->wq_head = s->wq_n = 0; +} + /* Append bytes to the write buffer (post-immediate-write remainder, or - * everything while connecting/buffered). */ -static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { + * everything while connecting/buffered). `counted` is the length of the + * write they belong to, which counts in full until they are sent. */ +static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len, size_t counted) { if (len == 0) return; /* compact the consumed head first */ if (s->whead > 0) { memmove(s->wbuf, s->wbuf + s->whead, s->wlen - s->whead); + s->wbase += s->whead; s->wlen -= s->whead; s->whead = 0; } @@ -1107,6 +1139,15 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { } memcpy(s->wbuf + s->wlen, data, len); s->wlen += len; + if (s->wq_n == s->wq_cap) { + s->wq_cap = s->wq_cap ? s->wq_cap * 2 : 8; + s->wq = realloc(s->wq, s->wq_cap * sizeof *s->wq); + if (!s->wq) scr_net_oom(); + } + s->wq[s->wq_n].end = s->wbase + s->wlen; + s->wq[s->wq_n].len = counted; + s->wq_n++; + s->wpending += counted; } /* Node's net.Socket writableHighWaterMark (the Writable default, @@ -1125,20 +1166,27 @@ static void scr_net_sock_maybe_drain(ScrNetSocket *s) { static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len); +/* The write half takes writes: not ending or closed, and connected, + * connecting, or waiting to dial (the agent's deferred dial, the caller + * lookup), which buffers them until the connection is established. */ +static bool scr_net_sock_write_open(ScrNetSocket *w) { + if (w->wr_ending || w->close_emitted) return false; + return w->fd >= 0 || w->dial_deferred || w->lookup_wait; +} + /* A user write: Writable.write's answer, as Node 24's writeOrBuffer - * computes it after the socket writes — the bytes the write path left - * unsent below the high-water mark — with needDrain set when it is false. - * A write the kernel takes at once answers true whatever its size; the - * buffered bytes of a false one drain from the flush. A write to an ended - * or closed socket answers false. */ + * computes it after the socket writes. Each write with bytes still + * unsent counts in full (wpending, Node's state.length), so the answer is + * false when the pending writes reach the high-water mark, and needDrain + * is set. A write the kernel takes at once answers true whatever its + * size. A write to an ended or closed socket answers false, and so does + * one whose send fails and closes the socket. */ static bool scr_net_sock_user_write(ScrNetSocket *s, const char *data, size_t len) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - bool pre_dial = w->fd < 0 && (w->dial_deferred || w->lookup_wait) && !w->wr_ending && - !w->close_emitted; - bool live = pre_dial || !(w->fd < 0 || w->wr_ending || w->close_emitted); + bool open = scr_net_sock_write_open(w); scr_net_sock_write_raw(s, data, len); - if (!live) return false; - bool ok = w->wlen - w->whead < SCR_NET_WRITE_HWM; + if (!open || !scr_net_sock_write_open(w)) return false; + bool ok = w->wpending < SCR_NET_WRITE_HWM; if (!ok) w->need_drain = true; return ok; } @@ -1176,7 +1224,7 @@ static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len (s->tops && !s->t_est)) { /* mid-connect and mid-handshake bytes buffer; the flush after * 'connect'/establishment sends them */ - scr_net_sock_buffer(s, data, len); + scr_net_sock_buffer(s, data, len, len); scr_net_sock_update_write(s); return; } @@ -1195,7 +1243,7 @@ static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len } } if ((size_t)sent < len) { - scr_net_sock_buffer(s, data + sent, len - (size_t)sent); + scr_net_sock_buffer(s, data + sent, len - (size_t)sent, len); scr_net_sock_update_write(s); } } @@ -1260,7 +1308,7 @@ void scr_net_sock_writev_native(ScrNetSocket *s, const ScrNetSlice *slices, size skip -= len; continue; } - scr_net_sock_buffer(s, (const char *)iov[i].iov_base + skip, len - skip); + scr_net_sock_buffer(s, (const char *)iov[i].iov_base + skip, len - skip, len); skip = 0; } scr_net_sock_update_write(s); @@ -1287,9 +1335,10 @@ static void scr_net_sock_flush(ScrNetSocket *s) { } if (sent > 0) scr_net_sock_touch(s); s->whead += (size_t)sent; + scr_net_sock_wq_settle(s); } if (s->wlen == s->whead) { - s->whead = s->wlen = 0; + scr_net_sock_wq_clear(s); scr_net_sock_update_write(s); scr_net_sock_maybe_finish_write(s); /* Node's afterWrite for a write the socket completed later: 'drain' @@ -2778,7 +2827,7 @@ ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nul * for a slot takes a pooled socket instead of dialing. */ void scr_net_sock_move_writes(ScrNetSocket *from, ScrNetSocket *to) { if (from->wlen > from->whead) scr_net_sock_write_native(to, from->wbuf + from->whead, from->wlen - from->whead); - from->whead = from->wlen = 0; + scr_net_sock_wq_clear(from); } void scr_net_sock_dial_start(ScrNetSocket *s) { @@ -3207,17 +3256,19 @@ bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { (data->len * scr_bytes_elem_size(data->elem))); } -/* socket.writableLength: the bytes written and not yet sent. */ +/* socket.writableLength: Node's state.length, the length of every write + * not yet completely sent. */ double scr_net_sock_writable_length(ScrNetSocket *s) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - return (double)(w->wlen - w->whead); + return (double)w->wpending; } /* socket.writableNeedDrain: a write answered false and 'drain' has not - * fired, on a socket whose write half is open. */ + * fired, on a socket whose write half is open (including one still + * waiting to dial). */ bool scr_net_sock_writable_need_drain(ScrNetSocket *s) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - return w->need_drain && !w->wr_ending && !w->close_emitted && w->fd >= 0; + return w->need_drain && scr_net_sock_write_open(w); } /* on('drain') / once('drain'). */ @@ -5331,7 +5382,7 @@ ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw) { tun->send_only = true; t->tunnel = tun; } - raw->whead = raw->wlen = 0; + scr_net_sock_wq_clear(raw); raw->adopted_by = scr_net_sock_retain(t); t->adopted_from = scr_net_sock_retain(raw); scr_net_sock_register(t); @@ -6260,10 +6311,12 @@ static ScrDyn *scr_net_dynh_sock_get(void *h, const char *key, size_t key_len) { } } if (strcmp(key, "readable") == 0) return scr_dyn_new_bool(scr_net_sock_readable(s)); + if (strcmp(key, "writableLength") == 0) return scr_dyn_new_num(scr_net_sock_writable_length(s)); + if (strcmp(key, "writableNeedDrain") == 0) + return scr_dyn_new_bool(scr_net_sock_writable_need_drain(s)); if (strcmp(key, "writableHighWaterMark") == 0 || strcmp(key, "readableHighWaterMark") == 0) { - /* Node's default stream highWaterMark — a constant here (backpressure - * is not modeled; SEMANTICS.md) */ - return scr_dyn_new_num(16384); + /* Node's default stream highWaterMark (getDefaultHighWaterMark()) */ + return scr_dyn_new_num(SCR_NET_WRITE_HWM); } if (scr_net_dynh_tls != NULL) { ScrDyn *out = NULL; diff --git a/tests/corpus/7515-net-socket-backpressure.ts b/tests/corpus/7515-net-socket-backpressure.ts index a265d3f77..260726a15 100644 --- a/tests/corpus/7515-net-socket-backpressure.ts +++ b/tests/corpus/7515-net-socket-backpressure.ts @@ -1,47 +1,136 @@ // net.Socket write() backpressure, as in Node 24: write answers false when -// the bytes it leaves unsent reach the 65536-byte high-water mark, which a -// write made before the connection does, writableNeedDrain stays true -// until 'drain', and 'drain' fires once the buffered bytes are sent. An -// echo client that waits for 'drain' when write answers false moves every -// byte. +// the writes not yet completely sent reach the 65536-byte high-water mark, +// each counting in full, which a write made before the connection does; +// writableNeedDrain stays true until 'drain', and 'drain' fires once the +// buffered bytes are sent. The same holds while a custom lookup is pending, +// writableLength counts a large write the kernel took in part in full, +// 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. import { connect, createServer, Socket } from "node:net"; +import type { LookupAddress, LookupAllOptions, LookupOneOptions } from "node:dns"; const CHUNK = 65536; const TOTAL = 64 * CHUNK; const chunk = Buffer.alloc(CHUNK, 120); +function untyped(socket: any): string { + return `${socket.writableLength} ${socket.writableNeedDrain} ${socket.writableHighWaterMark}`; +} + +function echo(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1"); + console.log("small write before connect", client.write("hi"), client.writableLength); + console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); + console.log("need drain", client.writableNeedDrain, "untyped", untyped(client)); + let sent = 70002; + let received = 0; + const pump = (): void => { + while (sent < TOTAL) { + sent += CHUNK; + if (!client.write(chunk)) { + client.once("drain", pump); + return; + } + } + client.end(); + }; + client.once("drain", () => { + console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); + pump(); + }); + client.on("data", (data: Buffer) => { + received += data.length; + }); + client.on("end", () => { + console.log("received", received, "of", sent); + resolve(); + }); + }); +} + +function lookupPending(port: number): Promise { + return new Promise((resolve) => { + const client = connect({ + port, + host: "localhost", + lookup: ( + _host: string, + _options: LookupOneOptions | LookupAllOptions | number | undefined, + callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family?: number) => void, + ) => { + const answer: LookupAddress[] = [{ address: "127.0.0.1", family: 4 }]; + setTimeout(() => callback(null, answer), 20); + }, + }); + console.log("lookup write", client.write(Buffer.alloc(70000, 97))); + console.log("lookup state", client.writableLength, client.writableNeedDrain, "untyped", untyped(client)); + client.once("drain", () => { + console.log("lookup drain", client.writableLength, client.writableNeedDrain); + client.destroy(); + resolve(); + }); + }); +} + +function largeWrite(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1", () => { + const size = 16 * 1024 * 1024; + console.log("large write", client.write(Buffer.alloc(size, 98))); + console.log("large state", client.writableLength === size, client.writableNeedDrain); + client.once("drain", () => { + console.log("large drain", client.writableLength, client.writableNeedDrain); + client.destroy(); + resolve(); + }); + setTimeout(() => { + if (paused !== null) paused.resume(); + }, 30); + }); + }); +} + +function afterReset(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1", () => { + client.pause(); + setTimeout(() => { + console.log("first write after the peer closed", client.write("a")); + setTimeout(() => { + console.log("write after reset", client.write("b")); + }, 50); + }, 50); + }); + client.on("error", () => {}); + client.on("close", () => { + console.log("closed, destroyed", client.destroyed); + resolve(); + }); + }); +} + +let paused: Socket | null = null; +let mode = "echo"; const server = createServer((socket: Socket) => { - socket.pipe(socket); + if (mode === "echo") socket.pipe(socket); + else if (mode === "drain") socket.resume(); + else if (mode === "pause") { + socket.pause(); + paused = socket; + } else socket.destroy(); }); -server.listen(0, "127.0.0.1", () => { +server.listen(0, "127.0.0.1", async () => { const addr = server.address(); const port = typeof addr === "object" && addr !== null ? addr.port : 0; - const client = connect(port, "127.0.0.1"); - console.log("small write before connect", client.write("hi"), client.writableLength); - console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); - console.log("need drain", client.writableNeedDrain); - let sent = 70002; - let received = 0; - const pump = (): void => { - while (sent < TOTAL) { - sent += CHUNK; - if (!client.write(chunk)) { - client.once("drain", pump); - return; - } - } - client.end(); - }; - client.once("drain", () => { - console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); - pump(); - }); - client.on("data", (data: Buffer) => { - received += data.length; - }); - client.on("end", () => { - console.log("received", received, "of", sent); - server.close(); - }); + await echo(port); + mode = "drain"; + await lookupPending(port); + mode = "pause"; + await largeWrite(port); + mode = "end"; + await afterReset(port); + server.close(); }); From 256fbdba20804c8a478f7cb0f77090d6df6dd5a8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:54:28 -0500 Subject: [PATCH 207/395] Keep the helper for callbacks that read a local class's captures A lone array callback in a local class's method could read a binding the method reaches through its class's captures. Those bindings are shared by every method of the class, so another method can assign one while the loop runs, but the copied loop read the value once when the call started. stableTarget now counts a method's class captures as inherited, so such a callback keeps its helper and reads the current value on every call. --- .../lowering/containers/array-pipelines.ts | 7 +++++-- .../compiler/test/array-pipeline-fusion.test.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 7c2e07b1c..197af2a9e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -950,11 +950,14 @@ class PipelineFusion { * nothing assigns after their declaration: `fn`'s own locals and * parameters, written neither by `fn` nor by any closure that shares * them, and not by the callback body. A binding a local class captures - * counts as written, since its methods may assign it. The callback must + * counts as written, since its methods may assign it; so does a binding + * a method reads through its class's captures. The callback must * not observe the ambient receiver (AmbientReceiverReaders). */ private stableTarget(fn: IrFunction): InlineTarget { const locals = new Map(fn.locals.map((l) => [l.id, l])); - const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); + const inherited = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); const written = this.written.get(fn.name); const classCaptured = this.classCaptured(fn); return (arg) => { diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index e41e378a3..2c0c138ce 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -249,3 +249,20 @@ console.log(out.join(","), hit); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.loop", "%arr.loop"]); }); + +test("a lone call in a local class's method keeps its helper for the class's captures", async () => { + const mod = await lowerSource(`${prelude} +let k = 1; +class Box { + set(v: number): void { k = v; } + go(): number[] { + const b = new Box(); + return rows.map((r) => { b.set(r.id); return r.value + k; }); + } +} +console.log(new Box().go().join(",")); +${postlude}`); + const go = mod.functions.find((f) => f.name.endsWith(".Box.go")); + expect(go).toBeDefined(); + expect(helperCalls(go!)).toEqual(["%arr.map"]); +}); From f8b1f9ffc36ade3f470c4e6d98d7625f5124caff Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:02:40 -0500 Subject: [PATCH 208/395] Keep a spread tuple alive until all its fields are packed into the rest A spread tuple's first field carries the statements that evaluate the tuple into a hidden local, and its later fields read that local. When lowerArraySpreadItems stored the first field as one item's expression, the local's frame ended with that store, so a tuple returned by a call was released before its later fields were read (a use-after-free under SCRIPTC_SAN=1). The field's statements now join the packing sequence at the item's position, whose frame lasts until every item is stored. --- .../frontend/lowering/containers/array-construction.ts | 10 +++++++++- tests/corpus/8004-optional-element-conversions.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 63e2e6e07..5029abc36 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -174,7 +174,15 @@ export function lowerArraySpreadItems( ]; for (const node of nodes) { if (!(node instanceof AstNode)) { - const value = undefinedForNullish(lowerer, node, arrType); + // A spread tuple's first field declares the tuple's storage, which + // the later fields read: its statements join this sequence, whose + // frame lasts until every item is stored. + let item = node; + if (item.kind === "seqExpr") { + body.push(...item.stmts); + item = item.result; + } + const value = undefinedForNullish(lowerer, item, arrType); const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null || optionalElementValue(lowerer, value, elem) diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 9a4ac55ac..a592a4f32 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -58,6 +58,14 @@ const mixedChanging: [boolean, boolean | undefined] = [true, undefined]; console.log(flags(...mixedChanging, ...mixedMore, flip(mixedChanging, true), ...mixedChanging)); const mixedWords: [string, string | undefined] = ["a", undefined]; console.log(label("z", ...["b", undefined, "c"], ...mixedWords), label("y", ...mixedWords, ...new Set(["s", undefined]))); +// A tuple returned by a call keeps its string fields alive until every +// field is packed beside the other spreads. +function makeTuple(n: number): [string, string | undefined, string] { + return ["p" + n.toString(16).repeat(4), n % 2 ? undefined : "q" + n, "r".repeat((n % 7) + 3) + n]; +} +for (let i = 0; i < 3; i++) { + console.log(label("z", ...["m", undefined], ...makeTuple(i), ...mixedWords), label("y", ...makeTuple(i + 10), ...new Set(["s" + i]))); +} function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From cbc062a35f84e7a7099419e4d5b8a852e37c2e90 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:03:04 -0500 Subject: [PATCH 209/395] Skip NULL children and key-cache walks when disposing dynamic values Disposing a checked-dynamic node released each entry's getter and setter and the node's prototype and symbol tables through scr_dyn_release, though almost all of them are NULL: in a zod validation loop, over half of all dyn releases. Dispose now tests each child first. A pooled object also walked all its slots again on reuse, to take the key bytes it kept back from the pool's key budget. It now records that total when it is pooled, and reuse subtracts it. --- packages/runtime/src/scr_dyn.h | 7 ++++- packages/runtime/src/scr_json.c | 51 +++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index d6b05be71..f90fa1838 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -480,7 +480,12 @@ struct ScrDyn { * exits, and those clear it before release; reuse of a freed object * resets it (scr_dyn_alloc). */ const char *class_name; - struct ScrDynRegion *region; /* see ScrDynRegion */ + union { + struct ScrDynRegion *region; /* see ScrDynRegion */ + /* While the node is pooled: the key bytes it keeps for reuse, + * counted in the pool's key budget (scr_dyn_obj_drop_keys). */ + size_t cached_key_bytes; + }; /* The dynamic tier's hidden class (scr_js_object.c): the transition * tree node naming this object's key sequence, or NULL while it has * none. Only programs with dynamic-tier code read or maintain it: diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 1738ba848..bfc63270f 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -846,15 +846,21 @@ static SCR_TL size_t scr_dyn_cached_key_bytes; #define SCR_DYN_KEY_CACHE_LENGTH 64 #endif -static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { +/* Returns the key bytes it kept for reuse, which it added to the budget; + * the pooled node records them (v.obj.cached_key_bytes) so its reuse takes + * them back without walking its slots. */ +static size_t scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { + size_t kept = 0; for (size_t i = 0; i < d->v.obj.cap; i++) { ScrDynEntry *entry = &d->v.obj.entries[i]; if (!entry->key) continue; #ifndef SCR_RC_AUDIT + size_t bytes = sizeof(ScrDynKey) + entry->key_len + 1; if (cache && d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS && entry->key_len <= SCR_DYN_KEY_CACHE_LENGTH && - sizeof(ScrDynKey) + entry->key_len + 1 <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) { - scr_dyn_cached_key_bytes += sizeof(ScrDynKey) + entry->key_len + 1; + bytes <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) { + scr_dyn_cached_key_bytes += bytes; + kept += bytes; continue; } #else @@ -864,6 +870,7 @@ static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { entry->key = NULL; entry->key_len = 0; } + return kept; } /* Primitive kinds cannot acquire properties or change kind while live, so @@ -1054,15 +1061,11 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) { d->v.arr.region = NULL; } else if (kind == SCR_DYN_OBJ) { d->v.obj.len = 0; /* cap/entries preserved */ + /* The kept keys leave the pool's budget with the node. */ + scr_dyn_cached_key_bytes -= d->v.obj.cached_key_bytes; d->v.obj.region = NULL; d->v.obj.class_name = NULL; /* a released class prototype's name */ SCR_JS_SHAPE_RESET(d); - if (d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS) { - for (size_t i = 0; i < d->v.obj.cap; i++) { - ScrDynEntry *entry = &d->v.obj.entries[i]; - if (entry->key) scr_dyn_cached_key_bytes -= sizeof(ScrDynKey) + entry->key_len + 1; - } - } d->v.obj.source_identity = NULL; d->v.obj.source_access = NULL; } else if (kind != SCR_DYN_NUM && kind != SCR_DYN_STR) { @@ -1128,6 +1131,12 @@ static inline void scr_dyn_weak_dispose(ScrDyn *d) { if (scr_weak_observing) scr_weak_disposed(d); } +/* A disposed node's child: most getters, setters, prototypes and symbol + * tables are NULL, and the test costs less than the call. */ +static inline void scr_dyn_release_child(ScrDyn *child) { + if (child) scr_dyn_release(child); +} + static void scr_dyn_dispose(ScrDyn *d, bool collected) { #ifndef SCR_RC_AUDIT /* A capsule with nothing but its referent and adapters: the common end @@ -1149,9 +1158,9 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { d->v.obj.index = NULL; } if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) scr_dyn_region_leave(d); - if (!collected) scr_dyn_release(d->prototype); - if (!collected) scr_dyn_release(d->symbol_properties); - if (!collected) scr_dyn_release(d->symbol_keys); + if (!collected) scr_dyn_release_child(d->prototype); + if (!collected) scr_dyn_release_child(d->symbol_properties); + if (!collected) scr_dyn_release_child(d->symbol_keys); /* Pooled nodes keep all three NULL (scr_dyn_leaf_alloc relies on it). */ d->prototype = d->symbol_properties = d->symbol_keys = NULL; switch (d->kind) { @@ -1171,17 +1180,17 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { free(d->v.arr.presence); d->v.arr.presence = NULL; if (!collected) { - for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release(d->v.arr.items[i]); - scr_dyn_release(d->v.arr.properties); + for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release_child(d->v.arr.items[i]); + scr_dyn_release_child(d->v.arr.properties); } break; case SCR_DYN_OBJ: for (size_t i = 0; i < d->v.obj.len; i++) { ScrDyn *value = d->v.obj.entries[i].value; - if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release(value); + if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release_child(value); if (!collected) { - scr_dyn_release(d->v.obj.entries[i].getter); - scr_dyn_release(d->v.obj.entries[i].setter); + scr_dyn_release_child(d->v.obj.entries[i].getter); + scr_dyn_release_child(d->v.obj.entries[i].setter); } d->v.obj.entries[i].value = NULL; d->v.obj.entries[i].getter = NULL; @@ -1207,7 +1216,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { case SCR_DYN_TYPED_REF: scr_typed_ref_forget(d); - if (!collected) scr_dyn_release(d->v.typed_ref.materialized); + if (!collected) scr_dyn_release_child(d->v.typed_ref.materialized); while (d->v.typed_ref.casts) { ScrDynTypedCast *cast = d->v.typed_ref.casts; d->v.typed_ref.casts = cast->next; @@ -1218,8 +1227,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { break; case SCR_DYN_PROXY: if (!collected) { - scr_dyn_release(d->v.proxy.target); - scr_dyn_release(d->v.proxy.handler); + scr_dyn_release_child(d->v.proxy.target); + scr_dyn_release_child(d->v.proxy.handler); } break; default: @@ -1230,7 +1239,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { #endif #ifndef SCR_RC_AUDIT if (!collected && scr_dyn_free_count < SCR_DYN_FREE_MAX) { - if (d->kind == SCR_DYN_OBJ) scr_dyn_obj_drop_keys(d, true); + if (d->kind == SCR_DYN_OBJ) d->v.obj.cached_key_bytes = scr_dyn_obj_drop_keys(d, true); /* Pooled blocks retain their headers and count toward the bounded * allocated heap, but never remain in a candidate buffer. */ scr_dyn_weak_dispose(d); From 9abf35f6a2e999944ee503c545000f9033cf632a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:23:35 -0500 Subject: [PATCH 210/395] Build the large compiler tables on first use The native compiler ran four large module initializers at every startup: the builtin call signatures (LIB_FN_SIGS), the Node 24 fetch compatibility profile, and the builtin module function and ambient surface tables. Together their init functions are about 1.3 MB of code, and --version and warm cache-hit builds paged them in and built the tables without reading them. Each table is now built by a function on its first call and kept in a module variable: - libFnSignatures() replaces LIB_FN_SIGS - node24FetchCompatProfile() and staticFetchMembers() replace NODE24_FETCH_COMPAT_PROFILE and the STATIC_* member sets - builtinModuleFns() and ambientSurfaceFns() replace BUILTIN_MODULE_FNS and AMBIENT_SURFACE_FNS The @scriptc/compiler entry point keeps LIB_FN_SIGS and NODE24_FETCH_COMPAT_PROFILE as deprecated constants built from the new functions when the entry module loads. The native compiler does not load that module. The surface manifest's coverage notes read the fetch target through the profile function, so building the manifest module no longer builds the profile. The table contents are unchanged. --- packages/compiler/src/compat/fetch-profile.ts | 1511 +-- .../compiler/src/coverage/surface-manifest.ts | 53 +- .../src/frontend/lowering/lib-boundary.ts | 4 +- .../frontend/lowering/lower-builtin-values.ts | 6 +- .../src/frontend/lowering/lower-stdlib.ts | 40 +- .../src/frontend/lowering/surfaces.ts | 3116 ++--- packages/compiler/src/index.ts | 12 +- .../compiler/src/ir/builtin-signatures.ts | 10139 ++++++++-------- packages/compiler/src/ir/ir.ts | 2 +- packages/compiler/src/ir/validate.ts | 4 +- packages/compiler/src/library/fence-eval.ts | 12 +- packages/compiler/test/compat-exports.test.ts | 12 + packages/compiler/test/node-pin.test.ts | 4 +- tests/harness/README.md | 2 +- tests/harness/code-quality.test.ts | 2 +- tests/harness/fetch-conformance-program.ts | 2 +- tests/harness/fetch-conformance.test.ts | 4 +- tests/harness/surface-manifest.test.ts | 6 +- 18 files changed, 7525 insertions(+), 7406 deletions(-) create mode 100644 packages/compiler/test/compat-exports.test.ts diff --git a/packages/compiler/src/compat/fetch-profile.ts b/packages/compiler/src/compat/fetch-profile.ts index ff7cedb22..03d261aff 100644 --- a/packages/compiler/src/compat/fetch-profile.ts +++ b/packages/compiler/src/compat/fetch-profile.ts @@ -155,494 +155,104 @@ const typedInterfaceUnsupported = "typed source has no compiler bridge for this const metadataExclusion = "WebIDL brand metadata is observable reflection, not an executable compatibility operation"; -export const NODE24_FETCH_COMPAT_PROFILE = { - schemaVersion: 1, - target: { - node: "24.22.0", - undici: "7.29.1", - }, - requestInit: [ - { - id: "stdlib.fetch.request-init.method", - name: "RequestInit.method", - conversion: "WebIDL ByteString after all call arguments evaluate", - evidence: [fixture("static-coercion")], +function buildFetchCompatProfile() { + return { + schemaVersion: 1, + target: { + node: "24.22.0", + undici: "7.29.1", }, - { - id: "stdlib.fetch.request-init.headers", - name: "RequestInit.headers", - conversion: "Headers, record, or sequence-of-pairs snapshot", - evidence: [fixture("static"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.body", - name: "RequestInit.body", - conversion: "string, Uint8Array, ReadableStream, or null", - evidence: [fixture("static"), fixture("static-stream"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.duplex", - name: "RequestInit.duplex", - conversion: "WebIDL enum; 'half' required for streaming bodies", - evidence: [fixture("static-stream"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.redirect", - name: "RequestInit.redirect", - conversion: "WebIDL enum: follow, error, or manual", - evidence: [fixture("static"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.signal", - name: "RequestInit.signal", - conversion: "native AbortSignal handle or absent", - evidence: [fixture("static"), fixture("static-abort-throw")], - }, - ], - responseInit: [ - { - id: "stdlib.response-init.headers", - name: "ResponseInit.headers", - conversion: "Headers, record, or sequence-of-pairs snapshot", - evidence: [fixture("static")], - }, - { - id: "stdlib.response-init.status", - name: "ResponseInit.status", - conversion: "WebIDL unsigned-short conversion followed by the 200–599 range check", - evidence: [fixture("static")], - }, - { - id: "stdlib.response-init.statusText", - name: "ResponseInit.statusText", - conversion: "WebIDL ByteString with HTTP reason-phrase validation", - evidence: [fixture("static")], - }, - ], - members: { - responseReads: [ - "ok", - "status", - "statusText", - "url", - "redirected", - "headers", - "body", - "bodyUsed", - ], - responseCalls: ["json", "text", "bytes"], - readableStreamReads: ["locked"], - readableStreamCalls: ["cancel", "getReader"], - }, - operations: [ - { - id: "stdlib.headers.constructor", - name: "Headers constructor", - kind: "constructor", - facets: ["webidl-conversion", "mutation", "identity"], - scope: "Record and iterable header pairs, including copying native Headers", - evidence: [fixture("static-request")], - }, - { - id: "stdlib.request.constructor", - name: "Request constructor", - kind: "constructor", - facets: ["webidl-conversion", "body-consumption", "error-shape"], - scope: - "Absolute URLs with method, headers, body, duplex, redirect, and signal; other RequestInit members retain explicit fences", - evidence: [fixture("static-request")], - }, - ...[ - "method", - "url", - "headers", - "destination", - "referrer", - "referrerPolicy", - "mode", - "credentials", - "cache", - "redirect", - "integrity", - "keepalive", - "isReloadNavigation", - "isHistoryNavigation", - "signal", - "body", - "bodyUsed", - "duplex", - ].map((member): FetchCompatOperation => ({ - id: `stdlib.request.${member}`, - name: `Request.${member}`, - kind: "property", - facets: ["property-read", "state-machine"], - scope: "Reads on native Requests within the constructor's supported RequestInit subset", - evidence: [fixture("static-request")], - })), - ...["arrayBuffer", "text", "json", "bytes"].map((member): FetchCompatOperation => ({ - id: `stdlib.request.${member}`, - name: `Request.${member}`, - kind: "method", - facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], - evidence: [fixture("static-request")], - })), - { - id: "stdlib.response.constructor", - name: "Response constructor", - kind: "constructor", - facets: ["webidl-conversion", "body-consumption", "state-machine", "error-shape"], - scope: - "BodyInit is string, Uint8Array/Buffer, ReadableStream, null/undefined, or a checked-dynamic value that follows the supported WebIDL string conversion; ResponseInit is headers/status/statusText", - evidence: [fixture("static")], - }, - { - id: "stdlib.fetch", - name: "fetch", - kind: "function", - facets: [ - "argument-evaluation", - "webidl-conversion", - "surplus-arguments", - "promise-settlement", - "transport", - "error-shape", - ], - evidence: [fixture("static"), fixture("static-coercion"), fixture("static-network-error")], - }, - { - id: "stdlib.abort-controller.constructor", - name: "AbortController constructor", - kind: "constructor", - facets: ["argument-evaluation", "identity", "state-machine", "surplus-arguments"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-controller.signal", - name: "AbortController.signal", - kind: "property", - facets: ["identity", "property-read", "state-machine"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-controller.abort", - name: "AbortController.abort", - kind: "method", - facets: ["callback-order", "identity", "liveness", "state-machine", "surplus-arguments"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-signal.abort", - name: "AbortSignal.abort", - kind: "static-method", - facets: ["identity", "liveness", "surplus-arguments", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.any", - name: "AbortSignal.any", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.timeout", - name: "AbortSignal.timeout", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "error-shape"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.aborted", - name: "AbortSignal.aborted", - kind: "property", - facets: ["property-read"], - evidence: [generated("webidl-operations")], - }, - { - id: "stdlib.abort-signal.reason", - name: "AbortSignal.reason", - kind: "property", - facets: ["identity", "liveness", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.onabort", - name: "AbortSignal.onabort", - kind: "property", - facets: ["callback-order", "callback-this", "mutation", "property-read"], - evidence: [generated("abort-events"), fixture("static-listener-this")], - }, - { - id: "stdlib.abort-signal.throw-if-aborted", - name: "AbortSignal.throwIfAborted", - kind: "method", - facets: ["identity", "error-shape"], - evidence: [generated("webidl-operations"), fixture("static-abort-throw")], - }, - { - id: "stdlib.abort-signal.add-event-listener", - name: "AbortSignal.addEventListener", - kind: "method", - facets: ["webidl-conversion", "identity", "callback-order", "callback-this"], - evidence: [ - generated("abort-events"), - fixture("static-listener-this"), - fixture("static-listener-noncallable"), - ], - }, - { - id: "stdlib.abort-signal.remove-event-listener", - name: "AbortSignal.removeEventListener", - kind: "method", - facets: ["webidl-conversion", "identity", "callback-order"], - evidence: [generated("abort-events"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.dispatch-event", - name: "AbortSignal.dispatchEvent", - kind: "method", - facets: ["callback-order", "callback-this", "error-shape"], - evidence: [fixture("static-dispatch-throw")], - }, - { - id: "stdlib.readable-stream.constructor", - name: "ReadableStream constructor", - kind: "constructor", - facets: ["webidl-conversion", "callback-this", "callback-order", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream-this")], - }, - { - id: "stdlib.readable-stream.from", - name: "ReadableStream.from", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "liveness"], - evidence: [generated("webidl-operations"), generated("stream-traces")], - }, - { - id: "stdlib.readable-stream.locked", - name: "ReadableStream.locked", - kind: "property", - facets: ["property-read", "state-machine"], - evidence: [generated("stream-traces")], - }, - { - id: "stdlib.readable-stream.cancel", - name: "ReadableStream.cancel", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream.get-reader", - name: "ReadableStream.getReader", - kind: "method", - facets: ["webidl-conversion", "identity", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.closed", - name: "ReadableStreamDefaultReader.closed", - kind: "property", - facets: ["promise-settlement", "property-read", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.read", - name: "ReadableStreamDefaultReader.read", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.cancel", - name: "ReadableStreamDefaultReader.cancel", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.release-lock", - name: "ReadableStreamDefaultReader.releaseLock", - kind: "method", - facets: ["promise-settlement", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.desired-size", - name: "ReadableStreamDefaultController.desiredSize", - kind: "property", - facets: ["property-read", "state-machine"], - evidence: [generated("stream-traces")], - }, - { - id: "stdlib.readable-stream-default-controller.enqueue", - name: "ReadableStreamDefaultController.enqueue", - kind: "method", - facets: ["identity", "liveness", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.close", - name: "ReadableStreamDefaultController.close", - kind: "method", - facets: ["state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.error", - name: "ReadableStreamDefaultController.error", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - ...["append", "delete", "get", "getSetCookie", "has", "set", "forEach"].map( - (member): FetchCompatOperation => ({ - id: `stdlib.headers.${member}`, - name: `Headers.${member}`, - kind: "method", - facets: - member === "forEach" - ? ["callback-order", "callback-this", "mutation"] - : member === "get" || member === "has" - ? ["webidl-conversion", "missing-arguments", "property-read"] - : ["webidl-conversion", "mutation", "error-shape"], + requestInit: [ + { + id: "stdlib.fetch.request-init.method", + name: "RequestInit.method", + conversion: "WebIDL ByteString after all call arguments evaluate", + evidence: [fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.headers", + name: "RequestInit.headers", + conversion: "Headers, record, or sequence-of-pairs snapshot", evidence: [fixture("static"), fixture("static-coercion")], - }), - ), - ...["ok", "status", "statusText", "url", "redirected", "headers", "body", "bodyUsed"].map( - (member): FetchCompatOperation => ({ - id: `stdlib.response.${member}`, - name: `Response.${member}`, - kind: "property", - facets: ["property-read"], - evidence: [fixture("static")], - }), - ), - { - id: "stdlib.response.static.json", - name: "Response.json", - kind: "static-method", - facets: ["webidl-conversion", "error-shape"], - scope: - "data that crosses the checked-dynamic boundary, serialized with JSON.stringify (toJSON hooks included); ResponseInit as for the constructor", - evidence: [fixture("static-response-json")], - }, - ...["json", "text", "bytes", "arrayBuffer"].map((member): FetchCompatOperation => ({ - id: `stdlib.response.${member}`, - name: `Response.${member}`, - kind: "method", - facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], - evidence: - member === "arrayBuffer" - ? [fixture("static")] - : [fixture("static"), fixture("static-stream")], - })), - ], - inventory: { - interfaces: [ - "AbortController", - "AbortSignal", - "Headers", - "Request", - "Response", - "ReadableStream", - "ReadableStreamDefaultReader", - "ReadableStreamDefaultController", + }, + { + id: "stdlib.fetch.request-init.body", + name: "RequestInit.body", + conversion: "string, Uint8Array, ReadableStream, or null", + evidence: [fixture("static"), fixture("static-stream"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.duplex", + name: "RequestInit.duplex", + conversion: "WebIDL enum; 'half' required for streaming bodies", + evidence: [fixture("static-stream"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.redirect", + name: "RequestInit.redirect", + conversion: "WebIDL enum: follow, error, or manual", + evidence: [fixture("static"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.signal", + name: "RequestInit.signal", + conversion: "native AbortSignal handle or absent", + evidence: [fixture("static"), fixture("static-abort-throw")], + }, ], - entries: [ - staticEntry("stdlib.fetch", "globalThis", "fetch", "global"), - - staticEntry( - "stdlib.abort-controller.constructor", - "AbortController", - "constructor", - "constructor", - ), - staticEntry("stdlib.abort-controller.signal", "AbortController", "signal", "prototype"), - staticEntry("stdlib.abort-controller.abort", "AbortController", "abort", "prototype"), - outOfScopeEntry( - "stdlib.abort-controller.symbol.toStringTag", - "AbortController", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - unsupportedEntry( - "stdlib.abort-signal.constructor", - "AbortSignal", - "constructor", - "constructor", - "Node exposes the interface object but constructing it throws; the compiler does not preserve that constructor behavior", - ), - staticEntry("stdlib.abort-signal.abort", "AbortSignal", "abort", "static"), - staticEntry("stdlib.abort-signal.timeout", "AbortSignal", "timeout", "static"), - staticEntry("stdlib.abort-signal.any", "AbortSignal", "any", "static"), - staticEntry("stdlib.abort-signal.aborted", "AbortSignal", "aborted", "prototype"), - staticEntry("stdlib.abort-signal.reason", "AbortSignal", "reason", "prototype"), - staticEntry( - "stdlib.abort-signal.throw-if-aborted", - "AbortSignal", - "throwIfAborted", - "prototype", - ), - staticEntry("stdlib.abort-signal.onabort", "AbortSignal", "onabort", "prototype"), - staticEntry( - "stdlib.abort-signal.add-event-listener", - "AbortSignal", - "addEventListener", - "prototype-inherited", - ), - staticEntry( - "stdlib.abort-signal.remove-event-listener", - "AbortSignal", - "removeEventListener", - "prototype-inherited", - ), - staticEntry( - "stdlib.abort-signal.dispatch-event", - "AbortSignal", - "dispatchEvent", - "prototype-inherited", - ), - outOfScopeEntry( - "stdlib.abort-signal.symbol.toStringTag", - "AbortSignal", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.headers.constructor", "Headers", "constructor", "constructor"), - ...["append", "delete", "get", "has", "set", "getSetCookie"].map((member) => - staticEntry(`stdlib.headers.${member}`, "Headers", member, "prototype"), - ), - ...["keys", "values", "entries"].map((member) => - unsupportedEntry( - `stdlib.headers.${member}`, - "Headers", - member, - "prototype", - "native Headers iteration does not yet expose a static iterator handle", - ), - ), - staticEntry("stdlib.headers.forEach", "Headers", "forEach", "prototype"), - unsupportedEntry( - "stdlib.headers.symbol.iterator", - "Headers", - "[Symbol.iterator]", - "prototype-symbol", - "native Headers iteration does not expose an engine-free iterator handle", - ), - outOfScopeEntry( - "stdlib.headers.symbol.toStringTag", - "Headers", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.request.constructor", "Request", "constructor", "constructor"), + responseInit: [ + { + id: "stdlib.response-init.headers", + name: "ResponseInit.headers", + conversion: "Headers, record, or sequence-of-pairs snapshot", + evidence: [fixture("static")], + }, + { + id: "stdlib.response-init.status", + name: "ResponseInit.status", + conversion: "WebIDL unsigned-short conversion followed by the 200–599 range check", + evidence: [fixture("static")], + }, + { + id: "stdlib.response-init.statusText", + name: "ResponseInit.statusText", + conversion: "WebIDL ByteString with HTTP reason-phrase validation", + evidence: [fixture("static")], + }, + ], + members: { + responseReads: [ + "ok", + "status", + "statusText", + "url", + "redirected", + "headers", + "body", + "bodyUsed", + ], + responseCalls: ["json", "text", "bytes"], + readableStreamReads: ["locked"], + readableStreamCalls: ["cancel", "getReader"], + }, + operations: [ + { + id: "stdlib.headers.constructor", + name: "Headers constructor", + kind: "constructor", + facets: ["webidl-conversion", "mutation", "identity"], + scope: "Record and iterable header pairs, including copying native Headers", + evidence: [fixture("static-request")], + }, + { + id: "stdlib.request.constructor", + name: "Request constructor", + kind: "constructor", + facets: ["webidl-conversion", "body-consumption", "error-shape"], + scope: + "Absolute URLs with method, headers, body, duplex, redirect, and signal; other RequestInit members retain explicit fences", + evidence: [fixture("static-request")], + }, ...[ "method", "url", @@ -662,276 +272,695 @@ export const NODE24_FETCH_COMPAT_PROFILE = { "body", "bodyUsed", "duplex", - "clone", - "blob", - "arrayBuffer", - "text", - "json", - "formData", - "bytes", - "attribute", - ].map((member) => - ["clone", "blob", "formData", "attribute"].includes(member) - ? unsupportedEntry( - `stdlib.request.${member}`, - "Request", - member, - "prototype", - typedInterfaceUnsupported, - ) - : staticEntry(`stdlib.request.${member}`, "Request", member, "prototype"), + ].map((member): FetchCompatOperation => ({ + id: `stdlib.request.${member}`, + name: `Request.${member}`, + kind: "property", + facets: ["property-read", "state-machine"], + scope: "Reads on native Requests within the constructor's supported RequestInit subset", + evidence: [fixture("static-request")], + })), + ...["arrayBuffer", "text", "json", "bytes"].map((member): FetchCompatOperation => ({ + id: `stdlib.request.${member}`, + name: `Request.${member}`, + kind: "method", + facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], + evidence: [fixture("static-request")], + })), + { + id: "stdlib.response.constructor", + name: "Response constructor", + kind: "constructor", + facets: ["webidl-conversion", "body-consumption", "state-machine", "error-shape"], + scope: + "BodyInit is string, Uint8Array/Buffer, ReadableStream, null/undefined, or a checked-dynamic value that follows the supported WebIDL string conversion; ResponseInit is headers/status/statusText", + evidence: [fixture("static")], + }, + { + id: "stdlib.fetch", + name: "fetch", + kind: "function", + facets: [ + "argument-evaluation", + "webidl-conversion", + "surplus-arguments", + "promise-settlement", + "transport", + "error-shape", + ], + evidence: [fixture("static"), fixture("static-coercion"), fixture("static-network-error")], + }, + { + id: "stdlib.abort-controller.constructor", + name: "AbortController constructor", + kind: "constructor", + facets: ["argument-evaluation", "identity", "state-machine", "surplus-arguments"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-controller.signal", + name: "AbortController.signal", + kind: "property", + facets: ["identity", "property-read", "state-machine"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-controller.abort", + name: "AbortController.abort", + kind: "method", + facets: ["callback-order", "identity", "liveness", "state-machine", "surplus-arguments"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-signal.abort", + name: "AbortSignal.abort", + kind: "static-method", + facets: ["identity", "liveness", "surplus-arguments", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.any", + name: "AbortSignal.any", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.timeout", + name: "AbortSignal.timeout", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "error-shape"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.aborted", + name: "AbortSignal.aborted", + kind: "property", + facets: ["property-read"], + evidence: [generated("webidl-operations")], + }, + { + id: "stdlib.abort-signal.reason", + name: "AbortSignal.reason", + kind: "property", + facets: ["identity", "liveness", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.onabort", + name: "AbortSignal.onabort", + kind: "property", + facets: ["callback-order", "callback-this", "mutation", "property-read"], + evidence: [generated("abort-events"), fixture("static-listener-this")], + }, + { + id: "stdlib.abort-signal.throw-if-aborted", + name: "AbortSignal.throwIfAborted", + kind: "method", + facets: ["identity", "error-shape"], + evidence: [generated("webidl-operations"), fixture("static-abort-throw")], + }, + { + id: "stdlib.abort-signal.add-event-listener", + name: "AbortSignal.addEventListener", + kind: "method", + facets: ["webidl-conversion", "identity", "callback-order", "callback-this"], + evidence: [ + generated("abort-events"), + fixture("static-listener-this"), + fixture("static-listener-noncallable"), + ], + }, + { + id: "stdlib.abort-signal.remove-event-listener", + name: "AbortSignal.removeEventListener", + kind: "method", + facets: ["webidl-conversion", "identity", "callback-order"], + evidence: [generated("abort-events"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.dispatch-event", + name: "AbortSignal.dispatchEvent", + kind: "method", + facets: ["callback-order", "callback-this", "error-shape"], + evidence: [fixture("static-dispatch-throw")], + }, + { + id: "stdlib.readable-stream.constructor", + name: "ReadableStream constructor", + kind: "constructor", + facets: ["webidl-conversion", "callback-this", "callback-order", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream-this")], + }, + { + id: "stdlib.readable-stream.from", + name: "ReadableStream.from", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "liveness"], + evidence: [generated("webidl-operations"), generated("stream-traces")], + }, + { + id: "stdlib.readable-stream.locked", + name: "ReadableStream.locked", + kind: "property", + facets: ["property-read", "state-machine"], + evidence: [generated("stream-traces")], + }, + { + id: "stdlib.readable-stream.cancel", + name: "ReadableStream.cancel", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream.get-reader", + name: "ReadableStream.getReader", + kind: "method", + facets: ["webidl-conversion", "identity", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.closed", + name: "ReadableStreamDefaultReader.closed", + kind: "property", + facets: ["promise-settlement", "property-read", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.read", + name: "ReadableStreamDefaultReader.read", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.cancel", + name: "ReadableStreamDefaultReader.cancel", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.release-lock", + name: "ReadableStreamDefaultReader.releaseLock", + kind: "method", + facets: ["promise-settlement", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.desired-size", + name: "ReadableStreamDefaultController.desiredSize", + kind: "property", + facets: ["property-read", "state-machine"], + evidence: [generated("stream-traces")], + }, + { + id: "stdlib.readable-stream-default-controller.enqueue", + name: "ReadableStreamDefaultController.enqueue", + kind: "method", + facets: ["identity", "liveness", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.close", + name: "ReadableStreamDefaultController.close", + kind: "method", + facets: ["state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.error", + name: "ReadableStreamDefaultController.error", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + ...["append", "delete", "get", "getSetCookie", "has", "set", "forEach"].map( + (member): FetchCompatOperation => ({ + id: `stdlib.headers.${member}`, + name: `Headers.${member}`, + kind: "method", + facets: + member === "forEach" + ? ["callback-order", "callback-this", "mutation"] + : member === "get" || member === "has" + ? ["webidl-conversion", "missing-arguments", "property-read"] + : ["webidl-conversion", "mutation", "error-shape"], + evidence: [fixture("static"), fixture("static-coercion")], + }), ), - outOfScopeEntry( - "stdlib.request.symbol.toStringTag", + ...["ok", "status", "statusText", "url", "redirected", "headers", "body", "bodyUsed"].map( + (member): FetchCompatOperation => ({ + id: `stdlib.response.${member}`, + name: `Response.${member}`, + kind: "property", + facets: ["property-read"], + evidence: [fixture("static")], + }), + ), + { + id: "stdlib.response.static.json", + name: "Response.json", + kind: "static-method", + facets: ["webidl-conversion", "error-shape"], + scope: + "data that crosses the checked-dynamic boundary, serialized with JSON.stringify (toJSON hooks included); ResponseInit as for the constructor", + evidence: [fixture("static-response-json")], + }, + ...["json", "text", "bytes", "arrayBuffer"].map((member): FetchCompatOperation => ({ + id: `stdlib.response.${member}`, + name: `Response.${member}`, + kind: "method", + facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], + evidence: + member === "arrayBuffer" + ? [fixture("static")] + : [fixture("static"), fixture("static-stream")], + })), + ], + inventory: { + interfaces: [ + "AbortController", + "AbortSignal", + "Headers", "Request", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.response.constructor", "Response", "constructor", "constructor"), - staticEntry("stdlib.response.static.json", "Response", "json", "static"), - ...["error", "redirect"].map((member) => - unsupportedEntry( - `stdlib.response.static.${member}`, - "Response", - member, - "static", - "Response static constructor-object operations have no compiler lowering", - ), - ), - unsupportedEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence), - ...["url", "redirected", "status", "ok", "statusText", "headers", "body", "bodyUsed"].map( - (member) => staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), - ), - staticEntry("stdlib.response.arrayBuffer", "Response", "arrayBuffer", "prototype"), - ...["clone", "blob", "formData"].map((member) => - unsupportedEntry( - `stdlib.response.${member}`, - "Response", - member, - "prototype", - "the native fetch runtime does not implement this Response operation", - ), - ), - ...["text", "json"].map((member) => - staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), - ), - staticEntry("stdlib.response.bytes", "Response", "bytes", "prototype"), - outOfScopeEntry( - "stdlib.response.symbol.toStringTag", "Response", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry( - "stdlib.readable-stream.constructor", "ReadableStream", - "constructor", - "constructor", - ), - staticEntry("stdlib.readable-stream.from", "ReadableStream", "from", "static"), - staticEntry("stdlib.readable-stream.locked", "ReadableStream", "locked", "prototype"), - staticEntry("stdlib.readable-stream.cancel", "ReadableStream", "cancel", "prototype"), - staticEntry("stdlib.readable-stream.get-reader", "ReadableStream", "getReader", "prototype"), - ...["pipeThrough", "values"].map((member) => - unsupportedEntry( - `stdlib.readable-stream.${member}`, - "ReadableStream", - member, - "prototype", - member === "values" - ? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle is unsupported" - : "the wider Web Streams graph is outside the native readable-stream slice", + "ReadableStreamDefaultReader", + "ReadableStreamDefaultController", + ], + entries: [ + staticEntry("stdlib.fetch", "globalThis", "fetch", "global"), + + staticEntry( + "stdlib.abort-controller.constructor", + "AbortController", + "constructor", + "constructor", ), - ), - ...["pipeTo", "tee"].map((member) => - unsupportedEntry( - `stdlib.readable-stream.${member}`, - "ReadableStream", - member, - "prototype", - "this operation is outside the native readable-stream implementation", + staticEntry("stdlib.abort-controller.signal", "AbortController", "signal", "prototype"), + staticEntry("stdlib.abort-controller.abort", "AbortController", "abort", "prototype"), + outOfScopeEntry( + "stdlib.abort-controller.symbol.toStringTag", + "AbortController", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, ), - ), - unsupportedEntry( - "stdlib.readable-stream.symbol.asyncIterator", - "ReadableStream", - "[Symbol.asyncIterator]", - "prototype-symbol", - "direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering", - ), - outOfScopeEntry( - "stdlib.readable-stream.symbol.toStringTag", - "ReadableStream", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - unsupportedEntry( - "stdlib.readable-stream-default-reader.constructor", - "ReadableStreamDefaultReader", - "constructor", - "constructor", - "reader handles come from ReadableStream.getReader(); the constructor object is unsupported", - ), - staticEntry( - "stdlib.readable-stream-default-reader.read", - "ReadableStreamDefaultReader", - "read", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.release-lock", - "ReadableStreamDefaultReader", - "releaseLock", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.closed", - "ReadableStreamDefaultReader", - "closed", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.cancel", - "ReadableStreamDefaultReader", - "cancel", - "prototype", - ), - outOfScopeEntry( - "stdlib.readable-stream-default-reader.symbol.toStringTag", - "ReadableStreamDefaultReader", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), + unsupportedEntry( + "stdlib.abort-signal.constructor", + "AbortSignal", + "constructor", + "constructor", + "Node exposes the interface object but constructing it throws; the compiler does not preserve that constructor behavior", + ), + staticEntry("stdlib.abort-signal.abort", "AbortSignal", "abort", "static"), + staticEntry("stdlib.abort-signal.timeout", "AbortSignal", "timeout", "static"), + staticEntry("stdlib.abort-signal.any", "AbortSignal", "any", "static"), + staticEntry("stdlib.abort-signal.aborted", "AbortSignal", "aborted", "prototype"), + staticEntry("stdlib.abort-signal.reason", "AbortSignal", "reason", "prototype"), + staticEntry( + "stdlib.abort-signal.throw-if-aborted", + "AbortSignal", + "throwIfAborted", + "prototype", + ), + staticEntry("stdlib.abort-signal.onabort", "AbortSignal", "onabort", "prototype"), + staticEntry( + "stdlib.abort-signal.add-event-listener", + "AbortSignal", + "addEventListener", + "prototype-inherited", + ), + staticEntry( + "stdlib.abort-signal.remove-event-listener", + "AbortSignal", + "removeEventListener", + "prototype-inherited", + ), + staticEntry( + "stdlib.abort-signal.dispatch-event", + "AbortSignal", + "dispatchEvent", + "prototype-inherited", + ), + outOfScopeEntry( + "stdlib.abort-signal.symbol.toStringTag", + "AbortSignal", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), - unsupportedEntry( - "stdlib.readable-stream-default-controller.constructor", - "ReadableStreamDefaultController", - "constructor", - "constructor", - "controller handles come from underlying-source callbacks; the constructor object is unsupported", - ), - staticEntry( - "stdlib.readable-stream-default-controller.desired-size", - "ReadableStreamDefaultController", - "desiredSize", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.close", - "ReadableStreamDefaultController", - "close", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.enqueue", - "ReadableStreamDefaultController", - "enqueue", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.error", - "ReadableStreamDefaultController", - "error", - "prototype", - ), - outOfScopeEntry( - "stdlib.readable-stream-default-controller.symbol.toStringTag", - "ReadableStreamDefaultController", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), + staticEntry("stdlib.headers.constructor", "Headers", "constructor", "constructor"), + ...["append", "delete", "get", "has", "set", "getSetCookie"].map((member) => + staticEntry(`stdlib.headers.${member}`, "Headers", member, "prototype"), + ), + ...["keys", "values", "entries"].map((member) => + unsupportedEntry( + `stdlib.headers.${member}`, + "Headers", + member, + "prototype", + "native Headers iteration does not yet expose a static iterator handle", + ), + ), + staticEntry("stdlib.headers.forEach", "Headers", "forEach", "prototype"), + unsupportedEntry( + "stdlib.headers.symbol.iterator", + "Headers", + "[Symbol.iterator]", + "prototype-symbol", + "native Headers iteration does not expose an engine-free iterator handle", + ), + outOfScopeEntry( + "stdlib.headers.symbol.toStringTag", + "Headers", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), - ...[ - "body", - "cache", - "credentials", - "dispatcher", - "duplex", - "headers", - "integrity", - "keepalive", - "method", - "mode", - "priority", - "redirect", - "referrer", - "referrerPolicy", - "signal", - "window", - ].map((member) => { - const id = `stdlib.fetch.request-init.${member}`; - const supported = new Set(["body", "duplex", "headers", "method", "redirect", "signal"]); - return supported.has(member) - ? staticEntry(id, "RequestInit", member, "dictionary") - : member === "dispatcher" + staticEntry("stdlib.request.constructor", "Request", "constructor", "constructor"), + ...[ + "method", + "url", + "headers", + "destination", + "referrer", + "referrerPolicy", + "mode", + "credentials", + "cache", + "redirect", + "integrity", + "keepalive", + "isReloadNavigation", + "isHistoryNavigation", + "signal", + "body", + "bodyUsed", + "duplex", + "clone", + "blob", + "arrayBuffer", + "text", + "json", + "formData", + "bytes", + "attribute", + ].map((member) => + ["clone", "blob", "formData", "attribute"].includes(member) ? unsupportedEntry( - id, - "RequestInit", + `stdlib.request.${member}`, + "Request", member, - "dictionary", - "custom dispatchers are unsupported; native environment-proxy routing uses NODE_USE_ENV_PROXY", + "prototype", + typedInterfaceUnsupported, ) - : unsupportedEntry( - id, - "RequestInit", - member, - "dictionary", - "the compiler does not preserve this RequestInit member's conversion or transport behavior", - ); - }), - ...["headers", "status", "statusText"].map((member) => - staticEntry(`stdlib.response-init.${member}`, "ResponseInit", member, "dictionary"), - ), - ], - excludedInterfaces: [ - { - name: "Blob/File/FormData bodies", - reason: - "fetch request and response bodies do not accept or produce Blob, File, or FormData values", - }, - { - name: "WritableStream/TransformStream", - reason: - "native support covers readable fetch bodies and the identity `new TransformStream()`; the WritableStream constructor, transformers, and general Web Streams graphs are unsupported", - }, - { - name: "Readable byte/BYOB streams", - reason: - "the native stream projection currently supports default readers and controllers only", - }, - { - name: "EventSource/WebSocket", - reason: "these are separate protocol clients, not fetch request/response compatibility", - }, - ], - }, -} satisfies FetchCompatProfile; + : staticEntry(`stdlib.request.${member}`, "Request", member, "prototype"), + ), + outOfScopeEntry( + "stdlib.request.symbol.toStringTag", + "Request", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_RESPONSE_READS = new Set(NODE24_FETCH_COMPAT_PROFILE.members.responseReads); + staticEntry("stdlib.response.constructor", "Response", "constructor", "constructor"), + staticEntry("stdlib.response.static.json", "Response", "json", "static"), + ...["error", "redirect"].map((member) => + unsupportedEntry( + `stdlib.response.static.${member}`, + "Response", + member, + "static", + "Response static constructor-object operations have no compiler lowering", + ), + ), + unsupportedEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence), + ...["url", "redirected", "status", "ok", "statusText", "headers", "body", "bodyUsed"].map( + (member) => staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), + ), + staticEntry("stdlib.response.arrayBuffer", "Response", "arrayBuffer", "prototype"), + ...["clone", "blob", "formData"].map((member) => + unsupportedEntry( + `stdlib.response.${member}`, + "Response", + member, + "prototype", + "the native fetch runtime does not implement this Response operation", + ), + ), + ...["text", "json"].map((member) => + staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), + ), + staticEntry("stdlib.response.bytes", "Response", "bytes", "prototype"), + outOfScopeEntry( + "stdlib.response.symbol.toStringTag", + "Response", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_RESPONSE_CALLS = new Set(NODE24_FETCH_COMPAT_PROFILE.members.responseCalls); + staticEntry( + "stdlib.readable-stream.constructor", + "ReadableStream", + "constructor", + "constructor", + ), + staticEntry("stdlib.readable-stream.from", "ReadableStream", "from", "static"), + staticEntry("stdlib.readable-stream.locked", "ReadableStream", "locked", "prototype"), + staticEntry("stdlib.readable-stream.cancel", "ReadableStream", "cancel", "prototype"), + staticEntry( + "stdlib.readable-stream.get-reader", + "ReadableStream", + "getReader", + "prototype", + ), + ...["pipeThrough", "values"].map((member) => + unsupportedEntry( + `stdlib.readable-stream.${member}`, + "ReadableStream", + member, + "prototype", + member === "values" + ? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle is unsupported" + : "the wider Web Streams graph is outside the native readable-stream slice", + ), + ), + ...["pipeTo", "tee"].map((member) => + unsupportedEntry( + `stdlib.readable-stream.${member}`, + "ReadableStream", + member, + "prototype", + "this operation is outside the native readable-stream implementation", + ), + ), + unsupportedEntry( + "stdlib.readable-stream.symbol.asyncIterator", + "ReadableStream", + "[Symbol.asyncIterator]", + "prototype-symbol", + "direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering", + ), + outOfScopeEntry( + "stdlib.readable-stream.symbol.toStringTag", + "ReadableStream", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_HEADERS_CALLS = new Set( - NODE24_FETCH_COMPAT_PROFILE.inventory.entries - .filter( - (entry) => - entry.owner === "Headers" && entry.placement === "prototype" && entry.status === "static", - ) - .map((entry) => entry.member), -); + unsupportedEntry( + "stdlib.readable-stream-default-reader.constructor", + "ReadableStreamDefaultReader", + "constructor", + "constructor", + "reader handles come from ReadableStream.getReader(); the constructor object is unsupported", + ), + staticEntry( + "stdlib.readable-stream-default-reader.read", + "ReadableStreamDefaultReader", + "read", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.release-lock", + "ReadableStreamDefaultReader", + "releaseLock", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.closed", + "ReadableStreamDefaultReader", + "closed", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.cancel", + "ReadableStreamDefaultReader", + "cancel", + "prototype", + ), + outOfScopeEntry( + "stdlib.readable-stream-default-reader.symbol.toStringTag", + "ReadableStreamDefaultReader", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_READABLE_STREAM_READS = new Set( - NODE24_FETCH_COMPAT_PROFILE.members.readableStreamReads, -); + unsupportedEntry( + "stdlib.readable-stream-default-controller.constructor", + "ReadableStreamDefaultController", + "constructor", + "constructor", + "controller handles come from underlying-source callbacks; the constructor object is unsupported", + ), + staticEntry( + "stdlib.readable-stream-default-controller.desired-size", + "ReadableStreamDefaultController", + "desiredSize", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.close", + "ReadableStreamDefaultController", + "close", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.enqueue", + "ReadableStreamDefaultController", + "enqueue", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.error", + "ReadableStreamDefaultController", + "error", + "prototype", + ), + outOfScopeEntry( + "stdlib.readable-stream-default-controller.symbol.toStringTag", + "ReadableStreamDefaultController", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_READABLE_STREAM_CALLS = new Set( - NODE24_FETCH_COMPAT_PROFILE.members.readableStreamCalls, -); + ...[ + "body", + "cache", + "credentials", + "dispatcher", + "duplex", + "headers", + "integrity", + "keepalive", + "method", + "mode", + "priority", + "redirect", + "referrer", + "referrerPolicy", + "signal", + "window", + ].map((member) => { + const id = `stdlib.fetch.request-init.${member}`; + const supported = new Set(["body", "duplex", "headers", "method", "redirect", "signal"]); + return supported.has(member) + ? staticEntry(id, "RequestInit", member, "dictionary") + : member === "dispatcher" + ? unsupportedEntry( + id, + "RequestInit", + member, + "dictionary", + "custom dispatchers are unsupported; native environment-proxy routing uses NODE_USE_ENV_PROXY", + ) + : unsupportedEntry( + id, + "RequestInit", + member, + "dictionary", + "the compiler does not preserve this RequestInit member's conversion or transport behavior", + ); + }), + ...["headers", "status", "statusText"].map((member) => + staticEntry(`stdlib.response-init.${member}`, "ResponseInit", member, "dictionary"), + ), + ], + excludedInterfaces: [ + { + name: "Blob/File/FormData bodies", + reason: + "fetch request and response bodies do not accept or produce Blob, File, or FormData values", + }, + { + name: "WritableStream/TransformStream", + reason: + "native support covers readable fetch bodies and the identity `new TransformStream()`; the WritableStream constructor, transformers, and general Web Streams graphs are unsupported", + }, + { + name: "Readable byte/BYOB streams", + reason: + "the native stream projection currently supports default readers and controllers only", + }, + { + name: "EventSource/WebSocket", + reason: "these are separate protocol clients, not fetch request/response compatibility", + }, + ], + }, + } satisfies FetchCompatProfile; +} + +let fetchCompatProfile: ReturnType | undefined; + +/** The Node 24 fetch compatibility profile. It is built on the first call, + * so runs that do not lower a module do not construct it. */ +export function node24FetchCompatProfile(): ReturnType { + fetchCompatProfile ??= buildFetchCompatProfile(); + return fetchCompatProfile; +} + +/** The members the native static fetch handles project. */ +export interface StaticFetchMembers { + responseReads: ReadonlySet; + responseCalls: ReadonlySet; + headersCalls: ReadonlySet; + readableStreamReads: ReadonlySet; + readableStreamCalls: ReadonlySet; +} + +let staticFetchMemberSets: StaticFetchMembers | undefined; + +/** The profile's static members as sets, built on the first call. */ +export function staticFetchMembers(): StaticFetchMembers { + if (staticFetchMemberSets !== undefined) return staticFetchMemberSets; + const profile = node24FetchCompatProfile(); + staticFetchMemberSets = { + responseReads: new Set(profile.members.responseReads), + responseCalls: new Set(profile.members.responseCalls), + headersCalls: new Set( + profile.inventory.entries + .filter( + (entry) => + entry.owner === "Headers" && + entry.placement === "prototype" && + entry.status === "static", + ) + .map((entry) => entry.member), + ), + readableStreamReads: new Set(profile.members.readableStreamReads), + readableStreamCalls: new Set(profile.members.readableStreamCalls), + }; + return staticFetchMemberSets; +} diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index 9867a5617..b87a8065d 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -39,14 +39,14 @@ import { InternalCompilerError } from "../errors.js"; * byte-deterministic: entries sort by id, keys are emitted in one fixed * order, and the output carries no timestamps or absolute paths. */ import { FENCE_CODES, UNSUPPORTED, webAssemblyDiag } from "../diagnostics/diagnostic.js"; -import { NODE24_FETCH_COMPAT_PROFILE } from "../compat/fetch-profile.js"; +import { node24FetchCompatProfile } from "../compat/fetch-profile.js"; import { SUPPORTED_BUILTIN_MODULES, SUPPORTED_NODE_MODULES } from "../frontend/builtin-modules.js"; import { - AMBIENT_SURFACE_FNS, + ambientSurfaceFns, ARRAY_METHODS, BUILTIN_MODULE_CONSTS, BUILTIN_MODULE_FENCE_HINTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, COLLECTION_ITERATOR_METHODS, COMPOUND_ASSIGN_OPS, STDLIB_SURFACE, @@ -88,18 +88,21 @@ export const MANIFEST_SCHEMA_VERSION = 2; /** The manifest's own scope statement: what the projection covers and — * just as load-bearing — what it deliberately leaves out, so absence is * never read as a support claim in either direction. */ -const COVERAGE_NOTES: string[] = [ - "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", - "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", - "The ambient-nondeterminism and ambient-authority surfaces the library sidecar's determinism attestation scans ARE projected even where they lower through dedicated code paths — the Date compositions (stdlib.date.*), perf_hooks' performance.now, and the process global's ambient reads and authority calls (node-builtin.process.*) — so a determinism fence can name every surface the attestation demotes on.", - "stdlib and node-builtin member entries name surface whose LOWERED call forms are constrained (arity, argument shapes); declared call forms outside the lowered set are refused per site, with code SC2020 for standard-library and node-builtin surface.", - "Entries with status 'unsupported' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.", - `The engine-free fetch projection targets Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} with bundled Undici ${NODE24_FETCH_COMPAT_PROFILE.target.undici}. Each projected row names the differential evidence that guards it; changing the pinned Node or Undici version is an explicit profile update.`, - "The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.", - "Process-level diagnostic codes are not surface entries: SC0001-SC0004 are preflight gates, SC1110 is a comptime evaluation failure, SC3001/SC3002 are backend/target tier refusals, SC9001/SC9002 are internal errors.", - "Entry statuses are projected for the desktop targets. The mobile targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android) compile library-mode archives only: the library-admissible surface (what SC4005's async_free requirement and the library link set admit) is supported there, the executable lane refuses those triples with SC3002, and no entry outside the library-admissible surface carries a mobile support claim. iOS archives build for iOS 15.0 on darwin hosts; Android archives build against NDK API level 26.", - "No scheduling metadata is published; entry ids are the stable diff keys across releases.", -]; +function coverageNotes(): string[] { + const fetchTarget = node24FetchCompatProfile().target; + return [ + "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", + "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", + "The ambient-nondeterminism and ambient-authority surfaces the library sidecar's determinism attestation scans ARE projected even where they lower through dedicated code paths — the Date compositions (stdlib.date.*), perf_hooks' performance.now, and the process global's ambient reads and authority calls (node-builtin.process.*) — so a determinism fence can name every surface the attestation demotes on.", + "stdlib and node-builtin member entries name surface whose LOWERED call forms are constrained (arity, argument shapes); declared call forms outside the lowered set are refused per site, with code SC2020 for standard-library and node-builtin surface.", + "Entries with status 'unsupported' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.", + `The engine-free fetch projection targets Node ${fetchTarget.node} with bundled Undici ${fetchTarget.undici}. Each projected row names the differential evidence that guards it; changing the pinned Node or Undici version is an explicit profile update.`, + "The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.", + "Process-level diagnostic codes are not surface entries: SC0001-SC0004 are preflight gates, SC1110 is a comptime evaluation failure, SC3001/SC3002 are backend/target tier refusals, SC9001/SC9002 are internal errors.", + "Entry statuses are projected for the desktop targets. The mobile targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android) compile library-mode archives only: the library-admissible surface (what SC4005's async_free requirement and the library link set admit) is supported there, the executable lane refuses those triples with SC3002, and no entry outside the library-admissible surface carries a mobile support claim. iOS archives build for iOS 15.0 on darwin hosts; Android archives build against NDK API level 26.", + "No scheduling metadata is published; entry ids are the stable diff keys across releases.", + ]; +} /** Stable kebab id segment from prose: quotes dropped, the parenthetical * tail dropped, non-alphanumeric runs collapsed to one dash. */ @@ -410,7 +413,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes : "recognized module (bare and node:-prefixed specifiers)", }); } - for (const [mod, members] of Object.entries(BUILTIN_MODULE_FNS)) { + for (const [mod, members] of Object.entries(builtinModuleFns())) { for (const member of Object.keys(members!)) { add({ id: `${moduleId(mod)}.${member}`, @@ -459,10 +462,9 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes } // ── fetch/Web platform: one explicit, versioned compatibility profile ─ - const fetchTarget = - `Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} / ` + - `Undici ${NODE24_FETCH_COMPAT_PROFILE.target.undici}`; - for (const operation of NODE24_FETCH_COMPAT_PROFILE.operations) { + const fetchProfile = node24FetchCompatProfile(); + const fetchTarget = `Node ${fetchProfile.target.node} / Undici ${fetchProfile.target.undici}`; + for (const operation of fetchProfile.operations) { const evidence = operation.evidence.map((item) => item.generated !== undefined ? `generated:${item.generated}` : `fixture:${item.fixture!}`, ); @@ -478,10 +480,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes `differential evidence: ${evidence.join(", ")}`, }); } - for (const options of [ - NODE24_FETCH_COMPAT_PROFILE.requestInit, - NODE24_FETCH_COMPAT_PROFILE.responseInit, - ]) { + for (const options of [fetchProfile.requestInit, fetchProfile.responseInit]) { for (const option of options) { const evidence = option.evidence.map((item) => item.generated !== undefined ? `generated:${item.generated}` : `fixture:${item.fixture!}`, @@ -497,7 +496,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes }); } } - for (const row of NODE24_FETCH_COMPAT_PROFILE.inventory.entries) { + for (const row of fetchProfile.inventory.entries) { if (row.status !== "unsupported") continue; if (row.code === undefined || row.reason === undefined) { throw new InternalCompilerError(`non-static fetch inventory row '${row.id}' is incomplete`); @@ -524,7 +523,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes // Date/perf_hooks/process rows from the one ambient table — each entry // is fenceable by construction (its row carries the reach-witnessing // libCall spellings the fence detector uses). - for (const row of AMBIENT_SURFACE_FNS) { + for (const row of ambientSurfaceFns()) { add({ id: row.id, kind: row.kind, @@ -578,7 +577,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes return { schemaVersion: MANIFEST_SCHEMA_VERSION, compilerVersion, - coverage: COVERAGE_NOTES, + coverage: coverageNotes(), entries, }; } diff --git a/packages/compiler/src/frontend/lowering/lib-boundary.ts b/packages/compiler/src/frontend/lowering/lib-boundary.ts index 5be10e997..8a264d346 100644 --- a/packages/compiler/src/frontend/lowering/lib-boundary.ts +++ b/packages/compiler/src/frontend/lowering/lib-boundary.ts @@ -57,7 +57,7 @@ import { isUnitType, typeEquals, } from "../../ir/ir.js"; -import { LIB_FN_SIGS } from "../../ir/builtin-signatures.js"; +import { libFnSignatures } from "../../ir/builtin-signatures.js"; import { REGEX_INTRINSIC_SIGS, STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js"; import { unionMismatchDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js"; @@ -162,7 +162,7 @@ function enforceExprBoundary(lowerer: Lowerer, node: IrExpr): void { const kind = node.kind; if (kind === "libCall") { const e = node; - const sig = LIB_FN_SIGS[e.fn]; + const sig = libFnSignatures()[e.fn]; if (!sig) return; e.args.forEach((a, i) => { const want = sig.argTypes[i]; diff --git a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts index e5ebc6d8e..d59b234d3 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts @@ -16,7 +16,7 @@ import { strLit, varRef } from "../../ir/build.js"; import type { Lowerer } from "./lowerer.js"; import { BUILTIN_MODULE_CONSTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, OBJECT_CALLABLE_VALUES, builtinConstLit, builtinModuleConstOf, @@ -430,7 +430,7 @@ export function pathModuleValue(lowerer: Lowerer, module: string, loc: SrcLoc): const add = (member: string, value: IrExpr): void => { fields.push({ key: strLit(member, loc), value: box(value) }); }; - for (const member of Object.keys(BUILTIN_MODULE_FNS[module] ?? {})) { + for (const member of Object.keys(builtinModuleFns()[module] ?? {})) { const value = lowerer.lowerBuiltinCallableValue({ module, member }, loc); if (value) add(member, value); } @@ -512,7 +512,7 @@ function moduleValue(lowerer: Lowerer, module: string, loc: SrcLoc): IrExpr { loc, }); }; - for (const member of Object.keys(BUILTIN_MODULE_FNS[module] ?? {})) { + for (const member of Object.keys(builtinModuleFns()[module] ?? {})) { const callable = lowerer.lowerBuiltinCallableValue({ module, member }, loc); if (callable) add(member, callable); } diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index fc0fd338f..fe748bb93 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -35,14 +35,7 @@ import { esmNamedImportLinkCrash, isCjsJsFile, locOf, npmPackageNameOf } from ". import { foldedStringKeyOf, lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { noteEventListeners } from "./lower-event-target.js"; import { PoisonError, newFnCtx, own } from "./lowerer.js"; -import { - NODE24_FETCH_COMPAT_PROFILE, - STATIC_HEADERS_CALLS, - STATIC_READABLE_STREAM_CALLS, - STATIC_READABLE_STREAM_READS, - STATIC_RESPONSE_CALLS, - STATIC_RESPONSE_READS, -} from "../../compat/fetch-profile.js"; +import { node24FetchCompatProfile, staticFetchMembers } from "../../compat/fetch-profile.js"; import { classGeneratorIterable } from "./class-iterables.js"; import { generatorDrain } from "./iterator-adapters.js"; @@ -434,7 +427,7 @@ function fenceRequestInitObject( : contextual ? [contextual] : []; - const rows = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.filter( + const rows = node24FetchCompatProfile().inventory.entries.filter( (entry) => entry.owner === "RequestInit" && entry.placement === "dictionary", ); const shadowed = new Set(); @@ -837,7 +830,7 @@ function fetchInventoryStatus( placement: "static" | "prototype" | "prototype-symbol", ): "static" | "unsupported" | "out-of-scope" | null { return ( - NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && entry.placement === placement, )?.status ?? null ); @@ -848,7 +841,7 @@ function fetchInventoryReason( member: string, placement: "static" | "prototype" | "prototype-symbol", ): string | undefined { - return NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + return node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && entry.placement === placement, )?.reason; } @@ -951,8 +944,9 @@ export function fenceStaticResponseMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); + const projected = staticFetchMembers(); const supported = (member: string) => - use === "call" ? STATIC_RESPONSE_CALLS.has(member) : STATIC_RESPONSE_READS.has(member); + use === "call" ? projected.responseCalls.has(member) : projected.responseReads.has(member); if (members.every(supported)) return null; const member = members.find((candidate) => !supported(candidate))!; if (status(member) === "unsupported") { @@ -993,12 +987,9 @@ export function fenceStaticHeadersMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); - if (use === "call" && members.every((member) => STATIC_HEADERS_CALLS.has(member))) { - return null; - } - const member = members.find( - (candidate) => use !== "call" || !STATIC_HEADERS_CALLS.has(candidate), - )!; + const headersCalls = staticFetchMembers().headersCalls; + if (use === "call" && members.every((member) => headersCalls.has(member))) return null; + const member = members.find((candidate) => use !== "call" || !headersCalls.has(candidate))!; if (status(member) === "unsupported") { lowerer.noLowering( `Headers.${member}`, @@ -1108,7 +1099,7 @@ function fetchInterfaceInventoryOwner( const symbol = [type.getSymbol(), type.getAliasSymbol()].find( (candidate) => candidate !== undefined && - NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(ts.symbolName(candidate)) && + node24FetchCompatProfile().inventory.interfaces.includes(ts.symbolName(candidate)) && lowerer.isStdlibSymbol(candidate), ); return symbol ? { owner: ts.symbolName(symbol), symbol } : null; @@ -1123,7 +1114,7 @@ function fenceFetchObjectMembers( if (!resolved) return; const { owner, symbol } = resolved; for (const { member, node } of members) { - const row = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + const row = node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && @@ -1136,7 +1127,7 @@ function fenceFetchObjectMembers( // Static data properties retain the ordinary checked-dynamic keyed-read // lowering. Only a method extraction loses the receiver that its direct // call bridge would preserve; non-static rows keep their inventory fence. - const operation = NODE24_FETCH_COMPAT_PROFILE.operations.find( + const operation = node24FetchCompatProfile().operations.find( (candidate) => candidate.id === row.id, ); if (row.status === "static" && operation?.kind === "property") { @@ -1235,7 +1226,7 @@ export function lowerFetchElementMethodCall( return null; } const rows = members.map((member) => - NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === resolved.owner && entry.member === member && @@ -1397,10 +1388,11 @@ export function fenceStaticReadableStreamMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); + const projected = staticFetchMembers(); const supported = (member: string) => use === "call" - ? STATIC_READABLE_STREAM_CALLS.has(member) - : STATIC_READABLE_STREAM_READS.has(member); + ? projected.readableStreamCalls.has(member) + : projected.readableStreamReads.has(member); if (members.every(supported)) return null; const member = members.find((candidate) => !supported(candidate))!; if (status(member) === "unsupported") { diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 0303d34d7..b5051d79d 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1129,921 +1129,940 @@ export const UTIL_TYPES_KEYS: readonly string[] = [ "isCryptoKey", ]; -export const BUILTIN_MODULE_FNS: Record< - string, - Record | undefined -> = { - fs: { - readFileSync: { fn: "fs.readFileSync", params: [STRING, STRING], result: STRING }, - // The exact-API subset is first-class. A fixed native ABI is not enough: - // rows whose Node function has behavior-bearing trailing options remain - // call-only, or function-width adaptation could discard those options - // without a diagnostic before entering the fixed runtime call. - writeFileSync: { fn: "fs.writeFileSync", params: [STRING, STRING], result: VOID }, - appendFileSync: { fn: "fs.appendFileSync", params: [STRING, STRING], result: VOID }, - existsSync: { - fn: "fs.existsSync", - params: [STRING], - result: BOOL, - valueParams: exactValueParams(STRING), - }, - mkdirSync: { fn: "fs.mkdirSync", params: [STRING], result: VOID }, - rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID }, - rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID }, - readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) }, - // Pattern and options are checked by the runtime (lowerFsGlobSyncCall). - globSync: { fn: "fs.globSync", params: [DYN, DYN], result: arrayOf(STRING) }, - statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T }, - mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING }, - // accessSync's omitted mode completes to 0 (F_OK) in the special case - // in lowerBuiltinModuleCall — string `defaults` can't spell a number. - accessSync: { fn: "fs.accessSync", params: [STRING, F64], result: VOID }, - unlinkSync: { - fn: "fs.unlinkSync", - params: [STRING], - result: VOID, - valueParams: exactValueParams(STRING), - }, - chmodSync: { - fn: "fs.chmodSync", - params: [STRING, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64), - }, - chownSync: { - fn: "fs.chownSync", - params: [STRING, F64, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64, F64), - }, - lchownSync: { - fn: "fs.lchownSync", - params: [STRING, F64, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64, F64), - }, - fchownSync: { - fn: "fs.fchownSync", - params: [F64, F64, F64], - result: VOID, - valueParams: exactValueParams(F64, F64, F64), - }, - // truncateSync's omitted len completes to 0 in lowerBuiltinModuleCall, - // like accessSync's mode. - truncateSync: { fn: "fs.truncateSync", params: [STRING, F64], result: VOID }, - // The 2-argument form only: Node's mode flags (COPYFILE_EXCL, ...) - // land on the arity fence. - copyFileSync: { fn: "fs.copyFileSync", params: [STRING, STRING], result: VOID }, - // The callback form is special-cased in lowerBuiltinModuleCall: its - // Error | null parameter is program-shaped and needs an emitted - // adapter. The row still projects the static surface and routes the - // dispatch. - rename: { fn: "fs.renameCb", params: [], result: VOID }, - renameSync: { - fn: "fs.renameSync", - params: [STRING, STRING], - result: VOID, - valueParams: exactValueParams(STRING, STRING), - }, - // statSync's no-follow sibling; stats.isSymbolicLink answers what the - // follow-free snapshot saw. - // opendirSync's options (a literal { bufferSize, encoding: "utf8" }) - // lower in lowerBuiltinModuleCall; the row routes the dispatch. - opendirSync: { fn: "fs.opendirSync", params: [STRING, F64], result: DIR_T }, - // createReadStream/createWriteStream lower in lowerBuiltinModuleCall - // (path and options cross as dyn values); the rows route the dispatch. - createReadStream: { - fn: "fs.createReadStream", - params: [DYN, DYN], - result: { kind: "object", className: "%Readable" }, - }, - createWriteStream: { - fn: "fs.createWriteStream", - params: [DYN, DYN], - result: { kind: "object", className: "%Writable" }, - }, - lstatSync: { fn: "fs.lstatSync", params: [STRING], result: STATS_T }, - fstatSync: { - fn: "fs.fstatSync", - params: [F64], - result: STATS_T, - valueParams: exactValueParams(F64), - }, - fchmodSync: { - fn: "fs.fchmodSync", - params: [F64, F64], - result: VOID, - valueParams: exactValueParams(F64, F64), - }, - fsyncSync: { - fn: "fs.fsyncSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - fdatasyncSync: { - fn: "fs.fdatasyncSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - ftruncateSync: { fn: "fs.ftruncateSync", params: [F64, F64], result: VOID }, - utimesSync: { fn: "fs.utimesSync", params: [DYN, DYN, DYN], result: VOID }, - futimesSync: { fn: "fs.futimesSync", params: [DYN, DYN, DYN], result: VOID }, - lutimesSync: { fn: "fs.lutimesSync", params: [DYN, DYN, DYN], result: VOID }, - readvSync: { fn: "fs.readvSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - writevSync: { fn: "fs.writevSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - linkSync: { - fn: "fs.linkSync", - params: [DYN, DYN], - result: VOID, - valueParams: exactValueParams(DYN, DYN), - }, - // Options and paths are checked by the runtime (scr_fs_cp_begin). - cpSync: { - fn: "fs.cpSync", - params: [DYN, DYN, DYN], - result: VOID, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - symlinkSync: { - fn: "fs.symlinkSync", - params: [DYN, DYN, DYN], - result: VOID, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - readlinkSync: { - fn: "fs.readlinkSync", - params: [DYN, DYN], - result: DYN, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - statfsSync: { - fn: "fs.statfsSync", - params: [DYN, DYN], - result: DYN, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - // realpath(3) — Node's realpathSync (failures spell syscall "lstat", - // Node's own message shape). - realpathSync: { fn: "fs.realpathSync", params: [STRING], result: STRING }, - "realpathSync.native": { fn: "fs.realpathNativeSync", params: [STRING], result: STRING }, - // The fd pair behind spawn's fd-stdio form (the daemon-log idiom: - // openSync(logPath, "a") → spawn stdio ["ignore", fd, fd] → - // closeSync). String flags use the established two-argument path; - // inline numeric O_* expressions route through openNumericSync, with - // an optional creation mode as the third argument. - openSync: { fn: "fs.openSync", params: [STRING, STRING], result: F64 }, - // The buffer forms (fd, buffer, offset, length[, position]). The - // lowering completes an omitted or literal-null position to -1 (the - // runtime's current-offset sentinel); numeric positions read without - // advancing the fd. The options-object and bigint forms still fence. - readSync: { fn: "fs.readSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - // Entirely special-cased: the classic Buffer window and utf8 string - // overloads lower to separate fixed-width runtime ABIs. As with - // readSync, -1 is the current-offset sentinel. - writeSync: { fn: "fs.writeSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - closeSync: { - fn: "fs.closeSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - // Entirely special-cased (lowerFsWatchCall — the callback needs an - // adapter per listener shape); this entry only routes the dispatch. - watch: { fn: "fs.watch", params: [], result: VOID }, - }, - "fs/promises": { - // The sync operations behind already-settled promises: failures - // REJECT (catchable at the await) instead of throwing. readFile is - // utf8-fenced exactly like readFileSync (same special case below). - readFile: { - fn: "fsp.readFile", - params: [STRING, STRING], - result: { kind: "promise", inner: STRING }, - }, - writeFile: { - fn: "fsp.writeFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - mkdir: { fn: "fsp.mkdir", params: [STRING], result: { kind: "promise", inner: VOID } }, - readdir: { - fn: "fsp.readdir", - params: [STRING], - result: { kind: "promise", inner: arrayOf(STRING) }, - }, - rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } }, - // The async iterator lowers only as a for await head (lowerFspGlobCall). - glob: { fn: "fsp.globStrings", params: [DYN, DYN], result: arrayOf(STRING) }, - stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, - realpath: { fn: "fsp.realpath", params: [STRING], result: { kind: "promise", inner: STRING } }, - lstat: { fn: "fsp.lstat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, - opendir: { - fn: "fsp.opendir", - params: [STRING, F64], - result: { kind: "promise", inner: DIR_T }, - }, - unlink: { - fn: "fsp.unlink", - params: [STRING], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING), - }, - chmod: { - fn: "fsp.chmod", - params: [STRING, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64), - }, - chown: { - fn: "fsp.chown", - params: [STRING, F64, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64, F64), - }, - lchown: { - fn: "fsp.lchown", - params: [STRING, F64, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64, F64), - }, - // access' omitted mode and truncate's omitted len complete to 0 in - // lowerBuiltinModuleCall, like their sync spellings. - access: { fn: "fsp.access", params: [STRING, F64], result: { kind: "promise", inner: VOID } }, - truncate: { - fn: "fsp.truncate", - params: [STRING, F64], - result: { kind: "promise", inner: VOID }, - }, - // String data; Uint8Array/Buffer data routes to fsp.appendFileBytes in - // lowerBuiltinModuleCall. - appendFile: { - fn: "fsp.appendFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - // The 2-argument form only, like copyFileSync. - copyFile: { - fn: "fsp.copyFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - mkdtemp: { fn: "fsp.mkdtemp", params: [STRING], result: { kind: "promise", inner: STRING } }, - rmdir: { fn: "fsp.rmdir", params: [STRING], result: { kind: "promise", inner: VOID } }, - link: { - fn: "fsp.link", - params: [DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(DYN, DYN), - }, - cp: { - fn: "fsp.cp", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - symlink: { - fn: "fsp.symlink", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - readlink: { - fn: "fsp.readlink", - params: [DYN, DYN], - result: { kind: "promise", inner: DYN }, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - statfs: { - fn: "fsp.statfs", - params: [DYN, DYN], - result: { kind: "promise", inner: DYN }, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - utimes: { fn: "fsp.utimes", params: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - lutimes: { - fn: "fsp.lutimes", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - rename: { - fn: "fsp.rename", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, STRING), - }, - // open's optional flags/mode completion is special-cased in - // lowerBuiltinModuleCall; this row routes all import spellings and - // gives coverage the static member. - open: { - fn: "fsp.open", - params: [STRING, STRING, F64], - result: { kind: "promise", inner: FILEHANDLE_T }, - valueParams: [ - { mode: "required", type: STRING }, - { mode: "optional", type: STRING, defaultValue: "r" }, - { mode: "optional", type: F64, defaultValue: 0o666 }, - ], - }, - }, - // The bare module's POSIX-target binding; a win32 target rebinds it to - // the win32 table (builtinModuleFnsOf — Node on Windows IS path.win32). - path: PATH_MODULE_FNS, - // Real Node modules ("node:path/posix" resolves), and the receivers of - // `path.posix.` / `path.win32.` through the namespace - // chain (builtinNamespaceModuleOf composes "path" + ".posix" to these - // keys). Both namespaces carry the FULL surface on every platform, - // Node's own rule: each answers its own platform's semantics anywhere. - "path/posix": PATH_MODULE_FNS, - "path/win32": PATH_WIN32_MODULE_FNS, - os: { - // One platform implementation: os.platform() === process.platform. - platform: { fn: "process.platform", params: [], result: STRING, valueParams: [] }, - homedir: { fn: "os.homedir", params: [], result: STRING, valueParams: [] }, - tmpdir: { fn: "os.tmpdir", params: [], result: STRING, valueParams: [] }, - // uname(2)'s release field — Node's own implementation. - release: { fn: "os.release", params: [], result: STRING, valueParams: [] }, - // uname(2)'s sysname field ("Darwin", "Linux", "Windows_NT") — the - // libFn/runtime pair predates this entry (the portless surface used - // them); this row makes the MODULE call reach them (test/common's - // isAIX/isIBMi getters are the canonical callers). - type: { fn: "os.type", params: [], result: STRING, valueParams: [] }, - // Total physical memory in bytes — same predating-pair story. - totalmem: { fn: "os.totalmem", params: [], result: F64, valueParams: [] }, - // uv_available_parallelism: the CPUs this process may use (affinity, - // then cgroup quota on Linux), at least 1. - availableParallelism: { - fn: "os.availableParallelism", - params: [], - result: F64, - valueParams: [], - }, - // os.arch() IS process.arch, like platform(). - arch: { fn: "process.arch", params: [], result: STRING, valueParams: [] }, - hostname: { fn: "os.hostname", params: [], result: STRING, valueParams: [] }, - // uv_os_uname's machine and version fields, read once at module load - // in Node (they never change while a process runs). - machine: { fn: "os.machine", params: [], result: STRING, valueParams: [] }, - version: { fn: "os.version", params: [], result: STRING, valueParams: [] }, - endianness: { fn: "os.endianness", params: [], result: STRING, valueParams: [] }, - freemem: { fn: "os.freemem", params: [], result: F64, valueParams: [] }, - uptime: { fn: "os.uptime", params: [], result: F64, valueParams: [] }, - loadavg: { fn: "os.loadavg", params: [], result: arrayOf(F64), valueParams: [] }, - // An omitted (or undefined) pid is 0, the current process - // (lowerOsGetPriorityCall completes it). - getPriority: { - fn: "os.getPriority", - params: [F64], - result: F64, - valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }], - }, - // A one-argument call is setPriority(0, priority) - // (lowerOsSetPriorityCall completes it). - setPriority: { fn: "os.setPriority", params: [F64, F64], result: VOID }, - // Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles - // from one uv_cpu_info snapshot. - cpus: { fn: "os.cpusTake", params: [], result: VOID }, - // Entirely special-cased (lowerOsNetworkInterfacesCall): the result is - // the call site's mapped Dict shape, verified - // structurally there — this entry only routes the dispatch. - networkInterfaces: { fn: "os.networkInterfaces", params: [], result: VOID }, - // Special-cased too (lowerOsUserInfoCall): the call site's mapped - // UserInfo record assembles field-by-field from scalar libCalls. - userInfo: { fn: "os.userName", params: [], result: VOID }, - }, - crypto: { - randomUUID: { - fn: "crypto.randomUUID", - params: [], - result: STRING, - // As a value (uuid's `export default { randomUUID }`), the optional - // options argument goes through the validating entry point. - valueFn: "crypto.randomUUIDOpts", - valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], - }, - // A real Buffer result. The composed randomBytes(n).toString(enc) - // form KEEPS its one-libCall lowering (lowerCryptoComposedCall runs - // first and the Buffer never materializes there); this entry covers - // the bare calls and non-composed uses. - randomBytes: { - fn: "crypto.randomBytes", - params: [F64], - result: BYTES_U8, - valueParams: exactValueParams(F64), - }, - // The crypto utility surface is special-cased for overloads and - // string/Buffer splits in lowerCryptoModuleCall. These rows carry the - // canonical signatures for manifest projection and fallback dispatch. - createHash: { fn: "crypto.hashNew", params: [STRING], result: CRYPTOHASH_T }, - createHmac: { fn: "crypto.hmacNewStr", params: [STRING, STRING], result: CRYPTOHMAC_T }, - hash: { fn: "crypto.hashDigestStr", params: [STRING, STRING, STRING], result: STRING }, - timingSafeEqual: { fn: "crypto.timingSafeEqual", params: [BYTES_U8, BYTES_U8], result: BOOL }, - randomFillSync: { fn: "crypto.randomFill", params: [BYTES_U8, F64, F64], result: BYTES_U8 }, - getRandomValues: { fn: "crypto.getRandomValues", params: [BYTES_U8], result: BYTES_U8 }, - randomInt: { fn: "crypto.randomInt", params: [F64, F64], result: F64 }, - randomFill: { fn: "crypto.randomFillCb", params: [], result: VOID }, - randomUUIDv7: { - fn: "crypto.randomUUIDv7", - params: [DYN], - result: STRING, - valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], - }, - createSecretKey: { fn: "crypto.createSecretKey", params: [DYN, DYN], result: DYN }, - generateKeySync: { fn: "crypto.generateKeySync", params: [DYN, DYN], result: DYN }, - generateKey: { fn: "crypto.generateKeyCb", params: [], result: VOID }, - // Asymmetric keys and signatures (lowerPkiCall): checked-dynamic - // arguments, results converted to the call site's type. - createPrivateKey: { fn: "crypto.createPrivateKey", params: [DYN], result: DYN }, - createPublicKey: { fn: "crypto.createPublicKey", params: [DYN], result: DYN }, - createSign: { fn: "crypto.createSign", params: [DYN], result: DYN }, - createVerify: { fn: "crypto.createVerify", params: [DYN], result: DYN }, - generateKeyPairSync: { fn: "crypto.generateKeyPairSync", params: [], result: DYN }, - generateKeyPair: { fn: "crypto.generateKeyPairCb", params: [], result: VOID }, - sign: { fn: "crypto.signOneshot", params: [], result: DYN }, - verify: { fn: "crypto.verifyOneshot", params: [], result: DYN }, - diffieHellman: { fn: "crypto.diffieHellman", params: [], result: DYN }, - pbkdf2Sync: { - fn: "crypto.pbkdf2", - params: [BYTES_U8, BYTES_U8, F64, F64, STRING], - result: BYTES_U8, - }, - pbkdf2: { fn: "crypto.pbkdf2Cb", params: [], result: VOID }, - hkdfSync: { - fn: "crypto.hkdf", - params: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], - result: DYN, - }, - hkdf: { fn: "crypto.hkdfCb", params: [], result: VOID }, - scryptSync: { fn: "crypto.scrypt", params: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, - scrypt: { fn: "crypto.scryptCb", params: [], result: VOID }, - // Checked-dynamic arguments; the result is a native Cipheriv or - // Decipheriv handle (lowerCryptoModuleCall). - createCipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - createDecipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - // A checked-dynamic curve name; the result is a native ECDH handle - // (lowerCreateEcdh). - createECDH: { fn: "crypto.ecdhNew", params: [DYN], result: DYN }, - // Checked-dynamic arguments; the result is a native DiffieHellman or - // DiffieHellmanGroup handle (lowerCreateDiffieHellman and - // lowerGetDiffieHellman). - createDiffieHellman: { fn: "crypto.dhNew", params: [DYN, DYN, DYN, DYN], result: DYN }, - getDiffieHellman: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, - createDiffieHellmanGroup: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, - // Prime checks and generation (lowerPrimeCall): checked-dynamic - // arguments, results converted to the call site's type. - checkPrimeSync: { fn: "crypto.checkPrimeSync", params: [], result: DYN }, - // Checked-dynamic arguments, the result converted to the call site's - // type (lowerPrimeCall). - getCipherInfo: { fn: "crypto.getCipherInfo", params: [], result: DYN }, - checkPrime: { fn: "crypto.checkPrimeCb", params: [], result: VOID }, - generatePrimeSync: { fn: "crypto.generatePrimeSync", params: [], result: DYN }, - generatePrime: { fn: "crypto.generatePrimeCb", params: [], result: VOID }, - }, - zlib: { - // The default-options convenience family is special-cased in - // lowerBuiltinModuleCall: string inputs UTF-8 encode, callback forms - // carry program-shaped closures, and explicit options fence per site. - // native-toolchain.ts links libz only when these appear on the IR. - deflate: { fn: "zlib.deflateCb", params: [], result: VOID }, - deflateSync: { fn: "zlib.deflateSync", params: [BYTES_U8], result: BYTES_U8 }, - inflate: { fn: "zlib.inflateCb", params: [], result: VOID }, - inflateSync: { fn: "zlib.inflateSync", params: [BYTES_U8], result: BYTES_U8 }, - deflateRaw: { fn: "zlib.deflateRawCb", params: [], result: VOID }, - deflateRawSync: { fn: "zlib.deflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, - inflateRaw: { fn: "zlib.inflateRawCb", params: [], result: VOID }, - inflateRawSync: { fn: "zlib.inflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, - gzip: { fn: "zlib.gzipCb", params: [], result: VOID }, - gzipSync: { fn: "zlib.gzipSync", params: [BYTES_U8], result: BYTES_U8 }, - gunzip: { fn: "zlib.gunzipCb", params: [], result: VOID }, - gunzipSync: { fn: "zlib.gunzipSync", params: [BYTES_U8], result: BYTES_U8 }, - unzip: { fn: "zlib.unzipCb", params: [], result: VOID }, - unzipSync: { fn: "zlib.unzipSync", params: [BYTES_U8], result: BYTES_U8 }, - crc32: { fn: "zlib.crc32", params: [], result: F64 }, - // Brotli (special-cased in lowerZlibModuleCall): every form takes the - // options entries with Node's BROTLI_ENCODE or BROTLI_DECODE mode. - brotliCompress: { fn: "zlib.cbOpts", params: [], result: VOID }, - brotliCompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, - brotliDecompress: { fn: "zlib.cbOpts", params: [], result: VOID }, - brotliDecompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, - // The stream factories (special-cased in lowerZlibModuleCall): each - // answers a runtime Transform stream over the zlib engine. - createDeflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createInflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createGzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createGunzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createDeflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createInflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createUnzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createBrotliCompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createBrotliDecompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - }, - url: { - fileURLToPath: { - fn: "url.fileURLToPathOptions", - params: [DYN, DYN], - result: STRING, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - fileURLToPathBuffer: { - fn: "url.fileURLToPathBuffer", - params: [DYN, DYN], - result: BYTES_U8, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - pathToFileURL: { - fn: "url.pathToFileURLChecked", - params: [DYN, DYN], - result: URL_T, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - // The legacy API and the domain pair (lowerLegacyUrlCall): arguments - // cross as dyn values for Node's own validators; url.parse() answers - // the checked-dynamic Url instance. - parse: { fn: "url.legacyParse", params: [DYN, DYN, DYN], result: DYN }, - format: { fn: "url.legacyFormat", params: [DYN, DYN], result: STRING }, - resolve: { fn: "url.legacyResolve", params: [DYN, DYN], result: STRING }, - domainToASCII: { fn: "url.domainToASCII", params: [DYN], result: STRING }, - domainToUnicode: { fn: "url.domainToUnicode", params: [DYN], result: STRING }, - urlToHttpOptions: { fn: "url.toHttpOptions", params: [DYN], result: DYN }, - }, - child_process: { - // spawnSync's and spawn's call completions are entirely special-cased - // (an omitted args list completes to an empty string[]; spawnSync - // accepts its documented literal options, while spawn supports the - // stdio/detached/env/cwd/windowsHide/shell subset — see - // lowerBuiltinModuleCall); the - // entries carry the canonical shapes. - spawnSync: { fn: "cp.spawnSync", params: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, - spawn: { fn: "cp.spawn", params: [STRING, arrayOf(STRING)], result: CHILD_T }, - fork: { fn: "cp.fork", params: [STRING, arrayOf(STRING)], result: CHILD_T }, - // execFileSync(file, args?, options?) and execSync(command, options?) - // share the cp.execSync runtime entry (execSync sets the shell flag); - // both call completions are special-cased in lowerBuiltinModuleCall. - // The entries carry canonical shapes for the coverage/fence machinery. - execFileSync: { fn: "cp.execSync", params: [STRING, arrayOf(STRING)], result: STRING }, - execSync: { fn: "cp.execSync", params: [STRING], result: STRING }, - }, - // node:util lowers through dedicated paths: promisify - // only in the const-binding-over-execFile shape — recognized in - // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and - // inspect/format/formatWithOptions plus parseArgs through the util spoke - // (lower-inspect.ts: per-type synthesized traversal helpers, - // compile-time format strings, checked-dynamic config/result for - // parseArgs), which both dispatch paths try before the generic table. - // parseArgs is tabled as well so the generated surface manifest records - // the dedicated static member; the spoke owns its optional arity and - // call-site-specific result type before this canonical signature is used. - util: { - styleText: { - fn: "util.styleText", - params: [DYN, DYN, DYN], - result: STRING, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - isDeepStrictEqual: { - fn: "util.isDeepStrictEqual", - params: [DYN, DYN, DYN], - result: BOOL, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - parseArgs: { fn: "util.parseArgs", params: [DYN], result: DYN }, - parseEnv: { fn: "util.parseEnv", params: [DYN], result: DYN }, - getSystemErrorName: { - fn: "util.getSystemErrorName", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - getSystemErrorMessage: { - fn: "util.getSystemErrorMessage", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - getSystemErrorMap: { fn: "util.systemErrorEntries", params: [], result: DYN }, - stripVTControlCharacters: { - fn: "util.stripVTControlCharacters", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - toUSVString: { - fn: "util.toUSVString", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - // The function helpers take checked-dynamic arguments and answer native - // dyn functions, which a static function slot checks when it receives - // one (lowerUtilCallableCall in the util spoke). - inherits: { - fn: "util.inherits", - params: [DYN, DYN], - result: VOID, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - ], - }, - deprecate: { - fn: "util.deprecate", - params: [DYN, DYN, DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - callbackify: { - fn: "util.callbackify", - params: [DYN], - result: DYN, - valueParams: [{ mode: "required", type: DYN }], - }, - debuglog: { - fn: "util.debuglog", - params: [DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - debug: { - fn: "util.debuglog", - params: [DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - }, - // The util/types spoke supplies the probe name to the shared brand check. - "util/types": { - isAnyArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isArrayBufferView: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isDataView: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isTypedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint8ClampedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isWeakMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isWeakSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isRegExp: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isNativeError: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isPromise: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isProxy: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isAsyncFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isGeneratorFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isGeneratorObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isDate: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSharedArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isMapIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSetIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isKeyObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isNumberObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isStringObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBooleanObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigIntObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSymbolObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBoxedPrimitive: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigInt64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigUint64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isCryptoKey: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isExternal: { fn: "util.typeIs", params: [DYN], result: BOOL }, - }, - buffer: { - isAscii: { fn: "buffer.isAscii", params: [DYN], result: BOOL }, - isUtf8: { fn: "buffer.isUtf8", params: [DYN], result: BOOL }, - transcode: { fn: "buffer.transcode", params: [DYN, DYN, DYN], result: BYTES_U8 }, - }, - tty: { - isatty: { fn: "process.isTTY", params: [F64], result: BOOL }, - }, - process: { - loadEnvFile: { fn: "process.loadEnvFile", params: [DYN], result: VOID }, - }, - // node:string_decoder's surface is the StringDecoder CLASS — new/write/ - // end are special-cased (lowerNew + lowerStringDecoderMethodCall); no - // function members exist to table. - string_decoder: {}, - // node:querystring — the legacy query-string codec (NOT URLSearchParams; - // the escaping and '+' rules differ). escape/unescape ride the table - // path directly; parse and stringify are special-cased in - // lowerBuiltinModuleCall (parse's result is the call site's mapped - // ParsedUrlQuery dictionary — the networkInterfaces verification stance - // — and its sep/eq/options complete there; stringify's object argument - // crosses as a dyn value). decode/encode are Node's own aliases of - // parse/stringify (`const decode = parse` in lib/querystring.js) and - // route to the same special cases; the entries carry canonical shapes. - querystring: { - parse: { fn: "qs.parse", params: [STRING], result: VOID }, - decode: { fn: "qs.parse", params: [STRING], result: VOID }, - stringify: { fn: "qs.stringify", params: [DYN], result: STRING }, - encode: { fn: "qs.stringify", params: [DYN], result: STRING }, - escape: { - fn: "qs.escape", - params: [STRING], - result: STRING, - valueParams: [{ mode: "required", type: STRING }], - }, - unescape: { - fn: "qs.unescape", - params: [STRING], - result: STRING, - valueParams: [{ mode: "required", type: STRING }], - }, - }, - // node:punycode (deprecated): punycode.js 2.1.0's conversions over - // strings (scr_url.c). ucs2.decode/encode are members of the module's - // ucs2 object, dispatched as module members (builtinMemberOf). The - // DEP0040 report rides the module's load sites (punycodeLoadWarning). - punycode: { - decode: { fn: "punycode.decode", params: [STRING], result: STRING }, - encode: { fn: "punycode.encode", params: [STRING], result: STRING }, - toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING }, - toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING }, - "ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) }, - "ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING }, - }, - // node:constants (deprecated) is one frozen object (Object.assign of - // os.constants' groups, fs.constants and crypto.constants): bindings of - // the module read it as a checked-dynamic value (builtinConstantsObject). - constants: {}, - // node:readline: createInterface's options are entirely special-cased - // (exactly { input: process.stdin, output?: process.stdout } — see - // lowerBuiltinModuleCall); the entry carries the canonical shape. The - // interface's question/close/on lower through - // lowerReadlineMethodCall. - readline: { - createInterface: { fn: "rl.create", params: [], result: F64 }, - }, - // node:readline/promises: the same Interface with the promise question - // (lowerReadlineMethodCall tells the forms apart by the call's result). - "readline/promises": { - createInterface: { fn: "rl.create", params: [], result: F64 }, - }, - // node:net and node:http lower ENTIRELY through the server spoke - // (lower-server.ts — every call shape is special-cased: closures, the - // optional connect host, writeHead's literal headers); the member list - // is the lower-server dispatch there. The modules still need keys here so future - // table entries have a home, and so the "recognized module, unlowered - // member" fence wording applies. - net: { - // The process-wide happy-eyeballs attempt budget: one runtime double - // in the core unit (never links scr_net.c by itself), default 250ms - // like Node's. - getDefaultAutoSelectFamilyAttemptTimeout: { - fn: "net.getAutoSelTimeout", - params: [], - result: F64, - }, - setDefaultAutoSelectFamilyAttemptTimeout: { - fn: "net.setAutoSelTimeout", - params: [F64], - result: VOID, - }, - // Node's IPv4Reg / IPv6Reg tests over String(input). - isIP: { fn: "net.isIP", params: [DYN], result: F64 }, - isIPv4: { fn: "net.isIPv4", params: [DYN], result: BOOL }, - isIPv6: { fn: "net.isIPv6", params: [DYN], result: BOOL }, - }, - http: {}, - // node:tls and node:https ride the same spoke (tls.createServer's - // options + secureConnection handler, https.createServer, and the - // https client's rejectUnauthorized/ca options are all special-cased - // in lower-server.ts). - tls: {}, - https: {}, - // node:dgram and node:dns lower ENTIRELY through the dgram spoke - // (lower-dgram.ts — createSocket's option literal, dns.lookup's - // options + callback, and the Socket method surface are all - // special-cased); the keys exist for the same reason net/http's do. - dgram: {}, - dns: {}, - // node:dns/promises rides the same spoke (lookup's promise form). - "dns/promises": {}, - // node:v8: getHeapStatistics answers allocator-derived figures in - // Node's shape (builtins/v8.ts — the entry carries the canonical - // shape); every other member fences with the by-design reason - // (builtinFenceHintOf). - v8: { - getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, - }, - // node:stream/web re-exports the web stream globals (the same objects); - // its members lower through the globals' own paths. - "stream/web": {}, - // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts - // — every call shape is special-cased: optional messages complete, the - // comparisons pick per-type libCalls, deep equality synthesizes - // helpers); the keys exist so unlowered members fence module-qualified - // with the hints below. - assert: {}, - "assert/strict": {}, - // node:test lowers ENTIRELY through the test spoke (lower-test.ts — - // registrations, suites, hooks, the TestContext surface); the key - // exists so unlowered members fence module-qualified. - test: {}, - // node:stream/promises lowers through the stream spoke too - // (lower-stream.ts — finished/pipeline's promise forms special-case - // their stream arguments exactly like the callback forms); the key - // exists so unlowered members fence module-qualified. - "stream/promises": {}, - // node:stream/consumers rides the stream spoke as well (text/json/ - // buffer/arrayBuffer special-case their stream argument); blob fences - // module-qualified with the pointed hint below. - "stream/consumers": {}, - // node:timers/promises — the delay-only setTimeout and bare setImmediate - // (void promises the shared timer heap settles), and scheduler.wait / - // scheduler.yield, which Node defines as setTimeout(delay, undefined, - // options) and setImmediate(). The arity completion (omitted delay = - // Node's 1ms) and the value/options fences are special-cased in - // lowerBuiltinModuleCall; these entries carry the canonical shapes and - // route the dispatch. - "timers/promises": { - setTimeout: { fn: "tp.setTimeout", params: [F64], result: { kind: "promise", inner: VOID } }, - setImmediate: { fn: "tp.setImmediate", params: [], result: { kind: "promise", inner: VOID } }, - "scheduler.wait": { - fn: "tp.setTimeout", - params: [F64], - result: { kind: "promise", inner: VOID }, - }, - "scheduler.yield": { - fn: "tp.setImmediate", - params: [], - result: { kind: "promise", inner: VOID }, - }, - }, - // node:diagnostics_channel — the pub/sub core. channel() answers an f64 - // handle (type-mapper.ts maps Channel to F64); the subscriber arguments box - // into the checked-dynamic tree (special-cased in lowerBuiltinModuleCall — the entries - // carry canonical shapes and route the dispatch). The Channel method - // surface lowers through lowerDiagnosticsChannelMethodCall/lowerDiagnosticsChannelProperty. - diagnostics_channel: { - channel: { fn: "dc.channel", params: [STRING], result: F64 }, - subscribe: { fn: "dc.subscribe", params: [STRING, DYN], result: VOID }, - unsubscribe: { fn: "dc.unsubscribe", params: [STRING, DYN], result: BOOL }, - hasSubscribers: { fn: "dc.hasSubscribers", params: [STRING], result: BOOL }, - // The string form's canonical shape; the collection form (an object - // literal of five Channels) is special-cased in lowerBuiltinModuleCall. - tracingChannel: { fn: "dc.tracingChannel", params: [STRING], result: F64 }, - }, -}; +type BuiltinModuleFnTable = Record | undefined>; + +let builtinModuleFnTable: BuiltinModuleFnTable | undefined; + +/** Every builtin module's lowered function members, keyed by module and + * member. The table is built on the first call, so runs that do not lower + * a module do not construct it. */ +export function builtinModuleFns(): BuiltinModuleFnTable { + builtinModuleFnTable ??= buildBuiltinModuleFns(); + return builtinModuleFnTable; +} + +function buildBuiltinModuleFns(): BuiltinModuleFnTable { + return { + fs: { + readFileSync: { fn: "fs.readFileSync", params: [STRING, STRING], result: STRING }, + // The exact-API subset is first-class. A fixed native ABI is not enough: + // rows whose Node function has behavior-bearing trailing options remain + // call-only, or function-width adaptation could discard those options + // without a diagnostic before entering the fixed runtime call. + writeFileSync: { fn: "fs.writeFileSync", params: [STRING, STRING], result: VOID }, + appendFileSync: { fn: "fs.appendFileSync", params: [STRING, STRING], result: VOID }, + existsSync: { + fn: "fs.existsSync", + params: [STRING], + result: BOOL, + valueParams: exactValueParams(STRING), + }, + mkdirSync: { fn: "fs.mkdirSync", params: [STRING], result: VOID }, + rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID }, + rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID }, + readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) }, + // Pattern and options are checked by the runtime (lowerFsGlobSyncCall). + globSync: { fn: "fs.globSync", params: [DYN, DYN], result: arrayOf(STRING) }, + statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T }, + mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING }, + // accessSync's omitted mode completes to 0 (F_OK) in the special case + // in lowerBuiltinModuleCall — string `defaults` can't spell a number. + accessSync: { fn: "fs.accessSync", params: [STRING, F64], result: VOID }, + unlinkSync: { + fn: "fs.unlinkSync", + params: [STRING], + result: VOID, + valueParams: exactValueParams(STRING), + }, + chmodSync: { + fn: "fs.chmodSync", + params: [STRING, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64), + }, + chownSync: { + fn: "fs.chownSync", + params: [STRING, F64, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64, F64), + }, + lchownSync: { + fn: "fs.lchownSync", + params: [STRING, F64, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64, F64), + }, + fchownSync: { + fn: "fs.fchownSync", + params: [F64, F64, F64], + result: VOID, + valueParams: exactValueParams(F64, F64, F64), + }, + // truncateSync's omitted len completes to 0 in lowerBuiltinModuleCall, + // like accessSync's mode. + truncateSync: { fn: "fs.truncateSync", params: [STRING, F64], result: VOID }, + // The 2-argument form only: Node's mode flags (COPYFILE_EXCL, ...) + // land on the arity fence. + copyFileSync: { fn: "fs.copyFileSync", params: [STRING, STRING], result: VOID }, + // The callback form is special-cased in lowerBuiltinModuleCall: its + // Error | null parameter is program-shaped and needs an emitted + // adapter. The row still projects the static surface and routes the + // dispatch. + rename: { fn: "fs.renameCb", params: [], result: VOID }, + renameSync: { + fn: "fs.renameSync", + params: [STRING, STRING], + result: VOID, + valueParams: exactValueParams(STRING, STRING), + }, + // statSync's no-follow sibling; stats.isSymbolicLink answers what the + // follow-free snapshot saw. + // opendirSync's options (a literal { bufferSize, encoding: "utf8" }) + // lower in lowerBuiltinModuleCall; the row routes the dispatch. + opendirSync: { fn: "fs.opendirSync", params: [STRING, F64], result: DIR_T }, + // createReadStream/createWriteStream lower in lowerBuiltinModuleCall + // (path and options cross as dyn values); the rows route the dispatch. + createReadStream: { + fn: "fs.createReadStream", + params: [DYN, DYN], + result: { kind: "object", className: "%Readable" }, + }, + createWriteStream: { + fn: "fs.createWriteStream", + params: [DYN, DYN], + result: { kind: "object", className: "%Writable" }, + }, + lstatSync: { fn: "fs.lstatSync", params: [STRING], result: STATS_T }, + fstatSync: { + fn: "fs.fstatSync", + params: [F64], + result: STATS_T, + valueParams: exactValueParams(F64), + }, + fchmodSync: { + fn: "fs.fchmodSync", + params: [F64, F64], + result: VOID, + valueParams: exactValueParams(F64, F64), + }, + fsyncSync: { + fn: "fs.fsyncSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + fdatasyncSync: { + fn: "fs.fdatasyncSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + ftruncateSync: { fn: "fs.ftruncateSync", params: [F64, F64], result: VOID }, + utimesSync: { fn: "fs.utimesSync", params: [DYN, DYN, DYN], result: VOID }, + futimesSync: { fn: "fs.futimesSync", params: [DYN, DYN, DYN], result: VOID }, + lutimesSync: { fn: "fs.lutimesSync", params: [DYN, DYN, DYN], result: VOID }, + readvSync: { fn: "fs.readvSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + writevSync: { fn: "fs.writevSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + linkSync: { + fn: "fs.linkSync", + params: [DYN, DYN], + result: VOID, + valueParams: exactValueParams(DYN, DYN), + }, + // Options and paths are checked by the runtime (scr_fs_cp_begin). + cpSync: { + fn: "fs.cpSync", + params: [DYN, DYN, DYN], + result: VOID, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + symlinkSync: { + fn: "fs.symlinkSync", + params: [DYN, DYN, DYN], + result: VOID, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + readlinkSync: { + fn: "fs.readlinkSync", + params: [DYN, DYN], + result: DYN, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + statfsSync: { + fn: "fs.statfsSync", + params: [DYN, DYN], + result: DYN, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + // realpath(3) — Node's realpathSync (failures spell syscall "lstat", + // Node's own message shape). + realpathSync: { fn: "fs.realpathSync", params: [STRING], result: STRING }, + "realpathSync.native": { fn: "fs.realpathNativeSync", params: [STRING], result: STRING }, + // The fd pair behind spawn's fd-stdio form (the daemon-log idiom: + // openSync(logPath, "a") → spawn stdio ["ignore", fd, fd] → + // closeSync). String flags use the established two-argument path; + // inline numeric O_* expressions route through openNumericSync, with + // an optional creation mode as the third argument. + openSync: { fn: "fs.openSync", params: [STRING, STRING], result: F64 }, + // The buffer forms (fd, buffer, offset, length[, position]). The + // lowering completes an omitted or literal-null position to -1 (the + // runtime's current-offset sentinel); numeric positions read without + // advancing the fd. The options-object and bigint forms still fence. + readSync: { fn: "fs.readSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + // Entirely special-cased: the classic Buffer window and utf8 string + // overloads lower to separate fixed-width runtime ABIs. As with + // readSync, -1 is the current-offset sentinel. + writeSync: { fn: "fs.writeSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + closeSync: { + fn: "fs.closeSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + // Entirely special-cased (lowerFsWatchCall — the callback needs an + // adapter per listener shape); this entry only routes the dispatch. + watch: { fn: "fs.watch", params: [], result: VOID }, + }, + "fs/promises": { + // The sync operations behind already-settled promises: failures + // REJECT (catchable at the await) instead of throwing. readFile is + // utf8-fenced exactly like readFileSync (same special case below). + readFile: { + fn: "fsp.readFile", + params: [STRING, STRING], + result: { kind: "promise", inner: STRING }, + }, + writeFile: { + fn: "fsp.writeFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + mkdir: { fn: "fsp.mkdir", params: [STRING], result: { kind: "promise", inner: VOID } }, + readdir: { + fn: "fsp.readdir", + params: [STRING], + result: { kind: "promise", inner: arrayOf(STRING) }, + }, + rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } }, + // The async iterator lowers only as a for await head (lowerFspGlobCall). + glob: { fn: "fsp.globStrings", params: [DYN, DYN], result: arrayOf(STRING) }, + stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, + realpath: { + fn: "fsp.realpath", + params: [STRING], + result: { kind: "promise", inner: STRING }, + }, + lstat: { fn: "fsp.lstat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, + opendir: { + fn: "fsp.opendir", + params: [STRING, F64], + result: { kind: "promise", inner: DIR_T }, + }, + unlink: { + fn: "fsp.unlink", + params: [STRING], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING), + }, + chmod: { + fn: "fsp.chmod", + params: [STRING, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64), + }, + chown: { + fn: "fsp.chown", + params: [STRING, F64, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64, F64), + }, + lchown: { + fn: "fsp.lchown", + params: [STRING, F64, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64, F64), + }, + // access' omitted mode and truncate's omitted len complete to 0 in + // lowerBuiltinModuleCall, like their sync spellings. + access: { fn: "fsp.access", params: [STRING, F64], result: { kind: "promise", inner: VOID } }, + truncate: { + fn: "fsp.truncate", + params: [STRING, F64], + result: { kind: "promise", inner: VOID }, + }, + // String data; Uint8Array/Buffer data routes to fsp.appendFileBytes in + // lowerBuiltinModuleCall. + appendFile: { + fn: "fsp.appendFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + // The 2-argument form only, like copyFileSync. + copyFile: { + fn: "fsp.copyFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + mkdtemp: { fn: "fsp.mkdtemp", params: [STRING], result: { kind: "promise", inner: STRING } }, + rmdir: { fn: "fsp.rmdir", params: [STRING], result: { kind: "promise", inner: VOID } }, + link: { + fn: "fsp.link", + params: [DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(DYN, DYN), + }, + cp: { + fn: "fsp.cp", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + symlink: { + fn: "fsp.symlink", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + readlink: { + fn: "fsp.readlink", + params: [DYN, DYN], + result: { kind: "promise", inner: DYN }, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + statfs: { + fn: "fsp.statfs", + params: [DYN, DYN], + result: { kind: "promise", inner: DYN }, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + utimes: { + fn: "fsp.utimes", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + lutimes: { + fn: "fsp.lutimes", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + rename: { + fn: "fsp.rename", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, STRING), + }, + // open's optional flags/mode completion is special-cased in + // lowerBuiltinModuleCall; this row routes all import spellings and + // gives coverage the static member. + open: { + fn: "fsp.open", + params: [STRING, STRING, F64], + result: { kind: "promise", inner: FILEHANDLE_T }, + valueParams: [ + { mode: "required", type: STRING }, + { mode: "optional", type: STRING, defaultValue: "r" }, + { mode: "optional", type: F64, defaultValue: 0o666 }, + ], + }, + }, + // The bare module's POSIX-target binding; a win32 target rebinds it to + // the win32 table (builtinModuleFnsOf — Node on Windows IS path.win32). + path: PATH_MODULE_FNS, + // Real Node modules ("node:path/posix" resolves), and the receivers of + // `path.posix.` / `path.win32.` through the namespace + // chain (builtinNamespaceModuleOf composes "path" + ".posix" to these + // keys). Both namespaces carry the FULL surface on every platform, + // Node's own rule: each answers its own platform's semantics anywhere. + "path/posix": PATH_MODULE_FNS, + "path/win32": PATH_WIN32_MODULE_FNS, + os: { + // One platform implementation: os.platform() === process.platform. + platform: { fn: "process.platform", params: [], result: STRING, valueParams: [] }, + homedir: { fn: "os.homedir", params: [], result: STRING, valueParams: [] }, + tmpdir: { fn: "os.tmpdir", params: [], result: STRING, valueParams: [] }, + // uname(2)'s release field — Node's own implementation. + release: { fn: "os.release", params: [], result: STRING, valueParams: [] }, + // uname(2)'s sysname field ("Darwin", "Linux", "Windows_NT") — the + // libFn/runtime pair predates this entry (the portless surface used + // them); this row makes the MODULE call reach them (test/common's + // isAIX/isIBMi getters are the canonical callers). + type: { fn: "os.type", params: [], result: STRING, valueParams: [] }, + // Total physical memory in bytes — same predating-pair story. + totalmem: { fn: "os.totalmem", params: [], result: F64, valueParams: [] }, + // uv_available_parallelism: the CPUs this process may use (affinity, + // then cgroup quota on Linux), at least 1. + availableParallelism: { + fn: "os.availableParallelism", + params: [], + result: F64, + valueParams: [], + }, + // os.arch() IS process.arch, like platform(). + arch: { fn: "process.arch", params: [], result: STRING, valueParams: [] }, + hostname: { fn: "os.hostname", params: [], result: STRING, valueParams: [] }, + // uv_os_uname's machine and version fields, read once at module load + // in Node (they never change while a process runs). + machine: { fn: "os.machine", params: [], result: STRING, valueParams: [] }, + version: { fn: "os.version", params: [], result: STRING, valueParams: [] }, + endianness: { fn: "os.endianness", params: [], result: STRING, valueParams: [] }, + freemem: { fn: "os.freemem", params: [], result: F64, valueParams: [] }, + uptime: { fn: "os.uptime", params: [], result: F64, valueParams: [] }, + loadavg: { fn: "os.loadavg", params: [], result: arrayOf(F64), valueParams: [] }, + // An omitted (or undefined) pid is 0, the current process + // (lowerOsGetPriorityCall completes it). + getPriority: { + fn: "os.getPriority", + params: [F64], + result: F64, + valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }], + }, + // A one-argument call is setPriority(0, priority) + // (lowerOsSetPriorityCall completes it). + setPriority: { fn: "os.setPriority", params: [F64, F64], result: VOID }, + // Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles + // from one uv_cpu_info snapshot. + cpus: { fn: "os.cpusTake", params: [], result: VOID }, + // Entirely special-cased (lowerOsNetworkInterfacesCall): the result is + // the call site's mapped Dict shape, verified + // structurally there — this entry only routes the dispatch. + networkInterfaces: { fn: "os.networkInterfaces", params: [], result: VOID }, + // Special-cased too (lowerOsUserInfoCall): the call site's mapped + // UserInfo record assembles field-by-field from scalar libCalls. + userInfo: { fn: "os.userName", params: [], result: VOID }, + }, + crypto: { + randomUUID: { + fn: "crypto.randomUUID", + params: [], + result: STRING, + // As a value (uuid's `export default { randomUUID }`), the optional + // options argument goes through the validating entry point. + valueFn: "crypto.randomUUIDOpts", + valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], + }, + // A real Buffer result. The composed randomBytes(n).toString(enc) + // form KEEPS its one-libCall lowering (lowerCryptoComposedCall runs + // first and the Buffer never materializes there); this entry covers + // the bare calls and non-composed uses. + randomBytes: { + fn: "crypto.randomBytes", + params: [F64], + result: BYTES_U8, + valueParams: exactValueParams(F64), + }, + // The crypto utility surface is special-cased for overloads and + // string/Buffer splits in lowerCryptoModuleCall. These rows carry the + // canonical signatures for manifest projection and fallback dispatch. + createHash: { fn: "crypto.hashNew", params: [STRING], result: CRYPTOHASH_T }, + createHmac: { fn: "crypto.hmacNewStr", params: [STRING, STRING], result: CRYPTOHMAC_T }, + hash: { fn: "crypto.hashDigestStr", params: [STRING, STRING, STRING], result: STRING }, + timingSafeEqual: { fn: "crypto.timingSafeEqual", params: [BYTES_U8, BYTES_U8], result: BOOL }, + randomFillSync: { fn: "crypto.randomFill", params: [BYTES_U8, F64, F64], result: BYTES_U8 }, + getRandomValues: { fn: "crypto.getRandomValues", params: [BYTES_U8], result: BYTES_U8 }, + randomInt: { fn: "crypto.randomInt", params: [F64, F64], result: F64 }, + randomFill: { fn: "crypto.randomFillCb", params: [], result: VOID }, + randomUUIDv7: { + fn: "crypto.randomUUIDv7", + params: [DYN], + result: STRING, + valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], + }, + createSecretKey: { fn: "crypto.createSecretKey", params: [DYN, DYN], result: DYN }, + generateKeySync: { fn: "crypto.generateKeySync", params: [DYN, DYN], result: DYN }, + generateKey: { fn: "crypto.generateKeyCb", params: [], result: VOID }, + // Asymmetric keys and signatures (lowerPkiCall): checked-dynamic + // arguments, results converted to the call site's type. + createPrivateKey: { fn: "crypto.createPrivateKey", params: [DYN], result: DYN }, + createPublicKey: { fn: "crypto.createPublicKey", params: [DYN], result: DYN }, + createSign: { fn: "crypto.createSign", params: [DYN], result: DYN }, + createVerify: { fn: "crypto.createVerify", params: [DYN], result: DYN }, + generateKeyPairSync: { fn: "crypto.generateKeyPairSync", params: [], result: DYN }, + generateKeyPair: { fn: "crypto.generateKeyPairCb", params: [], result: VOID }, + sign: { fn: "crypto.signOneshot", params: [], result: DYN }, + verify: { fn: "crypto.verifyOneshot", params: [], result: DYN }, + diffieHellman: { fn: "crypto.diffieHellman", params: [], result: DYN }, + pbkdf2Sync: { + fn: "crypto.pbkdf2", + params: [BYTES_U8, BYTES_U8, F64, F64, STRING], + result: BYTES_U8, + }, + pbkdf2: { fn: "crypto.pbkdf2Cb", params: [], result: VOID }, + hkdfSync: { + fn: "crypto.hkdf", + params: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], + result: DYN, + }, + hkdf: { fn: "crypto.hkdfCb", params: [], result: VOID }, + scryptSync: { fn: "crypto.scrypt", params: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, + scrypt: { fn: "crypto.scryptCb", params: [], result: VOID }, + // Checked-dynamic arguments; the result is a native Cipheriv or + // Decipheriv handle (lowerCryptoModuleCall). + createCipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + createDecipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + // A checked-dynamic curve name; the result is a native ECDH handle + // (lowerCreateEcdh). + createECDH: { fn: "crypto.ecdhNew", params: [DYN], result: DYN }, + // Checked-dynamic arguments; the result is a native DiffieHellman or + // DiffieHellmanGroup handle (lowerCreateDiffieHellman and + // lowerGetDiffieHellman). + createDiffieHellman: { fn: "crypto.dhNew", params: [DYN, DYN, DYN, DYN], result: DYN }, + getDiffieHellman: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, + createDiffieHellmanGroup: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, + // Prime checks and generation (lowerPrimeCall): checked-dynamic + // arguments, results converted to the call site's type. + checkPrimeSync: { fn: "crypto.checkPrimeSync", params: [], result: DYN }, + // Checked-dynamic arguments, the result converted to the call site's + // type (lowerPrimeCall). + getCipherInfo: { fn: "crypto.getCipherInfo", params: [], result: DYN }, + checkPrime: { fn: "crypto.checkPrimeCb", params: [], result: VOID }, + generatePrimeSync: { fn: "crypto.generatePrimeSync", params: [], result: DYN }, + generatePrime: { fn: "crypto.generatePrimeCb", params: [], result: VOID }, + }, + zlib: { + // The default-options convenience family is special-cased in + // lowerBuiltinModuleCall: string inputs UTF-8 encode, callback forms + // carry program-shaped closures, and explicit options fence per site. + // native-toolchain.ts links libz only when these appear on the IR. + deflate: { fn: "zlib.deflateCb", params: [], result: VOID }, + deflateSync: { fn: "zlib.deflateSync", params: [BYTES_U8], result: BYTES_U8 }, + inflate: { fn: "zlib.inflateCb", params: [], result: VOID }, + inflateSync: { fn: "zlib.inflateSync", params: [BYTES_U8], result: BYTES_U8 }, + deflateRaw: { fn: "zlib.deflateRawCb", params: [], result: VOID }, + deflateRawSync: { fn: "zlib.deflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, + inflateRaw: { fn: "zlib.inflateRawCb", params: [], result: VOID }, + inflateRawSync: { fn: "zlib.inflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, + gzip: { fn: "zlib.gzipCb", params: [], result: VOID }, + gzipSync: { fn: "zlib.gzipSync", params: [BYTES_U8], result: BYTES_U8 }, + gunzip: { fn: "zlib.gunzipCb", params: [], result: VOID }, + gunzipSync: { fn: "zlib.gunzipSync", params: [BYTES_U8], result: BYTES_U8 }, + unzip: { fn: "zlib.unzipCb", params: [], result: VOID }, + unzipSync: { fn: "zlib.unzipSync", params: [BYTES_U8], result: BYTES_U8 }, + crc32: { fn: "zlib.crc32", params: [], result: F64 }, + // Brotli (special-cased in lowerZlibModuleCall): every form takes the + // options entries with Node's BROTLI_ENCODE or BROTLI_DECODE mode. + brotliCompress: { fn: "zlib.cbOpts", params: [], result: VOID }, + brotliCompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, + brotliDecompress: { fn: "zlib.cbOpts", params: [], result: VOID }, + brotliDecompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, + // The stream factories (special-cased in lowerZlibModuleCall): each + // answers a runtime Transform stream over the zlib engine. + createDeflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createInflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createGzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createGunzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createDeflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createInflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createUnzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createBrotliCompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createBrotliDecompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + }, + url: { + fileURLToPath: { + fn: "url.fileURLToPathOptions", + params: [DYN, DYN], + result: STRING, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + fileURLToPathBuffer: { + fn: "url.fileURLToPathBuffer", + params: [DYN, DYN], + result: BYTES_U8, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + pathToFileURL: { + fn: "url.pathToFileURLChecked", + params: [DYN, DYN], + result: URL_T, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + // The legacy API and the domain pair (lowerLegacyUrlCall): arguments + // cross as dyn values for Node's own validators; url.parse() answers + // the checked-dynamic Url instance. + parse: { fn: "url.legacyParse", params: [DYN, DYN, DYN], result: DYN }, + format: { fn: "url.legacyFormat", params: [DYN, DYN], result: STRING }, + resolve: { fn: "url.legacyResolve", params: [DYN, DYN], result: STRING }, + domainToASCII: { fn: "url.domainToASCII", params: [DYN], result: STRING }, + domainToUnicode: { fn: "url.domainToUnicode", params: [DYN], result: STRING }, + urlToHttpOptions: { fn: "url.toHttpOptions", params: [DYN], result: DYN }, + }, + child_process: { + // spawnSync's and spawn's call completions are entirely special-cased + // (an omitted args list completes to an empty string[]; spawnSync + // accepts its documented literal options, while spawn supports the + // stdio/detached/env/cwd/windowsHide/shell subset — see + // lowerBuiltinModuleCall); the + // entries carry the canonical shapes. + spawnSync: { fn: "cp.spawnSync", params: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, + spawn: { fn: "cp.spawn", params: [STRING, arrayOf(STRING)], result: CHILD_T }, + fork: { fn: "cp.fork", params: [STRING, arrayOf(STRING)], result: CHILD_T }, + // execFileSync(file, args?, options?) and execSync(command, options?) + // share the cp.execSync runtime entry (execSync sets the shell flag); + // both call completions are special-cased in lowerBuiltinModuleCall. + // The entries carry canonical shapes for the coverage/fence machinery. + execFileSync: { fn: "cp.execSync", params: [STRING, arrayOf(STRING)], result: STRING }, + execSync: { fn: "cp.execSync", params: [STRING], result: STRING }, + }, + // node:util lowers through dedicated paths: promisify + // only in the const-binding-over-execFile shape — recognized in + // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and + // inspect/format/formatWithOptions plus parseArgs through the util spoke + // (lower-inspect.ts: per-type synthesized traversal helpers, + // compile-time format strings, checked-dynamic config/result for + // parseArgs), which both dispatch paths try before the generic table. + // parseArgs is tabled as well so the generated surface manifest records + // the dedicated static member; the spoke owns its optional arity and + // call-site-specific result type before this canonical signature is used. + util: { + styleText: { + fn: "util.styleText", + params: [DYN, DYN, DYN], + result: STRING, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + isDeepStrictEqual: { + fn: "util.isDeepStrictEqual", + params: [DYN, DYN, DYN], + result: BOOL, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + parseArgs: { fn: "util.parseArgs", params: [DYN], result: DYN }, + parseEnv: { fn: "util.parseEnv", params: [DYN], result: DYN }, + getSystemErrorName: { + fn: "util.getSystemErrorName", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + getSystemErrorMessage: { + fn: "util.getSystemErrorMessage", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + getSystemErrorMap: { fn: "util.systemErrorEntries", params: [], result: DYN }, + stripVTControlCharacters: { + fn: "util.stripVTControlCharacters", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + toUSVString: { + fn: "util.toUSVString", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + // The function helpers take checked-dynamic arguments and answer native + // dyn functions, which a static function slot checks when it receives + // one (lowerUtilCallableCall in the util spoke). + inherits: { + fn: "util.inherits", + params: [DYN, DYN], + result: VOID, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + ], + }, + deprecate: { + fn: "util.deprecate", + params: [DYN, DYN, DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + callbackify: { + fn: "util.callbackify", + params: [DYN], + result: DYN, + valueParams: [{ mode: "required", type: DYN }], + }, + debuglog: { + fn: "util.debuglog", + params: [DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + debug: { + fn: "util.debuglog", + params: [DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + }, + // The util/types spoke supplies the probe name to the shared brand check. + "util/types": { + isAnyArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isArrayBufferView: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isDataView: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isTypedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint8ClampedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isWeakMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isWeakSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isRegExp: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isNativeError: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isPromise: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isProxy: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isAsyncFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isGeneratorFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isGeneratorObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isDate: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSharedArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isMapIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSetIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isKeyObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isNumberObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isStringObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBooleanObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigIntObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSymbolObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBoxedPrimitive: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigInt64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigUint64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isCryptoKey: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isExternal: { fn: "util.typeIs", params: [DYN], result: BOOL }, + }, + buffer: { + isAscii: { fn: "buffer.isAscii", params: [DYN], result: BOOL }, + isUtf8: { fn: "buffer.isUtf8", params: [DYN], result: BOOL }, + transcode: { fn: "buffer.transcode", params: [DYN, DYN, DYN], result: BYTES_U8 }, + }, + tty: { + isatty: { fn: "process.isTTY", params: [F64], result: BOOL }, + }, + process: { + loadEnvFile: { fn: "process.loadEnvFile", params: [DYN], result: VOID }, + }, + // node:string_decoder's surface is the StringDecoder CLASS — new/write/ + // end are special-cased (lowerNew + lowerStringDecoderMethodCall); no + // function members exist to table. + string_decoder: {}, + // node:querystring — the legacy query-string codec (NOT URLSearchParams; + // the escaping and '+' rules differ). escape/unescape ride the table + // path directly; parse and stringify are special-cased in + // lowerBuiltinModuleCall (parse's result is the call site's mapped + // ParsedUrlQuery dictionary — the networkInterfaces verification stance + // — and its sep/eq/options complete there; stringify's object argument + // crosses as a dyn value). decode/encode are Node's own aliases of + // parse/stringify (`const decode = parse` in lib/querystring.js) and + // route to the same special cases; the entries carry canonical shapes. + querystring: { + parse: { fn: "qs.parse", params: [STRING], result: VOID }, + decode: { fn: "qs.parse", params: [STRING], result: VOID }, + stringify: { fn: "qs.stringify", params: [DYN], result: STRING }, + encode: { fn: "qs.stringify", params: [DYN], result: STRING }, + escape: { + fn: "qs.escape", + params: [STRING], + result: STRING, + valueParams: [{ mode: "required", type: STRING }], + }, + unescape: { + fn: "qs.unescape", + params: [STRING], + result: STRING, + valueParams: [{ mode: "required", type: STRING }], + }, + }, + // node:punycode (deprecated): punycode.js 2.1.0's conversions over + // strings (scr_url.c). ucs2.decode/encode are members of the module's + // ucs2 object, dispatched as module members (builtinMemberOf). The + // DEP0040 report rides the module's load sites (punycodeLoadWarning). + punycode: { + decode: { fn: "punycode.decode", params: [STRING], result: STRING }, + encode: { fn: "punycode.encode", params: [STRING], result: STRING }, + toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING }, + toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING }, + "ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) }, + "ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING }, + }, + // node:constants (deprecated) is one frozen object (Object.assign of + // os.constants' groups, fs.constants and crypto.constants): bindings of + // the module read it as a checked-dynamic value (builtinConstantsObject). + constants: {}, + // node:readline: createInterface's options are entirely special-cased + // (exactly { input: process.stdin, output?: process.stdout } — see + // lowerBuiltinModuleCall); the entry carries the canonical shape. The + // interface's question/close/on lower through + // lowerReadlineMethodCall. + readline: { + createInterface: { fn: "rl.create", params: [], result: F64 }, + }, + // node:readline/promises: the same Interface with the promise question + // (lowerReadlineMethodCall tells the forms apart by the call's result). + "readline/promises": { + createInterface: { fn: "rl.create", params: [], result: F64 }, + }, + // node:net and node:http lower ENTIRELY through the server spoke + // (lower-server.ts — every call shape is special-cased: closures, the + // optional connect host, writeHead's literal headers); the member list + // is the lower-server dispatch there. The modules still need keys here so future + // table entries have a home, and so the "recognized module, unlowered + // member" fence wording applies. + net: { + // The process-wide happy-eyeballs attempt budget: one runtime double + // in the core unit (never links scr_net.c by itself), default 250ms + // like Node's. + getDefaultAutoSelectFamilyAttemptTimeout: { + fn: "net.getAutoSelTimeout", + params: [], + result: F64, + }, + setDefaultAutoSelectFamilyAttemptTimeout: { + fn: "net.setAutoSelTimeout", + params: [F64], + result: VOID, + }, + // Node's IPv4Reg / IPv6Reg tests over String(input). + isIP: { fn: "net.isIP", params: [DYN], result: F64 }, + isIPv4: { fn: "net.isIPv4", params: [DYN], result: BOOL }, + isIPv6: { fn: "net.isIPv6", params: [DYN], result: BOOL }, + }, + http: {}, + // node:tls and node:https ride the same spoke (tls.createServer's + // options + secureConnection handler, https.createServer, and the + // https client's rejectUnauthorized/ca options are all special-cased + // in lower-server.ts). + tls: {}, + https: {}, + // node:dgram and node:dns lower ENTIRELY through the dgram spoke + // (lower-dgram.ts — createSocket's option literal, dns.lookup's + // options + callback, and the Socket method surface are all + // special-cased); the keys exist for the same reason net/http's do. + dgram: {}, + dns: {}, + // node:dns/promises rides the same spoke (lookup's promise form). + "dns/promises": {}, + // node:v8: getHeapStatistics answers allocator-derived figures in + // Node's shape (builtins/v8.ts — the entry carries the canonical + // shape); every other member fences with the by-design reason + // (builtinFenceHintOf). + v8: { + getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, + }, + // node:stream/web re-exports the web stream globals (the same objects); + // its members lower through the globals' own paths. + "stream/web": {}, + // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts + // — every call shape is special-cased: optional messages complete, the + // comparisons pick per-type libCalls, deep equality synthesizes + // helpers); the keys exist so unlowered members fence module-qualified + // with the hints below. + assert: {}, + "assert/strict": {}, + // node:test lowers ENTIRELY through the test spoke (lower-test.ts — + // registrations, suites, hooks, the TestContext surface); the key + // exists so unlowered members fence module-qualified. + test: {}, + // node:stream/promises lowers through the stream spoke too + // (lower-stream.ts — finished/pipeline's promise forms special-case + // their stream arguments exactly like the callback forms); the key + // exists so unlowered members fence module-qualified. + "stream/promises": {}, + // node:stream/consumers rides the stream spoke as well (text/json/ + // buffer/arrayBuffer special-case their stream argument); blob fences + // module-qualified with the pointed hint below. + "stream/consumers": {}, + // node:timers/promises — the delay-only setTimeout and bare setImmediate + // (void promises the shared timer heap settles), and scheduler.wait / + // scheduler.yield, which Node defines as setTimeout(delay, undefined, + // options) and setImmediate(). The arity completion (omitted delay = + // Node's 1ms) and the value/options fences are special-cased in + // lowerBuiltinModuleCall; these entries carry the canonical shapes and + // route the dispatch. + "timers/promises": { + setTimeout: { fn: "tp.setTimeout", params: [F64], result: { kind: "promise", inner: VOID } }, + setImmediate: { fn: "tp.setImmediate", params: [], result: { kind: "promise", inner: VOID } }, + "scheduler.wait": { + fn: "tp.setTimeout", + params: [F64], + result: { kind: "promise", inner: VOID }, + }, + "scheduler.yield": { + fn: "tp.setImmediate", + params: [], + result: { kind: "promise", inner: VOID }, + }, + }, + // node:diagnostics_channel — the pub/sub core. channel() answers an f64 + // handle (type-mapper.ts maps Channel to F64); the subscriber arguments box + // into the checked-dynamic tree (special-cased in lowerBuiltinModuleCall — the entries + // carry canonical shapes and route the dispatch). The Channel method + // surface lowers through lowerDiagnosticsChannelMethodCall/lowerDiagnosticsChannelProperty. + diagnostics_channel: { + channel: { fn: "dc.channel", params: [STRING], result: F64 }, + subscribe: { fn: "dc.subscribe", params: [STRING, DYN], result: VOID }, + unsubscribe: { fn: "dc.unsubscribe", params: [STRING, DYN], result: BOOL }, + hasSubscribers: { fn: "dc.hasSubscribers", params: [STRING], result: BOOL }, + // The string form's canonical shape; the collection form (an object + // literal of five Channels) is special-cased in lowerBuiltinModuleCall. + tracingChannel: { fn: "dc.tracingChannel", params: [STRING], result: F64 }, + }, + }; +} /** Builtin-module CONSTANTS (value reads of named imports): each lowers to * a literal of its value's kind. The bare-module values are the TARGET @@ -2080,7 +2099,7 @@ export const BUILTIN_MODULE_CONSTS: Record< * the sidecar's determinism attestation demotes on them by prefix, so the * member's fence detector must witness them too (fencing one spelling * fences the operation, the trimLeft/trimStart rule). Keyed like - * BUILTIN_MODULE_FNS; consumed by the fence taxonomy + * builtinModuleFns(); consumed by the fence taxonomy * (library/fence-eval.ts) and the attestation-parity test. */ export const BUILTIN_MODULE_FN_ALIASES: Record< string, @@ -2232,628 +2251,643 @@ export interface AmbientSurfaceRow { note?: string; } -export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ - { - id: "node-builtin.worker_threads.Worker", - kind: "node-builtin", - name: "worker_threads.Worker", - fns: ["worker.new"], - note: "native threads with statically compiled entry points, workerData, argv and message events", - }, - { - id: "node-builtin.worker_threads.metadata", - kind: "node-builtin", - name: "worker_threads metadata", - fns: [ - "worker.isMainThread", - "worker.threadId", - "worker.data", - "worker.parentPort", - "worker.resourceLimits", - "worker.threadName", - ], - note: "current-thread metadata and the worker's parent message port", - }, - { - id: "stdlib.sharedArrayBuffer", - kind: "stdlib", - name: "SharedArrayBuffer", - fns: ["sharedArrayBuffer.new"], - note: "fixed shared storage with context-local typed-array and DataView wrappers", - }, - { - id: "stdlib.blob", - kind: "stdlib", - name: "Blob", - fns: ["blob.new", "blob.slice", "blob.text", "blob.bytes", "blob.arrayBuffer", "blob.is"], - note: "immutable bytes with size, type, slice(), text(), bytes(), arrayBuffer() and structured cloning", - }, - { - id: "stdlib.file", - kind: "stdlib", - name: "File", - fns: ["blob.fileNew"], - note: "a Blob with name and lastModified (Date.now() when absent)", - }, - { - id: "stdlib.webcrypto.subtle", - kind: "stdlib", - name: "globalThis.crypto.subtle", - fns: [ - "webcrypto.generateKey", - "webcrypto.importKey", - "webcrypto.exportKey", - "webcrypto.encrypt", - "webcrypto.decrypt", - "webcrypto.wrapKey", - "webcrypto.unwrapKey", - "webcrypto.deriveBits", - "webcrypto.deriveKey", - "webcrypto.sign", - "webcrypto.verify", - "webcrypto.digest", - ], - note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused", - }, - { - id: "stdlib.webcrypto.CryptoKey", - kind: "stdlib", - name: "CryptoKey", - fns: ["webcrypto.isCryptoKey"], - note: "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey", - }, - { - id: "stdlib.atomics", - kind: "stdlib", - name: "Atomics", - fns: ["atomics.op", "atomics.wait", "atomics.notify"], - note: "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify", - }, - { - id: "node-builtin.fs.callbacks", - kind: "node-builtin", - name: "fs error-first callbacks", - fns: ["fs.callbackValue", "fs.callbackCall", "fs.callbackGlob"], - note: "native filesystem callbacks, including stored platform adapter functions", - }, - // ── the Date slice (lowerDateCall/lowerNew): statics, construction, - // stored read-only values, calendar getters, and ISO formatting. - { - id: "stdlib.date.now", - kind: "stdlib", - name: "Date.now", - fns: ["date.now"], - note: "the live clock", - }, - { - id: "stdlib.date.parse", - kind: "stdlib", - name: "Date.parse", - fns: ["date.parse"], - note: "one date string, using V8's date parser shared with new Date(dateString)", - }, - { - id: "stdlib.date.constructor", - kind: "stdlib", - name: "Date constructor", - fns: [ - "date.newNow", - "date.newMs", - "date.newString", - "date.nativeNew", - "date.newLocal", - "date.nativeNewFields", - "date.nativeNewValue", - "date.argumentsTime", - "date.classOperation", - ], - note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", - }, - { - id: "stdlib.date.setters", - kind: "stdlib", - name: "Date.prototype setters", - fns: ["date.setFields", "date.timeClip", "date.store"], - note: "setTime and the local and UTC field setters, with their optional arguments", - }, - { - id: "stdlib.date.UTC", - kind: "stdlib", - name: "Date.UTC", - fns: ["date.utc"], - note: "the lowered call form takes 1 to 7 number arguments", - }, - { - id: "stdlib.date.getTime", - kind: "stdlib", - name: "Date.prototype.getTime", - fns: ["date.getTime", "date.parseGetTime"], - note: "the millisecond value of a stored Date", - }, - { - id: "stdlib.date.valueOf", - kind: "stdlib", - name: "Date.prototype.valueOf", - fns: ["date.valueOf"], - note: "the same millisecond read as getTime()", - }, - { - id: "stdlib.date.toISOString", - kind: "stdlib", - name: "Date.prototype.toISOString", - fns: ["date.toISOString", "date.toISOStringValue"], - note: "UTC ISO formatting over constructed and stored Date values", - }, - { - id: "stdlib.date.toUTCString", - kind: "stdlib", - name: "Date.prototype.toUTCString", - fns: ["date.toUTCString"], - note: "RFC 7231 IMF-fixdate formatting over stored Date values", - }, - { - id: "stdlib.date.getFullYear", - kind: "stdlib", - name: "Date.prototype.getFullYear", - fns: ["date.getFullYear"], - }, - { - id: "stdlib.date.getUTCFullYear", - kind: "stdlib", - name: "Date.prototype.getUTCFullYear", - fns: ["date.getUTCFullYear"], - }, - { - id: "stdlib.date.getMonth", - kind: "stdlib", - name: "Date.prototype.getMonth", - fns: ["date.getMonth"], - }, - { - id: "stdlib.date.getUTCMonth", - kind: "stdlib", - name: "Date.prototype.getUTCMonth", - fns: ["date.getUTCMonth"], - }, - { - id: "stdlib.date.getDate", - kind: "stdlib", - name: "Date.prototype.getDate", - fns: ["date.getDate"], - }, - { - id: "stdlib.date.getUTCDate", - kind: "stdlib", - name: "Date.prototype.getUTCDate", - fns: ["date.getUTCDate"], - }, - { id: "stdlib.date.getDay", kind: "stdlib", name: "Date.prototype.getDay", fns: ["date.getDay"] }, - { - id: "stdlib.date.getUTCDay", - kind: "stdlib", - name: "Date.prototype.getUTCDay", - fns: ["date.getUTCDay"], - }, - { - id: "stdlib.date.getHours", - kind: "stdlib", - name: "Date.prototype.getHours", - fns: ["date.getHours"], - }, - { - id: "stdlib.date.getUTCHours", - kind: "stdlib", - name: "Date.prototype.getUTCHours", - fns: ["date.getUTCHours"], - }, - { - id: "stdlib.date.getMinutes", - kind: "stdlib", - name: "Date.prototype.getMinutes", - fns: ["date.getMinutes"], - }, - { - id: "stdlib.date.getUTCMinutes", - kind: "stdlib", - name: "Date.prototype.getUTCMinutes", - fns: ["date.getUTCMinutes"], - }, - { - id: "stdlib.date.getSeconds", - kind: "stdlib", - name: "Date.prototype.getSeconds", - fns: ["date.getSeconds"], - }, - { - id: "stdlib.date.getUTCSeconds", - kind: "stdlib", - name: "Date.prototype.getUTCSeconds", - fns: ["date.getUTCSeconds"], - }, - { - id: "stdlib.date.getMilliseconds", - kind: "stdlib", - name: "Date.prototype.getMilliseconds", - fns: ["date.getMilliseconds"], - }, - { - id: "stdlib.date.getUTCMilliseconds", - kind: "stdlib", - name: "Date.prototype.getUTCMilliseconds", - fns: ["date.getUTCMilliseconds"], - }, - { - id: "stdlib.date.getTimezoneOffset", - kind: "stdlib", - name: "Date.prototype.getTimezoneOffset", - fns: ["date.getTimezoneOffset"], - }, - { - id: "stdlib.date.toString", - kind: "stdlib", - name: "Date.prototype.toString", - fns: ["date.toString"], - note: "local time with the host zone's long English name; String(date) and template literals use it", - }, - { - id: "stdlib.date.toDateString", - kind: "stdlib", - name: "Date.prototype.toDateString", - fns: ["date.toDateString"], - }, - { - id: "stdlib.date.toTimeString", - kind: "stdlib", - name: "Date.prototype.toTimeString", - fns: ["date.toTimeString"], - }, - // ── perf_hooks (lowerPerfHooksCall): the monotonic clock. - { - id: "node-builtin.perf_hooks.performance.now", - kind: "node-builtin", - name: "perf_hooks.performance.now", - fns: ["perf.now"], - note: "the global performance object and the performance.now.bind(performance) function value reach the same clock", - }, - { - id: "node-builtin.perf_hooks.performance.mark", - kind: "node-builtin", - name: "perf_hooks.performance.mark", - fns: [ - "perf.mark", - "perf.entryName", - "perf.entryType", - "perf.entryStart", - "perf.entryDuration", - "perf.entryDetail", - "perf.entryKeep", - "perf.entryRef", - ], - note: "records a mark in the thread's performance timeline at the live clock or the given startTime", - }, - { - id: "node-builtin.perf_hooks.performance.measure", - kind: "node-builtin", - name: "perf_hooks.performance.measure", - fns: ["perf.measure"], - note: "the mark-name, timestamp, and options forms are one surface", - }, - { - id: "node-builtin.perf_hooks.performance.getEntries", - kind: "node-builtin", - name: "perf_hooks.performance.getEntries", - fns: ["perf.entries"], - note: "getEntries, getEntriesByName, and getEntriesByType read the same timeline", - }, - { - id: "node-builtin.perf_hooks.performance.clearMarks", - kind: "node-builtin", - name: "perf_hooks.performance.clearMarks", - fns: ["perf.clearMarks"], - }, - { - id: "node-builtin.perf_hooks.performance.clearMeasures", - kind: "node-builtin", - name: "perf_hooks.performance.clearMeasures", - fns: ["perf.clearMeasures"], - }, - { - id: "node-builtin.perf_hooks.performance.eventLoopUtilization", - kind: "node-builtin", - name: "perf_hooks.performance.eventLoopUtilization", - fns: ["perf.loopStart", "perf.idleTime"], - note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", - }, - { - id: "node-builtin.perf_hooks.monitorEventLoopDelay", - kind: "node-builtin", - name: "perf_hooks.monitorEventLoopDelay", - fns: [ - "perf.eldNew", - "perf.eldEnable", - "perf.eldDisable", - "perf.eldReset", - "perf.eldPercentile", - "perf.eldStat", - ], - note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", - }, - // ── the process global's ambient reads and authority calls - // (lowerProcessProperty/lowerProcessMethodCall — process is a - // provenance-checked global here, not an importable module). - { - id: "node-builtin.process.env", - kind: "node-builtin", - name: "process.env", - fns: ["process.envGet", "process.envSet", "process.envUnset", "process.envPairs"], - note: "reads, writes, deletes, and enumeration of the process environment (the process global)", - }, - { - id: "node-builtin.process.argv", - kind: "node-builtin", - name: "process.argv", - fns: ["process.argv"], - }, - { - id: "node-builtin.process.hrtime", - kind: "node-builtin", - name: "process.hrtime", - fns: ["process.hrtimeValue"], - note: "native monotonic tuple clock and bigint member; direct calls and stored JavaScript callable values", - }, - { - id: "node-builtin.process.getBuiltinModule", - kind: "node-builtin", - name: "process.getBuiltinModule", - fns: ["process.builtinId", "process.builtinModule", "process.builtinUnsupported"], - note: "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020", - }, - { - id: "node-builtin.process.versions", - kind: "node-builtin", - name: "process.versions", - fns: ["process.versions"], - note: "shared version dictionary containing node and openssl; other components are absent unless defined by the program", - }, - { - id: "node-builtin.process.release", - kind: "node-builtin", - name: "process.release", - fns: ["process.release"], - note: "the compatibility target's release metadata as one stable read-only object", - }, - { - id: "node-builtin.process.cwd", - kind: "node-builtin", - name: "process.cwd", - fns: ["process.cwd"], - }, - { - id: "node-builtin.process.chdir", - kind: "node-builtin", - name: "process.chdir", - fns: ["process.chdir"], - }, - { - id: "node-builtin.process.pid", - kind: "node-builtin", - name: "process.pid", - fns: ["process.pid"], - }, - { - id: "node-builtin.process.getuid", - kind: "node-builtin", - name: "process.getuid", - fns: ["process.getuid"], - }, - { - id: "node-builtin.process.getgid", - kind: "node-builtin", - name: "process.getgid", - fns: ["process.getgid"], - }, - { - id: "node-builtin.process.geteuid", - kind: "node-builtin", - name: "process.geteuid", - fns: ["process.geteuid"], - }, - { - id: "node-builtin.process.getegid", - kind: "node-builtin", - name: "process.getegid", - fns: ["process.getegid"], - }, - { - id: "node-builtin.process.getgroups", - kind: "node-builtin", - name: "process.getgroups", - fns: ["process.getgroups"], - }, - { - id: "node-builtin.process.ppid", - kind: "node-builtin", - name: "process.ppid", - fns: ["process.ppid"], - }, - { - id: "node-builtin.process.title", - kind: "node-builtin", - name: "process.title", - fns: ["process.title", "process.setTitle"], - note: "reads and writes of the process title are one surface", - }, - { - id: "node-builtin.process.argv0", - kind: "node-builtin", - name: "process.argv0", - fns: ["process.argv0"], - }, - { - id: "node-builtin.process.execArgv", - kind: "node-builtin", - name: "process.execArgv", - fns: ["process.execArgv"], - }, - { - id: "node-builtin.process.execPath", - kind: "node-builtin", - name: "process.execPath", - fns: ["process.execPath"], - }, - { - id: "node-builtin.process.uptime", - kind: "node-builtin", - name: "process.uptime", - fns: ["process.uptime"], - }, - { - id: "node-builtin.process.availableMemory", - kind: "node-builtin", - name: "process.availableMemory", - fns: ["process.availableMemory"], - }, - { - id: "node-builtin.process.constrainedMemory", - kind: "node-builtin", - name: "process.constrainedMemory", - fns: ["process.constrainedMemory"], - }, - { - id: "node-builtin.process.memoryUsage", - kind: "node-builtin", - name: "process.memoryUsage", - fns: ["process.memoryUsageTake", "process.memoryUsageField", "process.rss"], - note: "the record and memoryUsage.rss() read live memory state; external and arrayBuffers are 0", - }, - { - id: "node-builtin.process.resourceUsage", - kind: "node-builtin", - name: "process.resourceUsage", - fns: ["process.rusage"], - note: "getrusage's 16 fields — every field read samples live machine state", - }, - { - id: "node-builtin.process.cpuUsage", - kind: "node-builtin", - name: "process.cpuUsage", - fns: [ - "process.cpuUser", - "process.cpuSystem", - "process.cpuUserDiff", - "process.cpuSystemDiff", - "process.cpuPrevValidate", - ], - note: "the plain-sample and previous-value diff forms are one surface", - }, - { - id: "node-builtin.process.threadCpuUsage", - kind: "node-builtin", - name: "process.threadCpuUsage", - fns: [ - "process.threadCpuUser", - "process.threadCpuSystem", - "process.threadCpuUserDiff", - "process.threadCpuSystemDiff", - ], - note: "the plain-sample and previous-value diff forms are one surface", - }, - { - id: "node-builtin.process.isTTY", - kind: "node-builtin", - name: "process.isTTY", - fns: ["process.isTTY"], - note: "the isTTY read on process.stdin/stdout/stderr — one surface across the three streams", - }, - { - id: "node-builtin.process.columns", - kind: "node-builtin", - name: "process.columns", - fns: ["process.columns"], - note: "the columns read on the process stdio streams (terminal geometry)", - }, - { - id: "node-builtin.process.rows", - kind: "node-builtin", - name: "process.rows", - fns: ["process.rows"], - note: "the rows read on process.stdout/stderr (terminal geometry)", - }, - { - id: "node-builtin.process.kill", - kind: "node-builtin", - name: "process.kill", - fns: ["process.kill", "process.killNum"], - note: "the signal-name and signal-number forms are one surface", - }, - { - id: "node-builtin.process.umask", - kind: "node-builtin", - name: "process.umask", - fns: ["process.umask"], - }, - { - id: "node-builtin.process.exit", - kind: "node-builtin", - name: "process.exit", - fns: ["process.exit", "process.exiting"], - note: "process.exit and the process._exiting flag read are one surface", - }, - { - id: "node-builtin.process.exitCode", - kind: "node-builtin", - name: "process.exitCode", - fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], - note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", - }, - // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the - // host's trust anchors, read and replaced. Dedicated paths, and - // rootCertificates is a VALUE read with no call form at all, so none of - // the three can hang off a lowering-table row. Split three ways rather - // than folded into one "CA store" surface: a library author who denies - // REPLACING the trust anchors is making a different decision from one - // who denies reading them, and each Node spelling is the name they will - // reach for when writing the fence. - { - id: "node-builtin.tls.getCACertificates", - kind: "node-builtin", - name: "tls.getCACertificates", - fns: ["tlsca.get"], - note: "the per-type cached PEM bundle: 'default' and 'extra' additionally read NODE_EXTRA_CA_CERTS, 'system' the platform store", - }, - { - id: "node-builtin.tls.rootCertificates", - kind: "node-builtin", - name: "tls.rootCertificates", - fns: ["tlsca.root"], - note: "the value read; answers the same bundled array as getCACertificates('bundled'), but fenced under its own id — the spelling an author writes", - }, - { - id: "node-builtin.tls.setDefaultCACertificates", - kind: "node-builtin", - name: "tls.setDefaultCACertificates", - fns: ["tlsca.set"], - note: "replaces the default set and the client trust anchors for the rest of the process", - }, - // os.constants is a VALUE read (lowerNamespaceBuiltinProperty and the - // named-import path): the platform's null-prototype constants object. - { - id: "node-builtin.os.constants", - kind: "node-builtin", - name: "os.constants", - fns: ["os.constants"], - note: "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined", - }, - // fs.constants as a value, and its members beyond the baked access-mode - // bits: the target libc's null-prototype constants object. - { - id: "node-builtin.fs.constants", - kind: "node-builtin", - name: "fs.constants", - fns: ["fs.constants"], - note: "node_constants.cc's fs group under the target libc's macros; members a platform lacks read as undefined", - }, -]; +let ambientSurfaceRows: readonly AmbientSurfaceRow[] | undefined; + +/** The ambient surface rows, built on the first call. */ +export function ambientSurfaceFns(): readonly AmbientSurfaceRow[] { + ambientSurfaceRows ??= buildAmbientSurfaceFns(); + return ambientSurfaceRows; +} + +function buildAmbientSurfaceFns(): readonly AmbientSurfaceRow[] { + return [ + { + id: "node-builtin.worker_threads.Worker", + kind: "node-builtin", + name: "worker_threads.Worker", + fns: ["worker.new"], + note: "native threads with statically compiled entry points, workerData, argv and message events", + }, + { + id: "node-builtin.worker_threads.metadata", + kind: "node-builtin", + name: "worker_threads metadata", + fns: [ + "worker.isMainThread", + "worker.threadId", + "worker.data", + "worker.parentPort", + "worker.resourceLimits", + "worker.threadName", + ], + note: "current-thread metadata and the worker's parent message port", + }, + { + id: "stdlib.sharedArrayBuffer", + kind: "stdlib", + name: "SharedArrayBuffer", + fns: ["sharedArrayBuffer.new"], + note: "fixed shared storage with context-local typed-array and DataView wrappers", + }, + { + id: "stdlib.blob", + kind: "stdlib", + name: "Blob", + fns: ["blob.new", "blob.slice", "blob.text", "blob.bytes", "blob.arrayBuffer", "blob.is"], + note: "immutable bytes with size, type, slice(), text(), bytes(), arrayBuffer() and structured cloning", + }, + { + id: "stdlib.file", + kind: "stdlib", + name: "File", + fns: ["blob.fileNew"], + note: "a Blob with name and lastModified (Date.now() when absent)", + }, + { + id: "stdlib.webcrypto.subtle", + kind: "stdlib", + name: "globalThis.crypto.subtle", + fns: [ + "webcrypto.generateKey", + "webcrypto.importKey", + "webcrypto.exportKey", + "webcrypto.encrypt", + "webcrypto.decrypt", + "webcrypto.wrapKey", + "webcrypto.unwrapKey", + "webcrypto.deriveBits", + "webcrypto.deriveKey", + "webcrypto.sign", + "webcrypto.verify", + "webcrypto.digest", + ], + note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused", + }, + { + id: "stdlib.webcrypto.CryptoKey", + kind: "stdlib", + name: "CryptoKey", + fns: ["webcrypto.isCryptoKey"], + note: "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey", + }, + { + id: "stdlib.atomics", + kind: "stdlib", + name: "Atomics", + fns: ["atomics.op", "atomics.wait", "atomics.notify"], + note: "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify", + }, + { + id: "node-builtin.fs.callbacks", + kind: "node-builtin", + name: "fs error-first callbacks", + fns: ["fs.callbackValue", "fs.callbackCall", "fs.callbackGlob"], + note: "native filesystem callbacks, including stored platform adapter functions", + }, + // ── the Date slice (lowerDateCall/lowerNew): statics, construction, + // stored read-only values, calendar getters, and ISO formatting. + { + id: "stdlib.date.now", + kind: "stdlib", + name: "Date.now", + fns: ["date.now"], + note: "the live clock", + }, + { + id: "stdlib.date.parse", + kind: "stdlib", + name: "Date.parse", + fns: ["date.parse"], + note: "one date string, using V8's date parser shared with new Date(dateString)", + }, + { + id: "stdlib.date.constructor", + kind: "stdlib", + name: "Date constructor", + fns: [ + "date.newNow", + "date.newMs", + "date.newString", + "date.nativeNew", + "date.newLocal", + "date.nativeNewFields", + "date.nativeNewValue", + "date.argumentsTime", + "date.classOperation", + ], + note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", + }, + { + id: "stdlib.date.setters", + kind: "stdlib", + name: "Date.prototype setters", + fns: ["date.setFields", "date.timeClip", "date.store"], + note: "setTime and the local and UTC field setters, with their optional arguments", + }, + { + id: "stdlib.date.UTC", + kind: "stdlib", + name: "Date.UTC", + fns: ["date.utc"], + note: "the lowered call form takes 1 to 7 number arguments", + }, + { + id: "stdlib.date.getTime", + kind: "stdlib", + name: "Date.prototype.getTime", + fns: ["date.getTime", "date.parseGetTime"], + note: "the millisecond value of a stored Date", + }, + { + id: "stdlib.date.valueOf", + kind: "stdlib", + name: "Date.prototype.valueOf", + fns: ["date.valueOf"], + note: "the same millisecond read as getTime()", + }, + { + id: "stdlib.date.toISOString", + kind: "stdlib", + name: "Date.prototype.toISOString", + fns: ["date.toISOString", "date.toISOStringValue"], + note: "UTC ISO formatting over constructed and stored Date values", + }, + { + id: "stdlib.date.toUTCString", + kind: "stdlib", + name: "Date.prototype.toUTCString", + fns: ["date.toUTCString"], + note: "RFC 7231 IMF-fixdate formatting over stored Date values", + }, + { + id: "stdlib.date.getFullYear", + kind: "stdlib", + name: "Date.prototype.getFullYear", + fns: ["date.getFullYear"], + }, + { + id: "stdlib.date.getUTCFullYear", + kind: "stdlib", + name: "Date.prototype.getUTCFullYear", + fns: ["date.getUTCFullYear"], + }, + { + id: "stdlib.date.getMonth", + kind: "stdlib", + name: "Date.prototype.getMonth", + fns: ["date.getMonth"], + }, + { + id: "stdlib.date.getUTCMonth", + kind: "stdlib", + name: "Date.prototype.getUTCMonth", + fns: ["date.getUTCMonth"], + }, + { + id: "stdlib.date.getDate", + kind: "stdlib", + name: "Date.prototype.getDate", + fns: ["date.getDate"], + }, + { + id: "stdlib.date.getUTCDate", + kind: "stdlib", + name: "Date.prototype.getUTCDate", + fns: ["date.getUTCDate"], + }, + { + id: "stdlib.date.getDay", + kind: "stdlib", + name: "Date.prototype.getDay", + fns: ["date.getDay"], + }, + { + id: "stdlib.date.getUTCDay", + kind: "stdlib", + name: "Date.prototype.getUTCDay", + fns: ["date.getUTCDay"], + }, + { + id: "stdlib.date.getHours", + kind: "stdlib", + name: "Date.prototype.getHours", + fns: ["date.getHours"], + }, + { + id: "stdlib.date.getUTCHours", + kind: "stdlib", + name: "Date.prototype.getUTCHours", + fns: ["date.getUTCHours"], + }, + { + id: "stdlib.date.getMinutes", + kind: "stdlib", + name: "Date.prototype.getMinutes", + fns: ["date.getMinutes"], + }, + { + id: "stdlib.date.getUTCMinutes", + kind: "stdlib", + name: "Date.prototype.getUTCMinutes", + fns: ["date.getUTCMinutes"], + }, + { + id: "stdlib.date.getSeconds", + kind: "stdlib", + name: "Date.prototype.getSeconds", + fns: ["date.getSeconds"], + }, + { + id: "stdlib.date.getUTCSeconds", + kind: "stdlib", + name: "Date.prototype.getUTCSeconds", + fns: ["date.getUTCSeconds"], + }, + { + id: "stdlib.date.getMilliseconds", + kind: "stdlib", + name: "Date.prototype.getMilliseconds", + fns: ["date.getMilliseconds"], + }, + { + id: "stdlib.date.getUTCMilliseconds", + kind: "stdlib", + name: "Date.prototype.getUTCMilliseconds", + fns: ["date.getUTCMilliseconds"], + }, + { + id: "stdlib.date.getTimezoneOffset", + kind: "stdlib", + name: "Date.prototype.getTimezoneOffset", + fns: ["date.getTimezoneOffset"], + }, + { + id: "stdlib.date.toString", + kind: "stdlib", + name: "Date.prototype.toString", + fns: ["date.toString"], + note: "local time with the host zone's long English name; String(date) and template literals use it", + }, + { + id: "stdlib.date.toDateString", + kind: "stdlib", + name: "Date.prototype.toDateString", + fns: ["date.toDateString"], + }, + { + id: "stdlib.date.toTimeString", + kind: "stdlib", + name: "Date.prototype.toTimeString", + fns: ["date.toTimeString"], + }, + // ── perf_hooks (lowerPerfHooksCall): the monotonic clock. + { + id: "node-builtin.perf_hooks.performance.now", + kind: "node-builtin", + name: "perf_hooks.performance.now", + fns: ["perf.now"], + note: "the global performance object and the performance.now.bind(performance) function value reach the same clock", + }, + { + id: "node-builtin.perf_hooks.performance.mark", + kind: "node-builtin", + name: "perf_hooks.performance.mark", + fns: [ + "perf.mark", + "perf.entryName", + "perf.entryType", + "perf.entryStart", + "perf.entryDuration", + "perf.entryDetail", + "perf.entryKeep", + "perf.entryRef", + ], + note: "records a mark in the thread's performance timeline at the live clock or the given startTime", + }, + { + id: "node-builtin.perf_hooks.performance.measure", + kind: "node-builtin", + name: "perf_hooks.performance.measure", + fns: ["perf.measure"], + note: "the mark-name, timestamp, and options forms are one surface", + }, + { + id: "node-builtin.perf_hooks.performance.getEntries", + kind: "node-builtin", + name: "perf_hooks.performance.getEntries", + fns: ["perf.entries"], + note: "getEntries, getEntriesByName, and getEntriesByType read the same timeline", + }, + { + id: "node-builtin.perf_hooks.performance.clearMarks", + kind: "node-builtin", + name: "perf_hooks.performance.clearMarks", + fns: ["perf.clearMarks"], + }, + { + id: "node-builtin.perf_hooks.performance.clearMeasures", + kind: "node-builtin", + name: "perf_hooks.performance.clearMeasures", + fns: ["perf.clearMeasures"], + }, + { + id: "node-builtin.perf_hooks.performance.eventLoopUtilization", + kind: "node-builtin", + name: "perf_hooks.performance.eventLoopUtilization", + fns: ["perf.loopStart", "perf.idleTime"], + note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", + }, + { + id: "node-builtin.perf_hooks.monitorEventLoopDelay", + kind: "node-builtin", + name: "perf_hooks.monitorEventLoopDelay", + fns: [ + "perf.eldNew", + "perf.eldEnable", + "perf.eldDisable", + "perf.eldReset", + "perf.eldPercentile", + "perf.eldStat", + ], + note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", + }, + // ── the process global's ambient reads and authority calls + // (lowerProcessProperty/lowerProcessMethodCall — process is a + // provenance-checked global here, not an importable module). + { + id: "node-builtin.process.env", + kind: "node-builtin", + name: "process.env", + fns: ["process.envGet", "process.envSet", "process.envUnset", "process.envPairs"], + note: "reads, writes, deletes, and enumeration of the process environment (the process global)", + }, + { + id: "node-builtin.process.argv", + kind: "node-builtin", + name: "process.argv", + fns: ["process.argv"], + }, + { + id: "node-builtin.process.hrtime", + kind: "node-builtin", + name: "process.hrtime", + fns: ["process.hrtimeValue"], + note: "native monotonic tuple clock and bigint member; direct calls and stored JavaScript callable values", + }, + { + id: "node-builtin.process.getBuiltinModule", + kind: "node-builtin", + name: "process.getBuiltinModule", + fns: ["process.builtinId", "process.builtinModule", "process.builtinUnsupported"], + note: "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020", + }, + { + id: "node-builtin.process.versions", + kind: "node-builtin", + name: "process.versions", + fns: ["process.versions"], + note: "shared version dictionary containing node and openssl; other components are absent unless defined by the program", + }, + { + id: "node-builtin.process.release", + kind: "node-builtin", + name: "process.release", + fns: ["process.release"], + note: "the compatibility target's release metadata as one stable read-only object", + }, + { + id: "node-builtin.process.cwd", + kind: "node-builtin", + name: "process.cwd", + fns: ["process.cwd"], + }, + { + id: "node-builtin.process.chdir", + kind: "node-builtin", + name: "process.chdir", + fns: ["process.chdir"], + }, + { + id: "node-builtin.process.pid", + kind: "node-builtin", + name: "process.pid", + fns: ["process.pid"], + }, + { + id: "node-builtin.process.getuid", + kind: "node-builtin", + name: "process.getuid", + fns: ["process.getuid"], + }, + { + id: "node-builtin.process.getgid", + kind: "node-builtin", + name: "process.getgid", + fns: ["process.getgid"], + }, + { + id: "node-builtin.process.geteuid", + kind: "node-builtin", + name: "process.geteuid", + fns: ["process.geteuid"], + }, + { + id: "node-builtin.process.getegid", + kind: "node-builtin", + name: "process.getegid", + fns: ["process.getegid"], + }, + { + id: "node-builtin.process.getgroups", + kind: "node-builtin", + name: "process.getgroups", + fns: ["process.getgroups"], + }, + { + id: "node-builtin.process.ppid", + kind: "node-builtin", + name: "process.ppid", + fns: ["process.ppid"], + }, + { + id: "node-builtin.process.title", + kind: "node-builtin", + name: "process.title", + fns: ["process.title", "process.setTitle"], + note: "reads and writes of the process title are one surface", + }, + { + id: "node-builtin.process.argv0", + kind: "node-builtin", + name: "process.argv0", + fns: ["process.argv0"], + }, + { + id: "node-builtin.process.execArgv", + kind: "node-builtin", + name: "process.execArgv", + fns: ["process.execArgv"], + }, + { + id: "node-builtin.process.execPath", + kind: "node-builtin", + name: "process.execPath", + fns: ["process.execPath"], + }, + { + id: "node-builtin.process.uptime", + kind: "node-builtin", + name: "process.uptime", + fns: ["process.uptime"], + }, + { + id: "node-builtin.process.availableMemory", + kind: "node-builtin", + name: "process.availableMemory", + fns: ["process.availableMemory"], + }, + { + id: "node-builtin.process.constrainedMemory", + kind: "node-builtin", + name: "process.constrainedMemory", + fns: ["process.constrainedMemory"], + }, + { + id: "node-builtin.process.memoryUsage", + kind: "node-builtin", + name: "process.memoryUsage", + fns: ["process.memoryUsageTake", "process.memoryUsageField", "process.rss"], + note: "the record and memoryUsage.rss() read live memory state; external and arrayBuffers are 0", + }, + { + id: "node-builtin.process.resourceUsage", + kind: "node-builtin", + name: "process.resourceUsage", + fns: ["process.rusage"], + note: "getrusage's 16 fields — every field read samples live machine state", + }, + { + id: "node-builtin.process.cpuUsage", + kind: "node-builtin", + name: "process.cpuUsage", + fns: [ + "process.cpuUser", + "process.cpuSystem", + "process.cpuUserDiff", + "process.cpuSystemDiff", + "process.cpuPrevValidate", + ], + note: "the plain-sample and previous-value diff forms are one surface", + }, + { + id: "node-builtin.process.threadCpuUsage", + kind: "node-builtin", + name: "process.threadCpuUsage", + fns: [ + "process.threadCpuUser", + "process.threadCpuSystem", + "process.threadCpuUserDiff", + "process.threadCpuSystemDiff", + ], + note: "the plain-sample and previous-value diff forms are one surface", + }, + { + id: "node-builtin.process.isTTY", + kind: "node-builtin", + name: "process.isTTY", + fns: ["process.isTTY"], + note: "the isTTY read on process.stdin/stdout/stderr — one surface across the three streams", + }, + { + id: "node-builtin.process.columns", + kind: "node-builtin", + name: "process.columns", + fns: ["process.columns"], + note: "the columns read on the process stdio streams (terminal geometry)", + }, + { + id: "node-builtin.process.rows", + kind: "node-builtin", + name: "process.rows", + fns: ["process.rows"], + note: "the rows read on process.stdout/stderr (terminal geometry)", + }, + { + id: "node-builtin.process.kill", + kind: "node-builtin", + name: "process.kill", + fns: ["process.kill", "process.killNum"], + note: "the signal-name and signal-number forms are one surface", + }, + { + id: "node-builtin.process.umask", + kind: "node-builtin", + name: "process.umask", + fns: ["process.umask"], + }, + { + id: "node-builtin.process.exit", + kind: "node-builtin", + name: "process.exit", + fns: ["process.exit", "process.exiting"], + note: "process.exit and the process._exiting flag read are one surface", + }, + { + id: "node-builtin.process.exitCode", + kind: "node-builtin", + name: "process.exitCode", + fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], + note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", + }, + // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the + // host's trust anchors, read and replaced. Dedicated paths, and + // rootCertificates is a VALUE read with no call form at all, so none of + // the three can hang off a lowering-table row. Split three ways rather + // than folded into one "CA store" surface: a library author who denies + // REPLACING the trust anchors is making a different decision from one + // who denies reading them, and each Node spelling is the name they will + // reach for when writing the fence. + { + id: "node-builtin.tls.getCACertificates", + kind: "node-builtin", + name: "tls.getCACertificates", + fns: ["tlsca.get"], + note: "the per-type cached PEM bundle: 'default' and 'extra' additionally read NODE_EXTRA_CA_CERTS, 'system' the platform store", + }, + { + id: "node-builtin.tls.rootCertificates", + kind: "node-builtin", + name: "tls.rootCertificates", + fns: ["tlsca.root"], + note: "the value read; answers the same bundled array as getCACertificates('bundled'), but fenced under its own id — the spelling an author writes", + }, + { + id: "node-builtin.tls.setDefaultCACertificates", + kind: "node-builtin", + name: "tls.setDefaultCACertificates", + fns: ["tlsca.set"], + note: "replaces the default set and the client trust anchors for the rest of the process", + }, + // os.constants is a VALUE read (lowerNamespaceBuiltinProperty and the + // named-import path): the platform's null-prototype constants object. + { + id: "node-builtin.os.constants", + kind: "node-builtin", + name: "os.constants", + fns: ["os.constants"], + note: "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined", + }, + // fs.constants as a value, and its members beyond the baked access-mode + // bits: the target libc's null-prototype constants object. + { + id: "node-builtin.fs.constants", + kind: "node-builtin", + name: "fs.constants", + fns: ["fs.constants"], + note: "node_constants.cc's fs group under the target libc's macros; members a platform lacks read as undefined", + }, + ]; +} /** The win32-target overrides of the bare modules' constants: path's * constants ARE the path/win32 namespace's, and os.EOL is CRLF (Node's @@ -2866,7 +2900,7 @@ const WIN32_TARGET_CONSTS: Record< os: { EOL: "\r\n", devNull: "\\\\.\\nul" }, }; -/** BUILTIN_MODULE_FNS with the platform-conditional modules bound per +/** builtinModuleFns() with the platform-conditional modules bound per * TARGET: a win32 triple compiles Node-on-Windows semantics — `path` is * path.win32 and url's bridge selects the Windows runtime behavior. Fence wording is * unaffected — callers keep naming the module the source spelled. */ @@ -2875,9 +2909,9 @@ function builtinModuleFnsOf( module: string, ): Record | undefined { if (lowerer.targetPlatform === "win32") { - if (module === "path") return BUILTIN_MODULE_FNS["path/win32"]; + if (module === "path") return builtinModuleFns()["path/win32"]; } - return ownEntry(BUILTIN_MODULE_FNS, module); + return ownEntry(builtinModuleFns(), module); } /** Own-property lookup — lowerer.ts's own(), duplicated locally because diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 44b5f63ec..3e4871084 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -46,6 +46,8 @@ export type { AnalyzeResult, } from "./compile-types.js"; import { compilePackedLibrary } from "./backend/library-pack.js"; +import { node24FetchCompatProfile } from "./compat/fetch-profile.js"; +import { libFnSignatures } from "./ir/builtin-signatures.js"; import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; @@ -249,7 +251,7 @@ export { type SurfaceManifestEntry, } from "./coverage/surface-manifest.js"; export { - NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, type FetchCompatEvidence, type FetchCompatFacet, type FetchCompatInventory, @@ -261,7 +263,13 @@ export { type FetchCompatOption, type FetchCompatProfile, } from "./compat/fetch-profile.js"; -export { LIB_FN_SIGS } from "./ir/builtin-signatures.js"; +export { libFnSignatures } from "./ir/builtin-signatures.js"; +/** @deprecated Use `libFnSignatures()`. Built when this module loads; the + * native compiler does not load this module. */ +export const LIB_FN_SIGS = libFnSignatures(); +/** @deprecated Use `node24FetchCompatProfile()`. Built when this module + * loads; the native compiler does not load this module. */ +export const NODE24_FETCH_COMPAT_PROFILE = node24FetchCompatProfile(); export { validateModule } from "./ir/validate.js"; export { assignLegacyTiers, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e768f257f..916c64583 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -58,5058 +58,5103 @@ function defineLibFnSignatures( * together here; frontend coercion and IR validation consume the same table. * All arguments are required. Lowering supplies optional-source defaults * before this boundary and preserves their evaluation order. */ -export const LIB_FN_SIGS = defineLibFnSignatures({ - /** Native static fetch and its Web-platform companions. fetch.start - * answers once the response head arrives; the response body readers - * consume the native body stream. AbortSignal and ReadableStream values - * are opaque checked-dynamic handles. */ - "fetch.start": { argTypes: [STRING, DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseNew": { argTypes: [DYN, DYN], result: DYN }, - "fetch.responseJsonStatic": { argTypes: [DYN, DYN], result: DYN }, - "fetch.responseLight": { argTypes: [DYN], result: arrayOf(STRING) }, - "fetch.input": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "abort.controllerNew": { argTypes: [], result: DYN }, - /** EventTarget / Event / CustomEvent construction (scr_event_target.c): - * the Event constructors take the argument vector as one dyn array and - * throw Node's validation errors; `event.is` is `v instanceof Event` - * (kind 0), `CustomEvent` (1), `EventTarget` (2). addAbortListener - * answers the disposer (the Disposable's [Symbol.dispose]). */ - "event.targetNew": { argTypes: [], result: DYN }, - "event.new": { argTypes: [DYN], result: DYN }, - "event.customNew": { argTypes: [DYN], result: DYN }, - "event.is": { argTypes: [DYN, F64], result: BOOL }, - /** Compiled EventTarget and Event subclasses: the receiver is the - * instance (an object rooted at %EventTarget / %Event). targetInit and - * init are super() (init takes the argument vector as one dyn array - * and throws the constructor's validation errors; initType is the - * super(type) form with a string); targetCall runs an EventTarget method - * with the instance's own capsule as `self` (targetDispatch is its - * dispatchEvent(event) form, answering the bool); call, get and set - * reach the Event members. */ - "event.targetInit": { argTypes: [null], result: VOID }, - "event.init": { argTypes: [null, DYN], result: VOID }, - "event.initType": { argTypes: [null, STRING], result: VOID }, - /** %main hands the engine the compiled `listener.handleEvent` reader - * (lower-event-target.ts noteEventListeners). */ - "event.handleEventReader": { argTypes: [DYN], result: VOID }, - "event.targetDispatch": { argTypes: [null, DYN, DYN], result: BOOL }, - "event.targetCall": { argTypes: [null, DYN, STRING, DYN], result: DYN }, - "event.call": { argTypes: [null, STRING, DYN], result: DYN }, - "event.get": { argTypes: [null, STRING], result: DYN }, - "event.set": { argTypes: [null, STRING, DYN], result: VOID }, - "events.addAbortListener": { argTypes: [DYN, DYN], result: DYN }, - /** Blob / File (scr_blob.c): the constructors and slice() take their - * argument vector as one dyn array and throw Node's validation errors; - * the readers settle on Node's microtask turn for the Blob's parts. - * `blob.is` is `v instanceof Blob` (kind 0) or `File` (1). */ - "blob.new": { argTypes: [DYN], result: DYN }, - "blob.fileNew": { argTypes: [DYN], result: DYN }, - "blob.slice": { argTypes: [DYN, DYN], result: DYN }, - "blob.text": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, - "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "blob.is": { argTypes: [DYN, F64], result: BOOL }, - /** WebAssembly (scr_wasm.c): modules translated at build time - * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. - * compile and instantiate over bytes settle on a later loop turn; - * instantiate over a Module settles now, as in Node. `wasm.is` answers - * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) - * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` - * constructs one of the error classes (kind 5-7, message, options). */ - "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "wasm.validate": { argTypes: [DYN], result: BOOL }, - "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "wasm.moduleNew": { argTypes: [DYN], result: DYN }, - "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.is": { argTypes: [DYN, F64], result: BOOL }, - "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, - /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and - * Global(descriptor, value) (an undefined value is the type's default); - * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, - * Table or Global (kinds 0-4) called without new, after its arguments - * evaluate. */ - "wasm.memoryNew": { argTypes: [DYN], result: DYN }, - "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, - /** WebAssembly.Module.imports(module), exports(module) and - * customSections(module, name): checked-dynamic arrays of descriptor - * objects and ArrayBuffers. */ - "wasm.moduleImports": { argTypes: [DYN], result: DYN }, - "wasm.moduleExports": { argTypes: [DYN], result: DYN }, - "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, - /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, - * dyn.valueOf's answer for anything else. */ - "wasm.valueOf": { argTypes: [DYN], result: DYN }, - /** WebAssembly.compile (0), instantiate (1) and validate (2) as function - * values, and the namespace object holding them. */ - "wasm.function": { argTypes: [F64], result: DYN }, - "wasm.namespace": { argTypes: [], result: DYN }, - /** The global object viewed through a cast to a type with a WebAssembly - * member: an object holding the namespace. */ - "wasm.globalView": { argTypes: [], result: DYN }, - "abort.timeout": { argTypes: [DYN], result: DYN }, - "abort.now": { argTypes: [DYN], result: DYN }, - "abort.any": { argTypes: [DYN], result: DYN }, - "fetch.function": { argTypes: [], result: DYN }, - "fetch.requestNew": { argTypes: [DYN, DYN], result: DYN }, - "fetch.headersNew": { argTypes: [DYN], result: DYN }, - "fetch.responseArrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseJson": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseText": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, - "fetch.responseBytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "fetch.abortControllerNew": { argTypes: [], result: DYN }, - "fetch.abortTimeout": { argTypes: [DYN], result: DYN }, - "fetch.abortNow": { argTypes: [DYN], result: DYN }, - "fetch.abortAny": { argTypes: [DYN], result: DYN }, - "fetch.streamNew": { argTypes: [DYN], result: DYN }, - "fetch.transformNew": { argTypes: [], result: DYN }, - // Program-dependent iterable: typed arrays/bytes/string stay intact so - // the native stream can pull lazily; checked-dynamic values are the - // fallback. The libCall validator in validate.ts checks the closed set. - "fetch.webIs": { argTypes: [DYN, STRING], result: BOOL }, - "fetch.streamIs": { argTypes: [DYN], result: BOOL }, - "fetch.streamFrom": { argTypes: [null], result: DYN }, - // The chunk/result record depends on ReadableStream; validated by validate.ts. - "fetch.readerRead": { argTypes: [DYN], result: VOID }, - /** Readable.fromWeb (scr_stream_from_web.c): the stream, objectMode, - * highWaterMark (< 0: the mode's default) and encoding ("": none). */ - "fetch.readableFromWeb": { argTypes: [DYN, BOOL, F64, STRING], result: VOID }, - "json.parse": { argTypes: [STRING], result: DYN }, - "json.parseReviver": { argTypes: [STRING, DYN], result: DYN }, - "json.stringifyReplacer": { argTypes: [DYN, DYN, STRING], result: DYN }, - "json.stringifyValue": { argTypes: [DYN, DYN, DYN], result: DYN }, - /** Keyed WRITE on a dyn value — `h.onDone = cb` / `h["k"] = v` on a - * checked-dynamic object (args: receiver, key string, value — all - * borrowed; the runtime copies the key and retains the value in). An - * OBJ receiver sets the member (later writes win, insertion order - * preserved — JS exactly); undefined/null throws Node's catchable - * "Cannot set properties of undefined (setting 'k')"; every other kind - * throws Node's STRICT-mode "Cannot create property 'k' on " — - * primitives quoting their rendering, V8's "on number '5'" - * (sloppy mode would ignore the write — suite tests are 'use strict'; - * SEMANTICS.md notes the sloppy divergence: the write throws). Void - * result; in the may-throw seed set. */ - "dyn.keySet": { argTypes: [DYN, STRING, DYN], result: VOID }, - "dyn.keySetComputed": { argTypes: [DYN, DYN, DYN], result: VOID }, - /** Delete an ordinary checked-native object's own key. Borrows both - * arguments; other receiver representations retain a runtime refusal. */ - "dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID }, - "dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID }, - "dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL }, - /** Native own properties on globalThis keyed by symbol identity. */ - "dyn.globalSymbolGet": { argTypes: [SYMBOL_T], result: DYN }, - "dyn.globalSymbolSet": { argTypes: [SYMBOL_T, DYN], result: VOID }, - "dyn.globalSymbolHas": { argTypes: [SYMBOL_T], result: BOOL }, - "dyn.globalSymbolDelete": { argTypes: [SYMBOL_T], result: VOID }, - /** Exact native class capsule identity, without materializing its fields. */ - "dyn.typedRefIs": { argTypes: [DYN, STRING], result: BOOL }, - /** A capsule boxed by this program from exactly this class: one key - * pointer test, false for equal keys boxed elsewhere. Guards fast paths - * whose fallback handles every other value. The key is a literal. */ - "dyn.typedRefKeyIs": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.classIs": { argTypes: [DYN, STRING], result: BOOL }, - /** Destructuring pack over a dyn source — `const [a, b] = d`, a - * destructured dyn callback param (args: the source and the STATIC - * TypeError spelling, "" when the source has none — both borrowed; - * result: a fresh dyn array, +1). Iterable kinds collect like spread - * (arrays element-by-element, strings by code point, bytes by byte); - * every other kind throws V8's destructuring TypeError — the spelling - * verbatim when non-empty, else the runtime kind wording ("number 5 is - * not iterable (cannot read property Symbol(Symbol.iterator))"). In the - * may-throw seed set. */ - "dyn.iterPack": { argTypes: [DYN, STRING], result: DYN }, - "dyn.mathFold": { argTypes: [DYN, BOOL, STRING], result: F64 }, - "dyn.destructureRest": { argTypes: [DYN, STRING], result: DYN }, - "dyn.arrayFromIterator": { argTypes: [DYN], result: DYN }, - "dyn.iterator": { argTypes: [DYN, STRING], result: DYN }, - /** for await over a checked-dynamic value: the loop's iterator (a - * compiled async generator, Symbol.asyncIterator's result, or the sync - * iterator), and whether that iterator is synchronous. */ - "dyn.asyncIteratorOpen": { argTypes: [DYN, STRING], result: DYN }, - "dyn.asyncIteratorWasSync": { argTypes: [], result: BOOL }, - "dyn.iteratorResult": { argTypes: [DYN], result: DYN }, - "dyn.iteratorCanStep": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.iteratorStep": { argTypes: [DYN], result: DYN }, - "dyn.iteratorStepDone": { argTypes: [DYN], result: BOOL }, - "dyn.setGeneratorMembers": { argTypes: [DYN, DYN], result: VOID }, - "dyn.iteratorHelperMethod": { argTypes: [STRING], result: DYN }, - "dyn.collectionIteratorIndex": { argTypes: [DYN], result: F64 }, - "dyn.mapSeedEntries": { argTypes: [DYN], result: DYN }, - "dyn.mapSeedEntry": { argTypes: [DYN], result: DYN }, - /** The for-of-over-dyn pack accessors — the emitted index loop drives - * them over a dyn.iterPack result (ARR by construction). arrLen: the - * ARR length as f64 (0 for non-ARR kinds; arg borrowed). arrAt: the - * element at index (+1; the undefined singleton past the end). Neither - * throws. */ - "dyn.arrLen": { argTypes: [DYN], result: F64 }, - "dyn.arrAt": { argTypes: [DYN, F64], result: DYN }, - /** `key in v` with a RUNTIME (string) key on a checked-dynamic - * receiver (args: value dyn, key string; result bool): OBJ answers - * own-member presence, ARR answers 'length'/a valid index — exactly - * the literal-key dynHasKey path. Proxy has traps may throw. */ - "dyn.hasKey": { argTypes: [DYN, STRING], result: BOOL }, - /** The `in` operator with a runtime string key: dyn.hasKey plus the - * members inherited from builtin and compiled class prototypes. */ - "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.freeze": { argTypes: [DYN], result: DYN }, - "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, - /** Object.defineProperty on a declared class field (scr_json.c): the - * field's current value (borrowed), its FIELD_* attribute word, the - * instance's CLASS_* integrity word, the field name and the descriptor. - * Validates as Node does (TypeError "Cannot redefine property: f") and - * answers a one-property holder of the defined value and attributes (+1), - * read back with class.definedBits and class.definedValue. */ - "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, - "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, - "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, - /** Whether a property descriptor has a `value` member. */ - "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, - /** Throws Node's TypeError for a write to a read-only class field: the - * field name and the instance's class name. */ - "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, - /** Throws Node's TypeError for a class field initializer on a - * non-extensible instance: the field name. */ - "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, - /** Object.freeze of a record whose shape is freezable (frozen-records.ts): - * sets the frozen bit in its rc word (scr_record_freeze). The receiver is - * any record type. */ - "record.freeze": { argTypes: [null], result: VOID }, - /** Whether a record is frozen: its rc word's frozen bit, set by - * record.freeze and dyn.freeze of a capsule. Emitted inline. The receiver - * is any record type. */ - "record.isFrozen": { argTypes: [null], result: BOOL }, - /** Throws Node's TypeError for a write to a frozen record: the key, the - * object's text in the message ("#") and the operation (0 assign, - * 1 add, 2 delete). */ - "record.throwFrozen": { argTypes: [STRING, STRING, F64], result: VOID }, - /** Object.freeze of an array (scr_arr_freeze): sets its frozen bit, which - * the write guards of IrModule.freezableArrays test, and isFrozen reads. - * The receiver is any array type. */ - "array.freeze": { argTypes: [null], result: VOID }, - "array.isFrozen": { argTypes: [null], result: BOOL }, - /** Object.freeze, seal or preventExtensions of a Map or Set - * (scr_map_prevent_extensions), which only makes it not extensible: its - * entries stay writable. The receiver is any map or set type. */ - "collection.preventExtensions": { argTypes: [null], result: VOID }, - "collection.isExtensible": { argTypes: [null], result: BOOL }, - "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, - "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeDateHandleIs": { argTypes: [DYN], result: BOOL }, - /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, - * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one - * interned function per name, the same one dynamic values answer for - * `.constructor`. The name is a compiler-chosen literal. */ - "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, - /** A builtin global function (setTimeout, setInterval, setImmediate, - * the clear functions, queueMicrotask, process.nextTick) read as a - * JavaScript value: one interned function per name whose calls run the - * timer APIs (it pulls in the event loop). The name is a compiler-chosen - * literal. */ - "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, - "dyn.namespaceNew": { argTypes: [STRING, DYN], result: DYN }, - "dyn.namespaceDefine": { argTypes: [DYN, STRING, DYN, F64, F64], result: VOID }, - "date.nativeNew": { argTypes: [DYN], result: DYN }, - /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's - * first seven values pass through ToNumber in order (later ones are - * evaluated, never converted), then the local-time field constructor. */ - "date.nativeNewFields": { argTypes: [DYN], result: DYN }, - /** `new Date(value)` in JavaScript: a string argument parses with V8's - * full date parser; every other value constructs as date.nativeNew. */ - "date.nativeNewValue": { argTypes: [DYN], result: DYN }, - "date.argumentsTime": { argTypes: [DYN], result: DATE_T }, - "date.classOperation": { argTypes: [DYN, DYN], result: VOID }, - "date.checkedValue": { argTypes: [DYN], result: DATE_T }, - "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, - /** toString() on a checked-dynamic receiver: runtime kind dispatch - * (bytes decode per the literal encoding — utf8 default; strings, - * numbers, booleans, arrays, objects answer JS-exactly; undefined and - * null throw the catchable TypeError). */ - "dyn.toString": { argTypes: [DYN, DYN, STRING], result: STRING }, - "dyn.valueOf": { argTypes: [DYN, STRING], result: DYN }, - /** Object.defineProperties over dyn values (args: target, descriptors — - * both borrowed dyn; result: the target, +1 — JS's return value). - * Value descriptors become own properties on OBJ and FUNC targets; - * OBJ targets preserve data attributes. get/set - * descriptors and non-object targets/descriptors throw catchably - * (Node's TypeError texts; accessors the "not supported yet" Error). In - * the may-throw seed set. */ - "dyn.defineProps": { argTypes: [DYN, DYN], result: DYN }, - "dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN }, - "dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN }, - /** Promise.all / Promise.allSettled / Promise.any over a spread - * checked-dynamic array: the values, the settled-record array, or the - * first fulfillment (AggregateError once every entry rejects). - * Thenable elements adopt; the second argument reads a program class - * instance's `then` (the compiled property dispatch). */ - "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** PromiseResolve over one checked-dynamic value: the promise it holds, - * a promise that adopts a thenable's settlement, or a promise fulfilled - * with the value. The second argument reads `then` as above. */ - "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, - * borrowed; result: an owned string) — the dyn kind's JS answer: - * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest - * wart preserved), boolean/number/string by kind, function→"function". - * Never throws. */ - "dyn.typeof": { argTypes: [DYN], result: STRING }, - /** Object.prototype.toString.call on a checked-dynamic value. */ - "dyn.objectTag": { argTypes: [DYN], result: STRING }, - /** Engine-free CommonJS module graph. Module values are scalar f64 - * handles; generated startup defines the registry, init wrappers update - * loading/cache state, and the read surface answers Node's live metadata. */ - "module.registryInit": { argTypes: [F64], result: VOID }, - "module.define": { argTypes: [F64, STRING, STRING, STRING, arrayOf(STRING), BOOL], result: VOID }, - "module.enter": { argTypes: [F64], result: VOID }, - "module.link": { argTypes: [F64, F64], result: VOID }, - "module.finish": { argTypes: [F64], result: VOID }, - "module.fail": { argTypes: [F64], result: VOID }, - "module.filename": { argTypes: [F64], result: STRING }, - "module.id": { argTypes: [F64], result: STRING }, - "module.path": { argTypes: [F64], result: STRING }, - "module.paths": { argTypes: [F64], result: arrayOf(STRING) }, - "module.children": { argTypes: [F64], result: arrayOf(F64) }, - "module.parent": { argTypes: [F64], result: F64 }, - "module.loaded": { argTypes: [F64], result: BOOL }, - "module.cacheGet": { argTypes: [STRING], result: F64 }, - "module.cacheHas": { argTypes: [STRING], result: BOOL }, - "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, - "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, - /** The repeating timer pair. setInterval takes (callback, ms) like - * setTimeout and RETURNS the f64 handle the fallback declarations - * promise (ids start at 1, so truthiness narrowing works); the loop - * owns the callback until clearInterval removes the entry (eagerly — a - * live interval keeps the loop alive, a cleared one releases it, like - * Node). Neither throws; a throw ESCAPING an interval callback ends the - * program like a setTimeout throw. */ - "timers.setInterval": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: F64 }, - "timers.clearInterval": { argTypes: [F64], result: VOID }, - /** setTimeout WITH a clear handle (the f64 id) — the clearable/unref-able - * one-shot; clearTimeout cancels it, .unref() drops it from loop - * liveness. The plain timers.setTimeout stays the handle-less - * fire-and-forget. */ - "timers.setTimeoutHandle": { - argTypes: [{ kind: "func", params: [], ret: VOID }, F64], - result: F64, - }, - "timers.clearTimeout": { argTypes: [F64], result: VOID }, - /** Timeout.unref()/ref()/hasRef() — loop-liveness bookkeeping over the - * handle id. unref/ref RETURN the handle (f64) for chaining; hasRef - * returns bool. */ - "timers.unref": { argTypes: [F64], result: F64 }, - "timers.ref": { argTypes: [F64], result: F64 }, - "timers.hasRef": { argTypes: [F64], result: BOOL }, - /** Timeout.refresh() — re-arm to now + the original delay (from the - * heap or from inside the firing callback; a one-shot that fired on an - * earlier turn is gone and no-ops — documented divergence). Returns - * the handle for chaining like unref/ref. */ - "timers.refresh": { argTypes: [F64], result: F64 }, - /** setImmediate/clearImmediate — Node's check phase: callbacks run - * once per loop turn AFTER due timers, FIFO, and immediates queued - * mid-phase wait for the next turn. setImmediate returns the f64 - * handle (its own id space — clearTimeout of an Immediate no-ops, - * like Node); the Immediate ref trio mirrors the Timeout one (an - * unref'd pending immediate neither keeps the loop alive nor fires - * once nothing reffed remains). */ - "timers.setImmediate": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: F64 }, - /** queueMicrotask (scr_async.c): the callback enters the SAME FIFO - * promise continuations ride — one microtask order, like V8's queue — - * and a throw is an UNCAUGHT exception (never a rejection). - * timers.queueMicrotask takes an owned zero-param closure and never - * throws; the Dyn form (checked-dynamic arguments — the mustCall - * wrapper, the suite's invalid-input probes) throws Node's - * ERR_INVALID_ARG_TYPE synchronously on a non-function value and calls - * the function with zero arguments at drain. */ - "timers.queueMicrotask": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - "timers.queueMicrotaskDyn": { argTypes: [DYN], result: VOID }, - "timers.clearImmediate": { argTypes: [F64], result: VOID }, - /** process.nextTick(cb, ...args) — the user tick queue. args: [cb - * (() => void; trailing call arguments ride the timer surface's - * interned dyn thunk)]. Ticks drain BEFORE promise jobs at every loop - * checkpoint, to joint exhaustion with them (Node's tick-then- - * microtask order); ticks enqueued by station listeners run at the - * NEXT checkpoint (the stream-tick station divergence, SEMANTICS.md). - * Pending ticks are always-ready work — the loop neither sleeps nor - * exits while any exist; ticks scheduled from 'exit' listeners never - * run (Node). The enqueue itself never throws. */ - "process.nextTick": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - /** The process introspection statics. uptime: seconds since the - * binary's own start (fractional — a load-time monotonic anchor). - * availableMemory/constrainedMemory: libuv's numbers (free-ish bytes; - * the cgroup cap or 0). cpuUser/cpuSystem (+ threadCpu twins): the - * process/thread CPU clocks in microseconds — the frontend composes - * the {user, system} records. The *Diff forms answer current − prev - * for one field; cpuPrevValidate throws Node's ERR_INVALID_ARG_VALUE - * RangeError for negative/non-finite prev fields (user first, then - * system — Node's order; MAY THROW). rusage(idx): one - * process.resourceUsage() field by canonical index (Node's units). - * activeResources: the loop's own bookkeeping — 'Timeout' per armed - * (or firing, uncleared) timer, 'Immediate' per queued unfired - * immediate; unmodeled resource kinds are absent (SEMANTICS.md). */ - "process.uptime": { argTypes: [], result: F64 }, - /** perf_hooks performance.now(): fractional ms since process start. */ - "perf.now": { argTypes: [], result: F64 }, - /** perf_hooks user timing over the thread's performance timeline. The - * producing calls take the call's whole argument vector as one dyn - * array (Node's validation and errors run in the runtime, MAY THROW): - * mark/measure answer 1, entries(args, which) the entry count of - * getEntries (0), getEntriesByName (1) or getEntriesByType (2). The - * entryName/Type/Start/Duration/Detail accessors read entry i of that - * answer; the frontend builds a new entry's record from them and hands - * it to entryKeep (a typed reference capsule), and the getters read the - * kept records back through entryRef, so entries keep their identity. */ - "perf.mark": { argTypes: [DYN], result: F64 }, - "perf.measure": { argTypes: [DYN], result: F64 }, - "perf.entries": { argTypes: [DYN, F64], result: F64 }, - "perf.clearMarks": { argTypes: [DYN], result: VOID }, - "perf.clearMeasures": { argTypes: [DYN], result: VOID }, - "perf.entryName": { argTypes: [F64], result: STRING }, - "perf.entryType": { argTypes: [F64], result: STRING }, - "perf.entryStart": { argTypes: [F64], result: F64 }, - "perf.entryDuration": { argTypes: [F64], result: F64 }, - "perf.entryDetail": { argTypes: [F64], result: DYN }, - "perf.entryKeep": { argTypes: [DYN], result: VOID }, - "perf.entryRef": { argTypes: [F64], result: DYN }, - /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the - * loop's start in performance.now() ms (0 before it), idleTime the idle - * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay - * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; - * eldPercentile expects Node's validated range; eldStat(which) reads - * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ - "perf.loopStart": { argTypes: [], result: F64 }, - "perf.idleTime": { argTypes: [], result: F64 }, - "perf.eldNew": { argTypes: [F64], result: F64 }, - "perf.eldEnable": { argTypes: [F64], result: BOOL }, - "perf.eldDisable": { argTypes: [F64], result: BOOL }, - "perf.eldReset": { argTypes: [F64], result: VOID }, - "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, - "perf.eldStat": { argTypes: [F64, F64], result: F64 }, - /** process.memoryUsage(): memoryUsageTake samples the resident set and - * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) - * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 - * heapUsed, 3 external, 4 arrayBuffers). rss is memoryUsage.rss(). */ - "process.memoryUsageTake": { argTypes: [], result: VOID }, - "process.memoryUsageField": { argTypes: [F64], result: F64 }, - "process.rss": { argTypes: [], result: F64 }, - "process.availableMemory": { argTypes: [], result: F64 }, - "process.constrainedMemory": { argTypes: [], result: F64 }, - "process.cpuUser": { argTypes: [], result: F64 }, - "process.cpuSystem": { argTypes: [], result: F64 }, - "process.cpuUserDiff": { argTypes: [F64], result: F64 }, - "process.cpuSystemDiff": { argTypes: [F64], result: F64 }, - "process.threadCpuUser": { argTypes: [], result: F64 }, - "process.threadCpuSystem": { argTypes: [], result: F64 }, - "process.threadCpuUserDiff": { argTypes: [F64], result: F64 }, - "process.threadCpuSystemDiff": { argTypes: [F64], result: F64 }, - "process.cpuPrevValidate": { argTypes: [F64, F64], result: VOID }, - "process.rusage": { argTypes: [F64], result: F64 }, - "process.activeResources": { argTypes: [], result: arrayOf(STRING) }, - "timers.immediateUnref": { argTypes: [F64], result: F64 }, - "timers.immediateRef": { argTypes: [F64], result: F64 }, - "timers.immediateHasRef": { argTypes: [F64], result: BOOL }, - /** The tolerated non-handle clear (`clearTimeout(null)`, - * `clearInterval({})`, zero-argument forms): Node ignores anything - * that is not a live handle without an error — a VOID no-op the emitter drops - * (only syntactically side-effect-free arguments take this path). */ - "timers.clearNoop": { argTypes: [], result: VOID }, - // Signal listeners are zero-param (the ambient shape); exit/stdin - // callbacks carry program-dependent one-param shapes — null slots, the - // libCall case checks them (child.onExit precedent). - /** Named process signal events: [name string, callback dyn, once bool] - * or [name, callback] for removal. The runtime resolves platform signal - * numbers and delivers (name, number). Listeners do not keep the loop - * alive. Invalid callbacks and uncatchable signals throw. */ - "process.onSignal": { argTypes: [STRING, DYN, BOOL], result: VOID }, - "process.offSignal": { argTypes: [STRING, DYN], result: VOID }, - /** The process 'exit' event — process.on/once/off("exit"). args: [cb, - * once bool] / [cb]. Listeners run SYNCHRONOUSLY at termination - * (normal exit, process.exit(), the uncaught/unhandled exit-1 paths) - * with the exit code; anything they schedule never runs, like Node. - * Callback shapes: () => void or (code: number) => void — the emitter - * picks the runtime adapter. Never throw. */ - "process.onExit": { argTypes: [null, BOOL], result: VOID }, - "process.offExit": { argTypes: [null], result: VOID }, - /** process.stdin listener registration — stdin.on/once("data" | "end" | - * "error", cb). args: [cb, once bool]. data callbacks: () => void or - * (chunk: Uint8Array) => void; end: () => void; error: () => void or - * (err: Error) => void (the child error adapters are reused). While a - * data listener (or a parked for-await chunk promise) exists, stdin - * keeps the loop alive — Node's flowing-stdin keep-alive. Never - * throw. */ - "stdin.onData": { argTypes: [null, BOOL], result: VOID }, - "stdin.onEnd": { argTypes: [{ kind: "func", params: [], ret: VOID }, BOOL], result: VOID }, - "stdin.onError": { argTypes: [null, BOOL], result: VOID }, - /** process.stdin.on/once('keypress', cb) (scr_readline_tty.c): cb is the - * listener as a checked-dynamic function, called with (str, key). */ - "stdin.onKeypress": { argTypes: [DYN, BOOL], result: VOID }, - /** The for-await chunk source over process.stdin: no args, result - * Promise (+1). Fulfills with the next arrived chunk; the - * EMPTY bytes value is the done sentinel (POSIX reads never deliver - * empty data chunks), which the for-await desugar turns into loop - * exit. Never throws itself; awaiting it re-throws nothing (stdin - * errors surface through 'error' listeners, not the iterator). */ - "stdin.nextChunk": { argTypes: [], result: { kind: "promise", inner: BYTES_U8 } }, - "fs.readFileSync": { argTypes: [STRING, STRING], result: STRING }, - "fs.readFileEncoded": { argTypes: [STRING, STRING], result: STRING }, - "fs.readFdEncoded": { argTypes: [F64, STRING], result: STRING }, - /** readFileSync(path) — the Buffer read (+1 bytes); throws catchably - * like the utf8 form. */ - "fs.readFileSyncBuf": { argTypes: [STRING], result: BYTES_U8 }, - /** readFileSync(path, enc) with a RUNTIME encoding (an untyped JS - * parameter): undefined/null answer Buffers, utf8 a string, other real - * encodings throw the "not supported yet" Error, unknown names throw - * ERR_UNKNOWN_ENCODING. */ - "fs.readFileSyncDyn": { argTypes: [STRING, DYN], result: DYN }, - "fs.writeFileSync": { argTypes: [STRING, STRING], result: VOID }, - "fs.appendFileSync": { argTypes: [STRING, STRING], result: VOID }, - "fs.existsSync": { argTypes: [STRING], result: BOOL }, - "fs.mkdirSync": { argTypes: [STRING], result: VOID }, - "fs.rmSync": { argTypes: [STRING], result: VOID }, - "fs.rmdirSync": { argTypes: [STRING], result: VOID }, - "fs.readdirSync": { 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 - * one readdir pass (scr_lib.c's scandir snapshot; DT_UNKNOWN falls back - * to lstat, Node's getDirents rule). The result type is the call site's - * interned Dirent record array (name, parentPath, hidden %dtype in - * libuv's UV_DIRENT encoding) — verified by the frontend; the emitter - * assembles the rows from the snapshot. Byte order on POSIX (libuv - * sorts), the directory's order on Windows; no "."/"..". Throws - * Node's scandir errno error (may-throw seed set); fresh +1 array. */ - "fs.readdirTypesSync": { argTypes: [STRING], result: VOID }, - /** fs.globSync / fs.glob / fs/promises glob (scr_glob.c, Node's - * lib/internal/fs/glob.js walk): pattern and options are dyn values the - * runtime validates in Node's order. The string forms answer string[]; - * the Dirent forms answer the call site's Dirent record array (validated - * in validate.ts) assembled from the snapshot; fs.callbackGlob takes the - * argument vector and calls back with string[]. */ - "fs.globSync": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, - "fs.globTypesSync": { argTypes: [DYN, DYN], result: VOID }, - "fs.callbackGlob": { argTypes: [DYN], result: VOID }, - "fsp.globStrings": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, - "fsp.globTypes": { argTypes: [DYN, DYN], result: VOID }, - /** The error an fs/promises glob walk ended with (undefined when none), - * read once right after fsp.globStrings / fsp.globTypes. */ - "fsp.globError": { argTypes: [], result: DYN }, - /** fs.Dir (scr_lib.c): opendirSync(path, bufferSize) throws Node's - * opendir error (or the options.bufferSize RangeError); dir.readSync - * answers the call site's `Dirent | null` (VOID here — the emitter builds - * the row like readdirTypesSync) and dir.read is its promise twin; - * readSync and closeSync throw ERR_DIR_CLOSED on a closed Dir, read and - * close reject with it. */ - "fs.opendirSync": { argTypes: [STRING, F64], result: DIR_T }, - "fsp.opendir": { argTypes: [STRING, F64], result: { kind: "promise", inner: DIR_T } }, - "dir.path": { argTypes: [DIR_T], result: STRING }, - "dir.readSync": { argTypes: [DIR_T], result: VOID }, - "dir.read": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, - "dir.closeSync": { argTypes: [DIR_T], result: VOID }, - "dir.close": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, - /** node:path (scr_path.c ports BOTH of Node's implementations - * function-by-function): the `path.*` family is posix, the - * `path.win32*` family is Node v24's path.win32 ported exactly — the - * frontend binds the bare module to the TARGET platform's family - * (Node on Windows IS path.win32) and the path.posix / path.win32 - * namespaces to their own family everywhere. join and resolve take ONE - * string[] arg — the frontend packs the variadic call's arguments into - * an array literal. basename always receives its suffix (the frontend - * completes an omitted one with "", a Node no-op). toNamespacedPath is - * the posix identity and the win32 \\?\-prefixer. None of these throw; - * the resolves consult the process cwd like Node's. */ - "path.join": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.resolve": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.normalize": { argTypes: [STRING], result: STRING }, - "path.dirname": { argTypes: [STRING], result: STRING }, - "path.basename": { argTypes: [STRING, STRING], result: STRING }, - "path.extname": { argTypes: [STRING], result: STRING }, - "path.isAbsolute": { argTypes: [STRING], result: BOOL }, - "path.relative": { argTypes: [STRING, STRING], result: STRING }, - "path.toNamespacedPath": { argTypes: [STRING], result: STRING }, - "path.win32Join": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.win32Resolve": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.win32Normalize": { argTypes: [STRING], result: STRING }, - "path.win32Dirname": { argTypes: [STRING], result: STRING }, - "path.win32Basename": { argTypes: [STRING, STRING], result: STRING }, - "path.win32Extname": { argTypes: [STRING], result: STRING }, - "path.win32IsAbsolute": { argTypes: [STRING], result: BOOL }, - "path.win32Relative": { argTypes: [STRING, STRING], result: STRING }, - "path.win32ToNamespacedPath": { argTypes: [STRING], result: STRING }, - /** path.parse: [root, dir, base, ext, name] as a fresh string[] (the - * frontend builds the ParsedPath record); path.format: the pathObject's - * root, dir, base, name and ext with undefined spelled "". */ - "path.parse": { argTypes: [STRING], result: arrayOf(STRING) }, - "path.win32Parse": { argTypes: [STRING], result: arrayOf(STRING) }, - "path.format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, - "path.win32Format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, - /** path.matchesGlob(path, pattern): Node's matchGlobPattern over its - * bundled minimatch (scr_glob.c). Both families match case-insensitively - * on macOS and Windows hosts like Node's; win32 also accepts either - * separator. Throws TypeError for a pattern over 64 Ki code units, - * RangeError for a brace sequence too long to expand, and SyntaxError - * when a segment's regex is invalid under the u flag. */ - "path.matchesGlob": { argTypes: [STRING, STRING], result: BOOL }, - "path.win32MatchesGlob": { argTypes: [STRING, STRING], result: BOOL }, - /** node:os: homedir is $HOME else getpwuid(3); tmpdir is Node's env - * cascade ($TMPDIR/$TMP/$TEMP else /tmp, one trailing slash trimmed). - * os.platform() lowers to process.platform — one implementation. */ - "os.homedir": { argTypes: [], result: STRING }, - /** os.release(): uname(2)'s release field — Node's own implementation - * (the kernel version string, e.g. "24.6.0" on macOS 15). Interned; +1 - * per read. Never throws. */ - "os.release": { argTypes: [], result: STRING }, - /** os.type(): uname(2)'s sysname ("Darwin", "Linux"; "Windows_NT" on - * win32) — Node's uv_os_uname answer. Interned per call; never throws. */ - "os.type": { argTypes: [], result: STRING }, - /** os.totalmem(): total physical memory in bytes. Never throws. */ - "os.totalmem": { argTypes: [], result: F64 }, - /** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */ - "os.availableParallelism": { argTypes: [], result: F64 }, - /** os.hostname/machine/version/endianness, freemem, uptime and loadavg: - * libuv's answers (uname's machine and version fields, gethostname, - * "LE"/"BE", free memory in bytes, uptime in seconds, the three load - * averages). Fresh results; never throw. */ - "os.hostname": { argTypes: [], result: STRING }, - "os.machine": { argTypes: [], result: STRING }, - "os.version": { argTypes: [], result: STRING }, - "os.endianness": { argTypes: [], result: STRING }, - "os.freemem": { argTypes: [], result: F64 }, - "os.uptime": { argTypes: [], result: F64 }, - "os.loadavg": { argTypes: [], result: arrayOf(F64) }, - /** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE), - * then the priority; a failure throws Node's SystemError. */ - "os.getPriority": { argTypes: [F64], result: F64 }, - /** os.setPriority(pid, priority): validateInt32 for both (priority in - * -20..19, RangeError ERR_OUT_OF_RANGE), then the priority; a failure - * throws Node's SystemError. */ - "os.setPriority": { argTypes: [F64, F64], result: VOID }, - /** os.cpus(): cpusTake snapshots every CPU and answers the count; - * cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys, - * 4 idle, 5 irq) read the snapshot. Never throw. */ - "os.cpusTake": { argTypes: [], result: F64 }, - "os.cpusModel": { argTypes: [F64], result: STRING }, - "os.cpusField": { argTypes: [F64, F64], result: F64 }, - /** os.constants: the per-thread null-prototype constants object (dlopen, - * errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */ - "os.constants": { argTypes: [], result: DYN }, - /** fs.constants: the target's cached null-prototype constants object. */ - "fs.constants": { argTypes: [], result: DYN }, - /** The deprecated node:constants object (scr_os.c): the os groups and - * fs.constants merged with the borrowed baked crypto.constants table, - * frozen, one per thread. */ - "constants.object": { argTypes: [DYN], result: DYN }, - /** umask(2): arg < 0 reads without setting (umask has no read-only form - * — set 0, restore); otherwise sets and answers the PREVIOUS mask. - * Never throws. */ - "process.umask": { argTypes: [F64], result: F64 }, - /** chdir(2) — throws Node's fs-shaped error (ENOENT/EACCES/ENOTDIR, - * syscall "chdir") on failure. */ - "process.chdir": { argTypes: [STRING], result: VOID }, - "process.loadEnvFile": { argTypes: [DYN], result: VOID }, - /** process._exiting: true once the exit sequence began (exit listeners - * running) — the runtime flag scr_run_exit_listeners/process.exit set. - * Never throws. */ - "process.exiting": { argTypes: [], result: BOOL }, - /** net's process-wide happy-eyeballs attempt budget (Node's default - * 250ms): one runtime double in the core unit, so reading/writing it - * never forces the net unit into the link. Never throw. */ - "net.getAutoSelTimeout": { argTypes: [], result: F64 }, - "net.setAutoSelTimeout": { argTypes: [F64], result: VOID }, - /** realpath(3) with Node's error shape (syscall "lstat" in the message, - * Node's own spelling for realpathSync failures). +1 fresh string. */ - "fs.realpathSync": { argTypes: [STRING], result: STRING }, - /** fs.realpathSync (or its .native when the flag is true) as a function - * value: path and options arrive unchecked; the result is a string or a - * Buffer by the options' encoding. */ - "fs.realpathValue": { argTypes: [DYN, DYN, BOOL], result: DYN }, - /** Native realpath with syscall "realpath" on failure. +1 fresh string. */ - "fs.realpathNativeSync": { argTypes: [STRING], result: STRING }, - /** The os.userInfo() field trio (uv_os_get_passwd's slices): pw_name, - * pw_shell, pw_dir — the PASSWD homedir, not os.homedir's $HOME-first - * cascade (Node's own split). The frontend assembles the UserInfo - * record from these plus getuid/getgid. +1 fresh strings; a passwd - * lookup failure aborts (Node throws a system error there — no - * compiled program path reaches it for the running uid). */ - "os.userName": { argTypes: [], result: STRING }, - "os.userShell": { argTypes: [], result: STRING }, - "os.userHomedir": { argTypes: [], result: STRING }, - "os.tmpdir": { argTypes: [], result: STRING }, - // Result is the call site's Dict record (VOID is - // the dgram.address sentinel — the libCall case checks the structure). - /** os.networkInterfaces(): getifaddrs(3) → the Dict - * record, built inline by the emitter from a runtime snapshot (scr_lib.c). - * The result type is the CALL SITE's mapped @types/node shape — a pure - * index-signature record whose value is `Info[] | undefined`, Info a - * two-record union (IPv4: scopeid `number | undefined` holding undefined; - * IPv6: scopeid number) — verified structurally by the frontend. Rows - * match libuv's filter (IFF_UP && IFF_RUNNING, AF_INET/AF_INET6; loopback - * = internal; MACs from the interface's link-level sibling entry, zeros - * when absent; cidr from the netmask's contiguous prefix, the null arm - * when it is missing or non-contiguous). Key/row order follows the OS's - * getifaddrs enumeration — Node itself does not guarantee an order. - * Fresh +1 record; never throws (a getifaddrs failure yields {}). */ - "os.networkInterfaces": { argTypes: [], result: VOID }, - /** `Math.max(...xs)` / `Math.min(...xs)` over one spread number[] - * (scr_number.c): the JS fold exactly — any NaN element poisons the - * result, ±0 order by the JS comparison (max prefers +0, min prefers - * -0), and the empty array yields -Infinity / +Infinity like the - * zero-argument calls. Borrows the array; never throws. */ - "math.maxArr": { argTypes: [arrayOf(F64)], result: F64 }, - "math.minArr": { argTypes: [arrayOf(F64)], result: F64 }, - "math.hypotArr": { argTypes: [arrayOf(F64)], result: F64 }, - /** `Math.floor(x)` — C floor() IS the JS operation (NaN/±0/±Infinity - * pass through bit-exactly). Never throws. */ - "math.floor": { argTypes: [F64], result: F64 }, - /** Math.abs (C fabs — IS the JS operation) and Math.round (scr_lib.c: - * ECMA half-toward-+Infinity with the exact-fraction comparison — C - * round() is half-away-from-zero and floor(x+0.5) drifts at the - * epsilon boundary). Borrow nothing; never throw. */ - "math.abs": { argTypes: [F64], result: F64 }, - "math.round": { argTypes: [F64], result: F64 }, +function buildLibFnSignatures() { + return defineLibFnSignatures({ + /** Native static fetch and its Web-platform companions. fetch.start + * answers once the response head arrives; the response body readers + * consume the native body stream. AbortSignal and ReadableStream values + * are opaque checked-dynamic handles. */ + "fetch.start": { argTypes: [STRING, DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseNew": { argTypes: [DYN, DYN], result: DYN }, + "fetch.responseJsonStatic": { argTypes: [DYN, DYN], result: DYN }, + "fetch.responseLight": { argTypes: [DYN], result: arrayOf(STRING) }, + "fetch.input": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "abort.controllerNew": { argTypes: [], result: DYN }, + /** EventTarget / Event / CustomEvent construction (scr_event_target.c): + * the Event constructors take the argument vector as one dyn array and + * throw Node's validation errors; `event.is` is `v instanceof Event` + * (kind 0), `CustomEvent` (1), `EventTarget` (2). addAbortListener + * answers the disposer (the Disposable's [Symbol.dispose]). */ + "event.targetNew": { argTypes: [], result: DYN }, + "event.new": { argTypes: [DYN], result: DYN }, + "event.customNew": { argTypes: [DYN], result: DYN }, + "event.is": { argTypes: [DYN, F64], result: BOOL }, + /** Compiled EventTarget and Event subclasses: the receiver is the + * instance (an object rooted at %EventTarget / %Event). targetInit and + * init are super() (init takes the argument vector as one dyn array + * and throws the constructor's validation errors; initType is the + * super(type) form with a string); targetCall runs an EventTarget method + * with the instance's own capsule as `self` (targetDispatch is its + * dispatchEvent(event) form, answering the bool); call, get and set + * reach the Event members. */ + "event.targetInit": { argTypes: [null], result: VOID }, + "event.init": { argTypes: [null, DYN], result: VOID }, + "event.initType": { argTypes: [null, STRING], result: VOID }, + /** %main hands the engine the compiled `listener.handleEvent` reader + * (lower-event-target.ts noteEventListeners). */ + "event.handleEventReader": { argTypes: [DYN], result: VOID }, + "event.targetDispatch": { argTypes: [null, DYN, DYN], result: BOOL }, + "event.targetCall": { argTypes: [null, DYN, STRING, DYN], result: DYN }, + "event.call": { argTypes: [null, STRING, DYN], result: DYN }, + "event.get": { argTypes: [null, STRING], result: DYN }, + "event.set": { argTypes: [null, STRING, DYN], result: VOID }, + "events.addAbortListener": { argTypes: [DYN, DYN], result: DYN }, + /** Blob / File (scr_blob.c): the constructors and slice() take their + * argument vector as one dyn array and throw Node's validation errors; + * the readers settle on Node's microtask turn for the Blob's parts. + * `blob.is` is `v instanceof Blob` (kind 0) or `File` (1). */ + "blob.new": { argTypes: [DYN], result: DYN }, + "blob.fileNew": { argTypes: [DYN], result: DYN }, + "blob.slice": { argTypes: [DYN, DYN], result: DYN }, + "blob.text": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, + "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "blob.is": { argTypes: [DYN, F64], result: BOOL }, + /** WebAssembly (scr_wasm.c): modules translated at build time + * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. + * compile and instantiate over bytes settle on a later loop turn; + * instantiate over a Module settles now, as in Node. `wasm.is` answers + * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) + * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` + * constructs one of the error classes (kind 5-7, message, options). */ + "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "wasm.validate": { argTypes: [DYN], result: BOOL }, + "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "wasm.moduleNew": { argTypes: [DYN], result: DYN }, + "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.is": { argTypes: [DYN, F64], result: BOOL }, + "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and + * Global(descriptor, value) (an undefined value is the type's default); + * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, + * Table or Global (kinds 0-4) called without new, after its arguments + * evaluate. */ + "wasm.memoryNew": { argTypes: [DYN], result: DYN }, + "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** WebAssembly.Module.imports(module), exports(module) and + * customSections(module, name): checked-dynamic arrays of descriptor + * objects and ArrayBuffers. */ + "wasm.moduleImports": { argTypes: [DYN], result: DYN }, + "wasm.moduleExports": { argTypes: [DYN], result: DYN }, + "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, + /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, + * dyn.valueOf's answer for anything else. */ + "wasm.valueOf": { argTypes: [DYN], result: DYN }, + /** WebAssembly.compile (0), instantiate (1) and validate (2) as function + * values, and the namespace object holding them. */ + "wasm.function": { argTypes: [F64], result: DYN }, + "wasm.namespace": { argTypes: [], result: DYN }, + /** The global object viewed through a cast to a type with a WebAssembly + * member: an object holding the namespace. */ + "wasm.globalView": { argTypes: [], result: DYN }, + "abort.timeout": { argTypes: [DYN], result: DYN }, + "abort.now": { argTypes: [DYN], result: DYN }, + "abort.any": { argTypes: [DYN], result: DYN }, + "fetch.function": { argTypes: [], result: DYN }, + "fetch.requestNew": { argTypes: [DYN, DYN], result: DYN }, + "fetch.headersNew": { argTypes: [DYN], result: DYN }, + "fetch.responseArrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseJson": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseText": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, + "fetch.responseBytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "fetch.abortControllerNew": { argTypes: [], result: DYN }, + "fetch.abortTimeout": { argTypes: [DYN], result: DYN }, + "fetch.abortNow": { argTypes: [DYN], result: DYN }, + "fetch.abortAny": { argTypes: [DYN], result: DYN }, + "fetch.streamNew": { argTypes: [DYN], result: DYN }, + "fetch.transformNew": { argTypes: [], result: DYN }, + // Program-dependent iterable: typed arrays/bytes/string stay intact so + // the native stream can pull lazily; checked-dynamic values are the + // fallback. The libCall validator in validate.ts checks the closed set. + "fetch.webIs": { argTypes: [DYN, STRING], result: BOOL }, + "fetch.streamIs": { argTypes: [DYN], result: BOOL }, + "fetch.streamFrom": { argTypes: [null], result: DYN }, + // The chunk/result record depends on ReadableStream; validated by validate.ts. + "fetch.readerRead": { argTypes: [DYN], result: VOID }, + /** Readable.fromWeb (scr_stream_from_web.c): the stream, objectMode, + * highWaterMark (< 0: the mode's default) and encoding ("": none). */ + "fetch.readableFromWeb": { argTypes: [DYN, BOOL, F64, STRING], result: VOID }, + "json.parse": { argTypes: [STRING], result: DYN }, + "json.parseReviver": { argTypes: [STRING, DYN], result: DYN }, + "json.stringifyReplacer": { argTypes: [DYN, DYN, STRING], result: DYN }, + "json.stringifyValue": { argTypes: [DYN, DYN, DYN], result: DYN }, + /** Keyed WRITE on a dyn value — `h.onDone = cb` / `h["k"] = v` on a + * checked-dynamic object (args: receiver, key string, value — all + * borrowed; the runtime copies the key and retains the value in). An + * OBJ receiver sets the member (later writes win, insertion order + * preserved — JS exactly); undefined/null throws Node's catchable + * "Cannot set properties of undefined (setting 'k')"; every other kind + * throws Node's STRICT-mode "Cannot create property 'k' on " — + * primitives quoting their rendering, V8's "on number '5'" + * (sloppy mode would ignore the write — suite tests are 'use strict'; + * SEMANTICS.md notes the sloppy divergence: the write throws). Void + * result; in the may-throw seed set. */ + "dyn.keySet": { argTypes: [DYN, STRING, DYN], result: VOID }, + "dyn.keySetComputed": { argTypes: [DYN, DYN, DYN], result: VOID }, + /** Delete an ordinary checked-native object's own key. Borrows both + * arguments; other receiver representations retain a runtime refusal. */ + "dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID }, + "dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID }, + "dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL }, + /** Native own properties on globalThis keyed by symbol identity. */ + "dyn.globalSymbolGet": { argTypes: [SYMBOL_T], result: DYN }, + "dyn.globalSymbolSet": { argTypes: [SYMBOL_T, DYN], result: VOID }, + "dyn.globalSymbolHas": { argTypes: [SYMBOL_T], result: BOOL }, + "dyn.globalSymbolDelete": { argTypes: [SYMBOL_T], result: VOID }, + /** Exact native class capsule identity, without materializing its fields. */ + "dyn.typedRefIs": { argTypes: [DYN, STRING], result: BOOL }, + /** A capsule boxed by this program from exactly this class: one key + * pointer test, false for equal keys boxed elsewhere. Guards fast paths + * whose fallback handles every other value. The key is a literal. */ + "dyn.typedRefKeyIs": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.classIs": { argTypes: [DYN, STRING], result: BOOL }, + /** Destructuring pack over a dyn source — `const [a, b] = d`, a + * destructured dyn callback param (args: the source and the STATIC + * TypeError spelling, "" when the source has none — both borrowed; + * result: a fresh dyn array, +1). Iterable kinds collect like spread + * (arrays element-by-element, strings by code point, bytes by byte); + * every other kind throws V8's destructuring TypeError — the spelling + * verbatim when non-empty, else the runtime kind wording ("number 5 is + * not iterable (cannot read property Symbol(Symbol.iterator))"). In the + * may-throw seed set. */ + "dyn.iterPack": { argTypes: [DYN, STRING], result: DYN }, + "dyn.mathFold": { argTypes: [DYN, BOOL, STRING], result: F64 }, + "dyn.destructureRest": { argTypes: [DYN, STRING], result: DYN }, + "dyn.arrayFromIterator": { argTypes: [DYN], result: DYN }, + "dyn.iterator": { argTypes: [DYN, STRING], result: DYN }, + /** for await over a checked-dynamic value: the loop's iterator (a + * compiled async generator, Symbol.asyncIterator's result, or the sync + * iterator), and whether that iterator is synchronous. */ + "dyn.asyncIteratorOpen": { argTypes: [DYN, STRING], result: DYN }, + "dyn.asyncIteratorWasSync": { argTypes: [], result: BOOL }, + "dyn.iteratorResult": { argTypes: [DYN], result: DYN }, + "dyn.iteratorCanStep": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.iteratorStep": { argTypes: [DYN], result: DYN }, + "dyn.iteratorStepDone": { argTypes: [DYN], result: BOOL }, + "dyn.setGeneratorMembers": { argTypes: [DYN, DYN], result: VOID }, + "dyn.iteratorHelperMethod": { argTypes: [STRING], result: DYN }, + "dyn.collectionIteratorIndex": { argTypes: [DYN], result: F64 }, + "dyn.mapSeedEntries": { argTypes: [DYN], result: DYN }, + "dyn.mapSeedEntry": { argTypes: [DYN], result: DYN }, + /** The for-of-over-dyn pack accessors — the emitted index loop drives + * them over a dyn.iterPack result (ARR by construction). arrLen: the + * ARR length as f64 (0 for non-ARR kinds; arg borrowed). arrAt: the + * element at index (+1; the undefined singleton past the end). Neither + * throws. */ + "dyn.arrLen": { argTypes: [DYN], result: F64 }, + "dyn.arrAt": { argTypes: [DYN, F64], result: DYN }, + /** `key in v` with a RUNTIME (string) key on a checked-dynamic + * receiver (args: value dyn, key string; result bool): OBJ answers + * own-member presence, ARR answers 'length'/a valid index — exactly + * the literal-key dynHasKey path. Proxy has traps may throw. */ + "dyn.hasKey": { argTypes: [DYN, STRING], result: BOOL }, + /** The `in` operator with a runtime string key: dyn.hasKey plus the + * members inherited from builtin and compiled class prototypes. */ + "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.freeze": { argTypes: [DYN], result: DYN }, + "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, + /** Object.defineProperty on a declared class field (scr_json.c): the + * field's current value (borrowed), its FIELD_* attribute word, the + * instance's CLASS_* integrity word, the field name and the descriptor. + * Validates as Node does (TypeError "Cannot redefine property: f") and + * answers a one-property holder of the defined value and attributes (+1), + * read back with class.definedBits and class.definedValue. */ + "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, + "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, + "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, + /** Whether a property descriptor has a `value` member. */ + "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, + /** Throws Node's TypeError for a write to a read-only class field: the + * field name and the instance's class name. */ + "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, + /** Throws Node's TypeError for a class field initializer on a + * non-extensible instance: the field name. */ + "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, + /** Object.freeze of a record whose shape is freezable (frozen-records.ts): + * sets the frozen bit in its rc word (scr_record_freeze). The receiver is + * any record type. */ + "record.freeze": { argTypes: [null], result: VOID }, + /** Whether a record is frozen: its rc word's frozen bit, set by + * record.freeze and dyn.freeze of a capsule. Emitted inline. The receiver + * is any record type. */ + "record.isFrozen": { argTypes: [null], result: BOOL }, + /** Throws Node's TypeError for a write to a frozen record: the key, the + * object's text in the message ("#") and the operation (0 assign, + * 1 add, 2 delete). */ + "record.throwFrozen": { argTypes: [STRING, STRING, F64], result: VOID }, + /** Object.freeze of an array (scr_arr_freeze): sets its frozen bit, which + * the write guards of IrModule.freezableArrays test, and isFrozen reads. + * The receiver is any array type. */ + "array.freeze": { argTypes: [null], result: VOID }, + "array.isFrozen": { argTypes: [null], result: BOOL }, + /** Object.freeze, seal or preventExtensions of a Map or Set + * (scr_map_prevent_extensions), which only makes it not extensible: its + * entries stay writable. The receiver is any map or set type. */ + "collection.preventExtensions": { argTypes: [null], result: VOID }, + "collection.isExtensible": { argTypes: [null], result: BOOL }, + "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, + "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeDateHandleIs": { argTypes: [DYN], result: BOOL }, + /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, + * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one + * interned function per name, the same one dynamic values answer for + * `.constructor`. The name is a compiler-chosen literal. */ + "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, + /** A builtin global function (setTimeout, setInterval, setImmediate, + * the clear functions, queueMicrotask, process.nextTick) read as a + * JavaScript value: one interned function per name whose calls run the + * timer APIs (it pulls in the event loop). The name is a compiler-chosen + * literal. */ + "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, + "dyn.namespaceNew": { argTypes: [STRING, DYN], result: DYN }, + "dyn.namespaceDefine": { argTypes: [DYN, STRING, DYN, F64, F64], result: VOID }, + "date.nativeNew": { argTypes: [DYN], result: DYN }, + /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's + * first seven values pass through ToNumber in order (later ones are + * evaluated, never converted), then the local-time field constructor. */ + "date.nativeNewFields": { argTypes: [DYN], result: DYN }, + /** `new Date(value)` in JavaScript: a string argument parses with V8's + * full date parser; every other value constructs as date.nativeNew. */ + "date.nativeNewValue": { argTypes: [DYN], result: DYN }, + "date.argumentsTime": { argTypes: [DYN], result: DATE_T }, + "date.classOperation": { argTypes: [DYN, DYN], result: VOID }, + "date.checkedValue": { argTypes: [DYN], result: DATE_T }, + "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, + /** toString() on a checked-dynamic receiver: runtime kind dispatch + * (bytes decode per the literal encoding — utf8 default; strings, + * numbers, booleans, arrays, objects answer JS-exactly; undefined and + * null throw the catchable TypeError). */ + "dyn.toString": { argTypes: [DYN, DYN, STRING], result: STRING }, + "dyn.valueOf": { argTypes: [DYN, STRING], result: DYN }, + /** Object.defineProperties over dyn values (args: target, descriptors — + * both borrowed dyn; result: the target, +1 — JS's return value). + * Value descriptors become own properties on OBJ and FUNC targets; + * OBJ targets preserve data attributes. get/set + * descriptors and non-object targets/descriptors throw catchably + * (Node's TypeError texts; accessors the "not supported yet" Error). In + * the may-throw seed set. */ + "dyn.defineProps": { argTypes: [DYN, DYN], result: DYN }, + "dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN }, + "dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN }, + /** Promise.all / Promise.allSettled / Promise.any over a spread + * checked-dynamic array: the values, the settled-record array, or the + * first fulfillment (AggregateError once every entry rejects). + * Thenable elements adopt; the second argument reads a program class + * instance's `then` (the compiled property dispatch). */ + "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** PromiseResolve over one checked-dynamic value: the promise it holds, + * a promise that adopts a thenable's settlement, or a promise fulfilled + * with the value. The second argument reads `then` as above. */ + "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, + * borrowed; result: an owned string) — the dyn kind's JS answer: + * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest + * wart preserved), boolean/number/string by kind, function→"function". + * Never throws. */ + "dyn.typeof": { argTypes: [DYN], result: STRING }, + /** Object.prototype.toString.call on a checked-dynamic value. */ + "dyn.objectTag": { argTypes: [DYN], result: STRING }, + /** Engine-free CommonJS module graph. Module values are scalar f64 + * handles; generated startup defines the registry, init wrappers update + * loading/cache state, and the read surface answers Node's live metadata. */ + "module.registryInit": { argTypes: [F64], result: VOID }, + "module.define": { + argTypes: [F64, STRING, STRING, STRING, arrayOf(STRING), BOOL], + result: VOID, + }, + "module.enter": { argTypes: [F64], result: VOID }, + "module.link": { argTypes: [F64, F64], result: VOID }, + "module.finish": { argTypes: [F64], result: VOID }, + "module.fail": { argTypes: [F64], result: VOID }, + "module.filename": { argTypes: [F64], result: STRING }, + "module.id": { argTypes: [F64], result: STRING }, + "module.path": { argTypes: [F64], result: STRING }, + "module.paths": { argTypes: [F64], result: arrayOf(STRING) }, + "module.children": { argTypes: [F64], result: arrayOf(F64) }, + "module.parent": { argTypes: [F64], result: F64 }, + "module.loaded": { argTypes: [F64], result: BOOL }, + "module.cacheGet": { argTypes: [STRING], result: F64 }, + "module.cacheHas": { argTypes: [STRING], result: BOOL }, + "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, + "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, + /** The repeating timer pair. setInterval takes (callback, ms) like + * setTimeout and RETURNS the f64 handle the fallback declarations + * promise (ids start at 1, so truthiness narrowing works); the loop + * owns the callback until clearInterval removes the entry (eagerly — a + * live interval keeps the loop alive, a cleared one releases it, like + * Node). Neither throws; a throw ESCAPING an interval callback ends the + * program like a setTimeout throw. */ + "timers.setInterval": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: F64 }, + "timers.clearInterval": { argTypes: [F64], result: VOID }, + /** setTimeout WITH a clear handle (the f64 id) — the clearable/unref-able + * one-shot; clearTimeout cancels it, .unref() drops it from loop + * liveness. The plain timers.setTimeout stays the handle-less + * fire-and-forget. */ + "timers.setTimeoutHandle": { + argTypes: [{ kind: "func", params: [], ret: VOID }, F64], + result: F64, + }, + "timers.clearTimeout": { argTypes: [F64], result: VOID }, + /** Timeout.unref()/ref()/hasRef() — loop-liveness bookkeeping over the + * handle id. unref/ref RETURN the handle (f64) for chaining; hasRef + * returns bool. */ + "timers.unref": { argTypes: [F64], result: F64 }, + "timers.ref": { argTypes: [F64], result: F64 }, + "timers.hasRef": { argTypes: [F64], result: BOOL }, + /** Timeout.refresh() — re-arm to now + the original delay (from the + * heap or from inside the firing callback; a one-shot that fired on an + * earlier turn is gone and no-ops — documented divergence). Returns + * the handle for chaining like unref/ref. */ + "timers.refresh": { argTypes: [F64], result: F64 }, + /** setImmediate/clearImmediate — Node's check phase: callbacks run + * once per loop turn AFTER due timers, FIFO, and immediates queued + * mid-phase wait for the next turn. setImmediate returns the f64 + * handle (its own id space — clearTimeout of an Immediate no-ops, + * like Node); the Immediate ref trio mirrors the Timeout one (an + * unref'd pending immediate neither keeps the loop alive nor fires + * once nothing reffed remains). */ + "timers.setImmediate": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: F64 }, + /** queueMicrotask (scr_async.c): the callback enters the SAME FIFO + * promise continuations ride — one microtask order, like V8's queue — + * and a throw is an UNCAUGHT exception (never a rejection). + * timers.queueMicrotask takes an owned zero-param closure and never + * throws; the Dyn form (checked-dynamic arguments — the mustCall + * wrapper, the suite's invalid-input probes) throws Node's + * ERR_INVALID_ARG_TYPE synchronously on a non-function value and calls + * the function with zero arguments at drain. */ + "timers.queueMicrotask": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + "timers.queueMicrotaskDyn": { argTypes: [DYN], result: VOID }, + "timers.clearImmediate": { argTypes: [F64], result: VOID }, + /** process.nextTick(cb, ...args) — the user tick queue. args: [cb + * (() => void; trailing call arguments ride the timer surface's + * interned dyn thunk)]. Ticks drain BEFORE promise jobs at every loop + * checkpoint, to joint exhaustion with them (Node's tick-then- + * microtask order); ticks enqueued by station listeners run at the + * NEXT checkpoint (the stream-tick station divergence, SEMANTICS.md). + * Pending ticks are always-ready work — the loop neither sleeps nor + * exits while any exist; ticks scheduled from 'exit' listeners never + * run (Node). The enqueue itself never throws. */ + "process.nextTick": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + /** The process introspection statics. uptime: seconds since the + * binary's own start (fractional — a load-time monotonic anchor). + * availableMemory/constrainedMemory: libuv's numbers (free-ish bytes; + * the cgroup cap or 0). cpuUser/cpuSystem (+ threadCpu twins): the + * process/thread CPU clocks in microseconds — the frontend composes + * the {user, system} records. The *Diff forms answer current − prev + * for one field; cpuPrevValidate throws Node's ERR_INVALID_ARG_VALUE + * RangeError for negative/non-finite prev fields (user first, then + * system — Node's order; MAY THROW). rusage(idx): one + * process.resourceUsage() field by canonical index (Node's units). + * activeResources: the loop's own bookkeeping — 'Timeout' per armed + * (or firing, uncleared) timer, 'Immediate' per queued unfired + * immediate; unmodeled resource kinds are absent (SEMANTICS.md). */ + "process.uptime": { argTypes: [], result: F64 }, + /** perf_hooks performance.now(): fractional ms since process start. */ + "perf.now": { argTypes: [], result: F64 }, + /** perf_hooks user timing over the thread's performance timeline. The + * producing calls take the call's whole argument vector as one dyn + * array (Node's validation and errors run in the runtime, MAY THROW): + * mark/measure answer 1, entries(args, which) the entry count of + * getEntries (0), getEntriesByName (1) or getEntriesByType (2). The + * entryName/Type/Start/Duration/Detail accessors read entry i of that + * answer; the frontend builds a new entry's record from them and hands + * it to entryKeep (a typed reference capsule), and the getters read the + * kept records back through entryRef, so entries keep their identity. */ + "perf.mark": { argTypes: [DYN], result: F64 }, + "perf.measure": { argTypes: [DYN], result: F64 }, + "perf.entries": { argTypes: [DYN, F64], result: F64 }, + "perf.clearMarks": { argTypes: [DYN], result: VOID }, + "perf.clearMeasures": { argTypes: [DYN], result: VOID }, + "perf.entryName": { argTypes: [F64], result: STRING }, + "perf.entryType": { argTypes: [F64], result: STRING }, + "perf.entryStart": { argTypes: [F64], result: F64 }, + "perf.entryDuration": { argTypes: [F64], result: F64 }, + "perf.entryDetail": { argTypes: [F64], result: DYN }, + "perf.entryKeep": { argTypes: [DYN], result: VOID }, + "perf.entryRef": { argTypes: [F64], result: DYN }, + /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the + * loop's start in performance.now() ms (0 before it), idleTime the idle + * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay + * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; + * eldPercentile expects Node's validated range; eldStat(which) reads + * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ + "perf.loopStart": { argTypes: [], result: F64 }, + "perf.idleTime": { argTypes: [], result: F64 }, + "perf.eldNew": { argTypes: [F64], result: F64 }, + "perf.eldEnable": { argTypes: [F64], result: BOOL }, + "perf.eldDisable": { argTypes: [F64], result: BOOL }, + "perf.eldReset": { argTypes: [F64], result: VOID }, + "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, + "perf.eldStat": { argTypes: [F64, F64], result: F64 }, + /** process.memoryUsage(): memoryUsageTake samples the resident set and + * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) + * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 + * heapUsed, 3 external, 4 arrayBuffers). rss is memoryUsage.rss(). */ + "process.memoryUsageTake": { argTypes: [], result: VOID }, + "process.memoryUsageField": { argTypes: [F64], result: F64 }, + "process.rss": { argTypes: [], result: F64 }, + "process.availableMemory": { argTypes: [], result: F64 }, + "process.constrainedMemory": { argTypes: [], result: F64 }, + "process.cpuUser": { argTypes: [], result: F64 }, + "process.cpuSystem": { argTypes: [], result: F64 }, + "process.cpuUserDiff": { argTypes: [F64], result: F64 }, + "process.cpuSystemDiff": { argTypes: [F64], result: F64 }, + "process.threadCpuUser": { argTypes: [], result: F64 }, + "process.threadCpuSystem": { argTypes: [], result: F64 }, + "process.threadCpuUserDiff": { argTypes: [F64], result: F64 }, + "process.threadCpuSystemDiff": { argTypes: [F64], result: F64 }, + "process.cpuPrevValidate": { argTypes: [F64, F64], result: VOID }, + "process.rusage": { argTypes: [F64], result: F64 }, + "process.activeResources": { argTypes: [], result: arrayOf(STRING) }, + "timers.immediateUnref": { argTypes: [F64], result: F64 }, + "timers.immediateRef": { argTypes: [F64], result: F64 }, + "timers.immediateHasRef": { argTypes: [F64], result: BOOL }, + /** The tolerated non-handle clear (`clearTimeout(null)`, + * `clearInterval({})`, zero-argument forms): Node ignores anything + * that is not a live handle without an error — a VOID no-op the emitter drops + * (only syntactically side-effect-free arguments take this path). */ + "timers.clearNoop": { argTypes: [], result: VOID }, + // Signal listeners are zero-param (the ambient shape); exit/stdin + // callbacks carry program-dependent one-param shapes — null slots, the + // libCall case checks them (child.onExit precedent). + /** Named process signal events: [name string, callback dyn, once bool] + * or [name, callback] for removal. The runtime resolves platform signal + * numbers and delivers (name, number). Listeners do not keep the loop + * alive. Invalid callbacks and uncatchable signals throw. */ + "process.onSignal": { argTypes: [STRING, DYN, BOOL], result: VOID }, + "process.offSignal": { argTypes: [STRING, DYN], result: VOID }, + /** The process 'exit' event — process.on/once/off("exit"). args: [cb, + * once bool] / [cb]. Listeners run SYNCHRONOUSLY at termination + * (normal exit, process.exit(), the uncaught/unhandled exit-1 paths) + * with the exit code; anything they schedule never runs, like Node. + * Callback shapes: () => void or (code: number) => void — the emitter + * picks the runtime adapter. Never throw. */ + "process.onExit": { argTypes: [null, BOOL], result: VOID }, + "process.offExit": { argTypes: [null], result: VOID }, + /** process.stdin listener registration — stdin.on/once("data" | "end" | + * "error", cb). args: [cb, once bool]. data callbacks: () => void or + * (chunk: Uint8Array) => void; end: () => void; error: () => void or + * (err: Error) => void (the child error adapters are reused). While a + * data listener (or a parked for-await chunk promise) exists, stdin + * keeps the loop alive — Node's flowing-stdin keep-alive. Never + * throw. */ + "stdin.onData": { argTypes: [null, BOOL], result: VOID }, + "stdin.onEnd": { argTypes: [{ kind: "func", params: [], ret: VOID }, BOOL], result: VOID }, + "stdin.onError": { argTypes: [null, BOOL], result: VOID }, + /** process.stdin.on/once('keypress', cb) (scr_readline_tty.c): cb is the + * listener as a checked-dynamic function, called with (str, key). */ + "stdin.onKeypress": { argTypes: [DYN, BOOL], result: VOID }, + /** The for-await chunk source over process.stdin: no args, result + * Promise (+1). Fulfills with the next arrived chunk; the + * EMPTY bytes value is the done sentinel (POSIX reads never deliver + * empty data chunks), which the for-await desugar turns into loop + * exit. Never throws itself; awaiting it re-throws nothing (stdin + * errors surface through 'error' listeners, not the iterator). */ + "stdin.nextChunk": { argTypes: [], result: { kind: "promise", inner: BYTES_U8 } }, + "fs.readFileSync": { argTypes: [STRING, STRING], result: STRING }, + "fs.readFileEncoded": { argTypes: [STRING, STRING], result: STRING }, + "fs.readFdEncoded": { argTypes: [F64, STRING], result: STRING }, + /** readFileSync(path) — the Buffer read (+1 bytes); throws catchably + * like the utf8 form. */ + "fs.readFileSyncBuf": { argTypes: [STRING], result: BYTES_U8 }, + /** readFileSync(path, enc) with a RUNTIME encoding (an untyped JS + * parameter): undefined/null answer Buffers, utf8 a string, other real + * encodings throw the "not supported yet" Error, unknown names throw + * ERR_UNKNOWN_ENCODING. */ + "fs.readFileSyncDyn": { argTypes: [STRING, DYN], result: DYN }, + "fs.writeFileSync": { argTypes: [STRING, STRING], result: VOID }, + "fs.appendFileSync": { argTypes: [STRING, STRING], result: VOID }, + "fs.existsSync": { argTypes: [STRING], result: BOOL }, + "fs.mkdirSync": { argTypes: [STRING], result: VOID }, + "fs.rmSync": { argTypes: [STRING], result: VOID }, + "fs.rmdirSync": { argTypes: [STRING], result: VOID }, + "fs.readdirSync": { 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 + * one readdir pass (scr_lib.c's scandir snapshot; DT_UNKNOWN falls back + * to lstat, Node's getDirents rule). The result type is the call site's + * interned Dirent record array (name, parentPath, hidden %dtype in + * libuv's UV_DIRENT encoding) — verified by the frontend; the emitter + * assembles the rows from the snapshot. Byte order on POSIX (libuv + * sorts), the directory's order on Windows; no "."/"..". Throws + * Node's scandir errno error (may-throw seed set); fresh +1 array. */ + "fs.readdirTypesSync": { argTypes: [STRING], result: VOID }, + /** fs.globSync / fs.glob / fs/promises glob (scr_glob.c, Node's + * lib/internal/fs/glob.js walk): pattern and options are dyn values the + * runtime validates in Node's order. The string forms answer string[]; + * the Dirent forms answer the call site's Dirent record array (validated + * in validate.ts) assembled from the snapshot; fs.callbackGlob takes the + * argument vector and calls back with string[]. */ + "fs.globSync": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, + "fs.globTypesSync": { argTypes: [DYN, DYN], result: VOID }, + "fs.callbackGlob": { argTypes: [DYN], result: VOID }, + "fsp.globStrings": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, + "fsp.globTypes": { argTypes: [DYN, DYN], result: VOID }, + /** The error an fs/promises glob walk ended with (undefined when none), + * read once right after fsp.globStrings / fsp.globTypes. */ + "fsp.globError": { argTypes: [], result: DYN }, + /** fs.Dir (scr_lib.c): opendirSync(path, bufferSize) throws Node's + * opendir error (or the options.bufferSize RangeError); dir.readSync + * answers the call site's `Dirent | null` (VOID here — the emitter builds + * the row like readdirTypesSync) and dir.read is its promise twin; + * readSync and closeSync throw ERR_DIR_CLOSED on a closed Dir, read and + * close reject with it. */ + "fs.opendirSync": { argTypes: [STRING, F64], result: DIR_T }, + "fsp.opendir": { argTypes: [STRING, F64], result: { kind: "promise", inner: DIR_T } }, + "dir.path": { argTypes: [DIR_T], result: STRING }, + "dir.readSync": { argTypes: [DIR_T], result: VOID }, + "dir.read": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, + "dir.closeSync": { argTypes: [DIR_T], result: VOID }, + "dir.close": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, + /** node:path (scr_path.c ports BOTH of Node's implementations + * function-by-function): the `path.*` family is posix, the + * `path.win32*` family is Node v24's path.win32 ported exactly — the + * frontend binds the bare module to the TARGET platform's family + * (Node on Windows IS path.win32) and the path.posix / path.win32 + * namespaces to their own family everywhere. join and resolve take ONE + * string[] arg — the frontend packs the variadic call's arguments into + * an array literal. basename always receives its suffix (the frontend + * completes an omitted one with "", a Node no-op). toNamespacedPath is + * the posix identity and the win32 \\?\-prefixer. None of these throw; + * the resolves consult the process cwd like Node's. */ + "path.join": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.resolve": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.normalize": { argTypes: [STRING], result: STRING }, + "path.dirname": { argTypes: [STRING], result: STRING }, + "path.basename": { argTypes: [STRING, STRING], result: STRING }, + "path.extname": { argTypes: [STRING], result: STRING }, + "path.isAbsolute": { argTypes: [STRING], result: BOOL }, + "path.relative": { argTypes: [STRING, STRING], result: STRING }, + "path.toNamespacedPath": { argTypes: [STRING], result: STRING }, + "path.win32Join": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.win32Resolve": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.win32Normalize": { argTypes: [STRING], result: STRING }, + "path.win32Dirname": { argTypes: [STRING], result: STRING }, + "path.win32Basename": { argTypes: [STRING, STRING], result: STRING }, + "path.win32Extname": { argTypes: [STRING], result: STRING }, + "path.win32IsAbsolute": { argTypes: [STRING], result: BOOL }, + "path.win32Relative": { argTypes: [STRING, STRING], result: STRING }, + "path.win32ToNamespacedPath": { argTypes: [STRING], result: STRING }, + /** path.parse: [root, dir, base, ext, name] as a fresh string[] (the + * frontend builds the ParsedPath record); path.format: the pathObject's + * root, dir, base, name and ext with undefined spelled "". */ + "path.parse": { argTypes: [STRING], result: arrayOf(STRING) }, + "path.win32Parse": { argTypes: [STRING], result: arrayOf(STRING) }, + "path.format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, + "path.win32Format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, + /** path.matchesGlob(path, pattern): Node's matchGlobPattern over its + * bundled minimatch (scr_glob.c). Both families match case-insensitively + * on macOS and Windows hosts like Node's; win32 also accepts either + * separator. Throws TypeError for a pattern over 64 Ki code units, + * RangeError for a brace sequence too long to expand, and SyntaxError + * when a segment's regex is invalid under the u flag. */ + "path.matchesGlob": { argTypes: [STRING, STRING], result: BOOL }, + "path.win32MatchesGlob": { argTypes: [STRING, STRING], result: BOOL }, + /** node:os: homedir is $HOME else getpwuid(3); tmpdir is Node's env + * cascade ($TMPDIR/$TMP/$TEMP else /tmp, one trailing slash trimmed). + * os.platform() lowers to process.platform — one implementation. */ + "os.homedir": { argTypes: [], result: STRING }, + /** os.release(): uname(2)'s release field — Node's own implementation + * (the kernel version string, e.g. "24.6.0" on macOS 15). Interned; +1 + * per read. Never throws. */ + "os.release": { argTypes: [], result: STRING }, + /** os.type(): uname(2)'s sysname ("Darwin", "Linux"; "Windows_NT" on + * win32) — Node's uv_os_uname answer. Interned per call; never throws. */ + "os.type": { argTypes: [], result: STRING }, + /** os.totalmem(): total physical memory in bytes. Never throws. */ + "os.totalmem": { argTypes: [], result: F64 }, + /** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */ + "os.availableParallelism": { argTypes: [], result: F64 }, + /** os.hostname/machine/version/endianness, freemem, uptime and loadavg: + * libuv's answers (uname's machine and version fields, gethostname, + * "LE"/"BE", free memory in bytes, uptime in seconds, the three load + * averages). Fresh results; never throw. */ + "os.hostname": { argTypes: [], result: STRING }, + "os.machine": { argTypes: [], result: STRING }, + "os.version": { argTypes: [], result: STRING }, + "os.endianness": { argTypes: [], result: STRING }, + "os.freemem": { argTypes: [], result: F64 }, + "os.uptime": { argTypes: [], result: F64 }, + "os.loadavg": { argTypes: [], result: arrayOf(F64) }, + /** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE), + * then the priority; a failure throws Node's SystemError. */ + "os.getPriority": { argTypes: [F64], result: F64 }, + /** os.setPriority(pid, priority): validateInt32 for both (priority in + * -20..19, RangeError ERR_OUT_OF_RANGE), then the priority; a failure + * throws Node's SystemError. */ + "os.setPriority": { argTypes: [F64, F64], result: VOID }, + /** os.cpus(): cpusTake snapshots every CPU and answers the count; + * cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys, + * 4 idle, 5 irq) read the snapshot. Never throw. */ + "os.cpusTake": { argTypes: [], result: F64 }, + "os.cpusModel": { argTypes: [F64], result: STRING }, + "os.cpusField": { argTypes: [F64, F64], result: F64 }, + /** os.constants: the per-thread null-prototype constants object (dlopen, + * errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */ + "os.constants": { argTypes: [], result: DYN }, + /** fs.constants: the target's cached null-prototype constants object. */ + "fs.constants": { argTypes: [], result: DYN }, + /** The deprecated node:constants object (scr_os.c): the os groups and + * fs.constants merged with the borrowed baked crypto.constants table, + * frozen, one per thread. */ + "constants.object": { argTypes: [DYN], result: DYN }, + /** umask(2): arg < 0 reads without setting (umask has no read-only form + * — set 0, restore); otherwise sets and answers the PREVIOUS mask. + * Never throws. */ + "process.umask": { argTypes: [F64], result: F64 }, + /** chdir(2) — throws Node's fs-shaped error (ENOENT/EACCES/ENOTDIR, + * syscall "chdir") on failure. */ + "process.chdir": { argTypes: [STRING], result: VOID }, + "process.loadEnvFile": { argTypes: [DYN], result: VOID }, + /** process._exiting: true once the exit sequence began (exit listeners + * running) — the runtime flag scr_run_exit_listeners/process.exit set. + * Never throws. */ + "process.exiting": { argTypes: [], result: BOOL }, + /** net's process-wide happy-eyeballs attempt budget (Node's default + * 250ms): one runtime double in the core unit, so reading/writing it + * never forces the net unit into the link. Never throw. */ + "net.getAutoSelTimeout": { argTypes: [], result: F64 }, + "net.setAutoSelTimeout": { argTypes: [F64], result: VOID }, + /** realpath(3) with Node's error shape (syscall "lstat" in the message, + * Node's own spelling for realpathSync failures). +1 fresh string. */ + "fs.realpathSync": { argTypes: [STRING], result: STRING }, + /** fs.realpathSync (or its .native when the flag is true) as a function + * value: path and options arrive unchecked; the result is a string or a + * Buffer by the options' encoding. */ + "fs.realpathValue": { argTypes: [DYN, DYN, BOOL], result: DYN }, + /** Native realpath with syscall "realpath" on failure. +1 fresh string. */ + "fs.realpathNativeSync": { argTypes: [STRING], result: STRING }, + /** The os.userInfo() field trio (uv_os_get_passwd's slices): pw_name, + * pw_shell, pw_dir — the PASSWD homedir, not os.homedir's $HOME-first + * cascade (Node's own split). The frontend assembles the UserInfo + * record from these plus getuid/getgid. +1 fresh strings; a passwd + * lookup failure aborts (Node throws a system error there — no + * compiled program path reaches it for the running uid). */ + "os.userName": { argTypes: [], result: STRING }, + "os.userShell": { argTypes: [], result: STRING }, + "os.userHomedir": { argTypes: [], result: STRING }, + "os.tmpdir": { argTypes: [], result: STRING }, + // Result is the call site's Dict record (VOID is + // the dgram.address sentinel — the libCall case checks the structure). + /** os.networkInterfaces(): getifaddrs(3) → the Dict + * record, built inline by the emitter from a runtime snapshot (scr_lib.c). + * The result type is the CALL SITE's mapped @types/node shape — a pure + * index-signature record whose value is `Info[] | undefined`, Info a + * two-record union (IPv4: scopeid `number | undefined` holding undefined; + * IPv6: scopeid number) — verified structurally by the frontend. Rows + * match libuv's filter (IFF_UP && IFF_RUNNING, AF_INET/AF_INET6; loopback + * = internal; MACs from the interface's link-level sibling entry, zeros + * when absent; cidr from the netmask's contiguous prefix, the null arm + * when it is missing or non-contiguous). Key/row order follows the OS's + * getifaddrs enumeration — Node itself does not guarantee an order. + * Fresh +1 record; never throws (a getifaddrs failure yields {}). */ + "os.networkInterfaces": { argTypes: [], result: VOID }, + /** `Math.max(...xs)` / `Math.min(...xs)` over one spread number[] + * (scr_number.c): the JS fold exactly — any NaN element poisons the + * result, ±0 order by the JS comparison (max prefers +0, min prefers + * -0), and the empty array yields -Infinity / +Infinity like the + * zero-argument calls. Borrows the array; never throws. */ + "math.maxArr": { argTypes: [arrayOf(F64)], result: F64 }, + "math.minArr": { argTypes: [arrayOf(F64)], result: F64 }, + "math.hypotArr": { argTypes: [arrayOf(F64)], result: F64 }, + /** `Math.floor(x)` — C floor() IS the JS operation (NaN/±0/±Infinity + * pass through bit-exactly). Never throws. */ + "math.floor": { argTypes: [F64], result: F64 }, + /** Math.abs (C fabs — IS the JS operation) and Math.round (scr_lib.c: + * ECMA half-toward-+Infinity with the exact-fraction comparison — C + * round() is half-away-from-zero and floor(x+0.5) drifts at the + * epsilon boundary). Borrow nothing; never throw. */ + "math.abs": { argTypes: [F64], result: F64 }, + "math.round": { argTypes: [F64], result: F64 }, - "math.trunc": { argTypes: [F64], result: F64 }, - "math.ceil": { argTypes: [F64], result: F64 }, - "math.sin": { argTypes: [F64], result: F64 }, - "math.sinh": { argTypes: [F64], result: F64 }, - "math.cos": { argTypes: [F64], result: F64 }, - "math.cosh": { argTypes: [F64], result: F64 }, - "math.tan": { argTypes: [F64], result: F64 }, - "math.tanh": { argTypes: [F64], result: F64 }, - "math.asin": { argTypes: [F64], result: F64 }, - "math.asinh": { argTypes: [F64], result: F64 }, - "math.acos": { argTypes: [F64], result: F64 }, - "math.acosh": { argTypes: [F64], result: F64 }, - "math.atan": { argTypes: [F64], result: F64 }, - "math.atanh": { argTypes: [F64], result: F64 }, - "math.cbrt": { argTypes: [F64], result: F64 }, - "math.clz32": { argTypes: [F64], result: F64 }, - /** Return -1, +1, or the original NaN/zero. In particular, -0 remains -0. */ - "math.sign": { argTypes: [F64], result: F64 }, - "math.exp": { argTypes: [F64], result: F64 }, - "math.expm1": { argTypes: [F64], result: F64 }, - "math.f16round": { argTypes: [F64], result: F64 }, - "math.fround": { argTypes: [F64], result: F64 }, - "math.sqrt": { argTypes: [F64], result: F64 }, - "math.log": { argTypes: [F64], result: F64 }, - "math.log1p": { argTypes: [F64], result: F64 }, - "math.log2": { argTypes: [F64], result: F64 }, - "math.log10": { argTypes: [F64], result: F64 }, - "math.atan2": { argTypes: [F64, F64], result: F64 }, - "math.pow": { argTypes: [F64, F64], result: F64 }, - "math.imul": { argTypes: [F64, F64], result: F64 }, - /** `Math.min(a, b)` / `Math.max(a, b)` — the two-argument scalar forms - * (scr_lib.c), JS-exact like the Arr folds: NaN poisons, max prefers +0 - * over -0 (min the reverse). C fmin/fmax are NOT these (they drop NaN). - * Never throw. */ - "math.min": { argTypes: [F64, F64], result: F64 }, - "math.max": { argTypes: [F64, F64], result: F64 }, - /** `Math.random()` — a uniform double in [0,1) with the spec's 53-bit - * granularity, drawn from arc4random_buf (the CSPRNG behind the crypto - * lowerings). Same distribution as Node, NECESSARILY different sequence - * (SEMANTICS.md 62 — no seeded sequence exists to match). Never throws. */ - "math.random": { argTypes: [], result: F64 }, - /** The static global parsers/tests (scr_string.c). num.parseInt is - * ECMA-262 19.2.5 exactly — JS whitespace, sign, ToInt32 radix (the - * frontend completes an omitted radix to 0 = the spec's "undefined": - * base 10 with the 0x hex escape), longest digit prefix, and the exact - * mathematical value correctly rounded (u64 fast path, bignum beyond — - * overflow is ±Infinity). num.isNaN is the NaN self-test on an - * already-number argument (tsc pins the argument to number, so no - * ToNumber coercion exists to model). Borrow; never throw. */ - "num.parseInt": { argTypes: [STRING, F64], result: F64 }, - /** ES parseFloat (scr_string.c): the longest StrDecimalLiteral prefix - * of the trimmed input (no hex, "Infinity" exact-case), NaN when none — - * ECMA-262 19.2.4 over a string argument (non-string arguments keep the - * fence: Node would ToNumber-coerce). Borrows; never throws. */ - "num.parseFloat": { argTypes: [STRING], result: F64 }, - /** `a.localeCompare(b)`: -1/0/1 in Node's root-locale collation for - * Latin-script text (scr_str_locale_compare). Never throws. */ - "str.localeCompare": { argTypes: [STRING, STRING], result: F64 }, - /** Annex B substr(start, length) with start ToNumber'd and length's - * presence; never throws. */ - "str.substr": { argTypes: [STRING, F64, F64, BOOL], result: STRING }, - /** toLocaleLowerCase/toLocaleUpperCase (scr_regex.c): the locales value - * as written, upper=true for toLocaleUpperCase. Throws for null and for - * locale forms with no native rules. */ - "str.localeCase": { argTypes: [STRING, DYN, BOOL], result: STRING }, - "str.replaceLiteral": { argTypes: [STRING, STRING, STRING, BOOL], result: STRING }, - // (subject, characters, NUL-separated replacement texts): the fused - // chain of global one-character replacements. - "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, - /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): - * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the - * whole span must be one StrNumericLiteral — signed decimal (Infinity - * included, strtod-over-validated-span correct rounding) or unsigned - * 0x/0o/0b (exact value, nearest-even; signed forms are NaN) — with - * any trailing garbage answering NaN. Number(aString), unary + on - * strings, and util.format %d over strings lower here. Borrows; never - * throws. */ - "num.fromString": { argTypes: [STRING], result: F64 }, - "num.isNaN": { argTypes: [F64], result: BOOL }, - /** The static URI component codecs (scr_string.c), ECMA-262 Encode/ - * Decode with the component sets over the runtime's UTF-8 strings. - * str.encodeUriComponent percent-encodes every byte outside the - * unreserved component set (ALPHA/DIGIT/- _ . ! ~ * ' ( )) as uppercase - * %XX — the spec's per-code-point UTF-8 encoding IS a byte scan here — - * and THROWS the spec's URIError ("URI malformed") on a lone surrogate. - * str.decodeUriComponent - * decodes %XX escapes bytewise (raw non-escape bytes copy through) and - * requires the escaped bytes to form strictly valid UTF-8 (overlong - * forms, surrogate code points, and >U+10FFFF refused, per UTF8-decode - * without replacement); bad hex or an invalid sequence THROWS the - * spec's URIError ("URI malformed"), catchable. Borrow; results +1. */ - "str.encodeUriComponent": { argTypes: [STRING], result: STRING }, - // The base64 globals: the argument is a dyn value (WebIDL ToString - // runs in the runtime); the zero-argument form always throws. - /** The WHATWG base64 globals (scr_string.c), Node-global since v16. - * Arguments are borrowed dyn values — WebIDL ToString runs in the - * runtime over the dyn kind (String(null) is "null": the html spec's - * coercion, which Node's atob(null) exercises). str.atob decodes - * forgiving-base64 (ASCII whitespace stripped, %4==0 strips up to two - * '=', %4==1 refuses, leftover bits discarded) into the latin1 code - * points as a string; a malformed input THROWS the catchable - * DOMException InvalidCharacterError ("The string to be decoded is not - * correctly encoded."). str.btoa encodes the string's code points as - * base64; any code point over U+00FF THROWS InvalidCharacterError - * ("Invalid character"). str.b64Missing is the zero-argument call of - * either: always throws Node's TypeError [ERR_MISSING_ARGS] "The - * \"input\" argument must be specified". Results +1. */ - "str.atob": { argTypes: [DYN], result: STRING }, - "str.btoa": { argTypes: [DYN], result: STRING }, - "str.b64Missing": { argTypes: [], result: STRING }, - "str.decodeUriComponent": { argTypes: [STRING], result: STRING }, - "str.decodeUri": { argTypes: [STRING], result: STRING }, - /** encodeURI: the same Encode() keeping the reserved set and '#' - * unescaped — throwing like the component encoder. Borrow; result +1. */ - "str.encodeUri": { argTypes: [STRING], result: STRING }, - /** RegExp.escape (ES2025): per-code-point EncodeForRegExpEscape — - * leading ASCII alphanumeric hex-escapes, syntax characters and '/' - * take a backslash, other punctuators/whitespace/line terminators - * hex-escape, the rest passes through. Total; borrows; result +1. */ - "regexp.escape": { argTypes: [STRING], result: STRING }, - /** Number.prototype formatters (scr_lib.c), JS-exact: - * num.toExponential is toExponential() with the spec's "as many digits - * as necessary"; num.toFixed0 is the non-throwing omitted-argument - * toFixed() fast path; num.toFixed implements an explicit fractionDigits - * with exact binary-value rounding and THROWS RangeError outside 0..100. - * Successful results +1. */ - "num.toExponential": { argTypes: [F64], result: STRING }, - "num.toFixed0": { argTypes: [F64], result: STRING }, - "num.toFixed": { argTypes: [F64, F64], result: STRING }, - /** toPrecision(precision) with the argument ToNumber'd and its presence - * (undefined answers ToString); toExponential with a DEFINED - * fractionDigits. Both throw V8's RangeError for out-of-range digits. */ - "num.toPrecision": { argTypes: [F64, F64, BOOL], result: STRING }, - "num.toExponentialDigits": { argTypes: [F64, F64], result: STRING }, - "num.toStringRadix": { argTypes: [F64, DYN], result: STRING }, - /** toString(radix) with a number radix (scr_lib.c): truncates it as - * ToIntegerOrInfinity does, THROWS RangeError outside 2..36. */ - "num.toStringRadixF64": { argTypes: [F64, F64], result: STRING }, - /** Object.is over two numbers — the spec's SameValue on doubles: NaN - * equals NaN, +0 differs from -0, everything else is `===`. Plain bool - * result; never throws. (Union-armed operands take unionEq's sameValue - * flag instead — this is the both-f64 fast path.) */ - "num.sameValue": { argTypes: [F64, F64], result: BOOL }, - /** `new Intl.NumberFormat("en-US").format(x)` and - * `x.toLocaleString("en-US")` with DEFAULT options — the one locale - * whose data the runtime embeds (Node's default-build locale): decimal - * notation, 0–3 fraction digits rounded half-up on the SHORTEST - * round-tripping decimal (ICU's rounding input, probed vs Node — - * format(1.0005) is "1.001" though toFixed(3) answers "1.000"), "," - * grouping every three integer digits, "∞"/"NaN" texts, and "-0" for - * negative inputs rounding to zero. Result +1; never throws. */ - "intl.numFormatEnUs": { argTypes: [F64], result: STRING }, - /** ES Symbol values (scr_symbol.c — link-gated by moduleUsesSymbol). - * sym.new: `Symbol(desc)` — a fresh runtime-unique identity (+1) whose - * one arg is the description string (borrowed); sym.newAnon is the - * description-less `Symbol()` form. sym.for: the Symbol.for global - * registry — one interned symbol per key (borrowed), +1 on every call. - * sym.toString: "Symbol(desc)" (+1 string; "Symbol()" when absent). - * sym.desc / sym.keyFor answer the interned `string | undefined` union - * (the runtime returns +1-or-NULL; the backend builds the union arms — - * the child.stdout pattern). None of these throw. */ - "sym.new": { argTypes: [STRING], result: SYMBOL_T }, - "sym.newAnon": { argTypes: [], result: SYMBOL_T }, - "sym.for": { argTypes: [STRING], result: SYMBOL_T }, - "sym.wellKnown": { argTypes: [STRING], result: SYMBOL_T }, - // Result is the interned `string | undefined` union — the libCall case - // checks the arms (the spawnRes.error pattern). - "sym.keyFor": { argTypes: [SYMBOL_T], result: VOID }, - "sym.desc": { argTypes: [SYMBOL_T], result: VOID }, - "sym.toString": { argTypes: [SYMBOL_T], result: STRING }, - /** Engine-free arbitrary-precision bigint operations (scr_bigint.c). - * parse accepts the ECMAScript BigInt string grammar; fromF64 accepts - * finite integral Numbers. Arithmetic returns fresh immutable values. - * cmp returns -1/0/1 as f64; toString accepts a numeric radix. */ - "bigint.parse": { argTypes: [STRING], result: BIGINT_T }, - "bigint.fromF64": { argTypes: [F64], result: BIGINT_T }, - "bigint.neg": { argTypes: [BIGINT_T], result: BIGINT_T }, - "bigint.not": { argTypes: [BIGINT_T], result: BIGINT_T }, - "bigint.add": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.sub": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.mul": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.div": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.mod": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.pow": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.and": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.or": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.xor": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.shl": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.shr": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.eq": { argTypes: [BIGINT_T, BIGINT_T], result: BOOL }, - "bigint.eqString": { argTypes: [BIGINT_T, STRING], result: BOOL }, - "bigint.cmp": { argTypes: [BIGINT_T, BIGINT_T], result: F64 }, - "bigint.cmpNumber": { argTypes: [BIGINT_T, F64], result: F64 }, - "bigint.truthy": { argTypes: [BIGINT_T], result: BOOL }, - "bigint.toString": { argTypes: [BIGINT_T, F64], result: STRING }, - "bigint.inspect": { argTypes: [BIGINT_T], result: STRING }, - "bigint.toF64": { argTypes: [BIGINT_T], result: F64 }, - "bigint.asUintN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, - "bigint.asIntN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, - "bigint.bufferRead": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, - "bigint.bufferWrite": { argTypes: [BYTES_U8, BIGINT_T, F64, BOOL, BOOL], result: F64 }, - "bigint.dataViewGet": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, - "bigint.dataViewSet": { argTypes: [BYTES_U8, F64, BIGINT_T, BOOL], result: VOID }, - /** node:url + the URL class (scr_url.c). url.new parses one absolute - * URL string into a mutable URL value (+1) — invalid input THROWS a - * catchable TypeError ("Invalid URL"), like Node's constructor. The - * getters (borrowed receiver, +1 string) never throw; url.href doubles - * as toString(). fileURLToPath has one libFn per receiver form (URL - * value / string) — both THROW Node's TypeErrors on non-file schemes, - * encoded slashes, and non-empty hosts. url.pathToFileURL resolves the - * path (getcwd) and never throws. */ - "url.new": { argTypes: [STRING], result: URL_T }, - "url.newBase": { argTypes: [STRING, STRING], result: URL_T }, - /** Factories consume already-converted input/base strings. canParse - * answers a boolean; parse returns a checked native URL or null (+1). - * Parser failures are suppressed; conversion occurs before these calls. */ - "url.canParse": { argTypes: [STRING], result: BOOL }, - "url.canParseBase": { argTypes: [STRING, STRING], result: BOOL }, - "url.parse": { argTypes: [STRING], result: DYN }, - "url.parseBase": { argTypes: [STRING, STRING], result: DYN }, - "url.protocol": { argTypes: [URL_T], result: STRING }, - "url.origin": { argTypes: [URL_T], result: STRING }, - "url.username": { argTypes: [URL_T], result: STRING }, - "url.password": { argTypes: [URL_T], result: STRING }, - "url.host": { argTypes: [URL_T], result: STRING }, - "url.hostname": { argTypes: [URL_T], result: STRING }, - "url.port": { argTypes: [URL_T], result: STRING }, - "url.pathname": { argTypes: [URL_T], result: STRING }, - "url.href": { argTypes: [URL_T], result: STRING }, - /** Selected component writes borrow the receiver and converted string. - * href replaces its parsed fields while retaining cached params identity. */ - "url.set": { argTypes: [URL_T, STRING, STRING], result: VOID }, - "url.setChecked": { argTypes: [URL_T, STRING, DYN], result: VOID }, - "url.fileURLToPathUrl": { argTypes: [URL_T], result: STRING }, - /** fileURLToPathUrl as a value: the path string, or the error it would - * throw (fs.existsSync and fs.promises URL paths). Never throws. */ - "url.fileURLToPathValue": { argTypes: [URL_T], result: DYN }, - /** getValidatedPath's null-byte check on a string path, and - * readFileSync's encoding check: what Node validates before it converts - * a later URL path. (path, argument name) and (encoding). */ - "fs.checkPath": { argTypes: [STRING, STRING], result: VOID }, - "fs.checkReadEncoding": { argTypes: [STRING], result: VOID }, - "url.fileURLToPathChecked": { argTypes: [DYN], result: STRING }, - "url.fileURLToPathOptions": { argTypes: [DYN, DYN], result: STRING }, - /** node:url's legacy API (scr_url_legacy.c): parse(url, parseQueryString, - * slashesDenoteHost) answers the checked-dynamic Url instance; format - * takes the urlObject (a Url-shaped object, a string, or a URL) and the - * URL form's options; resolve takes (from, to). The trailing bool says - * whether the call site emits DEP0169. domainToASCII/domainToUnicode - * take the domain and whether it was omitted. Node's validation errors - * throw catchably. */ - "url.legacyParse": { argTypes: [DYN, DYN, DYN, BOOL], result: DYN }, - "url.legacyFormat": { argTypes: [DYN, DYN, BOOL], result: STRING }, - "url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING }, - "url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING }, - "url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING }, - /** url.urlToHttpOptions over a checked-dynamic argument: a URL answers - * Node's null-prototype options object; a primitive or null throws - * ERR_INVALID_ARG_TYPE. */ - "url.toHttpOptions": { argTypes: [DYN], result: DYN }, - /** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions; - * decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the - * module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the - * module's first load (scr_async_dyn.c) and reports DEP0040 then unless - * the second argument is false: at once for a static ES import, on the - * next tick for a require() call. */ - "punycode.decode": { argTypes: [STRING], result: STRING }, - "punycode.encode": { argTypes: [STRING], result: STRING }, - "punycode.toASCII": { argTypes: [STRING], result: STRING }, - "punycode.toUnicode": { argTypes: [STRING], result: STRING }, - "punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) }, - "punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING }, - "punycode.warn": { argTypes: [BOOL, BOOL], result: VOID }, - "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, - "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T }, - "url.fileURLToPathStr": { argTypes: [STRING], result: STRING }, - "url.pathToFileURL": { argTypes: [STRING], result: URL_T }, - /** Explicit path syntax from the options.windows argument, independent - * of the executable's host platform. Windows UNC input can throw. */ - "url.pathToFileURLPlatform": { argTypes: [STRING, BOOL], result: URL_T }, - /** pathToFileURL under a win32 TARGET: the same scr_url_from_path call - * (the runtime selects the win32 arm by _WIN32), but a distinct IR name - * because that arm THROWS for malformed UNC inputs — may-throw seeds on - * it while posix pathToFileURL emission stays byte-identical. */ - "url.pathToFileURLWin32": { argTypes: [STRING], result: URL_T }, - /** URLSearchParams (scr_url.c — always linked with the url unit). - * Construction: sp.new (empty), sp.parse (one borrowed init string — - * a single leading '?' strips, Node's constructor), sp.copy (snapshot - * of another list), sp.fromPairs (a string[][] value — THROWS Node's - * ERR_INVALID_TUPLE TypeError on a non-[name, value] row; the one - * throwing entry in the family), sp.with (the record-literal init - * desugar: append one pair, answer the same list +1 — chains fold - * `new URLSearchParams({...})` into nested calls). url.searchParams - * answers the URL's LIVE cached view (+1, one identity per URL); - * url.search is the WHATWG search getter ("" for no/empty query). - * Methods mirror the WHATWG surface: sp.get answers +1-or-NULL (the - * sym.desc union pattern, null arm), sp.getAll a fresh string[]; - * sp.append/sp.set/sp.delete/sp.deleteValue/sp.sort mutate and - * re-serialize a live view's URL query; sp.has/sp.hasValue answer - * bools; sp.size/sp.toString are pure reads. sp.keyAt/sp.valAt are - * the for-of/forEach desugar's index reads (live — the loop re-reads - * sp.size each pass). */ - "sp.new": { argTypes: [], result: SEARCH_PARAMS_T }, - "sp.newChecked": { argTypes: [DYN], result: SEARCH_PARAMS_T }, - "sp.parse": { argTypes: [STRING], result: SEARCH_PARAMS_T }, - "sp.copy": { argTypes: [SEARCH_PARAMS_T], result: SEARCH_PARAMS_T }, - // The pairs argument is string[][] — checked structurally in validate.ts (the - // generic array-of slot has no named constant here). - "sp.fromPairs": { argTypes: [null], result: SEARCH_PARAMS_T }, - "sp.with": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: SEARCH_PARAMS_T }, - "url.searchParams": { argTypes: [URL_T], result: SEARCH_PARAMS_T }, - "url.search": { argTypes: [URL_T], result: STRING }, - "url.hash": { argTypes: [URL_T], result: STRING }, - // Result is the interned `string | null` union — the libCall case - // checks the arms (the spawnRes.signal pattern). - "sp.get": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, - "sp.getAll": { argTypes: [SEARCH_PARAMS_T, STRING], result: arrayOf(STRING) }, - "sp.append": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.set": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.delete": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, - "sp.deleteValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.has": { argTypes: [SEARCH_PARAMS_T, STRING], result: BOOL }, - "sp.hasValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: BOOL }, - "sp.sort": { argTypes: [SEARCH_PARAMS_T], result: VOID }, - "sp.size": { argTypes: [SEARCH_PARAMS_T], result: F64 }, - "sp.toString": { argTypes: [SEARCH_PARAMS_T], result: STRING }, - "sp.keyAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, - "sp.valAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, - // node:querystring. qs.parse's result is the call site's ParsedUrlQuery - // dictionary record (VOID is the networkInterfaces sentinel — the - // libCall case checks the structure); qs.stringify's object argument - // is a dyn value (the frontend dynFroms typed records). - /** node:querystring (scr_qs.c — link-gated by moduleUsesQs; NOT - * URLSearchParams: the legacy codec's escaping and '+' rules differ). - * qs.escape is Node's qsEscape, which encodes exactly the component - * unreserved set — it emits the always-linked scr_qs_escape, so - * escape-only programs never pull the unit. qs.unescape is Node's qsUnescape: strict decodeURIComponent - * first, the lenient legacy unescapeBuffer fallback on failure (never - * throws). qs.parse takes (str, sep, eq, maxKeys) — the frontend - * completes omitted/null sep/eq to "&"/"=" and the omitted maxKeys - * option to Node's 1000 (0 and negatives mean unlimited, Node's rule) — - * and its result type is the CALL SITE's mapped ParsedUrlQuery shape (a - * pure index-signature record over `string | string[]`, an undefined - * arm tolerated — @types/node's Dict), verified structurally by the - * frontend (lowerQuerystringParseCall); the emitters construct the - * record and hand its overflow map to scr_qs_parse_into with the two - * union tags. qs.stringify takes (obj, sep, eq) with obj a dyn value - * (the frontend dynFroms the typed record; JS-world dyn values pass - * through) — Node's encodeStringified rules run in the runtime, so - * arrays expand to repeated keys and null/undefined/nested objects are - * empty values. Custom encoder/decoder options fence at compile time. - * All borrow; string results +1; escape and stringify throw URIError - * [ERR_INVALID_URI] on a lone surrogate. */ - "qs.parse": { argTypes: [STRING, STRING, STRING, F64], result: VOID }, - "qs.stringify": { argTypes: [DYN, STRING, STRING], result: STRING }, - "qs.escape": { argTypes: [STRING], result: STRING }, - "qs.unescape": { argTypes: [STRING], result: STRING }, - /** node:util.parseArgs: one checked-dynamic config object in, one - * checked-dynamic ParsedResults tree out. Statically typed member reads - * validate their values when leaving that tree. The native parser may - * throw Node's coded validation/grammar TypeErrors. */ - "util.parseArgs": { argTypes: [DYN], result: DYN }, - "util.parseEnv": { argTypes: [DYN], result: DYN }, - "util.getSystemErrorName": { argTypes: [DYN], result: STRING }, - "util.getSystemErrorMessage": { argTypes: [DYN], result: STRING }, - "util.systemErrorEntries": { argTypes: [], result: DYN }, - "util.stripVTControlCharacters": { argTypes: [DYN], result: STRING }, - "util.toUSVString": { argTypes: [DYN], result: STRING }, - "util.isDeepStrictEqual": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "util.styleText": { argTypes: [DYN, DYN, DYN], result: STRING }, - /** util.inherits/deprecate/callbackify/debuglog over checked-dynamic - * arguments (scr_util_callable.c). The function results are native dyn - * wrappers; call sites with a static function type check them back. */ - "util.inherits": { argTypes: [DYN, DYN], result: VOID }, - "util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID }, - "util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - "util.callbackify": { argTypes: [DYN], result: DYN }, - /** util.promisify over an untyped function value: a native dyn function - * whose calls answer a dyn promise the appended callback settles. */ - "util.promisifyDyn": { argTypes: [DYN], result: DYN }, - "util.debuglog": { argTypes: [DYN, DYN], result: DYN }, - "util.typeIs": { argTypes: [DYN, STRING], result: BOOL }, - "util.typeValue": { argTypes: [DYN, STRING], result: DYN }, - /** fs.statSync → a Stats value (may throw, like the other sync fs - * calls); the stats.* getters are pure reads on it. */ - "fs.statSync": { argTypes: [STRING], result: STATS_T }, - "fs.lstatSync": { argTypes: [STRING], result: STATS_T }, - // statSync / lstatSync(path, { throwIfNoEntry }): arg 1 is the flag. - // Result is the interned `Stats | undefined` union (the runtime answers - // +1-or-NULL; the backend builds the arms). - "fs.statProbeSync": { argTypes: [STRING, BOOL], result: VOID }, - "fs.lstatProbeSync": { argTypes: [STRING, BOOL], result: VOID }, - "fs.fstatSync": { argTypes: [F64], result: STATS_T }, - "fs.fchmodSync": { argTypes: [F64, F64], result: VOID }, - "fs.fsyncSync": { argTypes: [F64], result: VOID }, - "fs.fdatasyncSync": { argTypes: [F64], result: VOID }, - "fs.ftruncateSync": { argTypes: [F64, F64], result: VOID }, - "fs.utimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.futimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.lutimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.readvSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - "fs.writevSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - "fs.linkSync": { argTypes: [DYN, DYN], result: VOID }, - "fs.symlinkSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - /** fs.cpSync / fs/promises cp (scr_fs_cp.c): src, dest and options are - * dyn values the runtime validates in Node's order. */ - "fs.cpSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.readlinkSync": { argTypes: [DYN, DYN], result: DYN }, - "fs.statfsSync": { argTypes: [DYN, DYN], result: DYN }, - "fs.readlinkSyncStr": { argTypes: [DYN, DYN], result: STRING }, - "fs.readlinkSyncBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, - /** fs.openSync(path, flags) → the raw fd as f64; fs.readSync/fs.writeSync - * over Buffer windows perform sequential I/O when position is -1 and - * offset-preserving positioned I/O otherwise; fs.writeStrSync is the - * utf8 string twin; and fs.closeSync(fd) closes the descriptor. This is - * the fd slice behind spawn/log-processing forms. - * flags is Node's string - * grammar ("r", "w", "a" and the +/x variants; unknown flags throw - * Node's ERR_INVALID_ARG_VALUE TypeError text). All throw Node-shaped - * fs errors (openSync ENOENT/EACCES..., readSync EBADF/range errors, - * closeSync EBADF). */ - "fs.openSync": { argTypes: [STRING, STRING], result: F64 }, - "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 }, - "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - "fs.readSyncDyn": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: F64 }, - "fs.writeSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - "fs.writeStrSync": { argTypes: [F64, STRING, F64, STRING], result: F64 }, - // fs.watch's callback func type is program-dependent (zero params, or - // the eventType string) — the slot pins arity and the path/receiver. - /** fs.watch(path, listener?) → an FSWatcher handle (scr_watch.c — - * linked, and scr_watch_install() emitted, only when these appear on - * the IR; moduleUsesFsWatch is the switch). The path opens NOW — - * failure throws Node's fs error synchronously ("ENOENT: ..., watch - * 'x'", the polling-fallback catch shape) — and the unit's kqueue - * (EVFILT_VNODE) delivers "rename"/"change" through the loop's watch - * hook. The callback (nullable) MOVES in with an adapter per listener - * shape (zero-param, or the (eventType: string) form — runtime- - * provided); an open watcher keeps the loop alive until watcher.close() - * (idempotent, receiver borrowed, statement position only). */ - "fs.watch": { argTypes: [STRING], result: FSWATCHER_T }, - "fs.watchCb": { argTypes: [STRING, null], result: FSWATCHER_T }, - "watcher.close": { argTypes: [FSWATCHER_T], result: VOID }, - /** The composed `new crypto.X509Certificate(data).fingerprint` read - * (scr_lib.c — no certificate handle exists): the SHA-1 of the DER, - * uppercase colon-separated, over PEM or raw-DER Buffer input; other - * inputs throw Node's ERR_OSSL_PEM_NO_START_LINE Error (may-throw). */ - "crypto.x509Fingerprint": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509FingerprintStr": { argTypes: [STRING], result: STRING }, - /** The certificate's Validity window (validFrom / validTo reads — - * scr_lib.c's minimal ASN.1 walk to the TBSCertificate validity - * SEQUENCE): UTCTime and GeneralizedTime render in Node's exact - * ASN1_TIME_print shape ("Jul 1 00:00:00 2026 GMT" — %2d space-padded - * day). Same input contract and PEM error as the fingerprint pair - * (may-throw). */ - "crypto.x509ValidFrom": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509ValidFromStr": { argTypes: [STRING], result: STRING }, - "crypto.x509ValidTo": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509ValidToStr": { argTypes: [STRING], result: STRING }, - /** The certificate's DER as a fresh Buffer (the raw field), decoded - * once from the constructor's input; the same PEM error (may-throw). */ - "crypto.x509Raw": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "crypto.x509RawDer": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "crypto.x509RawStr": { argTypes: [STRING], result: BYTES_U8 }, - /** The remaining X509Certificate fields over that DER, in Node's - * renderings (scr_lib.c). subjectAltName, infoAccess and - * signatureAlgorithm answer "" where Node answers undefined. Borrowed; - * never throw. */ - "crypto.x509Subject": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Issuer": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SubjectAltName": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509InfoAccess": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SerialNumber": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Fingerprint256": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Fingerprint512": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Ca": { argTypes: [BYTES_U8], result: BOOL }, - "crypto.x509SignatureAlgorithm": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SignatureAlgorithmOid": { argTypes: [BYTES_U8], result: STRING }, - /** X509Certificate methods over its raw DER. checkHost/checkEmail/checkIP - * answer the matching name or "" (undefined), checkIssued compares two - * certificates, x509Pem is toString's PEM (scr_x509.c); publicKey, - * verify and checkPrivateKey read keys (scr_pki.c). */ - "crypto.x509CheckHost": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckEmail": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckIP": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckIssued": { argTypes: [BYTES_U8, BYTES_U8], result: BOOL }, - "crypto.x509LegacyObject": { argTypes: [BYTES_U8], result: DYN }, - "crypto.x509Pem": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509PublicKey": { argTypes: [BYTES_U8], result: DYN }, - "crypto.x509Verify": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "crypto.x509CheckPrivateKey": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "fs.closeSync": { argTypes: [F64], result: VOID }, - "stats.isFile": { argTypes: [STATS_T], result: BOOL }, - "stats.isDirectory": { argTypes: [STATS_T], result: BOOL }, - "stats.isSymbolicLink": { argTypes: [STATS_T], result: BOOL }, - "stats.size": { argTypes: [STATS_T], result: F64 }, - "stats.dev": { argTypes: [STATS_T], result: F64 }, - "stats.ino": { argTypes: [STATS_T], result: F64 }, - "stats.blocks": { argTypes: [STATS_T], result: F64 }, - "stats.nlink": { argTypes: [STATS_T], result: F64 }, - "stats.atimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.mtimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.ctimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.birthtimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.mode": { argTypes: [STATS_T], result: F64 }, - "stats.uid": { argTypes: [STATS_T], result: F64 }, - "stats.gid": { argTypes: [STATS_T], result: F64 }, - "stats.rdev": { argTypes: [STATS_T], result: F64 }, - "stats.blksize": { argTypes: [STATS_T], result: F64 }, - "stats.isBlockDevice": { argTypes: [STATS_T], result: BOOL }, - "stats.isCharacterDevice": { argTypes: [STATS_T], result: BOOL }, - "stats.isFIFO": { argTypes: [STATS_T], result: BOOL }, - "stats.isSocket": { argTypes: [STATS_T], result: BOOL }, - /** The Date fields: the *Ms value rounded (Node's dateFromMs). */ - "stats.atime": { argTypes: [STATS_T], result: DATE_T }, - "stats.mtime": { argTypes: [STATS_T], result: DATE_T }, - "stats.ctime": { argTypes: [STATS_T], result: DATE_T }, - "stats.birthtime": { argTypes: [STATS_T], result: DATE_T }, - // The wider sync fs slice (unlink/chmod/chown/copyfile and the - // mode-carrying write/mkdir forms). - /** The wider sync fs slice (scr_lib.c), all throwing catchably with - * Node's errno message shapes and `.code` stamped like the rest of - * sync fs. unlink/chmod/chown wrap the syscalls 1:1 (Node reports the - * syscall's own name). copyFileSync copies contents into a fresh (or - * truncated) destination carrying the SOURCE's mode (libuv's - * uv_fs_copyfile behavior); its errors carry BOTH paths — Node's - * "copyfile 'src' -> 'dest'". lstatSync is statSync without following - * a trailing symlink (Node reports lstat); stats.isSymbolicLink / - * stats.dev / ino / blocks / nlink / atimeMs / mtimeMs / ctimeMs are pure reads on the widened - * snapshot (blocks is allocated 512-byte units; the times are milliseconds - * with their sub-second fractions, Node's arithmetic). - * writeFileModeSync is writeFileSync(path, data, { mode }): the mode - * applies at CREATION only (open(2) with O_CREAT, umask applying), - * exactly Node — an existing file keeps its permissions. mkdirModeSync - * / mkdirRecursiveModeSync are the mkdirSync option forms with an - * explicit mode (the recursive walk passes it to every directory it - * creates, like Node's). */ - "fs.unlinkSync": { argTypes: [STRING], result: VOID }, - "fs.chmodSync": { argTypes: [STRING, F64], result: VOID }, - "fs.chownSync": { argTypes: [STRING, F64, F64], result: VOID }, - "fs.lchownSync": { argTypes: [STRING, F64, F64], result: VOID }, - "fs.fchownSync": { argTypes: [F64, F64, F64], result: VOID }, - /** truncateSync opens the path with 'r+', then truncates (len checked and - * clamped like ftruncateSync's) and closes it. */ - "fs.truncateSync": { argTypes: [STRING, F64], result: VOID }, - "fs.copyFileSync": { argTypes: [STRING, STRING], result: VOID }, - /** renameSync is the direct two-path syscall wrapper. renameCb defers - * the operation to the loop and fires a program-shaped Error | null - * callback through a backend-emitted adapter. */ - "fs.renameSync": { argTypes: [STRING, STRING], result: VOID }, - // Callback type is program-dependent (zero params or Error | null). - "fs.renameCb": { argTypes: [STRING, STRING, null], result: VOID }, - "fs.writeFileModeSync": { argTypes: [STRING, STRING, F64], result: VOID }, - "fs.mkdirModeSync": { argTypes: [STRING, F64], result: VOID }, - "fs.mkdirRecursiveModeSync": { argTypes: [STRING, F64], result: VOID }, - "atomics.wait": { argTypes: [bytesOf("i32"), F64, F64, F64], result: STRING }, - "atomics.notify": { argTypes: [bytesOf("i32"), F64, F64], result: F64 }, - "atomics.op": { argTypes: [null, F64, F64, F64, F64], result: F64 }, - "atomics.opDyn": { argTypes: [DYN, F64, F64, F64, F64], result: F64 }, - "atomics.waitDyn": { argTypes: [DYN, F64, F64, F64], result: STRING }, - "atomics.notifyDyn": { argTypes: [DYN, F64, F64], result: F64 }, - "worker.new": { argTypes: [F64, STRING, DYN], result: DYN }, - "worker.isMainThread": { argTypes: [], result: BOOL }, - "worker.threadId": { argTypes: [], result: F64 }, - "worker.root": { argTypes: [], result: F64 }, - "worker.data": { argTypes: [], result: DYN }, - "worker.parentPort": { argTypes: [], result: DYN }, - /** worker_threads.resourceLimits: {} in the main thread; a refusal in a - * worker (scr_worker_events.c). */ - "worker.resourceLimits": { argTypes: [], result: DYN }, - /** worker_threads.threadName: the Worker's name in a worker, "" in the - * main thread (scr_worker.c). */ - "worker.threadName": { argTypes: [], result: DYN }, - /** new MessageChannel() ({ port1, port2 }) and receiveMessageOnPort(port) - * (scr_worker_events.c; the latter may throw). */ - "worker.channel": { argTypes: [], result: DYN }, - "worker.receiveMessageOnPort": { argTypes: [DYN], result: DYN }, - /** setEnvironmentData(key, value) and getEnvironmentData(key) - * (scr_worker_events.c; arguments borrowed). */ - "worker.setEnvironmentData": { argTypes: [DYN, DYN], result: VOID }, - "worker.getEnvironmentData": { argTypes: [DYN], result: DYN }, - /** markAsUntransferable(value) and isMarkedAsUntransferable(value) - * (scr_worker_events.c; the value borrowed). */ - "worker.markUntransferable": { argTypes: [DYN], result: VOID }, - "worker.isMarkedUntransferable": { argTypes: [DYN], result: BOOL }, - /** markAsUncloneable(value) (scr_json.c; the value borrowed). */ - "dyn.markUncloneable": { argTypes: [DYN], result: VOID }, - /** before new Worker() with the stdout or stderr option: links the stream - * unit that worker.stdout and worker.stderr read through (scr_stream.c). */ - "stream.workerStdio": { argTypes: [], result: VOID }, - /** new BroadcastChannel(...args) (scr_worker_events.c; may throw), after - * event.broadcastInstall, which installs its EventTarget side. */ - "worker.broadcastChannel": { argTypes: [DYN], result: DYN }, - "event.broadcastInstall": { argTypes: [], result: VOID }, - /** child_process.spawnSync (scr_child.c): posix_spawn + waitpid + piped - * utf8 capture — cmd borrowed, args one borrowed string[] (the frontend - * completes an omitted list to an empty literal), result an owned (+1) - * spawnRes. NEVER throws: spawn failure (nonexistent binary, EACCES) is - * data, like Node's error property — status null and empty outputs - * (SEMANTICS.md documents the divergence from Node's null stdout). - * The getters are pure reads: status is the interned `number | null` - * union (null = spawn failure or signal death, type-directed - * construction in the backend like process.envGet); stdout/stderr are - * +1 strings. */ - "cp.spawnSync": { argTypes: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, - // spawnSync's options form: timeout, killSignal name ("" = SIGTERM), - // and the three stdio modes. - /** spawnSync with options (scr_child.c): the cp.spawnSync core plus the - * option slice portless-class CLIs pass — timeout (killSignal fires at - * the deadline and the result carries error: ETIMEDOUT + the signal, - * Node's shape), killSignal (a signal NAME; "" = the SIGTERM default), - * and per-fd stdio modes (in: 0 /dev/null, 1 ignore, 2 inherit; out/ - * err: 0 capture, 1 ignore → "", 2 inherit → ""). NEVER throws — spawn - * failure and timeout are data on the result, like Node's error - * property. spawnRes.signal is the result's termination signal as the - * call site's `Signals | null` union (null = exited normally or spawn - * failure), constructed type-directedly like spawnRes.status. */ - "cp.spawnSyncOpts": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64], - result: SPAWNRES_T, - }, - /** cp.spawnSyncOpts with the `env` option: the trailing [k,v,...] - * pairs replace the child's environment (recordToEnvPairs). */ - "cp.spawnSyncEnv": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64, arrayOf(STRING)], - result: SPAWNRES_T, - }, - /** cp.spawnSyncOpts with the stdio carried as a RUNTIME string — - * "pipe" | "ignore" | "inherit", proven by the call site's TYPE (the - * defaultRunner idiom `stdio: options?.stdio ?? "pipe"`); the runtime - * maps the value to the three modes. Args (cmd, argv, timeout, - * killSignal, stdio). Never throws, like the other spawnSync forms. */ - "cp.spawnSyncStdioStr": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, STRING], - result: SPAWNRES_T, - }, - // node:net (scr_net.c). Listener slots are program-dependent closures — - // null slots, checked in the libCall case (the child.onExit precedent); - // the zero-param callbacks pin the exact func type here. The trailing - // BOOL on registrations is the once flag. - /** node:net (scr_net.c — linked, and scr_net_install() emitted, only - * when one of these appears on the IR; moduleUsesNet is the switch). - * Receivers are borrowed; CALLBACKS MOVE into the handle's listener - * registry and are released at settlement (the child.onExit story). - * All listener registrations carry a trailing BOOL once-flag (`on` vs - * `once`) and are void — chaining is fenced. None of these throw: - * listen/connect failures are the async 'error' event, like Node. - * - * net.createServer's optional connection handler and the - * serverOnConnection listeners take the runtime-provided adapters - * (zero-param, or the one-param socket shape); sockOnData's adapters - * are the stdin pair's shapes (zero-param / bytes chunk); the error - * events reuse the child %Error adapters. net.listen/net.listenCb bind - * NOW and defer 'listening' to the next loop turn (Node's next-tick - * emit); net.serverPort is the composed `server.address().port` read. - * net.connect's host argument is a string ("localhost" pins to - * 127.0.0.1 — SEMANTICS.md); the connect callback is once('connect'). */ - "net.createServer": { argTypes: [], result: NETSERVER_T }, - "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, - "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, - /** listen(path[, cb]): a Unix-socket server; the socket file is removed - * when the server closes. Failures are the async 'error'. */ - "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, - "net.listenPathCb": { - argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.listenCb": { - argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - /** listen({ port, host, ipv6Only }[, cb]) — the explicit-interface bind - * (portless's listenOnProxyInterface): args [server, port, host, - * ipv6Only]. host is an IP literal string ("" = the host-less - * dual-stack any default); ipv6Only sets IPV6_V6ONLY before the bind. - * These are the old-shaped calls, retained for omitted reusePort so - * serialized IR remains compatible. Failures are the async 'error', - * message in Node's listen shape with the requested host. Never throws. */ - "net.listenOpts": { argTypes: [NETSERVER_T, F64, STRING, BOOL], result: VOID }, - // The callback slot is a zero-param void closure OR its - // `(() => void) | undefined` optional-binding union (checked specially). - "net.listenOptsCb": { argTypes: [NETSERVER_T, F64, STRING, BOOL, null], result: VOID }, - /** Additive ABI for listen({ ..., reusePort }) — args - * [server, port, host, ipv6Only, reusePort], with the callback following - * those values in the callback form. The final BOOL is the requested - * kernel connection-distribution option; old spellings remain - * four/five-argument calls so serialized IR stays compatible. */ - "net.listenOptsReusePort": { argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL], result: VOID }, - // The callback is a zero-param void closure OR its optional-binding union; - // the callback follows the reusePort BOOL in this additive ABI. - "net.listenOptsReusePortCb": { - argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL, null], - result: VOID, - }, - "net.serverPort": { argTypes: [NETSERVER_T], result: F64 }, - "net.serverListening": { argTypes: [NETSERVER_T], result: BOOL }, - /** socket.ref()/unref() and server.ref()/unref(): whether the handle - * keeps the event loop alive. */ - "net.sockSetRef": { argTypes: [NETSOCKET_T, BOOL], result: VOID }, - "net.serverSetRef": { argTypes: [NETSERVER_T, BOOL], result: VOID }, - // net.serverAddress's record result is shape-checked in the libCall case - // (the dgram.address sentinel pattern). - /** server.address() as the full AddressInfo record (the dgram.address - * materialization pattern: the emitter builds the record from the three - * runtime reads; the frontend pinned the shape). Never throws — before - * listen it answers the any-form defaults with port 0 where Node - * answers null (the serverPort stance). */ - "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, - /** server.close((err) => ...): the callback (a dyn function) gets - * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING - * when it was not listening. */ - "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, - /** server.address() in programs that may listen on a Unix socket: the - * path a listening server answers ("" for a TCP server), and the check - * a record-typed address() makes first (throws for a Unix server). */ - "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, - "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverCloseCb": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.serverOnError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "net.serverOnClose": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.serverOnConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** 'secureConnection' — the TLS server's deferred 'connection' list - * (handshake-completion timing); on a server without deferred - * connections (a plain net server) the registration never fires, - * exactly Node's split. */ - "net.serverOnSecureConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "net.connect": { argTypes: [F64, STRING], result: NETSOCKET_T }, - /** connect with a validated autoSelectFamilyAttemptTimeout option (the - * budget runs Node's validateInt32-from-1 ladder and is then inert — - * the single dial has nothing to time). May-throw. */ - "net.connectAttempt": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - /** net.connect/createConnection over a RUNTIME option bag (computed - * keys — the invalid-input probes): Node-order validation (the - * objectMode trio's ERR_INVALID_ARG_VALUE, validatePort, host string, - * autoSelectFamily boolean, the attempt budget), then the trailing - * compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow - * polymorphic-result carve-out). May-throw seed. */ - "net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID }, - "net.connectCb": { - argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }], - result: NETSOCKET_T, - }, - // The lookup's exact func shape is program data (its answer callback's - // union/record types) — checked specially in the libCall case. - /** connect({ port, host, autoSelectFamily: true, lookup }) — the - * caller-resolver dial (portless's createLoopbackConnection): args - * [port, host, lookup]. The runtime creates the (connecting) socket - * handle, invokes the lookup as Node does — lookup(hostname, options, - * callback), options crossing as the dyn undefined — and the answer - * closure (an emitter-synthesized per-shape thunk over a boxed socket, - * the SNI-answer pattern) dials the answered addresses IN ORDER: each - * connect failure tries the next, the LAST failure's message is the - * socket's 'error' (Node's autoSelectFamily aggregate is a documented - * divergence), and a lookup error surfaces as the deferred socket - * 'error'. A synchronous throw INSIDE the lookup propagates out of the - * connect call (may-throw seed). */ - "net.connectLookup": { argTypes: [F64, STRING, null], result: NETSOCKET_T }, - /** connect({ host, port, lookup }) with a lookup of Node's own type, as - * a checked-dynamic function: the runtime calls it with { hints, all: - * true } and a callback taking an address string or list, then dials - * the answers in order. MAY THROW (host validation, a lookup throw). */ - "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, - "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a - * typed socket): the runtime dispatches STR/BYTES and throws Node's - * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ - "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, - "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, - /** socket.pipe(res) — raw socket chunks into a ServerResponse body - * (the extended-CONNECT bridge leg): each chunk is a body write (the - * response's own framing applies); source EOF end()s the response, - * pipe's default. Borrows both. Never throws. */ - "net.sockPipeRes": { argTypes: [NETSOCKET_T, HTTPRES_T], result: VOID }, - "net.sockOnData": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - "net.sockOnEnd": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockOnClose": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockOnError": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - "net.sockOnConnect": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - // node:dgram + node:dns (scr_dgram.c). The message/error listeners and - // dns.lookup's callback are program-dependent closures (null slots, - // checked in the libCall case); dgram.address's record result is - // program-dependent too (VOID here is the envGet sentinel — the libCall - // case checks the {address, family, port} shape). - /** node:dgram + node:dns (scr_dgram.c — linked, and - * scr_dgram_install() emitted, only when one of these appears on the - * IR; moduleUsesDgram is the switch — dns.lookup rides the same unit). - * The net listener discipline verbatim: receivers borrowed, CALLBACKS - * MOVE into the handle's registry and release at settlement, `on` vs - * `once` is the trailing bool, registrations are void. bind/connect - * bind NOW and defer 'listening'/'connect' to the next loop turn (the - * net.listen story); their optional-host completions are "" - * (bind → 0.0.0.0) and "127.0.0.1" (connect — udp4's Node default). - * bind/connect/send/close/address THROW Node's state errors ("Socket - * is already bound", "Already connected", "Not running") — may-throw - * seeded. dgram.address returns the AddressInfo RECORD (the frontend - * pins the {address, family, port} shape; the emitter builds it from - * runtime parts). onMessage adapters are emitted per rinfo record - * shape (the child.onExit precedent); onError reuses the child %Error - * adapters. dns.lookup resolves via getaddrinfo AT CALL TIME and - * defers the callback to the next turn (SEMANTICS.md documents the - * blocking divergence); its per-union adapter builds the - * `Error | null` first argument. */ - "dgram.createSocket": { argTypes: [BOOL], result: DGRAMSOCK_T }, - "dgram.bind": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, - "dgram.bindCb": { - argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.connect": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, - "dgram.connectCb": { - argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.sendStr": { argTypes: [DGRAMSOCK_T, STRING, F64, STRING], result: VOID }, - "dgram.sendBytes": { argTypes: [DGRAMSOCK_T, BYTES_U8, F64, STRING], result: VOID }, - /** Connected AF_UNIX datagram sockets for the hot-shots package shim - * (the unix-dgram addon's surface): fd or result >= 0, -errno on - * failure; send answers 1 when the socket would block. */ - "dgram.unixSocket": { argTypes: [], result: F64 }, - "dgram.unixConnect": { argTypes: [F64, STRING], result: F64 }, - "dgram.unixSend": { argTypes: [F64, BYTES_U8], result: F64 }, - "dgram.unixClose": { argTypes: [F64], result: F64 }, - /** The send argument-validation ladder over dyn arguments (Node's - * signature shuffle: slice bounds, list/type contracts, port/address - * validation, connected-state errors), then the send: lists, connected - * sends and completion callbacks included. Up to six arguments, the - * missing ones undefined. May-throw. */ - "dgram.sendChk": { argTypes: [DGRAMSOCK_T, DYN, DYN, DYN, DYN, DYN, DYN], result: VOID }, - "dgram.address": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.close": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.closeCb": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.unref": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.ref": { argTypes: [DGRAMSOCK_T], result: VOID }, - /** The socket options (scr_dgram.c): the membership calls take the - * interface and whether it was given. May-throw. */ - "dgram.setBroadcast": { argTypes: [DGRAMSOCK_T, BOOL], result: VOID }, - "dgram.setTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, - "dgram.setMulticastTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, - "dgram.setMulticastLoopback": { argTypes: [DGRAMSOCK_T, BOOL], result: BOOL }, - "dgram.setMulticastInterface": { argTypes: [DGRAMSOCK_T, STRING], result: VOID }, - "dgram.addMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, - "dgram.dropMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, - "dgram.getRecvBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.getSendBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.setRecvBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, - "dgram.setSendBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, - "dgram.getSendQueueSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.getSendQueueCount": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.disconnect": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.onMessage": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, - "dgram.onError": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, - "dgram.onListening": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dgram.onClose": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dgram.onConnect": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dns.lookup": { argTypes: [STRING, F64, null], result: VOID }, - /** Node's argument forms with dyn callbacks: lookup(hostname, options, - * callback) and lookupService(address, port, callback). */ - "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - /** node:dns/promises: lookup(hostname, options) and lookupService(address, - * port) over the callback forms; the promise fulfills with the dyn - * { address, family } (array for all: true) / { hostname, service }. - * Argument errors throw synchronously (may-throw), unlike the - * util.promisify forms below. */ - "dns.promisesLookup": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.promisesLookupService": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.getServers": { argTypes: [], result: DYN }, - "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, - "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, - // node:test (scr_test.c). Bodies are program-dependent closures (0 or - // 1 testCtx param, void or Promise result — the spoke pinned the - // shape): null slots. sub's result is the settled Promise the - // await consumes. - /** node:test (scr_test.c — linked only when one of these appears on - * the IR; moduleUsesNodeTest is the switch, and the main epilogue asks - * scr_test_exit_code() for the process's exit status). Strings are - * BORROWED, callbacks MOVE. register/suite/hook attach to the runner - * tree (register: name, mode 0|1|2 run/skip/todo, directive message "" - * = none, cb or absent via registerEmpty, flags 1 async | 2 takes-ctx - * | 4 only, "file:line:col"); suite runs its body AT registration - * (Node's collection phase). sub is t.test — runs the subtest INLINE - * on the runner fiber and returns the settled promise the await - * consumes. ctxSkip/ctxTodo mark the running test; ctxDiagnostic - * queues an ℹ line; ctxName reads t.name. Every registration keeps - * the loop-run emitted (usesTimers) so the runner fiber drains. */ - // The trailing three are the { plan, timeout, concurrency } options (plan - // -1 without one; timeout read under flag 8; concurrency read under flag - // 16, -1 for true). An out-of-range timeout or concurrency throws. - "test.register": { - argTypes: [STRING, F64, STRING, null, F64, STRING, F64, F64, F64], - result: VOID, - }, - "test.registerEmpty": { - argTypes: [STRING, F64, STRING, F64, STRING, F64, F64, F64], - result: VOID, - }, - // The suite body takes no parameters or its SuiteContext (flag 2). - "test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID }, - // The trailing string is the hook call's "file:line:col". - "test.hook": { argTypes: [F64, null, F64, STRING], result: VOID }, - "test.sub": { - argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - "test.subEmpty": { - argTypes: [TESTCTX_T, STRING, F64, STRING, F64, STRING, F64, F64, F64], - result: VOID, - }, - "test.ctxSkip": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxTodo": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxDiagnostic": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxName": { argTypes: [TESTCTX_T], result: STRING }, - "test.ctxFullName": { argTypes: [TESTCTX_T], result: STRING }, - // t.passed, and t.workerId's Number(NODE_TEST_WORKER_ID) with 0 for - // undefined. - "test.ctxPassed": { argTypes: [TESTCTX_T], result: BOOL }, - "test.workerId": { argTypes: [], result: F64 }, - // getTestContext(): whether a test, suite or hook is running in the - // current async context, and that context. - "test.hasContext": { argTypes: [], result: BOOL }, - "test.context": { argTypes: [], result: TESTCTX_T }, - // t.before/t.after/t.beforeEach/t.afterEach (which: 0 before, 1 after, - // 2 beforeEach, 3 afterEach); a throwing t.before is captured on the test. - "test.ctxHook": { argTypes: [TESTCTX_T, F64, null, F64], result: VOID }, - // t.plan(count), and the count of one t.assert call. - "test.ctxPlan": { argTypes: [TESTCTX_T, F64], result: VOID }, - "test.ctxCount": { argTypes: [TESTCTX_T], result: VOID }, - // The mock trackers: the module's `mock` and t.mock keep each mock's - // restore closure (moved) for restoreAll and reset. - "test.mockTrack": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - "test.mockRestoreAll": { argTypes: [], result: VOID }, - "test.mockReset": { argTypes: [], result: VOID }, - "test.ctxMockTrack": { - argTypes: [TESTCTX_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "test.ctxMockRestoreAll": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockReset": { argTypes: [TESTCTX_T], result: VOID }, - // mock.timers on the module's `mock` and on t.mock. enable takes one - // code per listed API ('T' setTimeout, 'I' setInterval), the `now` - // value and its flags (1 given, 2 from a Date). enable, tick, runAll and - // setTime throw Node's errors; tick and runAll run timer callbacks. - // t.assert.snapshot and fileSnapshot: the escaped, wrapped serialized - // value, and the stored value it is compared with (snapshot: the test, - // the value, the snapshot file; fileSnapshot: the value, the path). - // Both read or, in update mode, record; missing and unreadable - // snapshots throw Node's ERR_INVALID_STATE. - "test.snapshotValue": { argTypes: [STRING], result: STRING }, - "test.ctxSnapshot": { argTypes: [TESTCTX_T, STRING, STRING], result: STRING }, - "test.fileSnapshot": { argTypes: [STRING, STRING], result: STRING }, - // Node's serializeValue over a dyn array of serializers (undefined for - // the default JSON.stringify(value, null, 2)); a throw becomes Node's - // "The provided serializers did not generate a string." ERR_INVALID_STATE. - "test.snapshotSerialize": { argTypes: [DYN, DYN], result: DYN }, - "test.mockTimersEnable": { argTypes: [STRING, F64, F64], result: VOID }, - "test.mockTimersTick": { argTypes: [F64], result: VOID }, - "test.mockTimersRunAll": { argTypes: [], result: VOID }, - "test.mockTimersReset": { argTypes: [], result: VOID }, - "test.mockTimersSetTime": { argTypes: [F64], result: VOID }, - "test.ctxMockTimersEnable": { argTypes: [TESTCTX_T, STRING, F64, F64], result: VOID }, - "test.ctxMockTimersTick": { argTypes: [TESTCTX_T, F64], result: VOID }, - "test.ctxMockTimersRunAll": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockTimersReset": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockTimersSetTime": { argTypes: [TESTCTX_T, F64], result: VOID }, - // node:http (scr_http.c over scr_net.c). The handler and the data - // listeners are program-dependent closures (null slots, checked in the - // libCall case); reqHeader's result is the interned string|undefined - // union, checked like process.envGet. - /** node:http, the server slice (scr_http.c over scr_net.c — linked - * only when these appear on the IR; moduleUsesHttpServer is the - * switch, and any http.* libCall also counts as net use so scr_net.c - * links and installs). http.createServer's handler MOVES in and takes - * the runtime adapters for its (req, res) / (req) / () shapes; req - * body listeners follow the net.sockOnData story (bytes chunks, once - * flags); reqHeader answers the interned `string | undefined` union - * exactly like process.envGet; the res writers are borrowed-argument - * voids with Node's framing decided in the runtime (Content-Length - * for end-before-head, chunked after an explicit writeHead/write). */ - "http.createServer": { argTypes: [null], result: NETSERVER_T }, - /** http.createServer() / http.Server() with no handler — the - * on("request") route; option helpers set parser behavior on a fresh - * server before the constructor/factory result is returned. */ - "http.createServerEmpty": { argTypes: [], result: NETSERVER_T }, - "http.validateHeaderName": { argTypes: [STRING, STRING], result: VOID }, - "http.validateHeaderValue": { argTypes: [STRING, DYN], result: VOID }, - "http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID }, - /** The five writable numeric http.Server timeout fields use one - * selector ABI: 0 timeout, 1 keepAliveTimeout, 2 headersTimeout, - * 3 requestTimeout, 4 keepAliveTimeoutBuffer. These calls store/read - * the property values; timer enforcement remains outside this surface. - * The constructor-option setter takes dyn so explicit undefined remains - * distinguishable from a numeric value until its Node validation ladder. */ - "http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 }, - "http.serverSetTimeout": { argTypes: [NETSERVER_T, F64], result: VOID }, - "http.serverSetTimeoutCb": { argTypes: [NETSERVER_T, F64, null], result: VOID }, - "http.serverOnTimeout": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http.serverCloseAllConnections": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverCloseIdleConnections": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverTimeoutSet": { argTypes: [NETSERVER_T, F64, F64], result: VOID }, - "http.serverTimeoutOptionSet": { argTypes: [NETSERVER_T, F64, DYN], result: VOID }, - /** server.on("listening", cb) — the deferred listen-callback list. */ - "net.serverOnListening": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** The ServerResponse member surface: statusCode/statusMessage reads - * and assignments (Node's writable properties), the header CRUD trio - * (getHeader answers `string | undefined` like reqHeader), and - * end(cb)'s callback slot (resOnEndCallback registers, the end call - * follows — the callback fires deferred, just before Node's 'finish' - * emit; resOnFinish is the 'finish' listener slot). writeHead's - * statusMessage forms compose in interned helpers: resStatusMsgSet - * then the ordinary writeHead entry. */ - "http.resStatusGet": { argTypes: [HTTPRES_T], result: F64 }, - "http.resStatusSet": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resStatusMsgGet": { argTypes: [HTTPRES_T], result: VOID }, - "http.resStatusMsgSet": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resRequest": { argTypes: [HTTPRES_T], result: HTTPREQ_T }, - "http.resSocket": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWritableFinished": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resSendDateGet": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resSendDateSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, - "http.resStrictContentLengthGet": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resStrictContentLengthSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, - "http.resSetTimeout": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resSetTimeoutCb": { - argTypes: [HTTPRES_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - // resGetHeader answers the interned `string | undefined` union — the - // reqHeader/envGet sentinel pattern (VOID here, checked specially). - "http.resGetHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, - /** getHeader as Node answers it: the number, string or string[] that was - * set, or undefined, as a checked-dynamic value. */ - "http.resGetHeaderDyn": { argTypes: [HTTPRES_T, STRING], result: DYN }, - "http.resGetHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, - "http.resGetRawHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, - "http.resGetHeaders": { argTypes: [HTTPRES_T], result: DYN }, - "http.resHasHeader": { argTypes: [HTTPRES_T, STRING], result: BOOL }, - "http.resRemoveHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resOnFinish": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.resOnEndCallback": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.reqUrl": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqMethod": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqHeader": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqTrailer": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqHeaderValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqTrailerValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqOnData": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, - "http.reqOnEnd": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.resSetHeader": { argTypes: [HTTPRES_T, STRING, STRING], result: VOID }, - /** setHeader / appendHeader with a number, string or array value; the - * stored value keeps its type for getHeader/getHeaders. MAY THROW. */ - "http.resSetHeaderDyn": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, - "http.resAppendHeader": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, - "http.resWriteHead": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resWriteHeadN": { - argTypes: [HTTPRES_T, F64, arrayOf(STRING), arrayOf(STRING)], - result: VOID, - }, - /** HTTP/1.1 informational heads; early hints takes flat string pairs, - * with a lowercase `link` key required to send anything. */ - "http.resWriteContinue": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWriteProcessing": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWriteEarlyHints": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, - "http.resWrite": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resWriteBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, - "http.resEnd": { argTypes: [HTTPRES_T], result: VOID }, - "http.resEndStr": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resEndBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, - /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ - "http.resWriteDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, - "http.resEndDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, - "http.resHeadersSent": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resWritableEnded": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resFlushHeaders": { argTypes: [HTTPRES_T], result: VOID }, - "http.resAddTrailers": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, - "http.resCork": { argTypes: [HTTPRES_T], result: VOID }, - "http.resUncork": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWritableCorked": { argTypes: [HTTPRES_T], result: F64 }, - // The member follow-ups: reqStatusCode's `number | undefined` and - // sockRemoteAddress's `string | undefined` results are shape-checked in - // the special cases in validate.ts (like reqHeader/columns). - /** The server-surface member follow-ups: reqStatusCode answers the - * interned `number | undefined` union (negative = the undefined arm — - * a SERVER request, where Node's statusCode is undefined; every client - * response carries a real status); reqSocket is the underlying - * connection (+1, the same handle net.connect would give); - * sockRemoteAddress answers `string | undefined` (NULL after the fd - * closed, Node's destroyed-socket undefined; a dual-stack accept of an - * IPv4 peer reads "::ffff:a.b.c.d" like Node). reqResume/reqPause/reqDestroy and - * the req error/close listener slots complete the IncomingMessage - * surface portless's client responses use; resDestroy/resOnClose and - * resWriteHeadPairs ([k0,v0,k1,v1,...] — the env.pairs helper's flat - * shape) complete ServerResponse. sockSetTimeout arms the idle - * EVFILT_TIMER ('timeout' fires after ms of inactivity, once per idle - * period, never destroying the socket — Node's semantics). */ - "http.reqStatusCode": { argTypes: [HTTPREQ_T], result: VOID }, - // reqStatusMessage's `string | undefined` is program-interned like - // reqHeader's (the VOID here is the envGet sentinel). - "http.reqStatusMessage": { argTypes: [HTTPREQ_T], result: VOID }, - /** req.rawHeaders — [name, value, name, value, ...] in arrival order, - * names in their ORIGINAL case (Node's shape); a fresh string[] per - * read. reqStatusMessage answers the interned `string | undefined` - * union — the reason phrase on client responses ("" when the status - * line carried none), the undefined arm on server requests (the - * statusCode split). sockDestroyed is socket.destroyed — true once the - * fd is gone (destroy() or full close). */ - "http.reqRawHeaders": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - /** The `{ ...req.headers }` snapshot feed: [lowercased name, value, - * ...] pairs in arrival order — the interned %headers.snapshot helper - * builds the record over it, exactly the process.envPairs pattern. */ - "http.reqHeaderPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - /** Every value of one header in arrival order (empty when absent): the - * set-cookie array of a `{ ...req.headers }` snapshot. */ - "http.reqHeaderList": { argTypes: [HTTPREQ_T, STRING], result: arrayOf(STRING) }, - "http.reqRawTrailers": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - "http.reqTrailerPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - "net.sockDestroyed": { argTypes: [NETSOCKET_T], result: BOOL }, - /** socket.writable — the write half is open: no end() yet, no FIN sent, - * fd alive (connecting sockets answer true; writes queue). Node's - * stream flag. Borrows; never throws. */ - "net.sockWritable": { argTypes: [NETSOCKET_T], result: BOOL }, - // The 'upgrade' registrations: the callback shapes are program-typed - // ((req, socket, head) and shorter prefixes — the libCall case checks). - /** The 'upgrade' events, both sides (SEMANTICS.md — the WebSocket - * proxying surface): serverOnUpgrade registers (req, socket, head) - * listeners fired INSTEAD of 'request' for Connection: upgrade - * requests (the parser steps aside; the socket is raw; `head` carries - * bytes past the request head; no listener = the socket destroys, - * Node's default). clientOnUpgrade is the client twin: a 101 response - * fires (res, socket, head) INSTEAD of 'response'. */ - "http.serverOnUpgrade": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** server.on("connect", ...) — HTTP CONNECT tunneling, the 'upgrade' - * machinery's twin: (req, socket, head) fired INSTEAD of 'request' for - * CONNECT-method requests (no listener = the socket destroys, Node's - * default). The h2 compat server's 'connect' (portless's RFC 8441 - * handler) only ever sees the HTTP/1.1 arm under the allowHTTP1 - * lowering, so a listener whose second parameter is a UNION with a - * netSocket arm takes the socket wrapped at that arm (an emitted - * per-shape adapter — the tags are program data). */ - "http.serverOnConnect": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http.clientOnUpgrade": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.reqSocket": { argTypes: [HTTPREQ_T], result: NETSOCKET_T }, - // reqH2Stream's program-interned Http2Stream | undefined result is - // checked in the special libCall cases in validate.ts. - "http.reqH2Stream": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqH2StreamOrThrow": { argTypes: [HTTPREQ_T, STRING], result: HTTP2STREAM_T }, - /** req.pipe(dest) — the IncomingMessage body streaming into a - * ServerResponse (the proxy's response leg), a ClientRequest (the - * request-body forward), or a raw socket (the upgrade-rejection leg); - * chunk-for-chunk, natural end ends the destination (Node's pipe - * default; no backpressure — divergence 54's stream model). */ - "http.reqPipeRes": { argTypes: [HTTPREQ_T, HTTPRES_T], result: VOID }, - "http.reqPipeClient": { argTypes: [HTTPREQ_T, HTTPCLIENTREQ_T], result: VOID }, - "http.reqPipeSock": { argTypes: [HTTPREQ_T, NETSOCKET_T], result: VOID }, - "http.reqResume": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqPause": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqDestroy": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqOnError": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, - "http.reqOnClose": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.reqOnAborted": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.reqHttpVersion": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqHttpVersionMajor": { argTypes: [HTTPREQ_T], result: F64 }, - "http.reqHttpVersionMinor": { argTypes: [HTTPREQ_T], result: F64 }, - "http.reqAborted": { argTypes: [HTTPREQ_T], result: BOOL }, - "http.reqComplete": { argTypes: [HTTPREQ_T], result: BOOL }, - "http.resDestroy": { argTypes: [HTTPRES_T], result: VOID }, - "http.resOnClose": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.resWriteHeadPairs": { argTypes: [HTTPRES_T, F64, arrayOf(STRING)], result: VOID }, - /** writeHead(status, headers) with a checked-dynamic headers value — - * the runtime OBJ walk (string/number values; other values throw - * Node's invalid-argument TypeError; may throw). */ - "http.resWriteHeadDyn": { argTypes: [HTTPRES_T, F64, DYN], result: VOID }, - "net.sockSetTimeout": { argTypes: [NETSOCKET_T, F64], result: VOID }, - /** setEncoding('utf8') — 'data' delivers strings inside the chunk-encoding window; other real encodings throw the "not supported yet" Error, unknown names throw ERR_UNKNOWN_ENCODING (may throw). */ - "net.sockSetEncoding": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "http.reqSetEncoding": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "net.sockOnTimeout": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, - /** socket.encrypted — `boolean | undefined`: the true arm iff the - * socket carries a TLS transport (Node types `encrypted: true` on - * TLSSocket; plain sockets answer undefined — the proxy.ts isEncrypted - * idiom reads it through a cast). */ - "net.sockEncrypted": { argTypes: [NETSOCKET_T], result: VOID }, - // node:http, the client slice. The response callback shapes are - // program-dependent (checked by validate.ts); header pairs arrive flat. - /** node:http, the CLIENT slice (http.request/http.get over the net - * client machinery): request/requestCb take (host, port, path, method, - * timeoutMs, headerPairs, autoEnd[, responseCb]) — headerPairs is the - * flat [k0,v0,...] array (empty for none), autoEnd true is http.get's - * eager end(). The handle owns one dialed connection (NO pooling — the - * wire still carries Node's exact head: user headers, then Host, - * Connection: keep-alive, and the framing header; the socket closes - * when the response completes). The response delivered to responseCb / - * 'response' listeners IS an httpReq (IncomingMessage), status and - * headers parsed from the wire, body via reqOnData/reqOnEnd. Errors are - * Node-shaped ('connect ECONNREFUSED ip:port', 'socket hang up') and - * fire 'error' then 'close'; an unhandled 'error' exits 1 like every - * net handle. clientWrite before end commits to chunked framing unless - * the caller set content-length; clientEnd(data) before any write sends - * Content-Length exactly like Node. */ - "http.request": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL], - result: HTTPCLIENTREQ_T, - }, - "http.requestCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, null], - result: HTTPCLIENTREQ_T, - }, - /** new http.Agent(opts) / new https.Agent(opts): (secure, keepAlive, - * keepAliveMsecs, maxSockets, maxFreeSockets, timeoutMs, port) — the - * numeric options arrive < 0 for "unset" (Infinity/256/none; port - * seeds the settable defaultPort, Node's option merge). Returns the - * Agent as a checked-dynamic HANDLE (getName/destroy and the - * sockets/requests/freeSockets counters dispatch through the dyn - * handle ops). keepAlive: true THROWS the named construction fence — - * socket POOLING is not modeled (one dial per request); maxSockets - * accounting is real: over-limit requests defer their dial and queue. - * MAY THROW. */ - "http.agentNew": { argTypes: [BOOL, BOOL, F64, F64, F64, F64, F64, DYN], result: DYN }, - /** http.globalAgent / https.globalAgent (true): Node's keepAlive global - * agent, the one requests without an agent option use. */ - "http.globalAgent": { argTypes: [BOOL], result: DYN }, - /** http.request's `signal` option over the fresh request: an aborted - * signal destroys it with Node's AbortError now, a later abort event - * does the same. A falsy value is ignored; a non-signal throws - * ERR_INVALID_ARG_TYPE. */ - "http.clientSignal": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - /** options.signal checked before the request is built (ERR_INVALID_ARG_TYPE). */ - "http.clientSignalCheck": { argTypes: [DYN], result: VOID }, - /** https.request's servername option, stored for the construction call - * that follows: the TLS name sent and verified, and part of the pooled - * socket key. The "https." prefix keeps the TLS unit linked. */ - "https.nextServername": { argTypes: [STRING], result: VOID }, - /** The maxHeaderSize option of createServer / http.request: Node's - * validateInteger (ERR_OUT_OF_RANGE) then the header size bound. - * MAY THROW. */ - "http.serverMaxHeaderSize": { argTypes: [NETSERVER_T, F64], result: VOID }, - "http.clientMaxHeaderSize": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, - /** req.destroy(err): err becomes the request's 'error'. */ - "http.clientDestroyError": { - argTypes: [HTTPCLIENTREQ_T, { kind: "object", className: "%Error" }], - result: VOID, - }, - /** The agent-threaded request rows: the http.request/https.request - * shape with a trailing `agent` dyn argument (an Agent handle, false — - * the one-shot Connection: close dial — or null/undefined for the - * default path). port < 0 means "no port option": the agent's settable - * defaultPort, then the scheme's. MAY THROW (a non-Agent value is - * Node's ERR_INVALID_ARG_TYPE). */ - "http.requestAgent": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN], - result: HTTPCLIENTREQ_T, - }, - "http.requestAgentCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN, null], - result: HTTPCLIENTREQ_T, - }, - /** request/get with a URL-STRING first argument: the runtime parses it - * (WHATWG) and dials — throws catchably on an unparsable input or a - * non-http scheme. */ - "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, - "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, - /** request/get with a runtime options record: a RequestOptions - * variable, or a literal that spreads one or sets socketPath. Args - * (options, autoEnd[, cb]). The runtime reads the record in the - * ClientRequest constructor's order (host/port or socketPath, path, - * method, headers, timeout, agent, signal, maxHeaderSize) with Node's - * errors, and documented options it does not implement throw the - * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ - "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, - "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, - "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, - "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, - /** The paused-mode demux surface (portless's first-byte TLS peek: - * once('readable') + read(1) + unshift + emit('connection')). - * sockOnReadable registers a zero-param listener (a consumer: arrived - * bytes buffer instead of flowing, and EOF announces too); sockRead - * answers `Buffer | null` (exactly n buffered bytes, or null — Node's - * less-than-n answer; n <= 0 drains everything); sockUnshift returns - * bytes to the front of the stream; serverEmitConnection routes a - * socket into another server's protocol layer (a TLS target's - * 'connection' waits for its handshake). */ - "net.sockOnReadable": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - // sockRead's result is the interned `Buffer | null` union — checked - // specially in validate.ts (the reqHeader/envGet pattern; VOID here is a - // placeholder the special case overrides). - "net.sockRead": { argTypes: [NETSOCKET_T, F64], result: VOID }, - "net.sockUnshift": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - /** Socket flow control and the compat surface: pause/resume (reads - * gate off/on — kernel backpressure holds paused bytes; resume flows - * and discards sans listeners) and setNoDelay answer the SOCKET (+1, - * Node's chaining); destroySoon ends now and destroys once the FIN - * flushed; bytesWritten counts accepted bytes; readable is true until - * the read half ends. */ - "net.sockPause": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - "net.sockResume": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - "net.sockSetNoDelay": { argTypes: [NETSOCKET_T, BOOL], result: NETSOCKET_T }, - "net.sockDestroySoon": { argTypes: [NETSOCKET_T], result: VOID }, - /** RST close, answers the socket; throws ERR_INVALID_HANDLE_TYPE on TLS. */ - "net.sockResetAndDestroy": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - /** socket.address(): { address, family, port } or {} as a dyn. */ - "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, - "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, - /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), - * localFamily (4): dyn values, undefined when unknown. */ - "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, - "net.sockBytesRead": { argTypes: [NETSOCKET_T], result: F64 }, - "net.sockConnecting": { argTypes: [NETSOCKET_T], result: BOOL }, - "net.sockPending": { argTypes: [NETSOCKET_T], result: BOOL }, - "net.sockReadyState": { argTypes: [NETSOCKET_T], result: STRING }, - "net.sockSetKeepAlive": { argTypes: [NETSOCKET_T, BOOL, F64], result: NETSOCKET_T }, - "net.sockOnReady": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** Listeners with a parameter, as dyn functions: close(hadError), - * write's cb(err) and end's cb. */ - "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, - /** socket.on/once('lookup', listener): the listener (a dyn function) - * gets (err, address, family, host) once per resolved address, or once - * with the lookup error, before the connect. */ - "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, - "net.sockOnWriteFlush": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.sockOnWriteFlushDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockOnFinishDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockSetTimeoutCb": { - argTypes: [NETSOCKET_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.isIP": { argTypes: [DYN], result: F64 }, - "net.isIPv4": { argTypes: [DYN], result: BOOL }, - "net.isIPv6": { argTypes: [DYN], result: BOOL }, - "net.serverMaxConnectionsGet": { argTypes: [NETSERVER_T], result: DYN }, - "net.serverMaxConnectionsSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "net.serverDropMaxConnectionGet": { argTypes: [NETSERVER_T], result: DYN }, - "net.serverDropMaxConnectionSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, - /** net.SocketAddress and net.BlockList (scr_net.c): the constructors - * take the argument array; the handles are dyn values. */ - "net.socketAddressNew": { argTypes: [DYN], result: DYN }, - "net.socketAddressParse": { argTypes: [DYN], result: DYN }, - "net.isSocketAddress": { argTypes: [DYN], result: BOOL }, - "net.blockListNew": { argTypes: [DYN], result: DYN }, - "net.isBlockList": { argTypes: [DYN], result: BOOL }, - "net.blockListPrivateRanges": { argTypes: [], result: DYN }, - "net.serverOnDrop": { argTypes: [NETSERVER_T, DYN, BOOL], result: VOID }, - "net.serverGetConnections": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "net.sockReadable": { argTypes: [NETSOCKET_T], result: BOOL }, - /** socket.on('finish', cb) / end(cb): fires once when the FIN goes out - * (sweep-deferred, never the registering stack). */ - "net.sockOnFinish": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.serverEmitConnection": { argTypes: [NETSERVER_T, NETSOCKET_T], result: VOID }, - // tls/https: cert/key/ca PEM arguments are strings OR Buffers (null = - // both accepted; the emitter passes data+len either way). - /** node:tls + node:https (scr_tls.c over scr_net.c/scr_http.c, with - * the vendored mbedTLS archive — linked only when one of these appears - * on the IR; moduleUsesTls is the switch, and every tls/https libCall - * also counts as net AND http use so both units link and install). - * tls.createServer takes (cert, key[, handler]) — cert/key are PEM - * strings or Buffers (the emitter passes data+len for either); the - * handler is Node's 'secureConnection' (fires post-handshake with a - * socket that behaves exactly like a net socket, the same adapters as - * net.createServer). https.createServer is (cert, key, handler) with - * the http request-handler adapters. https.request/requestCb extend - * the http client row with (…, rejectUnauthorized: bool, ca: PEM - * string/Buffer — "" for none) and default port 443; everything else - * (write/end/destroy/events, the response surface) IS the http client - * surface — the handles are the same kinds. */ - "tls.createServer": { argTypes: [null, null], result: NETSERVER_T }, - "tls.createServerCb": { argTypes: [null, null, null], result: NETSERVER_T }, - /** The SNICallback forms: (cert, key, SNICallback[, handler]). The - * callback is http2.createSecureServerSni's shape, a function or a - * function | undefined union. */ - "tls.createServerSni": { argTypes: [null, null, null], result: NETSERVER_T }, - "tls.createServerSniCb": { argTypes: [null, null, null, null], result: NETSERVER_T }, - // The runtime options records (divergence 66's stance): a dyn options - // value whose members read at runtime; pemDyn extracts a runtime-valued - // cert/key member (arg 1 is the precomposed fence label). - /** RUNTIME options records (the divergence-66 stance): the *Dyn - * creators take a checked-dynamic (dyn) options value whose members - * read at runtime — cert/key extract like the literal path, members - * whose literal forms fence THROW the catchable fence at runtime, and - * undocumented keys drop like Node drops them. tls.pemDyn is the - * literal walk's runtime-valued cert/key extraction: (value, whatLit) - * → PEM bytes (strings/Buffers/one-element arrays of those) or the - * thrown fence. All of them may throw. */ - "tls.pemDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "tls.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "tls.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - "https.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "https.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - /** createSecureServer(options, handler) — the eager COMPAT handler as - * the first 'request' listener (Node's exact route), on both literal - * flavors: Req is the dual ALPN allowHTTP1 server, H2Req the ALPN=h2-only - * server (both use compat handles over h2 streams). Args are - * (cert, key, cb, enableConnectProtocol). */ - "http2.createSecureServerReq": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - "http2.createSecureServerH2Req": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - /** createSecureServer with a RUNTIME options record (the divergence-66 - * stance): allowHTTP1/cert/key read at runtime — allowHTTP1 picks the - * flavor, the TLS server walk fences its out-of-bounds members with - * the catchable runtime fence, and h2 session-tuning keys drop exactly - * like the literal walk ignores them. May throw. The Cb form carries - * the eager compat handler. */ - "http2.createSecureServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "http2.createSecureServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - // tls.connect(port, host, opts[, cb]) — port -1 / host "" read the - // options record; the callback fires post-handshake (secureConnect). - /** tls.connect — the TLS client socket: (port, host, opts[, cb]) where - * port -1 reads options.port, host "" reads options.host, and opts is - * the runtime (dyn) options record (rejectUnauthorized/ca/servername - * implemented; other documented members throw the runtime fence). The - * callback fires post-handshake — Node's secureConnect timing. */ - "tls.connect": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "tls.connectCb": { argTypes: [F64, STRING, DYN, null], result: NETSOCKET_T }, - // The TLSSocket member surface (authError's `string | null` union is - // shape-checked in the special cases in validate.ts — the reqHeader pattern). - /** The TLSSocket member surface on the socket kind: authorized (bool — - * Node's verify verdict), authorizationError (the verify-failure CODE - * STRING or null), the 'secureConnect' registration (a TLS socket's - * conn list fires at establishment; plain sockets never fire it), and - * the 'session' registration (fires once with the serialized session — - * a Buffer; the received-ticket event). */ - "tls.sockAuthorized": { argTypes: [NETSOCKET_T], result: BOOL }, - /** getProtocol() (string | null), getCipher() (CipherNameAndProtocol, - * undefined before the handshake, null after close) and alpnProtocol - * (string | false | null), as dyn values. */ - "tls.sockGetProtocol": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockGetCipher": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockAlpnProtocol": { argTypes: [NETSOCKET_T], result: DYN }, - /** getPeerCertificate(detailed) and getCertificate() (the legacy object, - * {} or null; scr_tls_x509.c), and the DER behind getPeerX509Certificate() - * and getX509Certificate() (a Buffer, empty without a certificate). */ - "tls.sockGetPeerCertificate": { argTypes: [NETSOCKET_T, DYN], result: DYN }, - "tls.sockGetCertificate": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockPeerCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, - "tls.sockOwnCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, - "tls.sockAuthError": { argTypes: [NETSOCKET_T], result: VOID }, - "tls.sockOnSecureConnect": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "tls.sockOnSession": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - /** The 'secure' event: the handshake completed (after secureConnect on - * a client). Plain sockets never fire it. */ - "tls.sockOnSecure": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** new tls.TLSSocket(socket, options) in server mode: a new TLS socket - * takes over the socket's connection (wrapSocketCtx passes a literal's - * secureContext member apart from the runtime options record). The - * record reads at runtime: isServer, cert, key, ALPNProtocols, - * minVersion and maxVersion are implemented, the client mode and other - * documented members throw the runtime fence. MAY THROW. */ - "tls.wrapSocket": { argTypes: [NETSOCKET_T, DYN], result: NETSOCKET_T }, - "tls.wrapSocketCtx": { argTypes: [NETSOCKET_T, DYN, SECURECTX_T], result: NETSOCKET_T }, - // createSecureContext({ cert, key }) mints the opaque SNI-answer handle. - /** tls.createSecureContext({ cert, key }) — parses the PEM pair into an - * opaque SecureContext handle (secureCtx kind) for SNI callbacks to - * answer with; cert/key are PEM strings or Buffers like createServer's. */ - "tls.createSecureContext": { argTypes: [null, null], result: SECURECTX_T }, - /** createSecureContext over a RUNTIME options record (the checked- - * dynamic lane): Node's typed option validations first (the ciphers/ - * passphrase/engine/version/timeout/ticketKeys ladders), then the pem - * walk — a validated { cert, key } bag builds the real context. - * May-throw. */ - "tls.createSecureContextDyn": { argTypes: [DYN], result: SECURECTX_T }, - /** server.addContext(servername, options | SecureContext), - * server.setSecureContext(options) and the 'tlsClientError' listener of - * a tls or https server (scr_tls.c). */ - "tls.serverAddContext": { argTypes: [NETSERVER_T, DYN, DYN], result: VOID }, - "tls.serverAddContextCtx": { argTypes: [NETSERVER_T, DYN, SECURECTX_T], result: VOID }, - "tls.serverSetSecureContext": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "tls.serverOnClientError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** tls.getCACertificates(type): validateString + the documented name - * set, then the trailing compiler-rendered fence — ALWAYS THROWS (the - * error.nodeThrow polymorphic-result carve-out). May-throw seed. */ - "tls.caCertsChk": { argTypes: [DYN, STRING], result: VOID }, - // The CA-store introspection unit (scr_tls_ca.c): per-type cached PEM - // string arrays and the default-set replacement. - /** The CA-store introspection surface (scr_tls_ca.c — its own unit and - * link gate, "tlsca." NOT "tls.", so a getCACertificates-only binary - * never pulls mbedTLS): tlsca.get is tls.getCACertificates(type) — the - * cached per-type string[] of PEM blocks (identity-stable across calls, - * Node's caching; an unknown type throws Node's ERR_INVALID_ARG_VALUE - * TypeError); tlsca.root is the tls.rootCertificates value read; - * tlsca.set is tls.setDefaultCACertificates(certs) — replaces the - * default set (deduped) and the anchors the TLS client verifies - * against, throwing Node's ERR_CRYPTO_OPERATION_FAILED when no entry - * carries a certificate block. */ - "tlsca.get": { argTypes: [STRING], result: arrayOf(STRING) }, - "tlsca.root": { argTypes: [], result: arrayOf(STRING) }, - "tlsca.set": { argTypes: [arrayOf(STRING)], result: VOID }, - "https.createServer": { argTypes: [null, null, null], result: NETSERVER_T }, - // http2's allowHTTP1 compatibility server (divergence 57): cert/key - // like tls.createServer; the 'request' handler arrives separately via - // http.serverOnRequest (shape checked in the libCall case, like - // http.createServer's). sessionError callback shape is checked by validate.ts. - /** node:http2 allowHTTP1: one TLS server advertises h2 + http/1.1 and - * dispatches to the matching parser after ALPN. Request listeners mirror - * into both compatibility lists; the remaining session/req.stream rows - * below retain their explicitly limited behavior. */ - "http2.createSecureServer": { argTypes: [null, null, BOOL], result: NETSERVER_T }, - // The SNI-callback form: arg 2 is the JS SNICallback closure — a - // `(servername, cb) => void` func, or its `| undefined` union from the - // conditional-spread spelling (the libCall case checks the shape). - /** createSecureServer with an SNI callback: args are (cert, key, sniCb) - * where sniCb is the JS SNICallback — a `(servername, cb) => void` - * closure, or the `SNICallback | undefined` union from the conditional- - * spread spelling (`...(x ? { SNICallback: x } : {})`; the emitter - * unwraps the union — the undefined arm means "no callback", exactly - * the no-SNI server). The runtime parses each connection's ClientHello - * for the server_name extension BEFORE the TLS handshake begins, calls - * the callback with (servername, answer-closure), and resumes the - * handshake when the answer arrives — cb(err) tears the socket down - * without an error event (Node's 'tlsClientError' default), cb(null, ctx) serves - * ctx's cert/key, cb(null, undefined) serves the default pair. */ - "http2.createSecureServerSni": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - // The ALPN=h2 server (createSecureServer without allowHTTP1): the real - // h2 session machinery behind the TLS handshake. - /** The REAL h2-over-TLS server (createSecureServer WITHOUT allowHTTP1): - * scr_http2_create_secure_server — the h2c session machinery behind an - * mbedTLS handshake whose ALPN advertises h2 alone (an http/1.1-only - * client fails the handshake with no_application_protocol, Node's - * h2-only split). args are (cert, key) — PEM strings or Buffers. */ - "http2.createSecureServerH2": { argTypes: [null, null, BOOL], result: NETSERVER_T }, - "http.serverOnRequest": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** server.on('checkContinue' | 'checkExpectation', (req, res)): the - * 'request' listener shape; the last argument picks checkExpectation. */ - "http.serverOnCheck": { argTypes: [NETSERVER_T, null, BOOL, BOOL], result: VOID }, - "http2.serverOnSessionError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** The guarded absent-session compatibility call remains a no-op. */ - "http2.streamNoop": { argTypes: [], result: VOID }, - /** The UNGUARDED h2-only stream call (`req.stream.on(...)`): stream is - * undefined on every connection the allowHTTP1 lowering accepts — and - * on every HTTP/1.1 connection of Node's own allowHTTP1 server — so the - * call IS Node's member read on undefined: throws the exact catchable - * TypeError ("Cannot read properties of undefined (reading 'on')"). - * One arg: the read member's name (a string literal). Never returns. */ - "http2.streamUndefCall": { argTypes: [STRING], result: VOID }, - // The REAL h2c surface (scr_http2.c). Callback slots are null (the - // emitter picks the thunk); pairs slots are string arrays; the - // endStream tri-state and event flags ride as f64. - /** node:http2, the REAL h2c surface (scr_http2.c — frame codec + HPACK - * over the net loop; the design note atop that file has the story). - * Sessions and streams are first-class handle kinds; 'stream'/'response' - * payloads cross as flat [name, value, ...] pairs arrays and an EMITTED - * adapter closure builds the program-side headers record (the response - * :status rides separately as a number). */ - "http2.createServer": { argTypes: [], result: NETSERVER_T }, - "http2.createServerReq": { argTypes: [null], result: NETSERVER_T }, - "http2.serverOnStream": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http2.serverOnSession": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - // connect(authority, reject, ca[, cb]): the TLS client knobs an https - // authority reads (inert on http ones, exactly Node); ca is a PEM - // string/Buffer ("" = the system anchors). - /** connect(authority[, listener]) — h2c prior knowledge; the listener - * closure (if any) is the 'connect' once-listener. */ - "http2.connect": { argTypes: [STRING, BOOL, null], result: HTTP2SESSION_T }, - "http2.connectCb": { argTypes: [STRING, BOOL, null, null], result: HTTP2SESSION_T }, - /** session.request(pairs?, endStream) — endStream is a tri-state f64: - * -1 the method's payload-meaningless default, 0/1 explicit. */ - "http2.sessionRequest": { - argTypes: [HTTP2SESSION_T, arrayOf(STRING), F64], - result: HTTP2STREAM_T, - }, - "http2.sessionClose": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionCloseCb": { argTypes: [HTTP2SESSION_T, null], result: VOID }, - "http2.sessionDestroy": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionOnClose": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnError": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnConnect": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnStream": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnGoaway": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionClosed": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionDestroyed": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionEncrypted": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionType": { argTypes: [HTTP2SESSION_T], result: F64 }, - "http2.sessionAlpn": { argTypes: [HTTP2SESSION_T], result: STRING }, - "http2.sessionSocket": { argTypes: [HTTP2SESSION_T], result: NETSOCKET_T }, - /** stream.respond(pairs?, endStream) — the server answer. */ - "http2.streamRespond": { argTypes: [HTTP2STREAM_T, arrayOf(STRING), BOOL], result: VOID }, - "http2.streamWrite": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamWriteBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, - "http2.streamEnd": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamEndStr": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamEndBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, - "http2.streamClose": { argTypes: [HTTP2STREAM_T, F64], result: VOID }, - "http2.streamCloseCb": { argTypes: [HTTP2STREAM_T, F64, null], result: VOID }, - "http2.streamDestroy": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.sessionSettings0": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionSettings": { argTypes: [HTTP2SESSION_T, DYN], result: VOID }, - "http2.sessionSettingsDynCb": { argTypes: [HTTP2SESSION_T, DYN, DYN], result: VOID }, - "http2.sessionSettingsCb0": { - argTypes: [HTTP2SESSION_T, DYN, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http2.sessionOnSettingsDyn": { argTypes: [HTTP2SESSION_T, DYN, BOOL, BOOL], result: VOID }, - "http2.sessionOnSettings0": { - argTypes: [HTTP2SESSION_T, { kind: "func", params: [], ret: VOID }, BOOL, BOOL], - result: VOID, - }, - "http2.sessionSettingsGet": { argTypes: [HTTP2SESSION_T, BOOL], result: DYN }, - "http2.sessionPendingSettingsAck": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.getDefaultSettings": { argTypes: [], result: DYN }, - "http2.streamSetEncoding": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamSetEncodingRet": { argTypes: [HTTP2STREAM_T, STRING], result: HTTP2STREAM_T }, - "http2.streamResume": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamPause": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamOnData": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnEnd": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnClose": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnAborted": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnError": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnResponse": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamId": { argTypes: [HTTP2STREAM_T], result: F64 }, - "http2.streamRstCode": { argTypes: [HTTP2STREAM_T], result: F64 }, - "http2.streamDestroyed": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamClosed": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamAborted": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamPending": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamSession": { argTypes: [HTTP2STREAM_T], result: HTTP2SESSION_T }, - // The createConnection forms: arg 0 is the dialer closure (() => - // Socket — the libCall case checks), then path/method/timeout/headers/ - // autoEnd like http.request. - /** The createConnection form (the proxy's own dialer): args are - * (connCb, path, method, timeout, headers, autoEnd[, cb]) — connCb is - * a `() => net.Socket` closure the runtime invokes ONCE, synchronously - * (Node's onSocket timing); everything else matches http.request. The - * Host header defaults to "localhost" — a headers.host entry wins - * verbatim, the proxy shape. */ - "http.requestConn": { - argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL], - result: HTTPCLIENTREQ_T, - }, - "http.requestConnCb": { - argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.request": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.requestCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], - result: HTTPCLIENTREQ_T, - }, - /** The URL-string first argument, the http.requestUrl row over TLS: - * no options means Node's defaults (verification on, default trust - * anchors), and a non-https scheme is ERR_INVALID_PROTOCOL. */ - "https.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, - "https.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, - "https.requestAgent": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN], - result: HTTPCLIENTREQ_T, - }, - "https.requestAgentCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN, null], - result: HTTPCLIENTREQ_T, - }, - // The requestFn binding's runtime-secure rows: https.request's shape - // with the leading `secure` bool. - /** A call through a `const requestFn = tls ? https.request : - * http.request` binding — the module-function-as-value ternary between - * the two known clients: the https.request row with a leading `secure` - * bool that picks the dial at RUNTIME (true = the TLS client, exactly - * https.request; false = the plain client, exactly http.request — - * rejectUnauthorized/ca ignored there like Node ignores TLS options on - * http.request). The "https." prefix keeps the TLS unit linked. */ - "https.requestFn": { - argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.requestFnCb": { - argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], - result: HTTPCLIENTREQ_T, - }, - "http.clientWrite": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientSetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING, STRING], result: VOID }, - "http.clientGetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientHasHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: BOOL }, - "http.clientRemoveHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientGetHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, - "http.clientGetRawHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, - "http.clientGetHeaders": { argTypes: [HTTPCLIENTREQ_T], result: DYN }, - "http.clientWriteBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, - "http.clientEnd": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientEndStr": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientEndBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, - /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ - "http.clientWriteDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - "http.clientEndDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - "http.clientFlushHeaders": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAddTrailers": { argTypes: [HTTPCLIENTREQ_T, arrayOf(STRING)], result: VOID }, - "http.clientCork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientUncork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientWritableCorked": { argTypes: [HTTPCLIENTREQ_T], result: F64 }, - "http.clientMethod": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientPath": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientHost": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientWritableFinished": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientSocket": { argTypes: [HTTPCLIENTREQ_T], result: NETSOCKET_T }, - "http.clientReusedSocket": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientSetNoDelay": { argTypes: [HTTPCLIENTREQ_T, BOOL], result: VOID }, - "http.clientSetSocketKeepAlive": { argTypes: [HTTPCLIENTREQ_T, BOOL, F64], result: VOID }, - "http.clientSetTimeout": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, - "http.clientSetTimeoutCb": { - argTypes: [HTTPCLIENTREQ_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.statusCodes": { argTypes: [], result: DYN }, - "http.methods": { argTypes: [], result: arrayOf(STRING) }, - "http.reqSetTimeout": { argTypes: [HTTPREQ_T, F64], result: VOID }, - "http.reqSetTimeoutCb": { - argTypes: [HTTPREQ_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAbort": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAborted": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnSocket": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnFinish": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnError": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnTimeout": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnClose": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnAbort": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnContinue": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** execFileSync/execSync as ONE entry (args: cmd, argv, shell, input, - * cwd, hasEnv, envPairs, timeoutMs, stdoutMode, stderrMode, buffers — - * see scr_child.h). Throws Node's exact errors: "Command failed: " - * (+ captured stderr) on non-zero exit or signal death, "spawnSync - * ENOENT" on spawn failure, "spawnSync ETIMEDOUT" after - * the SIGTERM timeout, with Node's status, signal, output, pid, stdout - * and stderr properties (Buffer outputs when buffers is set: no - * encoding option). Result is the captured utf8 stdout (+1). */ - "cp.execSync": { - argTypes: [ - STRING, - arrayOf(STRING), - BOOL, - STRING, - BOOL, - STRING, - BOOL, - arrayOf(STRING), - F64, - F64, - F64, - BOOL, - ], - result: STRING, - }, - /** The promisified-execFile/exec capture (args: cmd, argv, shell, cwd, - * hasEnv, envPairs, timeoutMs; shell marks exec's /bin/sh -c argv, as - * for execSync, so failures display the command string): the same exec - * core in the async shape — both streams captured, no echo, Node's ASYNC messages on the throw paths - * ("Command failed: \n" with the unconditional newline, - * "spawn ENOENT" with .code, timeouts reporting as ordinary - * SIGTERM command failures — never ETIMEDOUT). Result reuses the - * ScrSpawnRes container (+1; stdout/stderr strings, status unused). - * Called only from the frontend's interned %execFileAsync and - * %execAsync ASYNC helpers, whose fiber turns the throw into the - * rejection. */ - "cp.execCapture": { - argTypes: [STRING, arrayOf(STRING), BOOL, STRING, BOOL, arrayOf(STRING), F64], - result: SPAWNRES_T, - }, - /** child_process.spawn (scr_child.c + the scr_async.c loop): posix_spawnp - * with per-slot ignore/inherit/pipe modes (and number output fds), the - * all-piped Node default, and the child registered with the event loop, - * which polls waitpid(WNOHANG) at quiescence like timers. - * NEVER throws: spawn failure defers to the "error" event, Node-exact - * (the error message is Node's "spawn "; an "error" - * event with no listener prints it and exits 1 like an EventEmitter). - * cmd/args borrowed; result an owned (+1) child handle. The loop will - * not exhaust while any spawned child is unreaped — Node's keep-alive. - * - * child.onExit / child.onClose / child.onError — terminal listener - * registration through child.on/once. Close uses the exit adapter shape - * but fires only after every piped stdio handle reaches EOF. The - * receiver is borrowed, the CALLBACK MOVES into the child's listener - * registry (released after the terminal event fires, or at reap for - * the event that never fires). Both are void (chaining is fenced). - * onExit's third emitted ingredient is an ADAPTER the backend interns - * per callback shape: the runtime invokes adapter(cb, has_code, code) - * and the adapter builds the `number | null` union (tags are program- - * dependent) or ignores the code for a zero-param callback. onError's - * adapters are runtime-provided (zero-param, or the %Error one-param - * shape — scr_error_new needs no program types). "exit" fires once - * with the code (f64 arm) or null (signal death); "error" fires only - * for spawn failure, exactly Node's split. */ - "cp.spawn": { argTypes: [STRING, arrayOf(STRING)], result: CHILD_T }, - /** Static-native fork startup and IPC. process.forkTarget initializes a - * re-executed child's inherited channel and returns its embedded target id - * (-1 in the parent). The remaining entries mirror Node's JSON channel. */ - "process.forkTarget": { argTypes: [F64], result: F64 }, - "cp.fork": { - argTypes: [F64, arrayOf(STRING), F64, F64, F64, BOOL, arrayOf(STRING), STRING], - result: CHILD_T, - }, - "child.connected": { argTypes: [CHILD_T], result: BOOL }, - "child.hasChannel": { argTypes: [CHILD_T], result: BOOL }, - "child.send": { argTypes: [CHILD_T, STRING], result: BOOL }, - "child.sendCb": { argTypes: [CHILD_T, STRING, null], result: BOOL }, - "child.disconnect": { argTypes: [CHILD_T], result: VOID }, - "child.onMessage": { argTypes: [CHILD_T, null, BOOL], result: VOID }, - "child.onDisconnect": { argTypes: [CHILD_T, null, BOOL], result: VOID }, - "child.onSpawn": { argTypes: [CHILD_T, null], result: VOID }, - /** Rest listeners use the checked function bridge to pack event arguments. */ - "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, - // Own properties JavaScript adds to children (handle-properties.ts). - "child.propsInstall": { argTypes: [], result: VOID }, - "child.propsDefine": { argTypes: [DYN, DYN, DYN], result: DYN }, - "child.propsDescriptor": { argTypes: [DYN, DYN], result: DYN }, - "child.propsResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "child.propsOnDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, - "process.connected": { argTypes: [], result: BOOL }, - /** Whether this fork child opened its channel; part of process.send's - * presence in programs with fork targets. Never throws. */ - "process.ipcOpen": { argTypes: [], result: BOOL }, - "process.send": { argTypes: [STRING], result: BOOL }, - "process.sendCb": { argTypes: [STRING, null], result: BOOL }, - "process.disconnect": { argTypes: [], result: VOID }, - "process.onMessage": { argTypes: [null, BOOL], result: VOID }, - "process.onDisconnect": { argTypes: [null, BOOL], result: VOID }, - /** child_process.execFile's callback slice: starts an all-piped child, - * captures stdout/stderr, and moves the error-first callback into the - * child registry. The callback shape is program-dependent and checked - * by the validator/backends. The last argument is exec's shell flag: - * the argv is ["-c", command] and failure messages show the command. */ - "cp.execFile": { argTypes: [STRING, arrayOf(STRING), null, BOOL], result: CHILD_T }, - // spawn's options form: per-slot stdio modes (0 ignore / 1 inherit / - // 2 fd) with the out/err fds for mode 2, detached, env replacement - // pairs, cwd ("" = inherit). - /** spawn with options (scr_child.c): the cp.spawn core plus PER-SLOT - * stdio — args are (cmd, argv, inMode, outMode, errMode, outFd, errFd, - * detached, hasEnv, envPairs, cwd); modes 0 ignore (/dev/null), 1 - * inherit, 2 fd (out/err only — the fd dup2s into the child's slot, - * Node's stdio fd form; the daemon-log idiom ["ignore", logFd, logFd]). - * detached is POSIX_SPAWN_SETSID (the child gets its own session and - * process group, Node's semantics), env is a REPLACEMENT ([k,v,...] - * pairs like cp.execSync's), cwd ""=inherit. Same event/loop story as - * cp.spawn. */ - "cp.spawnOpts": { - argTypes: [ - STRING, - arrayOf(STRING), - F64, - F64, - F64, - F64, - F64, - BOOL, - BOOL, - BOOL, - arrayOf(STRING), - STRING, - ], - result: CHILD_T, - }, - /** Runtime options normalized into the native spawn core; unsupported - * process features retain explicit refusals. (command, args, options). */ - "cp.spawnDynamic": { argTypes: [STRING, arrayOf(STRING), DYN], result: CHILD_T }, - // The callback's func type is program-dependent (zero params, or the - // `number | null` union / the %Error class) — the libCall case checks - // the shape; the slot here only pins arity and the child receiver. - "child.onExit": { argTypes: [CHILD_T, null], result: VOID }, - "child.onClose": { argTypes: [CHILD_T, null], result: VOID }, - "child.onError": { argTypes: [CHILD_T, null], result: VOID }, - // Like process.envGet, spawnRes.status's result type is program-dependent - // (the interned `number | null` union) — the libCall case checks the arms. - "spawnRes.status": { argTypes: [SPAWNRES_T], result: VOID }, - // spawnRes.error's result is the interned `Error | undefined` union — - // the libCall case checks the arms, the spawnRes.status pattern. - /** Node's spawn-failure carrier `error?: Error`: a fresh +1 %Error - * ("spawnSync ENOENT", `code` stamped) when the spawn itself - * failed, the interned undefined arm otherwise — the result type is the - * call site's `Error | undefined` union, constructed type-directedly in - * the backend (the envGet convention). Never throws. */ - "spawnRes.error": { argTypes: [SPAWNRES_T], result: VOID }, - // spawnRes.signal's result is the interned `string | null` union — - // same pattern. - "spawnRes.signal": { argTypes: [SPAWNRES_T], result: VOID }, - // The ChildProcess lifecycle members: pid/exitCode are program-dependent - // unions (`number | undefined` / `number | null` — the libCall case - // checks the arms, the spawnRes.status pattern). - /** The ChildProcess lifecycle members (scr_child.c), Node's shapes - * exactly (SEMANTICS.md has the pinned matrix). child.pid is the - * checker's `number | undefined` (undefined = spawn failure) and - * child.exitCode its `number | null` (null while running and after a - * signal death; -errno once a spawn failure settled) — both unions are - * type-directed constructions in the backend over a has/get runtime - * pair, the spawnRes.status pattern. child.killed is Node's - * sent-a-signal flag. child.kill sends while the child is un-reaped - * (false after — Node's null-handle answer) and THROWS the - * Unknown-signal TypeError on bad names (may-throw); killNum passes - * numbers through (0 probes; never throws). child.unref drops the - * child from the loop's keep-alive set — it is still reaped while the - * loop runs for other reasons, and one the loop never reaps is left to - * the OS at exit. All receivers borrowed. */ - "child.pid": { argTypes: [CHILD_T], result: VOID }, - "child.exitCode": { argTypes: [CHILD_T], result: VOID }, - // The close-override pair: the bound close's result func type and the - // override's func argument are program-dependent (the callback union's - // tags) — the slots pin the server receiver; the libCall case checks - // the bound value's shape. - /** The close-override pair (the portless close-proxy idiom). - * serverCloseBind is `wrapper.close.bind(wrapper)` as a VALUE: a - * compiler-emitted closure over the server whose invocation runs the - * REAL close (scr_net_server_close_direct — never the override), so - * the override body's `origClose(cb)` cannot recurse; its callback - * argument (the `((err?: Error) => void) | undefined` union) registers - * as a once-'close' listener, a one-param callback wrapped by an - * emitted zero-arg trampoline firing the undefined arm (a clean close - * carries no error). serverSetCloseOverride is `wrapper.close = fn`: - * the override MOVES in behind an emitted zero-arg wrapper (it invokes - * the user function with the undefined-arm callback — tags are program - * data), and server.close() consults it before closing. */ - "net.serverCloseBind": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverSetCloseOverride": { argTypes: [NETSERVER_T, null], result: VOID }, - // The piped-output stream reads: `Readable | null` unions (the libCall - // case checks the arms — the child.pid pattern with a ref arm). - /** The piped-output streams (stdio mode 3 — scr_child.c's stream - * slice). child.stdout/child.stderr answer the checker's - * `Readable | null` union (type-directed construction in the backend - * over the +1-or-NULL runtime pair — the child.pid pattern with a ref - * arm). stream.onData/onEnd register 'data'/'end' listeners (receiver - * borrowed, CALLBACK MOVES, trailing once-flag, void — chaining - * fenced): 'data' fires one Buffer chunk per read (zero-param and - * Buffer/string adapters are runtime-provided; setEncoding threads - * split multibyte sequences through the shared StringDecoder core. A - * union-param listener — - * ngrok's `Buffer | string` — gets a compiler-emitted adapter wrapping - * the chunk at its Buffer arm), 'end' fires once at EOF, always BEFORE - * the child's 'exit' (the pinned ordering). A flowing stream keeps the - * loop alive: usesTimers. */ - "child.stdout": { argTypes: [CHILD_T], result: VOID }, - "child.stderr": { argTypes: [CHILD_T], result: VOID }, - /** The piped-input writer (stdio mode 3 on fd 0). child.stdin answers - * `Writable | null`; writes copy borrowed data into the nonblocking - * queue, end/destroy settle it, writable is a pure state read, and - * listener callbacks move into the writer registry. */ - "child.stdin": { argTypes: [CHILD_T], result: VOID }, - /** Under the general stream representation (general-streams.ts), a - * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream - * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge - * throws when the program lacks the stream unit's boxing helpers. */ - "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, - // Listener registrations: callback func shapes are program-dependent - // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, - // the stream receiver, and the once-flag. - "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.onEnd": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.childSetEncoding": { argTypes: [CHILDSTREAM_T, STRING], result: CHILDSTREAM_T }, - "writer.writeString": { argTypes: [CHILDWRITER_T, STRING], result: BOOL }, - "writer.writeBytes": { argTypes: [CHILDWRITER_T, BYTES_U8], result: BOOL }, - "writer.end": { argTypes: [CHILDWRITER_T], result: VOID }, - "writer.destroy": { argTypes: [CHILDWRITER_T], result: VOID }, - "writer.writable": { argTypes: [CHILDWRITER_T], result: BOOL }, - "writer.onDrain": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - "writer.onFinish": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - "writer.onError": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - /** The first-class WritableStream write (`output.write(line)` — the - * prefixStream idiom): the receiver IS the fd scalar (process.stdout/ - * stderr reads mint 1/2), dispatched onto the exact stdoutWrite/ - * stderrWrite paths so buffering and ordering stay identical. Data - * borrowed; the bool is Node's always-true backpressure signal. */ - "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, - /** process.stdout/stderr flowing into a runtime Writable slot under the - * general stream representation: an fd-backed Writable (+1) whose - * writes take process.stdout.write's synchronous path. */ - /** A pipeline() stage that is a checked-dynamic value: a boxed native - * stream unwraps by identity (+1); anything else throws the given - * not-supported message. Typed as a Duplex so every stage position - * accepts it; the runtime pipeline checks the stream's real sides. */ - "stream.unboxDyn": { - argTypes: [DYN, STRING], - result: { kind: "object", className: "%Duplex" }, - }, - /** Whether pipeline() pumps a checked-dynamic first stage as an - * iterable (a string, or a value with an iterator method that is not a - * stream with pipe() and on()) instead of piping it as a stream. */ - "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, - /** The Transform a Cipheriv or Decipheriv reads and writes as a stream - * (+1, created on first use and owned by the cipher). */ - "stream.handleStream": { - argTypes: [DYN], - result: { kind: "object", className: "%Transform" }, - }, - /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a - * checked-dynamic value boxes (+1) — a runtime stream with the readable - * (1) or writable (2) side — or null for any other value. Never throws; - * the result union is `%Readable | null` or `%Writable | null`. */ - "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, - /** stream.dynNative for a pipe or unpipe destination: also a - * constructor-function stream's runtime stream, in programs that link - * that unit, unless it overrides write() or end(). */ - "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, - /** readable.pipe(destination, end) into a checked-dynamic destination - * that is not a native stream: [source, the source boxed, destination, - * end]. Node's pipe listener protocol over the destination's members - * (scr_stream_dyn.c); throws what those members throw. */ - "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, - /** readable.unpipe(destination) for a checked-dynamic destination that - * is not a native stream: [source, destination]. Unpipes the pipe - * stream.pipeDyn registered into it, if any (scr_stream_dyn.c). */ - "stream.unpipeDyn": { argTypes: [null, DYN], result: VOID }, - "stream.stdioWritable": { - argTypes: [PROCSTREAM_T], - result: { kind: "object", className: "%Writable" }, - }, - "child.killed": { argTypes: [CHILD_T], result: BOOL }, - "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, - "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, - "child.unref": { argTypes: [CHILD_T], result: VOID }, - "child.ref": { argTypes: [CHILD_T], result: VOID }, - "spawnRes.stdout": { argTypes: [SPAWNRES_T], result: STRING }, - "spawnRes.stderr": { argTypes: [SPAWNRES_T], result: STRING }, - /** fs/promises (scr_lib.c over scr_async.c's settled minting): the SAME - * sync syscalls, wrapped in an ALREADY-SETTLED promise — failure - * REJECTS (catchable at the await) instead of throwing, so none of - * these are in the may-throw seed. readFile is utf8-fenced like - * readFileSync. The non-interleaving divergence is documented in - * SEMANTICS.md. */ - /** node:crypto randomness: randomUUID plus randomBytes as either a real - * Buffer or the fused randomBytes(n).toString("hex"|"base64") path. - * The size-taking forms throw Node's RangeError on invalid values. */ - "crypto.randomUUID": { argTypes: [], result: STRING }, - "crypto.randomBytesToString": { argTypes: [F64, STRING], result: STRING }, - /** crypto.randomBytes(n) → a real u8 Buffer (+1). THROWS Node's - * RangeError on out-of-range sizes, exactly like the composed - * randomBytesToString (which keeps its one-libCall lowering — the two - * coexist: the composed form never materializes the Buffer). */ - "crypto.randomBytes": { argTypes: [F64], result: BYTES_U8 }, - /** The fused createHash(alg).update(data).digest(enc) fast path and the - * one-shot crypto.hash implementation. First-class Hash/Hmac handles use - * the entries below; all paths share the incremental runtime core. */ - "crypto.hashDigestStr": { argTypes: [STRING, STRING, STRING], result: STRING }, - "crypto.hashDigestBytes": { argTypes: [STRING, BYTES_U8, STRING], result: STRING }, - /** First-class static Hash/Hmac handles. Constructors validate the - * runtime algorithm string (every digest Node 24 lists); update returns the same - * handle by retained identity, copy snapshots Hash state, and digest - * finalizes the handle and returns either a Buffer or encoded string. */ - "crypto.native": { argTypes: [], result: DYN }, - "crypto.hashNew": { argTypes: [STRING], result: CRYPTOHASH_T }, - /** The Transform a Hash or Hmac reads and writes as a stream (+1, - * created on first use and owned by the value; scr_stream.c). The Piped - * form, for pipe destinations and pipeline stages, also keeps the value - * alive until the stream flushes or is destroyed. */ - "crypto.hashStream": { argTypes: [null], result: { kind: "object", className: "%Transform" } }, - "crypto.hashStreamPiped": { - argTypes: [null], - result: { kind: "object", className: "%Transform" }, - }, - /** createCipheriv (false) / createDecipheriv (true) over checked-dynamic - * cipher, key, iv and options arguments (scr_cipher.c): a native - * Cipheriv/Decipheriv handle whose methods dispatch through dynInvoke. */ - "crypto.cipherNew": { argTypes: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - /** createECDH(curveName) over a checked-dynamic curve (scr_ecdh.c): a - * native ECDH handle whose methods dispatch through dynInvoke. */ - "crypto.ecdhNew": { argTypes: [DYN], result: DYN }, - /** ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format): - * the converted point as a Buffer or string. */ - "crypto.ecdhConvertKey": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: DYN }, - /** createDiffieHellman(sizeOrKey, keyEncoding, generator, - * generatorEncoding) over checked-dynamic arguments (scr_dh.c): a native - * DiffieHellman handle whose methods dispatch through dynInvoke. */ - "crypto.dhNew": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - /** getDiffieHellman(groupName) and createDiffieHellmanGroup(name): a - * native DiffieHellmanGroup handle. */ - "crypto.dhGroupNew": { argTypes: [DYN], result: DYN }, - /** checkPrimeSync(candidate, options) and generatePrimeSync(size, - * options) over checked-dynamic arguments (scr_dh.c): a boolean, and an - * ArrayBuffer or bigint, as checked-dynamic results. The Cb forms take - * the callback and whether its error parameter takes null for Node's - * undefined, and call it in a later macrotask; Node's argument errors - * throw (may-throw). */ - "crypto.checkPrimeSync": { argTypes: [DYN, DYN], result: DYN }, - /** getCipherInfo(nameOrNid, options) over checked-dynamic arguments - * (scr_cipher_info.c): the info object or undefined. */ - "crypto.getCipherInfo": { argTypes: [DYN, DYN], result: DYN }, - "crypto.checkPrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "crypto.generatePrimeSync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generatePrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "crypto.hmacNewStr": { argTypes: [STRING, STRING], result: CRYPTOHMAC_T }, - "crypto.hmacNewBytes": { argTypes: [STRING, BYTES_U8], result: CRYPTOHMAC_T }, - "crypto.hashUpdateStr": { argTypes: [CRYPTOHASH_T, STRING], result: CRYPTOHASH_T }, - "crypto.hashUpdateBytes": { argTypes: [CRYPTOHASH_T, BYTES_U8], result: CRYPTOHASH_T }, - "crypto.hmacUpdateStr": { argTypes: [CRYPTOHMAC_T, STRING], result: CRYPTOHMAC_T }, - "crypto.hmacUpdateBytes": { argTypes: [CRYPTOHMAC_T, BYTES_U8], result: CRYPTOHMAC_T }, - "crypto.hashCopy": { argTypes: [CRYPTOHASH_T], result: CRYPTOHASH_T }, - "crypto.hashDigestString": { argTypes: [CRYPTOHASH_T, STRING], result: STRING }, - "crypto.hashDigestBuffer": { argTypes: [CRYPTOHASH_T], result: BYTES_U8 }, - "crypto.hmacDigestString": { argTypes: [CRYPTOHMAC_T, STRING], result: STRING }, - "crypto.hmacDigestBuffer": { argTypes: [CRYPTOHMAC_T], result: BYTES_U8 }, - /** The option and any-value forms, with Node's checks at run time. The - * Dyn digests return a string or a Buffer, and crypto.hash's Dyn form - * either one by its outputEncoding. */ - "crypto.hashNewOpts": { argTypes: [STRING, DYN], result: CRYPTOHASH_T }, - "crypto.hmacNewDyn": { argTypes: [STRING, DYN, DYN], result: CRYPTOHMAC_T }, - "crypto.hashCopyOpts": { argTypes: [CRYPTOHASH_T, DYN], result: CRYPTOHASH_T }, - "crypto.hashUpdateEnc": { argTypes: [CRYPTOHASH_T, STRING, STRING], result: CRYPTOHASH_T }, - "crypto.hmacUpdateEnc": { argTypes: [CRYPTOHMAC_T, STRING, STRING], result: CRYPTOHMAC_T }, - "crypto.hashUpdateDyn": { argTypes: [CRYPTOHASH_T, DYN, DYN], result: CRYPTOHASH_T }, - "crypto.hmacUpdateDyn": { argTypes: [CRYPTOHMAC_T, DYN, DYN], result: CRYPTOHMAC_T }, - "crypto.hashDigestDyn": { argTypes: [CRYPTOHASH_T, DYN], result: DYN }, - "crypto.hmacDigestDyn": { argTypes: [CRYPTOHMAC_T, DYN], result: DYN }, - "crypto.hashOneshot": { argTypes: [STRING, DYN, DYN], result: DYN }, - /** Views of any element kind (the validator checks both are views). */ - "crypto.timingSafeEqual": { argTypes: [null, null], result: BOOL }, - "crypto.timingSafeEqualDyn": { argTypes: [DYN, DYN], result: BOOL }, - "crypto.randomFill": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, - "crypto.randomFillRest": { argTypes: [BYTES_U8, F64], result: BYTES_U8 }, - "crypto.randomInt": { argTypes: [F64, F64], result: F64 }, - "crypto.randomIntMax": { argTypes: [F64], result: F64 }, - /** randomFillSync over any view returns that view at its own type (the - * validator checks the result against the first argument). The Dyn form - * takes ArrayBuffers and other values. The callback forms carry - * randomFill's argument count before the callback; randomIntCb carries - * whether min was given. */ - "crypto.randomFillView": { argTypes: [null, F64, F64], result: BYTES_U8 }, - "crypto.randomFillViewRest": { argTypes: [null, F64], result: BYTES_U8 }, - /** crypto.getRandomValues: a view returns itself at its own type (the - * float/size DOMExceptions are thrown at run time); any other value - * takes the checked form. */ - "crypto.getRandomValues": { argTypes: [null], result: BYTES_U8 }, - "crypto.getRandomValuesDyn": { argTypes: [DYN], result: DYN }, - "crypto.randomFillSyncDyn": { argTypes: [DYN, DYN, DYN], result: DYN }, - "crypto.randomFillCb": { argTypes: [null, F64, F64, F64, null], result: VOID }, - "crypto.randomFillCbDyn": { argTypes: [DYN, DYN, DYN, F64, null], result: VOID }, - "crypto.randomIntCb": { argTypes: [F64, F64, F64, null], result: VOID }, - "crypto.randomUUIDOpts": { argTypes: [DYN], result: STRING }, - /** Secret KeyObjects: native handles whose getters and methods dispatch - * through the handle. keyObjectInput reads a key's bytes for hkdf. */ - "crypto.createSecretKey": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeySync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeyCb": { argTypes: [DYN, DYN, null], result: VOID }, - "crypto.keyObjectInput": { argTypes: [DYN], result: BYTES_U8 }, - "crypto.isKeyObject": { argTypes: [DYN], result: BOOL }, - /** WebCrypto (scr_webcrypto.c): each method takes its argument vector as - * one dyn array and answers a promise that argument and validation - * failures reject. CryptoKey values are native handles. */ - "webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.wrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.unwrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.deriveBits": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.deriveKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.sign": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.verify": { argTypes: [DYN], result: { kind: "promise", inner: BOOL } }, - "webcrypto.digest": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.isCryptoKey": { argTypes: [DYN], result: BOOL }, - "webcrypto.object": { argTypes: [], result: DYN }, - "crypto.randomUUIDv7": { argTypes: [DYN], result: STRING }, - "crypto.pbkdf2": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: BYTES_U8 }, - "crypto.randomBytesCb": { argTypes: [F64, null], result: VOID }, - "crypto.pbkdf2Cb": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING, null], result: VOID }, - "crypto.hkdf": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: DYN }, - "crypto.hkdfCb": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64, null], result: VOID }, - "crypto.scrypt": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, - "crypto.scryptCb": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN, null], result: VOID }, - /** util.promisify over the callback forms: the callback form's - * arguments without the callback → a promise the callback form settles, - * or rejected with an argument error; never throws. The result's value - * type is the declared one (a Buffer, the filled view, a number, or a - * checked-dynamic value); the validator checks it. */ - "crypto.randomBytesPromise": { argTypes: [F64], result: VOID }, - "crypto.pbkdf2Promise": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: VOID }, - "crypto.scryptPromise": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: VOID }, - "crypto.hkdfPromise": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: VOID }, - "crypto.randomFillPromise": { argTypes: [null, F64, F64, F64], result: VOID }, - "crypto.randomFillPromiseDyn": { argTypes: [DYN, DYN, DYN, F64], result: VOID }, - "crypto.randomIntPromise": { argTypes: [F64, F64, F64], result: VOID }, - "crypto.generateKeyPromise": { argTypes: [DYN, DYN], result: VOID }, - /** util.promisify(dns.lookup) and util.promisify(dns.lookupService): - * [hostname, options] and [address, port] → a promise of the - * checked-dynamic result object (the address array with options.all), - * or rejected with the error; never throws. */ - "dns.lookupPromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.lookupServicePromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - // The Buffer statics and the fs/zlib Buffer forms: fixed always-u8 - // signatures (Buffer IS a Uint8Array — one bytes kind). - /** The Buffer statics with fixed (always-u8) signatures. fromStr is - * `Buffer.from(string, enc)` — the frontend completes an omitted - * encoding to "utf8" and fences non-literal/unsupported ones; hex and - * base64 decode Node-leniently, so it never throws. concat takes ONE - * bytes[] arg (the list) and returns a fresh copy. `Buffer.from(u8)` - * and `Buffer.alloc(n)` need no libFn — they lower to bytesNew. */ - "buffer.fromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.fromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.brand": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** Buffer.isBuffer over a typed view: whether the view carries the - * Buffer brand (any element kind; only Buffers do). Never throws. */ - "buffer.isBranded": { argTypes: [null], result: BOOL }, - /** Buffer.from on checked-native strings, bytes, arrays and data-only - * array-like/Buffer-JSON objects. The encoding is a normalized literal; - * non-string inputs ignore it. Copies the input and may throw during - * element coercion or argument validation. Custom object valueOf and - * opaque reference inputs retain explicit runtime refusals. */ - "buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 }, - "buffer.concatDyn": { argTypes: [DYN], result: BYTES_U8 }, - /** The pooled Buffer factories (Node's allocate/fromStringFast/ - * fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays, - * number[] and checked values, and concat return views into the - * per-thread pool slab below Buffer.poolSize >>> 1 bytes. alloc and - * allocUnsafeSlow never pool. The size forms validate like Node - * (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */ - "buffer.alloc": { argTypes: [F64], result: BYTES_U8 }, - /** Buffer.alloc as a function value: the size, fill and encoding arrive - * unchecked, and the runtime fills as Node's alloc does. */ - "buffer.allocValue": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 }, - "buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 }, - "buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.poolFromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 }, - "buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - /** poolFromDyn that also copies an array crossing by reference. */ - "buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "buffer.poolFromDynOffset": { argTypes: [DYN, DYN], result: BYTES_U8 }, - /** Buffer.from(value, byteOffset, length): an ArrayBuffer's range with - * Node's fromArrayBuffer checks; other values as poolFromDynOffset. */ - "buffer.poolFromDynRange": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "buffer.poolSize": { argTypes: [], result: F64 }, - "buffer.setPoolSize": { argTypes: [F64], result: VOID }, - /** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like - * fromStr's — and Buffer.isEncoding(name) over a runtime string - * (case-insensitive against Node's alias set). Pure; never throw. */ - "buffer.byteLenDyn": { argTypes: [DYN, STRING], result: F64 }, - /** Buffer.byteLength as a function value: the encoding arrives at run time. */ - "buffer.byteLenValue": { argTypes: [DYN, DYN], result: F64 }, - "buffer.byteLenStr": { argTypes: [STRING, STRING], result: F64 }, - "buffer.isEncoding": { argTypes: [STRING], result: BOOL }, - /** Buffer.concat(list, totalLength): the concatenation truncated or - * zero-padded to the total. THROWS Node's 'length' RangeError on a - * negative/non-integer total (may-throw seed). */ - "buffer.concatLen": { argTypes: [arrayOf(BYTES_U8), F64], result: BYTES_U8 }, - "buffer.concatLenDyn": { argTypes: [DYN, F64], result: BYTES_U8 }, - // The checked-dynamic compare/equals validators (Node's argument - // ladders over dyn-boxed invalid-input probes). - /** The checked-dynamic compare/equals validators (scr_bytes_io.c) — - * the lowered form when an argument is NOT statically bytes (the - * invalid-input probes; a dyn from an untyped JS helper): Node's own - * argument ladders run at runtime — ERR_INVALID_ARG_TYPE with the - * API's argument name ("buf1"/"buf2", "otherBuffer", "target"; offsets - * "of type number"), validateOffset's ERR_OUT_OF_RANGE for bad - * numbers, undefined offsets taking their Node defaults — and a - * well-typed value still computes the real answer. All args borrowed - * dyn; compareChk's four offset slots pass the undefined dyn when - * syntactically absent. May-throw seeds. */ - "buffer.compareChk": { argTypes: [DYN, DYN], result: F64 }, - "bytes.equalsChk": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "bytes.compareChk": { argTypes: [BYTES_U8, DYN, DYN, DYN, DYN, DYN], result: F64 }, - /** The deprecated `new Buffer(number, 'enc')` string-arm rejection: - * always throws Node's ERR_INVALID_ARG_TYPE ("The \"string\" argument - * must be of type string. Received ..."). Borrowed dyn; may-throw. */ - "buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 }, - "buffer.deprecation": { argTypes: [], result: VOID }, - "buffer.deprecationCount": { argTypes: [], result: VOID }, - "buffer.constructor": { argTypes: [], result: DYN }, - "buffer.installValue": { argTypes: [], result: VOID }, - "buffer.staticValue": { argTypes: [STRING], result: DYN }, - "buffer.prototypeMember": { argTypes: [DYN], result: DYN }, - /** fs._toUnixTimestamp(time) over a dyn value: numeric strings and - * finite numbers coerce (negatives answer now/1000, Node's shape); - * everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */ - "fs.toUnixTimestamp": { argTypes: [DYN], result: F64 }, - /** The fs argument-validation ladders (checked-dynamic lane): each Chk - * replicates its API's Node-order validation over dyn values (Node's - * exact typed errors — ERR_INVALID_ARG_TYPE/VALUE, ERR_OUT_OF_RANGE), - * and a full pass meets the trailing compiler-rendered SC2020 fence - * string — so the ALWAYS-THROW forms take the error.nodeThrow - * polymorphic-result carve-out. mkdtempSyncChk and the lchmod sync/ - * promise pair run the REAL operation on a validated pass instead - * (macOS lchmod(2); non-APPLE answers Node's not-a-function / - * ERR_METHOD_NOT_IMPLEMENTED shapes). May-throw seeds, all of them. */ - "fs.existsChk": { argTypes: [DYN, DYN], result: DYN }, - "fs.mkdtempChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.mkdtempSyncChk": { argTypes: [DYN, DYN, STRING], result: STRING }, - "fs.readFileChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, - "fs.opendirChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.watchFileChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.lchmodChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, - "fs.lchmodSyncChk": { argTypes: [DYN, DYN], result: DYN }, - "fsp.lchmodChk": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fs.readChk": { argTypes: [DYN, DYN, DYN, DYN, DYN, STRING], result: VOID }, - "fs.streamOptsChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - /** fs.ReadStream / fs.WriteStream (scr_fs_stream.c): the constructors - * take the path and options as dyn values and throw Node's option - * errors; the members read bytesRead/bytesWritten, pending and the path - * value, and close() / close(cb) (a `() => void` callback). */ - "fs.createReadStream": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%Readable" }, - }, - "fs.createWriteStream": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%Writable" }, - }, - "fs.fsStreamBytes": { argTypes: [null], result: F64 }, - "fs.fsStreamPending": { argTypes: [null], result: BOOL }, - "fs.fsStreamPath": { argTypes: [null], result: DYN }, - "fs.fsStreamPropDyn": { argTypes: [null, STRING], result: DYN }, - "fileHandle.createReadStream": { - argTypes: [FILEHANDLE_T, DYN], - result: { kind: "object", className: "%Readable" }, - }, - "fileHandle.createWriteStream": { - argTypes: [FILEHANDLE_T, DYN], - result: { kind: "object", className: "%Writable" }, - }, - "fs.fsStreamClose": { argTypes: [null], result: VOID }, - "fs.fsStreamCloseCb": { argTypes: [null, funcOf([], VOID)], result: VOID }, - /** The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME- - * rendered Received tail: args [argname, "of type ..." clause, the - * offending dyn value]. ALWAYS THROWS; polymorphic result (the - * error.nodeThrow pattern). May-throw seed. */ - "error.argTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, - /** The property flavor of argTypeThrow ("The \"options.x\" property - * must be ..."): the option-bag ladders' provably-invalid arms. ALWAYS - * THROWS; polymorphic result. May-throw seed. */ - "error.propTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, - /** The checked-dynamic max-listeners ladders: setMaxChk is the - * instance form over a dyn n (non-numbers ERR_INVALID_ARG_TYPE, - * negatives/NaN ERR_OUT_OF_RANGE; +1 receiver back — chaining); - * setDefaultMaxChk is the static/property form, its second argument - * naming the message slot ("setMaxListeners" for the static call, - * "defaultMaxListeners" for the module-property assignment). */ - "emitter.setMaxChk": { argTypes: [null, DYN], result: VOID }, - "emitter.setDefaultMaxChk": { argTypes: [DYN, STRING], result: VOID }, - /** The Buffer forms of fs: readFileSync(path) with NO - * encoding → bytes (+1), writeFileSync/appendFileSync(path, bytes), and the - * fs/promises readFile(path) no-encoding form (an already-settled - * promise, rejecting on failure like the other fsp members). The sync - * forms THROW catchably on failure exactly like the utf8 forms. */ - "fs.readFileSyncBytes": { argTypes: [STRING], result: BYTES_U8 }, - "fs.writeFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, - "fs.appendFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, - "fsp.readFileBytes": { argTypes: [STRING], result: { kind: "promise", inner: BYTES_U8 } }, - /** node:zlib (scr_zlib.c — native-toolchain.ts compiles/links it ONLY when these - * appear on the IR, the regex/libcurl gating precedent): one-shot zlib, - * raw-DEFLATE, gzip, and auto-detect codecs over u8 bytes with Node's - * default options, or a validated literal compression level from -1 to 9. - * Compression never throws (OOM aborts); decompression - * of corrupt input THROWS Node's error catchably. */ - "zlib.deflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.inflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.deflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** bytes, mode (0 zlib / 1 raw / 2 gzip), validated compression level. */ - "zlib.deflateLevelSync": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, - "zlib.inflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.gzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.gunzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.unzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** The default-options callback twins run on the executable's shared - * native work pool and deliver (Error | null, Buffer) on a later loop - * turn. crc32 validates its optional uint32 seed synchronously. */ - "zlib.deflateCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.inflateCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.deflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.inflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.gzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.gunzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.unzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.crc32": { argTypes: [BYTES_U8, F64], result: F64 }, - /** The one-shot forms with an options value: node_zlib_mode, the input, - * the options (validated at runtime with Node's ladder). The Cb form - * takes the program-shaped (Error | null, Buffer) callback, which moves. */ - "zlib.syncOpts": { argTypes: [F64, BYTES_U8, DYN], result: BYTES_U8 }, - "zlib.cbOpts": { argTypes: [F64, BYTES_U8, DYN, null], result: VOID }, - /** The checked-dynamic input twins (the JS lane's untyped data). */ - "zlib.syncOptsDyn": { argTypes: [F64, DYN, DYN], result: BYTES_U8 }, - "zlib.cbOptsDyn": { argTypes: [F64, DYN, DYN, null], result: VOID }, - /** util.promisify over the callback one-shots: [mode, data, options] - * like zlib.cbOpts → a promise the worker-pool run settles with the - * Buffer or the callback form's Error. An options or input error rejects - * it; never throws. The Dyn twin takes the JS lane's untyped data. */ - "zlib.promise": { argTypes: [F64, BYTES_U8, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "zlib.promiseDyn": { argTypes: [F64, DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - /** The stream classes (scr_zlib_stream.c, linked with the stream unit): - * streamNew takes Node's node_zlib_mode number and the options value - * (undefined when absent), validates it with Node's ladder, and answers - * the +1 %Transform stream. flush takes the kind value, params the level - * and strategy values; the Cb forms move their () => void callback. The - * receivers are borrowed. */ - /** Connects scr_brotli.c to the zlib engine (%main, before any module). */ - "zlib.brotliInstall": { argTypes: [], result: VOID }, - "zlib.streamNew": { argTypes: [F64, DYN], result: VOID }, - "zlib.streamBytesWritten": { argTypes: [null], result: F64 }, - /** `stream instanceof zlib.Gzip`: the borrowed stream and the class's - * node_zlib_mode; true when the stream is a zlib stream of that mode. */ - "zlib.streamIs": { argTypes: [null, F64], result: BOOL }, - "zlib.streamFlush": { argTypes: [null, DYN], result: VOID }, - "zlib.streamFlushCb": { argTypes: [null, DYN, null], result: VOID }, - "zlib.streamParams": { argTypes: [null, DYN, DYN], result: VOID }, - "zlib.streamParamsCb": { argTypes: [null, DYN, DYN, null], result: VOID }, - "zlib.streamClose": { argTypes: [null], result: VOID }, - "zlib.streamCloseCb": { argTypes: [null, null], result: VOID }, - "zlib.streamReset": { argTypes: [null], result: VOID }, - /** The Buffer overloads of the raw stream writes — same promptly - * submitted streams as process.stdoutWrite/stderrWrite, constantly true. - * The encoding arg is evaluated but ignored for bytes, like Node. The Cb - * forms move their program-shaped completion callback to the tick queue. */ - "process.stdoutWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, - "process.stderrWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, - // Completion callback is program-dependent: zero params, checked-dynamic, - // or an optional Error | null slot (same success shape as fs.rename). - "process.stdoutWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, - "process.stderrWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, - /** The value of a FULFILLED promise, read without a microtask turn (+1 - * for references): the tuple form of Promise.all reads its entries this - * way once the combinator fulfills, each entry widened to Promise - * and read at the type it was built with (program-dependent). */ - "promise.fulfilledValue": { argTypes: [null], result: VOID }, - /** Promise.all's tuple form as a value: the entries (each widened to - * Promise) join the runtime combinator, and when it fulfills the - * function reads the fulfilled entries into the tuple inside that - * settlement, so the result settles on the combinator's turn; a - * rejection settles it the same way. The result is the tuple's promise - * (program-dependent). */ - "promise.allTuple": { argTypes: [null, null], result: VOID }, - "fsp.readFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, - "fsp.writeFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - /** fs.promises.writeFile(path, data, { mode }): the settled-promise - * twin of fs.writeFileModeSync. */ - "fsp.writeFileMode": { - argTypes: [STRING, STRING, F64], - result: { kind: "promise", inner: VOID }, - }, - "fsp.mkdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - /** The fs/promises option/member tail the certs pipeline uses: mkdir's - * literal { recursive?, mode? } options (the mkdirSync matrix behind - * settled promises), unlink, chmod. Failures REJECT (catchable at the - * await), like the rest of the fsp family. */ - "fsp.mkdirMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.mkdirRecursive": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.mkdirRecursiveMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.unlink": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.chmod": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.chown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, - "fsp.lchown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, - "fsp.access": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.truncate": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.appendFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.appendFileBytes": { argTypes: [STRING, BYTES_U8], result: { kind: "promise", inner: VOID } }, - "fsp.copyFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.mkdtemp": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, - /** readFile(path, encoding | { encoding }) for any runtime encoding name. */ - "fsp.readFileEncoded": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, - "fsp.rmdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.link": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.symlink": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.cp": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.readlink": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "fsp.statfs": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "fsp.readlinkStr": { argTypes: [DYN, DYN], result: { kind: "promise", inner: STRING } }, - "fsp.readlinkBuffer": { argTypes: [DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "fsp.utimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.lutimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.rename": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.readdir": { argTypes: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } }, - // Result is promise; the libCall case - // validates the program-dependent record shape in validate.ts. - /** `fs.promises.readdir(path, { withFileTypes: true })` — the settled- - * promise twin of fs.readdirTypesSync. The backends assemble the same - * call-site-shaped Dirent rows, then move the array into a promise; - * scandir failure becomes its rejection. */ - "fsp.readdirTypes": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.rm": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - /** `fs.promises.rm(path, options)`: fs.rmOptsSync and fs.rmRetrySync - * behind a settled promise; their errors become its rejection. */ - "fsp.rmOpts": { argTypes: [STRING, BOOL, BOOL], result: { kind: "promise", inner: VOID } }, - "fsp.rmRetry": { - argTypes: [STRING, BOOL, BOOL, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - "fsp.stat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, - "fsp.realpath": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, - "fsp.lstat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, - /** fs/promises.open and the statically represented FileHandle surface. - * Every operation returns an already-settled promise; syscall failures - * become rejections rather than escaping synchronously. read/write - * results are call-site record shapes ({ bytesRead/bytesWritten, - * buffer }) assembled by the backends around the fixed runtime ABI. */ - "fsp.open": { argTypes: [STRING, STRING, F64], result: { kind: "promise", inner: FILEHANDLE_T } }, - "fileHandle.fd": { argTypes: [FILEHANDLE_T], result: F64 }, - "fileHandle.close": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.sync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.datasync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.truncate": { - argTypes: [FILEHANDLE_T, F64], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.chmod": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } }, - "fileHandle.utimes": { - argTypes: [FILEHANDLE_T, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.readv": { - argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writev": { - argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], - result: { kind: "promise", inner: VOID }, - }, - // read/write carry call-site result record shapes; the validator checks - // those in validate.ts, so promise is only a table sentinel. - "fileHandle.read": { - argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeBytes": { - argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeStr": { - argTypes: [FILEHANDLE_T, STRING, F64, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.readFile": { - argTypes: [FILEHANDLE_T, STRING], - result: { kind: "promise", inner: STRING }, - }, - "fileHandle.readFileBytes": { - argTypes: [FILEHANDLE_T, STRING], - result: { kind: "promise", inner: BYTES_U8 }, - }, - "fileHandle.writeFile": { - argTypes: [FILEHANDLE_T, STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeFileBytes": { - argTypes: [FILEHANDLE_T, BYTES_U8, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.stat": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: STATS_T } }, - "process.argv": { argTypes: [], result: arrayOf(STRING) }, - // The Node runtime flag a node:test binary took out of process.argv - // (--test-update-snapshots), or [] — a fresh array per read. - "process.execArgv": { argTypes: [], result: arrayOf(STRING) }, - "process.platform": { argTypes: [], result: STRING }, - // The one libCall whose result type is program-dependent (union ids are - // per-module): the `result` here is a placeholder — the libCall case - // checks the union's ARMS ([string, undefinedT] in canonical order) - // against the module's registry instead. - /** getenv(3): one string key arg → the interned `string | undefined` - * union (present: +1 string wrapped into the string arm; absent: the - * interned undefined-arm instance). BOTH source forms — `process.env.FOO` - * and `process.env[expr]` — lower here. Purely static; never throws. */ - "process.envGet": { argTypes: [STRING], result: VOID }, - /** setenv(3): (name, value) string args → void. Later envGet reads and - * spawned children observe the write, like Node (values are strings — - * the frontend fences non-string RHS). Never throws. */ - "process.envSet": { argTypes: [STRING, STRING], result: VOID }, - /** `delete process.env[NAME]` — unsetenv(3): the mutation is visible to - * every later read (process.envGet asks getenv fresh) and inherited by - * spawned children, exactly Node. Statement position only (JS's boolean - * result is constant true there). Borrows the name; never throws. */ - "process.envUnset": { argTypes: [STRING], result: VOID }, - /** The whole environment as alternating [k0, v0, k1, v1, ...] strings in - * environ order — the raw material of the process.env SNAPSHOT record - * (the frontend's interned %env.snapshot helper keyed-writes the pairs - * into a fresh `{ [k: string]: string | undefined }` record). Fresh +1 - * array; never throws. */ - "process.envPairs": { argTypes: [], result: arrayOf(STRING) }, - "process.exit": { argTypes: [F64], result: VOID }, - /** Numeric process.exitCode write (integer validation, implicit exit status). */ - "process.setExitCode": { argTypes: [F64], result: VOID }, - /** The process.exitCode write of a program that also reads the - * property: the same validation, and the code counts as assigned. */ - "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, - /** The code for process.exit() with no argument, or zero when unset. */ - "process.currentExitCode": { argTypes: [], result: F64 }, - /** Whether process.exitCode reads a code rather than undefined. Never throws. */ - "process.hasExitCode": { argTypes: [], result: BOOL }, - "process.cwd": { argTypes: [], result: STRING }, - /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both - * always answer (the checker's `getuid?` optionality covers Windows — - * `process.getuid?.()` lowers as the plain call). Never throw. */ - "process.pid": { argTypes: [], result: F64 }, - "dyn.this": { argTypes: [], result: DYN }, - "dyn.generatorThis": { argTypes: [], result: DYN }, - "process.getuid": { argTypes: [], result: F64 }, - "process.getgid": { argTypes: [], result: F64 }, - /** geteuid(2) / getegid(2) / getgroups(2) (the effective gid appended - * when the list lacks it, like Node). Windows has no such members: the - * calls throw the TypeError a missing member does (MAY THROW). */ - "process.geteuid": { argTypes: [], result: F64 }, - "process.getegid": { argTypes: [], result: F64 }, - "process.getgroups": { argTypes: [], result: arrayOf(F64) }, - /** process.ppid: the parent's pid at the read. Never throws. */ - "process.ppid": { argTypes: [], result: F64 }, - /** process.title: libuv's process title (argv[0] until set); setTitle - * writes it (truncated to the original argument area on POSIX, the - * console title on Windows). Never throw. */ - "process.title": { argTypes: [], result: STRING }, - "process.setTitle": { argTypes: [STRING], result: VOID }, - /** process.argv0: the original argv[0]. Never throws. */ - "process.argv0": { argTypes: [], result: STRING }, - /** process.release: the compatibility target's stable release object - * (name, lts, sourceUrl, headersUrl, and libUrl on Windows). +1. */ - "process.release": { argTypes: [], result: DYN }, - /** process.execPath: the compiled binary's own resolved absolute path - * (one interned string, +1 per read) — the honest answer where Node's - * is the node executable's (SEMANTICS.md divergence 12). Never throws. */ - "process.execPath": { argTypes: [], result: STRING }, - /** process.arch: the compiled binary's OWN architecture ("arm64", - * "x64") — Node's answer for its own build on the same machine. - * Interned; +1 per read. Never throws. */ - "process.arch": { argTypes: [], result: STRING }, - /** Stable native version dictionary. node/openssl identify compatibility - * targets; components absent from the native runtime are omitted. */ - "process.versions": { argTypes: [], result: DYN }, - "process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING }, - "process.builtinModule": { argTypes: [STRING, DYN], result: DYN }, - "process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN }, - /** A module namespace object as a checked-dynamic value - * (scr_module_namespace_value): the module id, its sorted export names - * and the compiled getter. One object per module. Never throws. */ - "module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN }, - "fs.callbackValue": { argTypes: [STRING], result: DYN }, - "fs.callbackCall": { argTypes: [STRING, DYN], result: DYN }, - "process.hrtimeValue": { argTypes: [], result: DYN }, - /** process.versions.node: the runtime's Node COMPATIBILITY TARGET — - * there is no Node under the binary, so this reports the version whose - * semantics the runtime implements (SEMANTICS.md divergence 60, the - * execPath stance). Interned; +1 per read. Never throws. */ - "process.versionsNode": { argTypes: [], result: STRING }, - "process.versionsOpenssl": { argTypes: [], result: STRING }, - /** kill(2) with Node's exact semantics and error shapes: the pid must be - * an int32 (else the ERR_INVALID_ARG_TYPE TypeError text), the named - * form resolves Node's signal-name table (unknown names throw the - * ERR_UNKNOWN_SIGNAL TypeError), an omitted signal completes to - * "SIGTERM" in the frontend, signal 0 probes, and a kill(2) failure - * throws Node's `kill ESRCH`/`kill EPERM` Error. Result is Node's - * constant true. */ - "process.kill": { argTypes: [F64, STRING], result: BOOL }, - "process.killNum": { argTypes: [F64, F64], result: BOOL }, - /** The raw byte writes: one borrowed string arg → bool (constantly true - * — this synchronous runtime never queues backpressure). stdoutWrite - * shares console.log's stream, each call is submitted promptly, and - * interleaved output keeps source order. No newline, no formatting, - * never throws. */ - "process.stdoutWrite": { argTypes: [STRING], result: BOOL }, - "process.stdio": { argTypes: [F64], result: DYN }, - "process.stderrWrite": { argTypes: [STRING], result: BOOL }, - // error.new's result and the receiver slots are builtin-error classes — - // program-dependent object types, checked in the libCall case. - /** The runtime-provided Error hierarchy's entry points (scr_error.c). - * error.new: one borrowed string arg (the message), result an owned (+1) - * builtin error instance — the result TYPE names which builtin class - * (backends derive the runtime kind from it). error.ctor: the - * super(message) call of an `extends Error` constructor — borrowed - * receiver (already allocated by the derived class's new) + borrowed - * message, void; the receiver's type names the builtin class whose name - * field to stamp. error.toString: borrowed `%Error`-typed receiver, +1 - * string in Node's "name: message" shape. Formatting can invoke a user - * message accessor, which can throw. */ - "error.new": { argTypes: [STRING], result: VOID }, - /** ECMAScript constructors with raw checked-dynamic message/options. - * Options retain cause presence; constructor calls borrow their args. - * Message coercion may throw before the cause is installed. */ - "error.newOptions": { argTypes: [DYN, DYN], result: VOID }, - "error.ctorOptions": { argTypes: [null, DYN, DYN], result: VOID }, - /** Borrowed Error receiver. cause returns an owned dyn value (undefined - * when absent); hasCause distinguishes absence from present undefined. */ - "error.cause": { argTypes: [null], result: DYN }, - "error.hasCause": { argTypes: [null], result: BOOL }, - /** Assignment borrows both operands and retains the new cause. */ - "error.setCause": { argTypes: [null, DYN], result: VOID }, - /** `err.code = s` on an error-hierarchy receiver: the code slot that - * error.code reads and coded throws stamp. Borrows both; never throws. */ - "error.setCode": { argTypes: [null, STRING], result: VOID }, - /** `err.code = v` with a value that is not statically a string: strings - * take the code slot, anything else clears it and sets the dynamic own - * property. Borrows both; never throws. */ - "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, - /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver - * (args: the error, a dyn array of the sources): the own-member copy - * onto its checked-dynamic node, then the typed slots (name, message, - * stack, cause, code) re-read from it. Throws what the copy throws. */ - "error.assign": { argTypes: [null, DYN], result: VOID }, - /** `key in err` for a key no compiled class of the receiver declares - * (args: the error, the key): a runtime-set own property (Object.assign, - * throw-site fields, dynamic writes) or an Object.prototype member. */ - "error.hasKey": { argTypes: [null, STRING], result: BOOL }, - "error.hasCode": { argTypes: [null], result: BOOL }, - /** Whether the error's own `name` or `message` (the key) is an accessor - * a reflective definition installed. Borrows both; never throws. */ - "error.ownAccessor": { argTypes: [null, STRING], result: BOOL }, - /** That accessor's getter called on the error (owned result), and its - * setter called with the value (V8's TypeError without one). Borrow - * their arguments; throw what the accessor throws. */ - "error.accessorGet": { argTypes: [null, STRING], result: DYN }, - "error.accessorSet": { argTypes: [null, STRING, DYN], result: VOID }, - "error.defineCause": { argTypes: [null, DYN], result: VOID }, - "error.deleteCause": { argTypes: [null], result: VOID }, - /** The compiler-resolved Node-parity throw for always-throwing lowered - * arms (ERR_INVALID_THIS receivers, ERR_MISSING_ARGS arity ladders, - * the symbol-to-string TypeError): args are [error-kind f64 (the - * SCR_ERR_* index: 0 Error, 1 TypeError, 2 RangeError), code (empty = - * no code slot), message]. ALWAYS THROWS catchably; the result type is - * the replaced expression's own (never materialized — the - * global.undefRead pattern). May-throw seed. */ - "error.nodeThrow": { argTypes: [F64, STRING, STRING], result: VOID }, - /** JS ToString over a dyn value WITH the object protocol (a user - * toString/valueOf member is CALLED and its throw propagates; - * exhaustion throws "Cannot convert object to primitive value"; units - * render "null"/"undefined") — the WHATWG USVString conversions - * (URLSearchParams names/values). Borrowed dyn; +1 string. May-throw. */ - "dyn.toStringCoerce": { argTypes: [DYN], result: STRING }, - "dyn.stringConstructor": { argTypes: [DYN], result: STRING }, - "dyn.propertyKey": { argTypes: [DYN], result: DYN }, - "dyn.toNumeric": { argTypes: [DYN], result: DYN }, - "dyn.increment": { argTypes: [DYN, BOOL], result: DYN }, - /** JS ToNumber over a dyn value WITH the object protocol (number-hint - * valueOf/toString ordering; user throws propagate). Borrowed dyn; - * f64 result, or a throw. Used by statically lowered numeric coercions - * whose checker type remained any. */ - "dyn.numberConstructor": { argTypes: [DYN], result: F64 }, - "dyn.bigintConstructor": { argTypes: [DYN], result: BIGINT_T }, - "dyn.toNumberCoerce": { argTypes: [DYN], result: F64 }, - "dyn.add": { argTypes: [DYN, DYN], result: DYN }, - "dyn.arithmetic": { argTypes: [DYN, DYN, STRING], result: DYN }, - "dyn.compare": { argTypes: [DYN, DYN, STRING], result: BOOL }, - "dyn.bitwise": { argTypes: [DYN, DYN, STRING], result: DYN }, - "dyn.proxyNew": { argTypes: [DYN, DYN], result: DYN }, - /* Dynamic-tier values (jsval; two-tier design stage S3). The jsval pass - * (ir/jsval.ts) introduces these in dynamic-tier functions only; the - * runtime is scr_js_value.c. Arguments are borrowed and results owned, - * like every libCall. Boundary conversions: js.fromDyn (a cell keeps its - * identity), js.toDyn (an immediate is boxed), js.fromF64/fromBool/ - * fromStr/fromBigInt (the design's toJs: immediates, and strings and - * BigInts under a tag without a copy) and js.toF64 (fromJs to number: dynCheck's - * number check and TypeError). The operators are the dyn.* operators - * over jsvals, with identical JS semantics. */ - "js.fromDyn": { argTypes: [DYN], result: JSVAL }, - "js.toDyn": { argTypes: [JSVAL], result: DYN }, - "js.fromF64": { argTypes: [F64], result: JSVAL }, - "js.fromBool": { argTypes: [BOOL], result: JSVAL }, - "js.fromStr": { argTypes: [STRING], result: JSVAL }, - "js.fromBigInt": { argTypes: [BIGINT_T], result: JSVAL }, - "js.toF64": { argTypes: [JSVAL], result: F64 }, - "js.add": { argTypes: [JSVAL, JSVAL], result: JSVAL }, - "js.arithmetic": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, - "js.bitwise": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, - "js.compare": { argTypes: [JSVAL, JSVAL, STRING], result: BOOL }, - "js.toNumeric": { argTypes: [JSVAL], result: JSVAL }, - "js.increment": { argTypes: [JSVAL, BOOL], result: JSVAL }, - "js.toNumberCoerce": { argTypes: [JSVAL], result: F64 }, - // Always throws; the result is the READ's declared type (a typed dummy - // the unwind abandons) — the libCall case skips the result check. - /** A read of a `declare`d const NOTHING defines (the bundler-define - * pattern — __VERSION__): always throws the catchable ReferenceError - * Node raises at the access (" is not defined"). args[0] is the - * name; the result type is the read's declared type (a typed dummy the - * unwind abandons — the value never exists). */ - "global.undefRead": { argTypes: [STRING], result: VOID }, - "console.native": { argTypes: [], result: DYN }, - /** node:console Console instances (scr_console_native.c): new mints the - * handle over the stdout/stderr fds (1/2; 0 = a stream binds next) and - * the ignoreErrors flag; bind attaches a borrowed writable stream to - * target 0 (stdout) or 1 (stderr); write prints `text\n` to the - * selected target of an instance or the global console handle. */ - "console.new": { argTypes: [F64, F64, BOOL], result: DYN }, - "console.bind": { argTypes: [DYN, F64, null], result: VOID }, - "console.write": { argTypes: [DYN, STRING, BOOL], result: VOID }, - /** The global console's remaining members (scr_console_native.c; the - * group indentation itself lives with the writers in scr_console.c). - * Arguments are borrowed; string results are owned. noop is the - * emitter-dropped tail of a lowering whose effects sit in statements. - * label is `${label}` over a defaulted label (undefined -> "default"; - * a Symbol throws Node's TypeError). count prints "label: n". - * countReset answers the warning text for an unknown (or non-string) - * label, "" otherwise. time answers the duplicate-label warning text - * or ""; timeEnd/timeLog answer Node's formatted elapsed time, or "" - * for an unknown label (timeEnd drops the timer). group adds (+1) or - * removes (-1) one two-space indentation step. clear writes the - * cursor-home and erase-down sequences when stdout is a terminal. - * table renders and prints Node's table for a dyn value (undefined - * properties = all). dirDyn is console.dir's inspect of a dyn value - * (custom inspection off: a Buffer anywhere inside throws the fence). */ - "console.noop": { argTypes: [], result: VOID }, - "console.label": { argTypes: [DYN], result: STRING }, - "console.count": { argTypes: [STRING], result: VOID }, - "console.countReset": { argTypes: [DYN], result: STRING }, - "console.time": { argTypes: [STRING], result: STRING }, - "console.timeEnd": { argTypes: [STRING], result: STRING }, - "console.timeLog": { argTypes: [STRING], result: STRING }, - "console.group": { argTypes: [F64], result: VOID }, - "console.clear": { argTypes: [], result: VOID }, - "console.table": { argTypes: [DYN, DYN], result: VOID }, - "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, - /** A native reference to the global object; known names retain value fences. */ - "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, - // `X.name` through a class value: the arg is a program-dependent - // classval (a null slot; the libCall case checks the kind). - /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed - * classval; the result is the class object's stored .name string, - * retained (+1 — the string is an interned immortal, so the retain is a - * no-op, kept for ownership uniformity). Never throws. A direct - * `C.name` on the class name itself folds to a strLit instead. */ - "class.name": { argTypes: [null], result: STRING }, - "error.ctor": { argTypes: [null, STRING], result: VOID }, - "error.toString": { argTypes: [null], result: STRING }, - /** SameValue over checked values, including NaN, signed zero and reference identity. */ - "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, - "error.stack": { argTypes: [null], result: STRING }, - "error.setStack": { argTypes: [null, STRING], result: VOID }, - "error.stackLimitGet": { argTypes: [], result: F64 }, - "error.stackLimitSet": { argTypes: [F64], result: VOID }, - // Receiver (any error-hierarchy object) and the program-dependent - // `string | undefined` result are checked in the libCall case. - /** NodeJS.ErrnoException's `.code` read: borrowed error-hierarchy - * receiver → the interned `string | undefined` union (type-directed - * construction in the backend, the process.envGet pattern) — the errno - * name where a throw site stamped one (fs, exec spawn/timeout, - * process.kill, the spawn 'error' event), the undefined arm everywhere - * else. Never throws. */ - "error.code": { argTypes: [null], result: VOID }, - // DOMException: construction takes the two dyn args (WebIDL's - // resolution runs in the runtime); the %DOMException result is a - // program-dependent object type, checked in the libCall case. The - // read surface takes the %DOMException receiver (a null slot — the - // libCall case checks the class name). - /** `new DOMException(message?, nameOrOptions?)` (scr_error.c): both args - * are borrowed dyn values (the lowering passes the dyn undefined for an - * absent argument, so WebIDL's optionality lives in one place). The - * runtime ToStrings the message ("" for absent/undefined), resolves the - * name — absent/undefined → "Error", a non-null object → ToString of its - * `name` member plus the `cause` own-property record, anything else → - * ToString — and stamps the legacy code from the WebIDL name table (0 - * off-table). Result is an owned (+1) %DOMException. Never throws (dyn - * ToString is total). */ - "error.newDom": { argTypes: [DYN, DYN], result: VOID }, - /** DOMException's own read surface (scr_error.c; %DOMException receivers - * only — borrowed). domCode: the legacy numeric code. domHasCause: the - * options form's own-property record (`'cause' in e`). domCause: the - * cause value, +1 (the dyn undefined when absent — matching Node's - * undefined read). None throws. */ - "error.domCode": { argTypes: [null], result: F64 }, - "error.domHasCause": { argTypes: [null], result: BOOL }, - "error.domCause": { argTypes: [null], result: DYN }, - /** structuredClone of a %DOMException receiver (scr_error.c): WebIDL - * serialization — name/message copy, the legacy code re-derives, cause - * does not serialize. args are the borrowed receiver and the borrowed - * options dyn value (the dyn undefined when absent — the shared - * validation throws Node's exact option errors; any non-empty transfer - * list throws DataCloneError, nothing static is transferable). Result - * +1 %DOMException. */ - "error.domClone": { argTypes: [null, DYN], result: VOID }, - /** `new AggregateError(errors, message?, options?)` (scr_json.c), in - * spec order: newAggregate installs the message and cause (both dyn, - * borrowed — the message conversion can throw) and returns an owned - * %AggregateError; aggregateSetErrors then stores the iterated list - * (borrowed receiver, the dyn array borrowed and retained). - * aggregateErrors reads the list (+1, the same array every read). */ - "error.newAggregate": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%AggregateError" }, - }, - "error.aggregateSetErrors": { argTypes: [null, DYN], result: VOID }, - "error.aggregateErrors": { argTypes: [null], result: DYN }, - /** `new SuppressedError(error, suppressed, message?)` (scr_json.c): all - * three dyn and borrowed; the message conversion can throw. Returns an - * owned %SuppressedError. */ - "error.newSuppressed": { - argTypes: [DYN, DYN, DYN], - result: { kind: "object", className: "%SuppressedError" }, - }, - /** `d instanceof TypeError` (and the other BUILTIN error classes) on a - * checked-dynamic value (scr_json.c): the from_error cache holds the - * dyn↔error identity edge, so the test resolves the runtime error and - * asks its vtable's stamped preorder interval — exact for every error - * that crossed the boundary. A dyn object that never came from an - * error (a hand-built {%error} literal) answers false: subclass - * identity is unknowable there (the root keeps dynTest's marker - * answer). args are the borrowed dyn and the SCR_ERR_* kind literal. - * Never throws. */ - "dyn.errInstanceof": { argTypes: [DYN, F64], result: BOOL }, - "weakMap.is": { argTypes: [DYN], result: BOOL }, - "weakSet.is": { argTypes: [DYN], result: BOOL }, - "weakMap.new": { argTypes: [DYN], result: DYN }, - "weakSet.new": { argTypes: [DYN], result: DYN }, - "finalizationRegistry.new": { argTypes: [DYN], result: DYN }, - "finalizationRegistry.is": { argTypes: [DYN], result: BOOL }, - "dyn.fromEntries": { argTypes: [DYN], result: DYN }, - "bytes.constructor": { argTypes: [STRING], result: DYN }, - "bytes.instanceOf": { argTypes: [DYN, DYN, STRING], result: BOOL }, - "bytes.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, - /** The ambient receiver — JS `this` in a plain (non-method) function - * body: the innermost binding the current firing/dispatch window - * pushed (Node's listener receiver, a dyn OBJ method's object, an - * apply/call thisArg), or the undefined dyn singleton with none bound - * (the strict-mode plain-call answer, the old constant). Zero args → - * dyn (+1). Never throws. */ - "dyn.dataViewIs": { argTypes: [DYN], result: BOOL }, - "dyn.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, - "arrayBuffer.constructor": { argTypes: [], result: DYN }, - "sharedArrayBuffer.new": { argTypes: [DYN], result: DYN }, - "sharedArrayBuffer.is": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.new": { argTypes: [DYN], result: DYN }, - "ffi.argument": { argTypes: [DYN, STRING], result: DYN }, - "ffi.memoryModule": { argTypes: [DYN], result: DYN }, - "intl.segmenterNew": { argTypes: [], result: DYN }, - "intl.segmenterRefusing": { argTypes: [STRING], result: DYN }, - "arrayBuffer.is": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.isView": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 }, - "arrayBuffer.byteLengthDescriptor": { argTypes: [DYN], result: DYN }, - "arrayBuffer.viewU8C": { argTypes: [DYN, DYN, DYN], result: bytesOf("u8c") }, - "arrayBuffer.viewI8": { argTypes: [DYN, DYN, DYN], result: bytesOf("i8") }, - "arrayBuffer.viewU16": { argTypes: [DYN, DYN, DYN], result: bytesOf("u16") }, - "arrayBuffer.viewI16": { argTypes: [DYN, DYN, DYN], result: bytesOf("i16") }, - "arrayBuffer.viewU8": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "arrayBuffer.viewU32": { argTypes: [DYN, DYN, DYN], result: bytesOf("u32") }, - "arrayBuffer.viewI32": { argTypes: [DYN, DYN, DYN], result: bytesOf("i32") }, - "arrayBuffer.viewF16": { argTypes: [DYN, DYN, DYN], result: bytesOf("f16") }, - "arrayBuffer.viewF32": { argTypes: [DYN, DYN, DYN], result: bytesOf("f32") }, - "arrayBuffer.viewF64": { argTypes: [DYN, DYN, DYN], result: bytesOf("f64") }, - "arrayBuffer.viewDV": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - /** Object.keys/values/entries over a CHECKED-DYNAMIC receiver - * (scr_json.c): the runtime walks the dyn node's own members in JS - * own-key order (array-index keys ascending first, then insertion - * order) and answers a dyn array (entries: an array of [key, value] - * pairs; values RETAIN the member nodes — reference semantics, like - * JS). Strings/arrays/bytes answer their index keys; other scalars an - * empty array; null/undefined throw Node's catchable TypeError - * ("Cannot convert undefined or null to object"). */ - "dyn.objKeys": { argTypes: [DYN], result: DYN }, - "dyn.forInKeys": { argTypes: [DYN], result: DYN }, - "dyn.forInKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, - "dyn.forInStable": { argTypes: [DYN], result: BOOL }, - "dyn.objKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, - /** Object.entries as a native [string, any][] (VOID: the tuple row's - * shape is program-dependent; the validator checks it). */ - "dyn.objEntryPairs": { argTypes: [DYN], result: VOID }, - "dyn.hasOwn": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.propertyIsEnumerable": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.assign": { argTypes: [DYN, DYN], result: DYN }, - "dyn.copyDataProperties": { argTypes: [DYN, DYN], result: DYN }, - "dyn.objectRest": { argTypes: [DYN, DYN], result: DYN }, - /** Variadic Object.assign over CHECKED-DYNAMIC targets (`Object.assign( - * {}, ...arr.map(f), tail)` — the option-table merge): the lowering - * builds one fresh dyn pack of sources (packPush retains a plain source - * in; packPushSpread flattens a spread source through the spread-call - * walk — V8's exact TypeError texts, the string arg spelling the spread - * expression for the nullish form), so every source evaluates and - * flattens BEFORE any copying (JS's ArgumentListEvaluation), then - * assignAll copies each pack element's own enumerable keys onto the - * target left to right and answers the TARGET (+1) — identity, like JS. - * assignAll throws Node's ToObject TypeError on a nullish target. */ - "dyn.packPush": { argTypes: [DYN, DYN], result: VOID }, - "dyn.packPushSpread": { argTypes: [DYN, DYN, STRING], result: VOID }, - /** The iterated-path spread twin: V8 spells the optimized apply-path - * texts (packPushSpread's — the expression named for nullish sources) - * only for the SINGLE LAST argument's spread; any other spread position - * drives the real iterator protocol, whose failure describes the VALUE - * ("object null is not iterable (cannot read property - * Symbol(Symbol.iterator))"). The frontend picks by position. */ - "dyn.packPushSpreadIter": { argTypes: [DYN, DYN], result: VOID }, - "dyn.reflectApply": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.reflectGet": { argTypes: [DYN, DYN, DYN], result: DYN }, - /** `using` / `await using` with a dyn value (scr_json.c): usingMethod - * runs the declaration's checks and answers what usingDispose calls at - * scope exit (undefined for a null or undefined value). The bool selects - * `await using`. Both throw Node's TypeErrors or the method's error. */ - "dyn.usingMethod": { argTypes: [DYN, BOOL], result: DYN }, - "dyn.usingDispose": { argTypes: [DYN, DYN], result: DYN }, - /** DisposableStack (scr_symbol.c): a new stack handle; whether a value is - * one; and `stack.use(value)` for a class instance, registering the - * compiler's disposal function for it. The last throws Node's - * ReferenceError on a disposed stack. */ - "disposable.stackNew": { argTypes: [], result: DYN }, - "disposable.stackIs": { argTypes: [DYN], result: BOOL }, - "disposable.stackUseCallback": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "dyn.reflectSet": { argTypes: [DYN, DYN, DYN, DYN], result: BOOL }, - "dyn.reflectDefine": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "dyn.abstractEq": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.assignAll": { argTypes: [DYN, DYN], result: DYN }, + "math.trunc": { argTypes: [F64], result: F64 }, + "math.ceil": { argTypes: [F64], result: F64 }, + "math.sin": { argTypes: [F64], result: F64 }, + "math.sinh": { argTypes: [F64], result: F64 }, + "math.cos": { argTypes: [F64], result: F64 }, + "math.cosh": { argTypes: [F64], result: F64 }, + "math.tan": { argTypes: [F64], result: F64 }, + "math.tanh": { argTypes: [F64], result: F64 }, + "math.asin": { argTypes: [F64], result: F64 }, + "math.asinh": { argTypes: [F64], result: F64 }, + "math.acos": { argTypes: [F64], result: F64 }, + "math.acosh": { argTypes: [F64], result: F64 }, + "math.atan": { argTypes: [F64], result: F64 }, + "math.atanh": { argTypes: [F64], result: F64 }, + "math.cbrt": { argTypes: [F64], result: F64 }, + "math.clz32": { argTypes: [F64], result: F64 }, + /** Return -1, +1, or the original NaN/zero. In particular, -0 remains -0. */ + "math.sign": { argTypes: [F64], result: F64 }, + "math.exp": { argTypes: [F64], result: F64 }, + "math.expm1": { argTypes: [F64], result: F64 }, + "math.f16round": { argTypes: [F64], result: F64 }, + "math.fround": { argTypes: [F64], result: F64 }, + "math.sqrt": { argTypes: [F64], result: F64 }, + "math.log": { argTypes: [F64], result: F64 }, + "math.log1p": { argTypes: [F64], result: F64 }, + "math.log2": { argTypes: [F64], result: F64 }, + "math.log10": { argTypes: [F64], result: F64 }, + "math.atan2": { argTypes: [F64, F64], result: F64 }, + "math.pow": { argTypes: [F64, F64], result: F64 }, + "math.imul": { argTypes: [F64, F64], result: F64 }, + /** `Math.min(a, b)` / `Math.max(a, b)` — the two-argument scalar forms + * (scr_lib.c), JS-exact like the Arr folds: NaN poisons, max prefers +0 + * over -0 (min the reverse). C fmin/fmax are NOT these (they drop NaN). + * Never throw. */ + "math.min": { argTypes: [F64, F64], result: F64 }, + "math.max": { argTypes: [F64, F64], result: F64 }, + /** `Math.random()` — a uniform double in [0,1) with the spec's 53-bit + * granularity, drawn from arc4random_buf (the CSPRNG behind the crypto + * lowerings). Same distribution as Node, NECESSARILY different sequence + * (SEMANTICS.md 62 — no seeded sequence exists to match). Never throws. */ + "math.random": { argTypes: [], result: F64 }, + /** The static global parsers/tests (scr_string.c). num.parseInt is + * ECMA-262 19.2.5 exactly — JS whitespace, sign, ToInt32 radix (the + * frontend completes an omitted radix to 0 = the spec's "undefined": + * base 10 with the 0x hex escape), longest digit prefix, and the exact + * mathematical value correctly rounded (u64 fast path, bignum beyond — + * overflow is ±Infinity). num.isNaN is the NaN self-test on an + * already-number argument (tsc pins the argument to number, so no + * ToNumber coercion exists to model). Borrow; never throw. */ + "num.parseInt": { argTypes: [STRING, F64], result: F64 }, + /** ES parseFloat (scr_string.c): the longest StrDecimalLiteral prefix + * of the trimmed input (no hex, "Infinity" exact-case), NaN when none — + * ECMA-262 19.2.4 over a string argument (non-string arguments keep the + * fence: Node would ToNumber-coerce). Borrows; never throws. */ + "num.parseFloat": { argTypes: [STRING], result: F64 }, + /** `a.localeCompare(b)`: -1/0/1 in Node's root-locale collation for + * Latin-script text (scr_str_locale_compare). Never throws. */ + "str.localeCompare": { argTypes: [STRING, STRING], result: F64 }, + /** Annex B substr(start, length) with start ToNumber'd and length's + * presence; never throws. */ + "str.substr": { argTypes: [STRING, F64, F64, BOOL], result: STRING }, + /** toLocaleLowerCase/toLocaleUpperCase (scr_regex.c): the locales value + * as written, upper=true for toLocaleUpperCase. Throws for null and for + * locale forms with no native rules. */ + "str.localeCase": { argTypes: [STRING, DYN, BOOL], result: STRING }, + "str.replaceLiteral": { argTypes: [STRING, STRING, STRING, BOOL], result: STRING }, + // (subject, characters, NUL-separated replacement texts): the fused + // chain of global one-character replacements. + "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, + /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): + * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the + * whole span must be one StrNumericLiteral — signed decimal (Infinity + * included, strtod-over-validated-span correct rounding) or unsigned + * 0x/0o/0b (exact value, nearest-even; signed forms are NaN) — with + * any trailing garbage answering NaN. Number(aString), unary + on + * strings, and util.format %d over strings lower here. Borrows; never + * throws. */ + "num.fromString": { argTypes: [STRING], result: F64 }, + "num.isNaN": { argTypes: [F64], result: BOOL }, + /** The static URI component codecs (scr_string.c), ECMA-262 Encode/ + * Decode with the component sets over the runtime's UTF-8 strings. + * str.encodeUriComponent percent-encodes every byte outside the + * unreserved component set (ALPHA/DIGIT/- _ . ! ~ * ' ( )) as uppercase + * %XX — the spec's per-code-point UTF-8 encoding IS a byte scan here — + * and THROWS the spec's URIError ("URI malformed") on a lone surrogate. + * str.decodeUriComponent + * decodes %XX escapes bytewise (raw non-escape bytes copy through) and + * requires the escaped bytes to form strictly valid UTF-8 (overlong + * forms, surrogate code points, and >U+10FFFF refused, per UTF8-decode + * without replacement); bad hex or an invalid sequence THROWS the + * spec's URIError ("URI malformed"), catchable. Borrow; results +1. */ + "str.encodeUriComponent": { argTypes: [STRING], result: STRING }, + // The base64 globals: the argument is a dyn value (WebIDL ToString + // runs in the runtime); the zero-argument form always throws. + /** The WHATWG base64 globals (scr_string.c), Node-global since v16. + * Arguments are borrowed dyn values — WebIDL ToString runs in the + * runtime over the dyn kind (String(null) is "null": the html spec's + * coercion, which Node's atob(null) exercises). str.atob decodes + * forgiving-base64 (ASCII whitespace stripped, %4==0 strips up to two + * '=', %4==1 refuses, leftover bits discarded) into the latin1 code + * points as a string; a malformed input THROWS the catchable + * DOMException InvalidCharacterError ("The string to be decoded is not + * correctly encoded."). str.btoa encodes the string's code points as + * base64; any code point over U+00FF THROWS InvalidCharacterError + * ("Invalid character"). str.b64Missing is the zero-argument call of + * either: always throws Node's TypeError [ERR_MISSING_ARGS] "The + * \"input\" argument must be specified". Results +1. */ + "str.atob": { argTypes: [DYN], result: STRING }, + "str.btoa": { argTypes: [DYN], result: STRING }, + "str.b64Missing": { argTypes: [], result: STRING }, + "str.decodeUriComponent": { argTypes: [STRING], result: STRING }, + "str.decodeUri": { argTypes: [STRING], result: STRING }, + /** encodeURI: the same Encode() keeping the reserved set and '#' + * unescaped — throwing like the component encoder. Borrow; result +1. */ + "str.encodeUri": { argTypes: [STRING], result: STRING }, + /** RegExp.escape (ES2025): per-code-point EncodeForRegExpEscape — + * leading ASCII alphanumeric hex-escapes, syntax characters and '/' + * take a backslash, other punctuators/whitespace/line terminators + * hex-escape, the rest passes through. Total; borrows; result +1. */ + "regexp.escape": { argTypes: [STRING], result: STRING }, + /** Number.prototype formatters (scr_lib.c), JS-exact: + * num.toExponential is toExponential() with the spec's "as many digits + * as necessary"; num.toFixed0 is the non-throwing omitted-argument + * toFixed() fast path; num.toFixed implements an explicit fractionDigits + * with exact binary-value rounding and THROWS RangeError outside 0..100. + * Successful results +1. */ + "num.toExponential": { argTypes: [F64], result: STRING }, + "num.toFixed0": { argTypes: [F64], result: STRING }, + "num.toFixed": { argTypes: [F64, F64], result: STRING }, + /** toPrecision(precision) with the argument ToNumber'd and its presence + * (undefined answers ToString); toExponential with a DEFINED + * fractionDigits. Both throw V8's RangeError for out-of-range digits. */ + "num.toPrecision": { argTypes: [F64, F64, BOOL], result: STRING }, + "num.toExponentialDigits": { argTypes: [F64, F64], result: STRING }, + "num.toStringRadix": { argTypes: [F64, DYN], result: STRING }, + /** toString(radix) with a number radix (scr_lib.c): truncates it as + * ToIntegerOrInfinity does, THROWS RangeError outside 2..36. */ + "num.toStringRadixF64": { argTypes: [F64, F64], result: STRING }, + /** Object.is over two numbers — the spec's SameValue on doubles: NaN + * equals NaN, +0 differs from -0, everything else is `===`. Plain bool + * result; never throws. (Union-armed operands take unionEq's sameValue + * flag instead — this is the both-f64 fast path.) */ + "num.sameValue": { argTypes: [F64, F64], result: BOOL }, + /** `new Intl.NumberFormat("en-US").format(x)` and + * `x.toLocaleString("en-US")` with DEFAULT options — the one locale + * whose data the runtime embeds (Node's default-build locale): decimal + * notation, 0–3 fraction digits rounded half-up on the SHORTEST + * round-tripping decimal (ICU's rounding input, probed vs Node — + * format(1.0005) is "1.001" though toFixed(3) answers "1.000"), "," + * grouping every three integer digits, "∞"/"NaN" texts, and "-0" for + * negative inputs rounding to zero. Result +1; never throws. */ + "intl.numFormatEnUs": { argTypes: [F64], result: STRING }, + /** ES Symbol values (scr_symbol.c — link-gated by moduleUsesSymbol). + * sym.new: `Symbol(desc)` — a fresh runtime-unique identity (+1) whose + * one arg is the description string (borrowed); sym.newAnon is the + * description-less `Symbol()` form. sym.for: the Symbol.for global + * registry — one interned symbol per key (borrowed), +1 on every call. + * sym.toString: "Symbol(desc)" (+1 string; "Symbol()" when absent). + * sym.desc / sym.keyFor answer the interned `string | undefined` union + * (the runtime returns +1-or-NULL; the backend builds the union arms — + * the child.stdout pattern). None of these throw. */ + "sym.new": { argTypes: [STRING], result: SYMBOL_T }, + "sym.newAnon": { argTypes: [], result: SYMBOL_T }, + "sym.for": { argTypes: [STRING], result: SYMBOL_T }, + "sym.wellKnown": { argTypes: [STRING], result: SYMBOL_T }, + // Result is the interned `string | undefined` union — the libCall case + // checks the arms (the spawnRes.error pattern). + "sym.keyFor": { argTypes: [SYMBOL_T], result: VOID }, + "sym.desc": { argTypes: [SYMBOL_T], result: VOID }, + "sym.toString": { argTypes: [SYMBOL_T], result: STRING }, + /** Engine-free arbitrary-precision bigint operations (scr_bigint.c). + * parse accepts the ECMAScript BigInt string grammar; fromF64 accepts + * finite integral Numbers. Arithmetic returns fresh immutable values. + * cmp returns -1/0/1 as f64; toString accepts a numeric radix. */ + "bigint.parse": { argTypes: [STRING], result: BIGINT_T }, + "bigint.fromF64": { argTypes: [F64], result: BIGINT_T }, + "bigint.neg": { argTypes: [BIGINT_T], result: BIGINT_T }, + "bigint.not": { argTypes: [BIGINT_T], result: BIGINT_T }, + "bigint.add": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.sub": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.mul": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.div": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.mod": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.pow": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.and": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.or": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.xor": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.shl": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.shr": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.eq": { argTypes: [BIGINT_T, BIGINT_T], result: BOOL }, + "bigint.eqString": { argTypes: [BIGINT_T, STRING], result: BOOL }, + "bigint.cmp": { argTypes: [BIGINT_T, BIGINT_T], result: F64 }, + "bigint.cmpNumber": { argTypes: [BIGINT_T, F64], result: F64 }, + "bigint.truthy": { argTypes: [BIGINT_T], result: BOOL }, + "bigint.toString": { argTypes: [BIGINT_T, F64], result: STRING }, + "bigint.inspect": { argTypes: [BIGINT_T], result: STRING }, + "bigint.toF64": { argTypes: [BIGINT_T], result: F64 }, + "bigint.asUintN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, + "bigint.asIntN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, + "bigint.bufferRead": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, + "bigint.bufferWrite": { argTypes: [BYTES_U8, BIGINT_T, F64, BOOL, BOOL], result: F64 }, + "bigint.dataViewGet": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, + "bigint.dataViewSet": { argTypes: [BYTES_U8, F64, BIGINT_T, BOOL], result: VOID }, + /** node:url + the URL class (scr_url.c). url.new parses one absolute + * URL string into a mutable URL value (+1) — invalid input THROWS a + * catchable TypeError ("Invalid URL"), like Node's constructor. The + * getters (borrowed receiver, +1 string) never throw; url.href doubles + * as toString(). fileURLToPath has one libFn per receiver form (URL + * value / string) — both THROW Node's TypeErrors on non-file schemes, + * encoded slashes, and non-empty hosts. url.pathToFileURL resolves the + * path (getcwd) and never throws. */ + "url.new": { argTypes: [STRING], result: URL_T }, + "url.newBase": { argTypes: [STRING, STRING], result: URL_T }, + /** Factories consume already-converted input/base strings. canParse + * answers a boolean; parse returns a checked native URL or null (+1). + * Parser failures are suppressed; conversion occurs before these calls. */ + "url.canParse": { argTypes: [STRING], result: BOOL }, + "url.canParseBase": { argTypes: [STRING, STRING], result: BOOL }, + "url.parse": { argTypes: [STRING], result: DYN }, + "url.parseBase": { argTypes: [STRING, STRING], result: DYN }, + "url.protocol": { argTypes: [URL_T], result: STRING }, + "url.origin": { argTypes: [URL_T], result: STRING }, + "url.username": { argTypes: [URL_T], result: STRING }, + "url.password": { argTypes: [URL_T], result: STRING }, + "url.host": { argTypes: [URL_T], result: STRING }, + "url.hostname": { argTypes: [URL_T], result: STRING }, + "url.port": { argTypes: [URL_T], result: STRING }, + "url.pathname": { argTypes: [URL_T], result: STRING }, + "url.href": { argTypes: [URL_T], result: STRING }, + /** Selected component writes borrow the receiver and converted string. + * href replaces its parsed fields while retaining cached params identity. */ + "url.set": { argTypes: [URL_T, STRING, STRING], result: VOID }, + "url.setChecked": { argTypes: [URL_T, STRING, DYN], result: VOID }, + "url.fileURLToPathUrl": { argTypes: [URL_T], result: STRING }, + /** fileURLToPathUrl as a value: the path string, or the error it would + * throw (fs.existsSync and fs.promises URL paths). Never throws. */ + "url.fileURLToPathValue": { argTypes: [URL_T], result: DYN }, + /** getValidatedPath's null-byte check on a string path, and + * readFileSync's encoding check: what Node validates before it converts + * a later URL path. (path, argument name) and (encoding). */ + "fs.checkPath": { argTypes: [STRING, STRING], result: VOID }, + "fs.checkReadEncoding": { argTypes: [STRING], result: VOID }, + "url.fileURLToPathChecked": { argTypes: [DYN], result: STRING }, + "url.fileURLToPathOptions": { argTypes: [DYN, DYN], result: STRING }, + /** node:url's legacy API (scr_url_legacy.c): parse(url, parseQueryString, + * slashesDenoteHost) answers the checked-dynamic Url instance; format + * takes the urlObject (a Url-shaped object, a string, or a URL) and the + * URL form's options; resolve takes (from, to). The trailing bool says + * whether the call site emits DEP0169. domainToASCII/domainToUnicode + * take the domain and whether it was omitted. Node's validation errors + * throw catchably. */ + "url.legacyParse": { argTypes: [DYN, DYN, DYN, BOOL], result: DYN }, + "url.legacyFormat": { argTypes: [DYN, DYN, BOOL], result: STRING }, + "url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING }, + "url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING }, + "url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING }, + /** url.urlToHttpOptions over a checked-dynamic argument: a URL answers + * Node's null-prototype options object; a primitive or null throws + * ERR_INVALID_ARG_TYPE. */ + "url.toHttpOptions": { argTypes: [DYN], result: DYN }, + /** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions; + * decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the + * module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the + * module's first load (scr_async_dyn.c) and reports DEP0040 then unless + * the second argument is false: at once for a static ES import, on the + * next tick for a require() call. */ + "punycode.decode": { argTypes: [STRING], result: STRING }, + "punycode.encode": { argTypes: [STRING], result: STRING }, + "punycode.toASCII": { argTypes: [STRING], result: STRING }, + "punycode.toUnicode": { argTypes: [STRING], result: STRING }, + "punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) }, + "punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING }, + "punycode.warn": { argTypes: [BOOL, BOOL], result: VOID }, + "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, + "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T }, + "url.fileURLToPathStr": { argTypes: [STRING], result: STRING }, + "url.pathToFileURL": { argTypes: [STRING], result: URL_T }, + /** Explicit path syntax from the options.windows argument, independent + * of the executable's host platform. Windows UNC input can throw. */ + "url.pathToFileURLPlatform": { argTypes: [STRING, BOOL], result: URL_T }, + /** pathToFileURL under a win32 TARGET: the same scr_url_from_path call + * (the runtime selects the win32 arm by _WIN32), but a distinct IR name + * because that arm THROWS for malformed UNC inputs — may-throw seeds on + * it while posix pathToFileURL emission stays byte-identical. */ + "url.pathToFileURLWin32": { argTypes: [STRING], result: URL_T }, + /** URLSearchParams (scr_url.c — always linked with the url unit). + * Construction: sp.new (empty), sp.parse (one borrowed init string — + * a single leading '?' strips, Node's constructor), sp.copy (snapshot + * of another list), sp.fromPairs (a string[][] value — THROWS Node's + * ERR_INVALID_TUPLE TypeError on a non-[name, value] row; the one + * throwing entry in the family), sp.with (the record-literal init + * desugar: append one pair, answer the same list +1 — chains fold + * `new URLSearchParams({...})` into nested calls). url.searchParams + * answers the URL's LIVE cached view (+1, one identity per URL); + * url.search is the WHATWG search getter ("" for no/empty query). + * Methods mirror the WHATWG surface: sp.get answers +1-or-NULL (the + * sym.desc union pattern, null arm), sp.getAll a fresh string[]; + * sp.append/sp.set/sp.delete/sp.deleteValue/sp.sort mutate and + * re-serialize a live view's URL query; sp.has/sp.hasValue answer + * bools; sp.size/sp.toString are pure reads. sp.keyAt/sp.valAt are + * the for-of/forEach desugar's index reads (live — the loop re-reads + * sp.size each pass). */ + "sp.new": { argTypes: [], result: SEARCH_PARAMS_T }, + "sp.newChecked": { argTypes: [DYN], result: SEARCH_PARAMS_T }, + "sp.parse": { argTypes: [STRING], result: SEARCH_PARAMS_T }, + "sp.copy": { argTypes: [SEARCH_PARAMS_T], result: SEARCH_PARAMS_T }, + // The pairs argument is string[][] — checked structurally in validate.ts (the + // generic array-of slot has no named constant here). + "sp.fromPairs": { argTypes: [null], result: SEARCH_PARAMS_T }, + "sp.with": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: SEARCH_PARAMS_T }, + "url.searchParams": { argTypes: [URL_T], result: SEARCH_PARAMS_T }, + "url.search": { argTypes: [URL_T], result: STRING }, + "url.hash": { argTypes: [URL_T], result: STRING }, + // Result is the interned `string | null` union — the libCall case + // checks the arms (the spawnRes.signal pattern). + "sp.get": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, + "sp.getAll": { argTypes: [SEARCH_PARAMS_T, STRING], result: arrayOf(STRING) }, + "sp.append": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.set": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.delete": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, + "sp.deleteValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.has": { argTypes: [SEARCH_PARAMS_T, STRING], result: BOOL }, + "sp.hasValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: BOOL }, + "sp.sort": { argTypes: [SEARCH_PARAMS_T], result: VOID }, + "sp.size": { argTypes: [SEARCH_PARAMS_T], result: F64 }, + "sp.toString": { argTypes: [SEARCH_PARAMS_T], result: STRING }, + "sp.keyAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, + "sp.valAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, + // node:querystring. qs.parse's result is the call site's ParsedUrlQuery + // dictionary record (VOID is the networkInterfaces sentinel — the + // libCall case checks the structure); qs.stringify's object argument + // is a dyn value (the frontend dynFroms typed records). + /** node:querystring (scr_qs.c — link-gated by moduleUsesQs; NOT + * URLSearchParams: the legacy codec's escaping and '+' rules differ). + * qs.escape is Node's qsEscape, which encodes exactly the component + * unreserved set — it emits the always-linked scr_qs_escape, so + * escape-only programs never pull the unit. qs.unescape is Node's qsUnescape: strict decodeURIComponent + * first, the lenient legacy unescapeBuffer fallback on failure (never + * throws). qs.parse takes (str, sep, eq, maxKeys) — the frontend + * completes omitted/null sep/eq to "&"/"=" and the omitted maxKeys + * option to Node's 1000 (0 and negatives mean unlimited, Node's rule) — + * and its result type is the CALL SITE's mapped ParsedUrlQuery shape (a + * pure index-signature record over `string | string[]`, an undefined + * arm tolerated — @types/node's Dict), verified structurally by the + * frontend (lowerQuerystringParseCall); the emitters construct the + * record and hand its overflow map to scr_qs_parse_into with the two + * union tags. qs.stringify takes (obj, sep, eq) with obj a dyn value + * (the frontend dynFroms the typed record; JS-world dyn values pass + * through) — Node's encodeStringified rules run in the runtime, so + * arrays expand to repeated keys and null/undefined/nested objects are + * empty values. Custom encoder/decoder options fence at compile time. + * All borrow; string results +1; escape and stringify throw URIError + * [ERR_INVALID_URI] on a lone surrogate. */ + "qs.parse": { argTypes: [STRING, STRING, STRING, F64], result: VOID }, + "qs.stringify": { argTypes: [DYN, STRING, STRING], result: STRING }, + "qs.escape": { argTypes: [STRING], result: STRING }, + "qs.unescape": { argTypes: [STRING], result: STRING }, + /** node:util.parseArgs: one checked-dynamic config object in, one + * checked-dynamic ParsedResults tree out. Statically typed member reads + * validate their values when leaving that tree. The native parser may + * throw Node's coded validation/grammar TypeErrors. */ + "util.parseArgs": { argTypes: [DYN], result: DYN }, + "util.parseEnv": { argTypes: [DYN], result: DYN }, + "util.getSystemErrorName": { argTypes: [DYN], result: STRING }, + "util.getSystemErrorMessage": { argTypes: [DYN], result: STRING }, + "util.systemErrorEntries": { argTypes: [], result: DYN }, + "util.stripVTControlCharacters": { argTypes: [DYN], result: STRING }, + "util.toUSVString": { argTypes: [DYN], result: STRING }, + "util.isDeepStrictEqual": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "util.styleText": { argTypes: [DYN, DYN, DYN], result: STRING }, + /** util.inherits/deprecate/callbackify/debuglog over checked-dynamic + * arguments (scr_util_callable.c). The function results are native dyn + * wrappers; call sites with a static function type check them back. */ + "util.inherits": { argTypes: [DYN, DYN], result: VOID }, + "util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID }, + "util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + "util.callbackify": { argTypes: [DYN], result: DYN }, + /** util.promisify over an untyped function value: a native dyn function + * whose calls answer a dyn promise the appended callback settles. */ + "util.promisifyDyn": { argTypes: [DYN], result: DYN }, + "util.debuglog": { argTypes: [DYN, DYN], result: DYN }, + "util.typeIs": { argTypes: [DYN, STRING], result: BOOL }, + "util.typeValue": { argTypes: [DYN, STRING], result: DYN }, + /** fs.statSync → a Stats value (may throw, like the other sync fs + * calls); the stats.* getters are pure reads on it. */ + "fs.statSync": { argTypes: [STRING], result: STATS_T }, + "fs.lstatSync": { argTypes: [STRING], result: STATS_T }, + // statSync / lstatSync(path, { throwIfNoEntry }): arg 1 is the flag. + // Result is the interned `Stats | undefined` union (the runtime answers + // +1-or-NULL; the backend builds the arms). + "fs.statProbeSync": { argTypes: [STRING, BOOL], result: VOID }, + "fs.lstatProbeSync": { argTypes: [STRING, BOOL], result: VOID }, + "fs.fstatSync": { argTypes: [F64], result: STATS_T }, + "fs.fchmodSync": { argTypes: [F64, F64], result: VOID }, + "fs.fsyncSync": { argTypes: [F64], result: VOID }, + "fs.fdatasyncSync": { argTypes: [F64], result: VOID }, + "fs.ftruncateSync": { argTypes: [F64, F64], result: VOID }, + "fs.utimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.futimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.lutimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.readvSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + "fs.writevSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + "fs.linkSync": { argTypes: [DYN, DYN], result: VOID }, + "fs.symlinkSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + /** fs.cpSync / fs/promises cp (scr_fs_cp.c): src, dest and options are + * dyn values the runtime validates in Node's order. */ + "fs.cpSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.readlinkSync": { argTypes: [DYN, DYN], result: DYN }, + "fs.statfsSync": { argTypes: [DYN, DYN], result: DYN }, + "fs.readlinkSyncStr": { argTypes: [DYN, DYN], result: STRING }, + "fs.readlinkSyncBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, + /** fs.openSync(path, flags) → the raw fd as f64; fs.readSync/fs.writeSync + * over Buffer windows perform sequential I/O when position is -1 and + * offset-preserving positioned I/O otherwise; fs.writeStrSync is the + * utf8 string twin; and fs.closeSync(fd) closes the descriptor. This is + * the fd slice behind spawn/log-processing forms. + * flags is Node's string + * grammar ("r", "w", "a" and the +/x variants; unknown flags throw + * Node's ERR_INVALID_ARG_VALUE TypeError text). All throw Node-shaped + * fs errors (openSync ENOENT/EACCES..., readSync EBADF/range errors, + * closeSync EBADF). */ + "fs.openSync": { argTypes: [STRING, STRING], result: F64 }, + "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 }, + "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + "fs.readSyncDyn": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: F64 }, + "fs.writeSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + "fs.writeStrSync": { argTypes: [F64, STRING, F64, STRING], result: F64 }, + // fs.watch's callback func type is program-dependent (zero params, or + // the eventType string) — the slot pins arity and the path/receiver. + /** fs.watch(path, listener?) → an FSWatcher handle (scr_watch.c — + * linked, and scr_watch_install() emitted, only when these appear on + * the IR; moduleUsesFsWatch is the switch). The path opens NOW — + * failure throws Node's fs error synchronously ("ENOENT: ..., watch + * 'x'", the polling-fallback catch shape) — and the unit's kqueue + * (EVFILT_VNODE) delivers "rename"/"change" through the loop's watch + * hook. The callback (nullable) MOVES in with an adapter per listener + * shape (zero-param, or the (eventType: string) form — runtime- + * provided); an open watcher keeps the loop alive until watcher.close() + * (idempotent, receiver borrowed, statement position only). */ + "fs.watch": { argTypes: [STRING], result: FSWATCHER_T }, + "fs.watchCb": { argTypes: [STRING, null], result: FSWATCHER_T }, + "watcher.close": { argTypes: [FSWATCHER_T], result: VOID }, + /** The composed `new crypto.X509Certificate(data).fingerprint` read + * (scr_lib.c — no certificate handle exists): the SHA-1 of the DER, + * uppercase colon-separated, over PEM or raw-DER Buffer input; other + * inputs throw Node's ERR_OSSL_PEM_NO_START_LINE Error (may-throw). */ + "crypto.x509Fingerprint": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509FingerprintStr": { argTypes: [STRING], result: STRING }, + /** The certificate's Validity window (validFrom / validTo reads — + * scr_lib.c's minimal ASN.1 walk to the TBSCertificate validity + * SEQUENCE): UTCTime and GeneralizedTime render in Node's exact + * ASN1_TIME_print shape ("Jul 1 00:00:00 2026 GMT" — %2d space-padded + * day). Same input contract and PEM error as the fingerprint pair + * (may-throw). */ + "crypto.x509ValidFrom": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509ValidFromStr": { argTypes: [STRING], result: STRING }, + "crypto.x509ValidTo": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509ValidToStr": { argTypes: [STRING], result: STRING }, + /** The certificate's DER as a fresh Buffer (the raw field), decoded + * once from the constructor's input; the same PEM error (may-throw). */ + "crypto.x509Raw": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawDer": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawStr": { argTypes: [STRING], result: BYTES_U8 }, + /** The remaining X509Certificate fields over that DER, in Node's + * renderings (scr_lib.c). subjectAltName, infoAccess and + * signatureAlgorithm answer "" where Node answers undefined. Borrowed; + * never throw. */ + "crypto.x509Subject": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Issuer": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SubjectAltName": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509InfoAccess": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SerialNumber": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint256": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint512": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Ca": { argTypes: [BYTES_U8], result: BOOL }, + "crypto.x509SignatureAlgorithm": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SignatureAlgorithmOid": { argTypes: [BYTES_U8], result: STRING }, + /** X509Certificate methods over its raw DER. checkHost/checkEmail/checkIP + * answer the matching name or "" (undefined), checkIssued compares two + * certificates, x509Pem is toString's PEM (scr_x509.c); publicKey, + * verify and checkPrivateKey read keys (scr_pki.c). */ + "crypto.x509CheckHost": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckEmail": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIP": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIssued": { argTypes: [BYTES_U8, BYTES_U8], result: BOOL }, + "crypto.x509LegacyObject": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Pem": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509PublicKey": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Verify": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "crypto.x509CheckPrivateKey": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "fs.closeSync": { argTypes: [F64], result: VOID }, + "stats.isFile": { argTypes: [STATS_T], result: BOOL }, + "stats.isDirectory": { argTypes: [STATS_T], result: BOOL }, + "stats.isSymbolicLink": { argTypes: [STATS_T], result: BOOL }, + "stats.size": { argTypes: [STATS_T], result: F64 }, + "stats.dev": { argTypes: [STATS_T], result: F64 }, + "stats.ino": { argTypes: [STATS_T], result: F64 }, + "stats.blocks": { argTypes: [STATS_T], result: F64 }, + "stats.nlink": { argTypes: [STATS_T], result: F64 }, + "stats.atimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.mtimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.ctimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.birthtimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.mode": { argTypes: [STATS_T], result: F64 }, + "stats.uid": { argTypes: [STATS_T], result: F64 }, + "stats.gid": { argTypes: [STATS_T], result: F64 }, + "stats.rdev": { argTypes: [STATS_T], result: F64 }, + "stats.blksize": { argTypes: [STATS_T], result: F64 }, + "stats.isBlockDevice": { argTypes: [STATS_T], result: BOOL }, + "stats.isCharacterDevice": { argTypes: [STATS_T], result: BOOL }, + "stats.isFIFO": { argTypes: [STATS_T], result: BOOL }, + "stats.isSocket": { argTypes: [STATS_T], result: BOOL }, + /** The Date fields: the *Ms value rounded (Node's dateFromMs). */ + "stats.atime": { argTypes: [STATS_T], result: DATE_T }, + "stats.mtime": { argTypes: [STATS_T], result: DATE_T }, + "stats.ctime": { argTypes: [STATS_T], result: DATE_T }, + "stats.birthtime": { argTypes: [STATS_T], result: DATE_T }, + // The wider sync fs slice (unlink/chmod/chown/copyfile and the + // mode-carrying write/mkdir forms). + /** The wider sync fs slice (scr_lib.c), all throwing catchably with + * Node's errno message shapes and `.code` stamped like the rest of + * sync fs. unlink/chmod/chown wrap the syscalls 1:1 (Node reports the + * syscall's own name). copyFileSync copies contents into a fresh (or + * truncated) destination carrying the SOURCE's mode (libuv's + * uv_fs_copyfile behavior); its errors carry BOTH paths — Node's + * "copyfile 'src' -> 'dest'". lstatSync is statSync without following + * a trailing symlink (Node reports lstat); stats.isSymbolicLink / + * stats.dev / ino / blocks / nlink / atimeMs / mtimeMs / ctimeMs are pure reads on the widened + * snapshot (blocks is allocated 512-byte units; the times are milliseconds + * with their sub-second fractions, Node's arithmetic). + * writeFileModeSync is writeFileSync(path, data, { mode }): the mode + * applies at CREATION only (open(2) with O_CREAT, umask applying), + * exactly Node — an existing file keeps its permissions. mkdirModeSync + * / mkdirRecursiveModeSync are the mkdirSync option forms with an + * explicit mode (the recursive walk passes it to every directory it + * creates, like Node's). */ + "fs.unlinkSync": { argTypes: [STRING], result: VOID }, + "fs.chmodSync": { argTypes: [STRING, F64], result: VOID }, + "fs.chownSync": { argTypes: [STRING, F64, F64], result: VOID }, + "fs.lchownSync": { argTypes: [STRING, F64, F64], result: VOID }, + "fs.fchownSync": { argTypes: [F64, F64, F64], result: VOID }, + /** truncateSync opens the path with 'r+', then truncates (len checked and + * clamped like ftruncateSync's) and closes it. */ + "fs.truncateSync": { argTypes: [STRING, F64], result: VOID }, + "fs.copyFileSync": { argTypes: [STRING, STRING], result: VOID }, + /** renameSync is the direct two-path syscall wrapper. renameCb defers + * the operation to the loop and fires a program-shaped Error | null + * callback through a backend-emitted adapter. */ + "fs.renameSync": { argTypes: [STRING, STRING], result: VOID }, + // Callback type is program-dependent (zero params or Error | null). + "fs.renameCb": { argTypes: [STRING, STRING, null], result: VOID }, + "fs.writeFileModeSync": { argTypes: [STRING, STRING, F64], result: VOID }, + "fs.mkdirModeSync": { argTypes: [STRING, F64], result: VOID }, + "fs.mkdirRecursiveModeSync": { argTypes: [STRING, F64], result: VOID }, + "atomics.wait": { argTypes: [bytesOf("i32"), F64, F64, F64], result: STRING }, + "atomics.notify": { argTypes: [bytesOf("i32"), F64, F64], result: F64 }, + "atomics.op": { argTypes: [null, F64, F64, F64, F64], result: F64 }, + "atomics.opDyn": { argTypes: [DYN, F64, F64, F64, F64], result: F64 }, + "atomics.waitDyn": { argTypes: [DYN, F64, F64, F64], result: STRING }, + "atomics.notifyDyn": { argTypes: [DYN, F64, F64], result: F64 }, + "worker.new": { argTypes: [F64, STRING, DYN], result: DYN }, + "worker.isMainThread": { argTypes: [], result: BOOL }, + "worker.threadId": { argTypes: [], result: F64 }, + "worker.root": { argTypes: [], result: F64 }, + "worker.data": { argTypes: [], result: DYN }, + "worker.parentPort": { argTypes: [], result: DYN }, + /** worker_threads.resourceLimits: {} in the main thread; a refusal in a + * worker (scr_worker_events.c). */ + "worker.resourceLimits": { argTypes: [], result: DYN }, + /** worker_threads.threadName: the Worker's name in a worker, "" in the + * main thread (scr_worker.c). */ + "worker.threadName": { argTypes: [], result: DYN }, + /** new MessageChannel() ({ port1, port2 }) and receiveMessageOnPort(port) + * (scr_worker_events.c; the latter may throw). */ + "worker.channel": { argTypes: [], result: DYN }, + "worker.receiveMessageOnPort": { argTypes: [DYN], result: DYN }, + /** setEnvironmentData(key, value) and getEnvironmentData(key) + * (scr_worker_events.c; arguments borrowed). */ + "worker.setEnvironmentData": { argTypes: [DYN, DYN], result: VOID }, + "worker.getEnvironmentData": { argTypes: [DYN], result: DYN }, + /** markAsUntransferable(value) and isMarkedAsUntransferable(value) + * (scr_worker_events.c; the value borrowed). */ + "worker.markUntransferable": { argTypes: [DYN], result: VOID }, + "worker.isMarkedUntransferable": { argTypes: [DYN], result: BOOL }, + /** markAsUncloneable(value) (scr_json.c; the value borrowed). */ + "dyn.markUncloneable": { argTypes: [DYN], result: VOID }, + /** before new Worker() with the stdout or stderr option: links the stream + * unit that worker.stdout and worker.stderr read through (scr_stream.c). */ + "stream.workerStdio": { argTypes: [], result: VOID }, + /** new BroadcastChannel(...args) (scr_worker_events.c; may throw), after + * event.broadcastInstall, which installs its EventTarget side. */ + "worker.broadcastChannel": { argTypes: [DYN], result: DYN }, + "event.broadcastInstall": { argTypes: [], result: VOID }, + /** child_process.spawnSync (scr_child.c): posix_spawn + waitpid + piped + * utf8 capture — cmd borrowed, args one borrowed string[] (the frontend + * completes an omitted list to an empty literal), result an owned (+1) + * spawnRes. NEVER throws: spawn failure (nonexistent binary, EACCES) is + * data, like Node's error property — status null and empty outputs + * (SEMANTICS.md documents the divergence from Node's null stdout). + * The getters are pure reads: status is the interned `number | null` + * union (null = spawn failure or signal death, type-directed + * construction in the backend like process.envGet); stdout/stderr are + * +1 strings. */ + "cp.spawnSync": { argTypes: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, + // spawnSync's options form: timeout, killSignal name ("" = SIGTERM), + // and the three stdio modes. + /** spawnSync with options (scr_child.c): the cp.spawnSync core plus the + * option slice portless-class CLIs pass — timeout (killSignal fires at + * the deadline and the result carries error: ETIMEDOUT + the signal, + * Node's shape), killSignal (a signal NAME; "" = the SIGTERM default), + * and per-fd stdio modes (in: 0 /dev/null, 1 ignore, 2 inherit; out/ + * err: 0 capture, 1 ignore → "", 2 inherit → ""). NEVER throws — spawn + * failure and timeout are data on the result, like Node's error + * property. spawnRes.signal is the result's termination signal as the + * call site's `Signals | null` union (null = exited normally or spawn + * failure), constructed type-directedly like spawnRes.status. */ + "cp.spawnSyncOpts": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64], + result: SPAWNRES_T, + }, + /** cp.spawnSyncOpts with the `env` option: the trailing [k,v,...] + * pairs replace the child's environment (recordToEnvPairs). */ + "cp.spawnSyncEnv": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64, arrayOf(STRING)], + result: SPAWNRES_T, + }, + /** cp.spawnSyncOpts with the stdio carried as a RUNTIME string — + * "pipe" | "ignore" | "inherit", proven by the call site's TYPE (the + * defaultRunner idiom `stdio: options?.stdio ?? "pipe"`); the runtime + * maps the value to the three modes. Args (cmd, argv, timeout, + * killSignal, stdio). Never throws, like the other spawnSync forms. */ + "cp.spawnSyncStdioStr": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, STRING], + result: SPAWNRES_T, + }, + // node:net (scr_net.c). Listener slots are program-dependent closures — + // null slots, checked in the libCall case (the child.onExit precedent); + // the zero-param callbacks pin the exact func type here. The trailing + // BOOL on registrations is the once flag. + /** node:net (scr_net.c — linked, and scr_net_install() emitted, only + * when one of these appears on the IR; moduleUsesNet is the switch). + * Receivers are borrowed; CALLBACKS MOVE into the handle's listener + * registry and are released at settlement (the child.onExit story). + * All listener registrations carry a trailing BOOL once-flag (`on` vs + * `once`) and are void — chaining is fenced. None of these throw: + * listen/connect failures are the async 'error' event, like Node. + * + * net.createServer's optional connection handler and the + * serverOnConnection listeners take the runtime-provided adapters + * (zero-param, or the one-param socket shape); sockOnData's adapters + * are the stdin pair's shapes (zero-param / bytes chunk); the error + * events reuse the child %Error adapters. net.listen/net.listenCb bind + * NOW and defer 'listening' to the next loop turn (Node's next-tick + * emit); net.serverPort is the composed `server.address().port` read. + * net.connect's host argument is a string ("localhost" pins to + * 127.0.0.1 — SEMANTICS.md); the connect callback is once('connect'). */ + "net.createServer": { argTypes: [], result: NETSERVER_T }, + "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, + "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, + /** listen(path[, cb]): a Unix-socket server; the socket file is removed + * when the server closes. Failures are the async 'error'. */ + "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, + "net.listenPathCb": { + argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.listenCb": { + argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + /** listen({ port, host, ipv6Only }[, cb]) — the explicit-interface bind + * (portless's listenOnProxyInterface): args [server, port, host, + * ipv6Only]. host is an IP literal string ("" = the host-less + * dual-stack any default); ipv6Only sets IPV6_V6ONLY before the bind. + * These are the old-shaped calls, retained for omitted reusePort so + * serialized IR remains compatible. Failures are the async 'error', + * message in Node's listen shape with the requested host. Never throws. */ + "net.listenOpts": { argTypes: [NETSERVER_T, F64, STRING, BOOL], result: VOID }, + // The callback slot is a zero-param void closure OR its + // `(() => void) | undefined` optional-binding union (checked specially). + "net.listenOptsCb": { argTypes: [NETSERVER_T, F64, STRING, BOOL, null], result: VOID }, + /** Additive ABI for listen({ ..., reusePort }) — args + * [server, port, host, ipv6Only, reusePort], with the callback following + * those values in the callback form. The final BOOL is the requested + * kernel connection-distribution option; old spellings remain + * four/five-argument calls so serialized IR stays compatible. */ + "net.listenOptsReusePort": { argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL], result: VOID }, + // The callback is a zero-param void closure OR its optional-binding union; + // the callback follows the reusePort BOOL in this additive ABI. + "net.listenOptsReusePortCb": { + argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL, null], + result: VOID, + }, + "net.serverPort": { argTypes: [NETSERVER_T], result: F64 }, + "net.serverListening": { argTypes: [NETSERVER_T], result: BOOL }, + /** socket.ref()/unref() and server.ref()/unref(): whether the handle + * keeps the event loop alive. */ + "net.sockSetRef": { argTypes: [NETSOCKET_T, BOOL], result: VOID }, + "net.serverSetRef": { argTypes: [NETSERVER_T, BOOL], result: VOID }, + // net.serverAddress's record result is shape-checked in the libCall case + // (the dgram.address sentinel pattern). + /** server.address() as the full AddressInfo record (the dgram.address + * materialization pattern: the emitter builds the record from the three + * runtime reads; the frontend pinned the shape). Never throws — before + * listen it answers the any-form defaults with port 0 where Node + * answers null (the serverPort stance). */ + "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, + /** server.close((err) => ...): the callback (a dyn function) gets + * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING + * when it was not listening. */ + "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** server.address() in programs that may listen on a Unix socket: the + * path a listening server answers ("" for a TCP server), and the check + * a record-typed address() makes first (throws for a Unix server). */ + "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, + "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverCloseCb": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.serverOnError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "net.serverOnClose": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.serverOnConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** 'secureConnection' — the TLS server's deferred 'connection' list + * (handshake-completion timing); on a server without deferred + * connections (a plain net server) the registration never fires, + * exactly Node's split. */ + "net.serverOnSecureConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "net.connect": { argTypes: [F64, STRING], result: NETSOCKET_T }, + /** connect with a validated autoSelectFamilyAttemptTimeout option (the + * budget runs Node's validateInt32-from-1 ladder and is then inert — + * the single dial has nothing to time). May-throw. */ + "net.connectAttempt": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + /** net.connect/createConnection over a RUNTIME option bag (computed + * keys — the invalid-input probes): Node-order validation (the + * objectMode trio's ERR_INVALID_ARG_VALUE, validatePort, host string, + * autoSelectFamily boolean, the attempt budget), then the trailing + * compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow + * polymorphic-result carve-out). May-throw seed. */ + "net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID }, + "net.connectCb": { + argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }], + result: NETSOCKET_T, + }, + // The lookup's exact func shape is program data (its answer callback's + // union/record types) — checked specially in the libCall case. + /** connect({ port, host, autoSelectFamily: true, lookup }) — the + * caller-resolver dial (portless's createLoopbackConnection): args + * [port, host, lookup]. The runtime creates the (connecting) socket + * handle, invokes the lookup as Node does — lookup(hostname, options, + * callback), options crossing as the dyn undefined — and the answer + * closure (an emitter-synthesized per-shape thunk over a boxed socket, + * the SNI-answer pattern) dials the answered addresses IN ORDER: each + * connect failure tries the next, the LAST failure's message is the + * socket's 'error' (Node's autoSelectFamily aggregate is a documented + * divergence), and a lookup error surfaces as the deferred socket + * 'error'. A synchronous throw INSIDE the lookup propagates out of the + * connect call (may-throw seed). */ + "net.connectLookup": { argTypes: [F64, STRING, null], result: NETSOCKET_T }, + /** connect({ host, port, lookup }) with a lookup of Node's own type, as + * a checked-dynamic function: the runtime calls it with { hints, all: + * true } and a callback taking an address string or list, then dials + * the answers in order. MAY THROW (host validation, a lookup throw). */ + "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, + "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a + * typed socket): the runtime dispatches STR/BYTES and throws Node's + * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ + "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, + "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, + /** socket.pipe(res) — raw socket chunks into a ServerResponse body + * (the extended-CONNECT bridge leg): each chunk is a body write (the + * response's own framing applies); source EOF end()s the response, + * pipe's default. Borrows both. Never throws. */ + "net.sockPipeRes": { argTypes: [NETSOCKET_T, HTTPRES_T], result: VOID }, + "net.sockOnData": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + "net.sockOnEnd": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockOnClose": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockOnError": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + "net.sockOnConnect": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + // node:dgram + node:dns (scr_dgram.c). The message/error listeners and + // dns.lookup's callback are program-dependent closures (null slots, + // checked in the libCall case); dgram.address's record result is + // program-dependent too (VOID here is the envGet sentinel — the libCall + // case checks the {address, family, port} shape). + /** node:dgram + node:dns (scr_dgram.c — linked, and + * scr_dgram_install() emitted, only when one of these appears on the + * IR; moduleUsesDgram is the switch — dns.lookup rides the same unit). + * The net listener discipline verbatim: receivers borrowed, CALLBACKS + * MOVE into the handle's registry and release at settlement, `on` vs + * `once` is the trailing bool, registrations are void. bind/connect + * bind NOW and defer 'listening'/'connect' to the next loop turn (the + * net.listen story); their optional-host completions are "" + * (bind → 0.0.0.0) and "127.0.0.1" (connect — udp4's Node default). + * bind/connect/send/close/address THROW Node's state errors ("Socket + * is already bound", "Already connected", "Not running") — may-throw + * seeded. dgram.address returns the AddressInfo RECORD (the frontend + * pins the {address, family, port} shape; the emitter builds it from + * runtime parts). onMessage adapters are emitted per rinfo record + * shape (the child.onExit precedent); onError reuses the child %Error + * adapters. dns.lookup resolves via getaddrinfo AT CALL TIME and + * defers the callback to the next turn (SEMANTICS.md documents the + * blocking divergence); its per-union adapter builds the + * `Error | null` first argument. */ + "dgram.createSocket": { argTypes: [BOOL], result: DGRAMSOCK_T }, + "dgram.bind": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, + "dgram.bindCb": { + argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.connect": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, + "dgram.connectCb": { + argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.sendStr": { argTypes: [DGRAMSOCK_T, STRING, F64, STRING], result: VOID }, + "dgram.sendBytes": { argTypes: [DGRAMSOCK_T, BYTES_U8, F64, STRING], result: VOID }, + /** Connected AF_UNIX datagram sockets for the hot-shots package shim + * (the unix-dgram addon's surface): fd or result >= 0, -errno on + * failure; send answers 1 when the socket would block. */ + "dgram.unixSocket": { argTypes: [], result: F64 }, + "dgram.unixConnect": { argTypes: [F64, STRING], result: F64 }, + "dgram.unixSend": { argTypes: [F64, BYTES_U8], result: F64 }, + "dgram.unixClose": { argTypes: [F64], result: F64 }, + /** The send argument-validation ladder over dyn arguments (Node's + * signature shuffle: slice bounds, list/type contracts, port/address + * validation, connected-state errors), then the send: lists, connected + * sends and completion callbacks included. Up to six arguments, the + * missing ones undefined. May-throw. */ + "dgram.sendChk": { argTypes: [DGRAMSOCK_T, DYN, DYN, DYN, DYN, DYN, DYN], result: VOID }, + "dgram.address": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.close": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.closeCb": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.unref": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.ref": { argTypes: [DGRAMSOCK_T], result: VOID }, + /** The socket options (scr_dgram.c): the membership calls take the + * interface and whether it was given. May-throw. */ + "dgram.setBroadcast": { argTypes: [DGRAMSOCK_T, BOOL], result: VOID }, + "dgram.setTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, + "dgram.setMulticastTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, + "dgram.setMulticastLoopback": { argTypes: [DGRAMSOCK_T, BOOL], result: BOOL }, + "dgram.setMulticastInterface": { argTypes: [DGRAMSOCK_T, STRING], result: VOID }, + "dgram.addMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, + "dgram.dropMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, + "dgram.getRecvBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.getSendBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.setRecvBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, + "dgram.setSendBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, + "dgram.getSendQueueSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.getSendQueueCount": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.disconnect": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.onMessage": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, + "dgram.onError": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, + "dgram.onListening": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dgram.onClose": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dgram.onConnect": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dns.lookup": { argTypes: [STRING, F64, null], result: VOID }, + /** Node's argument forms with dyn callbacks: lookup(hostname, options, + * callback) and lookupService(address, port, callback). */ + "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + /** node:dns/promises: lookup(hostname, options) and lookupService(address, + * port) over the callback forms; the promise fulfills with the dyn + * { address, family } (array for all: true) / { hostname, service }. + * Argument errors throw synchronously (may-throw), unlike the + * util.promisify forms below. */ + "dns.promisesLookup": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.promisesLookupService": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.getServers": { argTypes: [], result: DYN }, + "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, + "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, + // node:test (scr_test.c). Bodies are program-dependent closures (0 or + // 1 testCtx param, void or Promise result — the spoke pinned the + // shape): null slots. sub's result is the settled Promise the + // await consumes. + /** node:test (scr_test.c — linked only when one of these appears on + * the IR; moduleUsesNodeTest is the switch, and the main epilogue asks + * scr_test_exit_code() for the process's exit status). Strings are + * BORROWED, callbacks MOVE. register/suite/hook attach to the runner + * tree (register: name, mode 0|1|2 run/skip/todo, directive message "" + * = none, cb or absent via registerEmpty, flags 1 async | 2 takes-ctx + * | 4 only, "file:line:col"); suite runs its body AT registration + * (Node's collection phase). sub is t.test — runs the subtest INLINE + * on the runner fiber and returns the settled promise the await + * consumes. ctxSkip/ctxTodo mark the running test; ctxDiagnostic + * queues an ℹ line; ctxName reads t.name. Every registration keeps + * the loop-run emitted (usesTimers) so the runner fiber drains. */ + // The trailing three are the { plan, timeout, concurrency } options (plan + // -1 without one; timeout read under flag 8; concurrency read under flag + // 16, -1 for true). An out-of-range timeout or concurrency throws. + "test.register": { + argTypes: [STRING, F64, STRING, null, F64, STRING, F64, F64, F64], + result: VOID, + }, + "test.registerEmpty": { + argTypes: [STRING, F64, STRING, F64, STRING, F64, F64, F64], + result: VOID, + }, + // The suite body takes no parameters or its SuiteContext (flag 2). + "test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID }, + // The trailing string is the hook call's "file:line:col". + "test.hook": { argTypes: [F64, null, F64, STRING], result: VOID }, + "test.sub": { + argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + "test.subEmpty": { + argTypes: [TESTCTX_T, STRING, F64, STRING, F64, STRING, F64, F64, F64], + result: VOID, + }, + "test.ctxSkip": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxTodo": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxDiagnostic": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxName": { argTypes: [TESTCTX_T], result: STRING }, + "test.ctxFullName": { argTypes: [TESTCTX_T], result: STRING }, + // t.passed, and t.workerId's Number(NODE_TEST_WORKER_ID) with 0 for + // undefined. + "test.ctxPassed": { argTypes: [TESTCTX_T], result: BOOL }, + "test.workerId": { argTypes: [], result: F64 }, + // getTestContext(): whether a test, suite or hook is running in the + // current async context, and that context. + "test.hasContext": { argTypes: [], result: BOOL }, + "test.context": { argTypes: [], result: TESTCTX_T }, + // t.before/t.after/t.beforeEach/t.afterEach (which: 0 before, 1 after, + // 2 beforeEach, 3 afterEach); a throwing t.before is captured on the test. + "test.ctxHook": { argTypes: [TESTCTX_T, F64, null, F64], result: VOID }, + // t.plan(count), and the count of one t.assert call. + "test.ctxPlan": { argTypes: [TESTCTX_T, F64], result: VOID }, + "test.ctxCount": { argTypes: [TESTCTX_T], result: VOID }, + // The mock trackers: the module's `mock` and t.mock keep each mock's + // restore closure (moved) for restoreAll and reset. + "test.mockTrack": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + "test.mockRestoreAll": { argTypes: [], result: VOID }, + "test.mockReset": { argTypes: [], result: VOID }, + "test.ctxMockTrack": { + argTypes: [TESTCTX_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "test.ctxMockRestoreAll": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockReset": { argTypes: [TESTCTX_T], result: VOID }, + // mock.timers on the module's `mock` and on t.mock. enable takes one + // code per listed API ('T' setTimeout, 'I' setInterval), the `now` + // value and its flags (1 given, 2 from a Date). enable, tick, runAll and + // setTime throw Node's errors; tick and runAll run timer callbacks. + // t.assert.snapshot and fileSnapshot: the escaped, wrapped serialized + // value, and the stored value it is compared with (snapshot: the test, + // the value, the snapshot file; fileSnapshot: the value, the path). + // Both read or, in update mode, record; missing and unreadable + // snapshots throw Node's ERR_INVALID_STATE. + "test.snapshotValue": { argTypes: [STRING], result: STRING }, + "test.ctxSnapshot": { argTypes: [TESTCTX_T, STRING, STRING], result: STRING }, + "test.fileSnapshot": { argTypes: [STRING, STRING], result: STRING }, + // Node's serializeValue over a dyn array of serializers (undefined for + // the default JSON.stringify(value, null, 2)); a throw becomes Node's + // "The provided serializers did not generate a string." ERR_INVALID_STATE. + "test.snapshotSerialize": { argTypes: [DYN, DYN], result: DYN }, + "test.mockTimersEnable": { argTypes: [STRING, F64, F64], result: VOID }, + "test.mockTimersTick": { argTypes: [F64], result: VOID }, + "test.mockTimersRunAll": { argTypes: [], result: VOID }, + "test.mockTimersReset": { argTypes: [], result: VOID }, + "test.mockTimersSetTime": { argTypes: [F64], result: VOID }, + "test.ctxMockTimersEnable": { argTypes: [TESTCTX_T, STRING, F64, F64], result: VOID }, + "test.ctxMockTimersTick": { argTypes: [TESTCTX_T, F64], result: VOID }, + "test.ctxMockTimersRunAll": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockTimersReset": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockTimersSetTime": { argTypes: [TESTCTX_T, F64], result: VOID }, + // node:http (scr_http.c over scr_net.c). The handler and the data + // listeners are program-dependent closures (null slots, checked in the + // libCall case); reqHeader's result is the interned string|undefined + // union, checked like process.envGet. + /** node:http, the server slice (scr_http.c over scr_net.c — linked + * only when these appear on the IR; moduleUsesHttpServer is the + * switch, and any http.* libCall also counts as net use so scr_net.c + * links and installs). http.createServer's handler MOVES in and takes + * the runtime adapters for its (req, res) / (req) / () shapes; req + * body listeners follow the net.sockOnData story (bytes chunks, once + * flags); reqHeader answers the interned `string | undefined` union + * exactly like process.envGet; the res writers are borrowed-argument + * voids with Node's framing decided in the runtime (Content-Length + * for end-before-head, chunked after an explicit writeHead/write). */ + "http.createServer": { argTypes: [null], result: NETSERVER_T }, + /** http.createServer() / http.Server() with no handler — the + * on("request") route; option helpers set parser behavior on a fresh + * server before the constructor/factory result is returned. */ + "http.createServerEmpty": { argTypes: [], result: NETSERVER_T }, + "http.validateHeaderName": { argTypes: [STRING, STRING], result: VOID }, + "http.validateHeaderValue": { argTypes: [STRING, DYN], result: VOID }, + "http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID }, + /** The five writable numeric http.Server timeout fields use one + * selector ABI: 0 timeout, 1 keepAliveTimeout, 2 headersTimeout, + * 3 requestTimeout, 4 keepAliveTimeoutBuffer. These calls store/read + * the property values; timer enforcement remains outside this surface. + * The constructor-option setter takes dyn so explicit undefined remains + * distinguishable from a numeric value until its Node validation ladder. */ + "http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 }, + "http.serverSetTimeout": { argTypes: [NETSERVER_T, F64], result: VOID }, + "http.serverSetTimeoutCb": { argTypes: [NETSERVER_T, F64, null], result: VOID }, + "http.serverOnTimeout": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http.serverCloseAllConnections": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverCloseIdleConnections": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverTimeoutSet": { argTypes: [NETSERVER_T, F64, F64], result: VOID }, + "http.serverTimeoutOptionSet": { argTypes: [NETSERVER_T, F64, DYN], result: VOID }, + /** server.on("listening", cb) — the deferred listen-callback list. */ + "net.serverOnListening": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** The ServerResponse member surface: statusCode/statusMessage reads + * and assignments (Node's writable properties), the header CRUD trio + * (getHeader answers `string | undefined` like reqHeader), and + * end(cb)'s callback slot (resOnEndCallback registers, the end call + * follows — the callback fires deferred, just before Node's 'finish' + * emit; resOnFinish is the 'finish' listener slot). writeHead's + * statusMessage forms compose in interned helpers: resStatusMsgSet + * then the ordinary writeHead entry. */ + "http.resStatusGet": { argTypes: [HTTPRES_T], result: F64 }, + "http.resStatusSet": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resStatusMsgGet": { argTypes: [HTTPRES_T], result: VOID }, + "http.resStatusMsgSet": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resRequest": { argTypes: [HTTPRES_T], result: HTTPREQ_T }, + "http.resSocket": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWritableFinished": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resSendDateGet": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resSendDateSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, + "http.resStrictContentLengthGet": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resStrictContentLengthSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, + "http.resSetTimeout": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resSetTimeoutCb": { + argTypes: [HTTPRES_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + // resGetHeader answers the interned `string | undefined` union — the + // reqHeader/envGet sentinel pattern (VOID here, checked specially). + "http.resGetHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, + /** getHeader as Node answers it: the number, string or string[] that was + * set, or undefined, as a checked-dynamic value. */ + "http.resGetHeaderDyn": { argTypes: [HTTPRES_T, STRING], result: DYN }, + "http.resGetHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, + "http.resGetRawHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, + "http.resGetHeaders": { argTypes: [HTTPRES_T], result: DYN }, + "http.resHasHeader": { argTypes: [HTTPRES_T, STRING], result: BOOL }, + "http.resRemoveHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resOnFinish": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.resOnEndCallback": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.reqUrl": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqMethod": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqHeader": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqTrailer": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqHeaderValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqTrailerValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqOnData": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, + "http.reqOnEnd": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.resSetHeader": { argTypes: [HTTPRES_T, STRING, STRING], result: VOID }, + /** setHeader / appendHeader with a number, string or array value; the + * stored value keeps its type for getHeader/getHeaders. MAY THROW. */ + "http.resSetHeaderDyn": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, + "http.resAppendHeader": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, + "http.resWriteHead": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resWriteHeadN": { + argTypes: [HTTPRES_T, F64, arrayOf(STRING), arrayOf(STRING)], + result: VOID, + }, + /** HTTP/1.1 informational heads; early hints takes flat string pairs, + * with a lowercase `link` key required to send anything. */ + "http.resWriteContinue": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWriteProcessing": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWriteEarlyHints": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, + "http.resWrite": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resWriteBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, + "http.resEnd": { argTypes: [HTTPRES_T], result: VOID }, + "http.resEndStr": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resEndBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, + /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ + "http.resWriteDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, + "http.resEndDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, + "http.resHeadersSent": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resWritableEnded": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resFlushHeaders": { argTypes: [HTTPRES_T], result: VOID }, + "http.resAddTrailers": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, + "http.resCork": { argTypes: [HTTPRES_T], result: VOID }, + "http.resUncork": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWritableCorked": { argTypes: [HTTPRES_T], result: F64 }, + // The member follow-ups: reqStatusCode's `number | undefined` and + // sockRemoteAddress's `string | undefined` results are shape-checked in + // the special cases in validate.ts (like reqHeader/columns). + /** The server-surface member follow-ups: reqStatusCode answers the + * interned `number | undefined` union (negative = the undefined arm — + * a SERVER request, where Node's statusCode is undefined; every client + * response carries a real status); reqSocket is the underlying + * connection (+1, the same handle net.connect would give); + * sockRemoteAddress answers `string | undefined` (NULL after the fd + * closed, Node's destroyed-socket undefined; a dual-stack accept of an + * IPv4 peer reads "::ffff:a.b.c.d" like Node). reqResume/reqPause/reqDestroy and + * the req error/close listener slots complete the IncomingMessage + * surface portless's client responses use; resDestroy/resOnClose and + * resWriteHeadPairs ([k0,v0,k1,v1,...] — the env.pairs helper's flat + * shape) complete ServerResponse. sockSetTimeout arms the idle + * EVFILT_TIMER ('timeout' fires after ms of inactivity, once per idle + * period, never destroying the socket — Node's semantics). */ + "http.reqStatusCode": { argTypes: [HTTPREQ_T], result: VOID }, + // reqStatusMessage's `string | undefined` is program-interned like + // reqHeader's (the VOID here is the envGet sentinel). + "http.reqStatusMessage": { argTypes: [HTTPREQ_T], result: VOID }, + /** req.rawHeaders — [name, value, name, value, ...] in arrival order, + * names in their ORIGINAL case (Node's shape); a fresh string[] per + * read. reqStatusMessage answers the interned `string | undefined` + * union — the reason phrase on client responses ("" when the status + * line carried none), the undefined arm on server requests (the + * statusCode split). sockDestroyed is socket.destroyed — true once the + * fd is gone (destroy() or full close). */ + "http.reqRawHeaders": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + /** The `{ ...req.headers }` snapshot feed: [lowercased name, value, + * ...] pairs in arrival order — the interned %headers.snapshot helper + * builds the record over it, exactly the process.envPairs pattern. */ + "http.reqHeaderPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + /** Every value of one header in arrival order (empty when absent): the + * set-cookie array of a `{ ...req.headers }` snapshot. */ + "http.reqHeaderList": { argTypes: [HTTPREQ_T, STRING], result: arrayOf(STRING) }, + "http.reqRawTrailers": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + "http.reqTrailerPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + "net.sockDestroyed": { argTypes: [NETSOCKET_T], result: BOOL }, + /** socket.writable — the write half is open: no end() yet, no FIN sent, + * fd alive (connecting sockets answer true; writes queue). Node's + * stream flag. Borrows; never throws. */ + "net.sockWritable": { argTypes: [NETSOCKET_T], result: BOOL }, + // The 'upgrade' registrations: the callback shapes are program-typed + // ((req, socket, head) and shorter prefixes — the libCall case checks). + /** The 'upgrade' events, both sides (SEMANTICS.md — the WebSocket + * proxying surface): serverOnUpgrade registers (req, socket, head) + * listeners fired INSTEAD of 'request' for Connection: upgrade + * requests (the parser steps aside; the socket is raw; `head` carries + * bytes past the request head; no listener = the socket destroys, + * Node's default). clientOnUpgrade is the client twin: a 101 response + * fires (res, socket, head) INSTEAD of 'response'. */ + "http.serverOnUpgrade": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** server.on("connect", ...) — HTTP CONNECT tunneling, the 'upgrade' + * machinery's twin: (req, socket, head) fired INSTEAD of 'request' for + * CONNECT-method requests (no listener = the socket destroys, Node's + * default). The h2 compat server's 'connect' (portless's RFC 8441 + * handler) only ever sees the HTTP/1.1 arm under the allowHTTP1 + * lowering, so a listener whose second parameter is a UNION with a + * netSocket arm takes the socket wrapped at that arm (an emitted + * per-shape adapter — the tags are program data). */ + "http.serverOnConnect": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http.clientOnUpgrade": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.reqSocket": { argTypes: [HTTPREQ_T], result: NETSOCKET_T }, + // reqH2Stream's program-interned Http2Stream | undefined result is + // checked in the special libCall cases in validate.ts. + "http.reqH2Stream": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqH2StreamOrThrow": { argTypes: [HTTPREQ_T, STRING], result: HTTP2STREAM_T }, + /** req.pipe(dest) — the IncomingMessage body streaming into a + * ServerResponse (the proxy's response leg), a ClientRequest (the + * request-body forward), or a raw socket (the upgrade-rejection leg); + * chunk-for-chunk, natural end ends the destination (Node's pipe + * default; no backpressure — divergence 54's stream model). */ + "http.reqPipeRes": { argTypes: [HTTPREQ_T, HTTPRES_T], result: VOID }, + "http.reqPipeClient": { argTypes: [HTTPREQ_T, HTTPCLIENTREQ_T], result: VOID }, + "http.reqPipeSock": { argTypes: [HTTPREQ_T, NETSOCKET_T], result: VOID }, + "http.reqResume": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqPause": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqDestroy": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqOnError": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, + "http.reqOnClose": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.reqOnAborted": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.reqHttpVersion": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqHttpVersionMajor": { argTypes: [HTTPREQ_T], result: F64 }, + "http.reqHttpVersionMinor": { argTypes: [HTTPREQ_T], result: F64 }, + "http.reqAborted": { argTypes: [HTTPREQ_T], result: BOOL }, + "http.reqComplete": { argTypes: [HTTPREQ_T], result: BOOL }, + "http.resDestroy": { argTypes: [HTTPRES_T], result: VOID }, + "http.resOnClose": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.resWriteHeadPairs": { argTypes: [HTTPRES_T, F64, arrayOf(STRING)], result: VOID }, + /** writeHead(status, headers) with a checked-dynamic headers value — + * the runtime OBJ walk (string/number values; other values throw + * Node's invalid-argument TypeError; may throw). */ + "http.resWriteHeadDyn": { argTypes: [HTTPRES_T, F64, DYN], result: VOID }, + "net.sockSetTimeout": { argTypes: [NETSOCKET_T, F64], result: VOID }, + /** setEncoding('utf8') — 'data' delivers strings inside the chunk-encoding window; other real encodings throw the "not supported yet" Error, unknown names throw ERR_UNKNOWN_ENCODING (may throw). */ + "net.sockSetEncoding": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "http.reqSetEncoding": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "net.sockOnTimeout": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, + /** socket.encrypted — `boolean | undefined`: the true arm iff the + * socket carries a TLS transport (Node types `encrypted: true` on + * TLSSocket; plain sockets answer undefined — the proxy.ts isEncrypted + * idiom reads it through a cast). */ + "net.sockEncrypted": { argTypes: [NETSOCKET_T], result: VOID }, + // node:http, the client slice. The response callback shapes are + // program-dependent (checked by validate.ts); header pairs arrive flat. + /** node:http, the CLIENT slice (http.request/http.get over the net + * client machinery): request/requestCb take (host, port, path, method, + * timeoutMs, headerPairs, autoEnd[, responseCb]) — headerPairs is the + * flat [k0,v0,...] array (empty for none), autoEnd true is http.get's + * eager end(). The handle owns one dialed connection (NO pooling — the + * wire still carries Node's exact head: user headers, then Host, + * Connection: keep-alive, and the framing header; the socket closes + * when the response completes). The response delivered to responseCb / + * 'response' listeners IS an httpReq (IncomingMessage), status and + * headers parsed from the wire, body via reqOnData/reqOnEnd. Errors are + * Node-shaped ('connect ECONNREFUSED ip:port', 'socket hang up') and + * fire 'error' then 'close'; an unhandled 'error' exits 1 like every + * net handle. clientWrite before end commits to chunked framing unless + * the caller set content-length; clientEnd(data) before any write sends + * Content-Length exactly like Node. */ + "http.request": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL], + result: HTTPCLIENTREQ_T, + }, + "http.requestCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, null], + result: HTTPCLIENTREQ_T, + }, + /** new http.Agent(opts) / new https.Agent(opts): (secure, keepAlive, + * keepAliveMsecs, maxSockets, maxFreeSockets, timeoutMs, port) — the + * numeric options arrive < 0 for "unset" (Infinity/256/none; port + * seeds the settable defaultPort, Node's option merge). Returns the + * Agent as a checked-dynamic HANDLE (getName/destroy and the + * sockets/requests/freeSockets counters dispatch through the dyn + * handle ops). keepAlive: true THROWS the named construction fence — + * socket POOLING is not modeled (one dial per request); maxSockets + * accounting is real: over-limit requests defer their dial and queue. + * MAY THROW. */ + "http.agentNew": { argTypes: [BOOL, BOOL, F64, F64, F64, F64, F64, DYN], result: DYN }, + /** http.globalAgent / https.globalAgent (true): Node's keepAlive global + * agent, the one requests without an agent option use. */ + "http.globalAgent": { argTypes: [BOOL], result: DYN }, + /** http.request's `signal` option over the fresh request: an aborted + * signal destroys it with Node's AbortError now, a later abort event + * does the same. A falsy value is ignored; a non-signal throws + * ERR_INVALID_ARG_TYPE. */ + "http.clientSignal": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + /** options.signal checked before the request is built (ERR_INVALID_ARG_TYPE). */ + "http.clientSignalCheck": { argTypes: [DYN], result: VOID }, + /** https.request's servername option, stored for the construction call + * that follows: the TLS name sent and verified, and part of the pooled + * socket key. The "https." prefix keeps the TLS unit linked. */ + "https.nextServername": { argTypes: [STRING], result: VOID }, + /** The maxHeaderSize option of createServer / http.request: Node's + * validateInteger (ERR_OUT_OF_RANGE) then the header size bound. + * MAY THROW. */ + "http.serverMaxHeaderSize": { argTypes: [NETSERVER_T, F64], result: VOID }, + "http.clientMaxHeaderSize": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, + /** req.destroy(err): err becomes the request's 'error'. */ + "http.clientDestroyError": { + argTypes: [HTTPCLIENTREQ_T, { kind: "object", className: "%Error" }], + result: VOID, + }, + /** The agent-threaded request rows: the http.request/https.request + * shape with a trailing `agent` dyn argument (an Agent handle, false — + * the one-shot Connection: close dial — or null/undefined for the + * default path). port < 0 means "no port option": the agent's settable + * defaultPort, then the scheme's. MAY THROW (a non-Agent value is + * Node's ERR_INVALID_ARG_TYPE). */ + "http.requestAgent": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN], + result: HTTPCLIENTREQ_T, + }, + "http.requestAgentCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN, null], + result: HTTPCLIENTREQ_T, + }, + /** request/get with a URL-STRING first argument: the runtime parses it + * (WHATWG) and dials — throws catchably on an unparsable input or a + * non-http scheme. */ + "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + /** request/get with a runtime options record: a RequestOptions + * variable, or a literal that spreads one or sets socketPath. Args + * (options, autoEnd[, cb]). The runtime reads the record in the + * ClientRequest constructor's order (host/port or socketPath, path, + * method, headers, timeout, agent, signal, maxHeaderSize) with Node's + * errors, and documented options it does not implement throw the + * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ + "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + /** The paused-mode demux surface (portless's first-byte TLS peek: + * once('readable') + read(1) + unshift + emit('connection')). + * sockOnReadable registers a zero-param listener (a consumer: arrived + * bytes buffer instead of flowing, and EOF announces too); sockRead + * answers `Buffer | null` (exactly n buffered bytes, or null — Node's + * less-than-n answer; n <= 0 drains everything); sockUnshift returns + * bytes to the front of the stream; serverEmitConnection routes a + * socket into another server's protocol layer (a TLS target's + * 'connection' waits for its handshake). */ + "net.sockOnReadable": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + // sockRead's result is the interned `Buffer | null` union — checked + // specially in validate.ts (the reqHeader/envGet pattern; VOID here is a + // placeholder the special case overrides). + "net.sockRead": { argTypes: [NETSOCKET_T, F64], result: VOID }, + "net.sockUnshift": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** Socket flow control and the compat surface: pause/resume (reads + * gate off/on — kernel backpressure holds paused bytes; resume flows + * and discards sans listeners) and setNoDelay answer the SOCKET (+1, + * Node's chaining); destroySoon ends now and destroys once the FIN + * flushed; bytesWritten counts accepted bytes; readable is true until + * the read half ends. */ + "net.sockPause": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + "net.sockResume": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + "net.sockSetNoDelay": { argTypes: [NETSOCKET_T, BOOL], result: NETSOCKET_T }, + "net.sockDestroySoon": { argTypes: [NETSOCKET_T], result: VOID }, + /** RST close, answers the socket; throws ERR_INVALID_HANDLE_TYPE on TLS. */ + "net.sockResetAndDestroy": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + /** socket.address(): { address, family, port } or {} as a dyn. */ + "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, + "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, + /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), + * localFamily (4): dyn values, undefined when unknown. */ + "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, + "net.sockBytesRead": { argTypes: [NETSOCKET_T], result: F64 }, + "net.sockConnecting": { argTypes: [NETSOCKET_T], result: BOOL }, + "net.sockPending": { argTypes: [NETSOCKET_T], result: BOOL }, + "net.sockReadyState": { argTypes: [NETSOCKET_T], result: STRING }, + "net.sockSetKeepAlive": { argTypes: [NETSOCKET_T, BOOL, F64], result: NETSOCKET_T }, + "net.sockOnReady": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** Listeners with a parameter, as dyn functions: close(hadError), + * write's cb(err) and end's cb. */ + "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + /** socket.on/once('lookup', listener): the listener (a dyn function) + * gets (err, address, family, host) once per resolved address, or once + * with the lookup error, before the connect. */ + "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + "net.sockOnWriteFlush": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.sockOnWriteFlushDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockOnFinishDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockSetTimeoutCb": { + argTypes: [NETSOCKET_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.isIP": { argTypes: [DYN], result: F64 }, + "net.isIPv4": { argTypes: [DYN], result: BOOL }, + "net.isIPv6": { argTypes: [DYN], result: BOOL }, + "net.serverMaxConnectionsGet": { argTypes: [NETSERVER_T], result: DYN }, + "net.serverMaxConnectionsSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "net.serverDropMaxConnectionGet": { argTypes: [NETSERVER_T], result: DYN }, + "net.serverDropMaxConnectionSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** net.SocketAddress and net.BlockList (scr_net.c): the constructors + * take the argument array; the handles are dyn values. */ + "net.socketAddressNew": { argTypes: [DYN], result: DYN }, + "net.socketAddressParse": { argTypes: [DYN], result: DYN }, + "net.isSocketAddress": { argTypes: [DYN], result: BOOL }, + "net.blockListNew": { argTypes: [DYN], result: DYN }, + "net.isBlockList": { argTypes: [DYN], result: BOOL }, + "net.blockListPrivateRanges": { argTypes: [], result: DYN }, + "net.serverOnDrop": { argTypes: [NETSERVER_T, DYN, BOOL], result: VOID }, + "net.serverGetConnections": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "net.sockReadable": { argTypes: [NETSOCKET_T], result: BOOL }, + /** socket.on('finish', cb) / end(cb): fires once when the FIN goes out + * (sweep-deferred, never the registering stack). */ + "net.sockOnFinish": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.serverEmitConnection": { argTypes: [NETSERVER_T, NETSOCKET_T], result: VOID }, + // tls/https: cert/key/ca PEM arguments are strings OR Buffers (null = + // both accepted; the emitter passes data+len either way). + /** node:tls + node:https (scr_tls.c over scr_net.c/scr_http.c, with + * the vendored mbedTLS archive — linked only when one of these appears + * on the IR; moduleUsesTls is the switch, and every tls/https libCall + * also counts as net AND http use so both units link and install). + * tls.createServer takes (cert, key[, handler]) — cert/key are PEM + * strings or Buffers (the emitter passes data+len for either); the + * handler is Node's 'secureConnection' (fires post-handshake with a + * socket that behaves exactly like a net socket, the same adapters as + * net.createServer). https.createServer is (cert, key, handler) with + * the http request-handler adapters. https.request/requestCb extend + * the http client row with (…, rejectUnauthorized: bool, ca: PEM + * string/Buffer — "" for none) and default port 443; everything else + * (write/end/destroy/events, the response surface) IS the http client + * surface — the handles are the same kinds. */ + "tls.createServer": { argTypes: [null, null], result: NETSERVER_T }, + "tls.createServerCb": { argTypes: [null, null, null], result: NETSERVER_T }, + /** The SNICallback forms: (cert, key, SNICallback[, handler]). The + * callback is http2.createSecureServerSni's shape, a function or a + * function | undefined union. */ + "tls.createServerSni": { argTypes: [null, null, null], result: NETSERVER_T }, + "tls.createServerSniCb": { argTypes: [null, null, null, null], result: NETSERVER_T }, + // The runtime options records (divergence 66's stance): a dyn options + // value whose members read at runtime; pemDyn extracts a runtime-valued + // cert/key member (arg 1 is the precomposed fence label). + /** RUNTIME options records (the divergence-66 stance): the *Dyn + * creators take a checked-dynamic (dyn) options value whose members + * read at runtime — cert/key extract like the literal path, members + * whose literal forms fence THROW the catchable fence at runtime, and + * undocumented keys drop like Node drops them. tls.pemDyn is the + * literal walk's runtime-valued cert/key extraction: (value, whatLit) + * → PEM bytes (strings/Buffers/one-element arrays of those) or the + * thrown fence. All of them may throw. */ + "tls.pemDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "tls.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "tls.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + "https.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "https.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + /** createSecureServer(options, handler) — the eager COMPAT handler as + * the first 'request' listener (Node's exact route), on both literal + * flavors: Req is the dual ALPN allowHTTP1 server, H2Req the ALPN=h2-only + * server (both use compat handles over h2 streams). Args are + * (cert, key, cb, enableConnectProtocol). */ + "http2.createSecureServerReq": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + "http2.createSecureServerH2Req": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + /** createSecureServer with a RUNTIME options record (the divergence-66 + * stance): allowHTTP1/cert/key read at runtime — allowHTTP1 picks the + * flavor, the TLS server walk fences its out-of-bounds members with + * the catchable runtime fence, and h2 session-tuning keys drop exactly + * like the literal walk ignores them. May throw. The Cb form carries + * the eager compat handler. */ + "http2.createSecureServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "http2.createSecureServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + // tls.connect(port, host, opts[, cb]) — port -1 / host "" read the + // options record; the callback fires post-handshake (secureConnect). + /** tls.connect — the TLS client socket: (port, host, opts[, cb]) where + * port -1 reads options.port, host "" reads options.host, and opts is + * the runtime (dyn) options record (rejectUnauthorized/ca/servername + * implemented; other documented members throw the runtime fence). The + * callback fires post-handshake — Node's secureConnect timing. */ + "tls.connect": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + "tls.connectCb": { argTypes: [F64, STRING, DYN, null], result: NETSOCKET_T }, + // The TLSSocket member surface (authError's `string | null` union is + // shape-checked in the special cases in validate.ts — the reqHeader pattern). + /** The TLSSocket member surface on the socket kind: authorized (bool — + * Node's verify verdict), authorizationError (the verify-failure CODE + * STRING or null), the 'secureConnect' registration (a TLS socket's + * conn list fires at establishment; plain sockets never fire it), and + * the 'session' registration (fires once with the serialized session — + * a Buffer; the received-ticket event). */ + "tls.sockAuthorized": { argTypes: [NETSOCKET_T], result: BOOL }, + /** getProtocol() (string | null), getCipher() (CipherNameAndProtocol, + * undefined before the handshake, null after close) and alpnProtocol + * (string | false | null), as dyn values. */ + "tls.sockGetProtocol": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockGetCipher": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockAlpnProtocol": { argTypes: [NETSOCKET_T], result: DYN }, + /** getPeerCertificate(detailed) and getCertificate() (the legacy object, + * {} or null; scr_tls_x509.c), and the DER behind getPeerX509Certificate() + * and getX509Certificate() (a Buffer, empty without a certificate). */ + "tls.sockGetPeerCertificate": { argTypes: [NETSOCKET_T, DYN], result: DYN }, + "tls.sockGetCertificate": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockPeerCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, + "tls.sockOwnCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, + "tls.sockAuthError": { argTypes: [NETSOCKET_T], result: VOID }, + "tls.sockOnSecureConnect": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "tls.sockOnSession": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + /** The 'secure' event: the handshake completed (after secureConnect on + * a client). Plain sockets never fire it. */ + "tls.sockOnSecure": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** new tls.TLSSocket(socket, options) in server mode: a new TLS socket + * takes over the socket's connection (wrapSocketCtx passes a literal's + * secureContext member apart from the runtime options record). The + * record reads at runtime: isServer, cert, key, ALPNProtocols, + * minVersion and maxVersion are implemented, the client mode and other + * documented members throw the runtime fence. MAY THROW. */ + "tls.wrapSocket": { argTypes: [NETSOCKET_T, DYN], result: NETSOCKET_T }, + "tls.wrapSocketCtx": { argTypes: [NETSOCKET_T, DYN, SECURECTX_T], result: NETSOCKET_T }, + // createSecureContext({ cert, key }) mints the opaque SNI-answer handle. + /** tls.createSecureContext({ cert, key }) — parses the PEM pair into an + * opaque SecureContext handle (secureCtx kind) for SNI callbacks to + * answer with; cert/key are PEM strings or Buffers like createServer's. */ + "tls.createSecureContext": { argTypes: [null, null], result: SECURECTX_T }, + /** createSecureContext over a RUNTIME options record (the checked- + * dynamic lane): Node's typed option validations first (the ciphers/ + * passphrase/engine/version/timeout/ticketKeys ladders), then the pem + * walk — a validated { cert, key } bag builds the real context. + * May-throw. */ + "tls.createSecureContextDyn": { argTypes: [DYN], result: SECURECTX_T }, + /** server.addContext(servername, options | SecureContext), + * server.setSecureContext(options) and the 'tlsClientError' listener of + * a tls or https server (scr_tls.c). */ + "tls.serverAddContext": { argTypes: [NETSERVER_T, DYN, DYN], result: VOID }, + "tls.serverAddContextCtx": { argTypes: [NETSERVER_T, DYN, SECURECTX_T], result: VOID }, + "tls.serverSetSecureContext": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "tls.serverOnClientError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** tls.getCACertificates(type): validateString + the documented name + * set, then the trailing compiler-rendered fence — ALWAYS THROWS (the + * error.nodeThrow polymorphic-result carve-out). May-throw seed. */ + "tls.caCertsChk": { argTypes: [DYN, STRING], result: VOID }, + // The CA-store introspection unit (scr_tls_ca.c): per-type cached PEM + // string arrays and the default-set replacement. + /** The CA-store introspection surface (scr_tls_ca.c — its own unit and + * link gate, "tlsca." NOT "tls.", so a getCACertificates-only binary + * never pulls mbedTLS): tlsca.get is tls.getCACertificates(type) — the + * cached per-type string[] of PEM blocks (identity-stable across calls, + * Node's caching; an unknown type throws Node's ERR_INVALID_ARG_VALUE + * TypeError); tlsca.root is the tls.rootCertificates value read; + * tlsca.set is tls.setDefaultCACertificates(certs) — replaces the + * default set (deduped) and the anchors the TLS client verifies + * against, throwing Node's ERR_CRYPTO_OPERATION_FAILED when no entry + * carries a certificate block. */ + "tlsca.get": { argTypes: [STRING], result: arrayOf(STRING) }, + "tlsca.root": { argTypes: [], result: arrayOf(STRING) }, + "tlsca.set": { argTypes: [arrayOf(STRING)], result: VOID }, + "https.createServer": { argTypes: [null, null, null], result: NETSERVER_T }, + // http2's allowHTTP1 compatibility server (divergence 57): cert/key + // like tls.createServer; the 'request' handler arrives separately via + // http.serverOnRequest (shape checked in the libCall case, like + // http.createServer's). sessionError callback shape is checked by validate.ts. + /** node:http2 allowHTTP1: one TLS server advertises h2 + http/1.1 and + * dispatches to the matching parser after ALPN. Request listeners mirror + * into both compatibility lists; the remaining session/req.stream rows + * below retain their explicitly limited behavior. */ + "http2.createSecureServer": { argTypes: [null, null, BOOL], result: NETSERVER_T }, + // The SNI-callback form: arg 2 is the JS SNICallback closure — a + // `(servername, cb) => void` func, or its `| undefined` union from the + // conditional-spread spelling (the libCall case checks the shape). + /** createSecureServer with an SNI callback: args are (cert, key, sniCb) + * where sniCb is the JS SNICallback — a `(servername, cb) => void` + * closure, or the `SNICallback | undefined` union from the conditional- + * spread spelling (`...(x ? { SNICallback: x } : {})`; the emitter + * unwraps the union — the undefined arm means "no callback", exactly + * the no-SNI server). The runtime parses each connection's ClientHello + * for the server_name extension BEFORE the TLS handshake begins, calls + * the callback with (servername, answer-closure), and resumes the + * handshake when the answer arrives — cb(err) tears the socket down + * without an error event (Node's 'tlsClientError' default), cb(null, ctx) serves + * ctx's cert/key, cb(null, undefined) serves the default pair. */ + "http2.createSecureServerSni": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + // The ALPN=h2 server (createSecureServer without allowHTTP1): the real + // h2 session machinery behind the TLS handshake. + /** The REAL h2-over-TLS server (createSecureServer WITHOUT allowHTTP1): + * scr_http2_create_secure_server — the h2c session machinery behind an + * mbedTLS handshake whose ALPN advertises h2 alone (an http/1.1-only + * client fails the handshake with no_application_protocol, Node's + * h2-only split). args are (cert, key) — PEM strings or Buffers. */ + "http2.createSecureServerH2": { argTypes: [null, null, BOOL], result: NETSERVER_T }, + "http.serverOnRequest": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** server.on('checkContinue' | 'checkExpectation', (req, res)): the + * 'request' listener shape; the last argument picks checkExpectation. */ + "http.serverOnCheck": { argTypes: [NETSERVER_T, null, BOOL, BOOL], result: VOID }, + "http2.serverOnSessionError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** The guarded absent-session compatibility call remains a no-op. */ + "http2.streamNoop": { argTypes: [], result: VOID }, + /** The UNGUARDED h2-only stream call (`req.stream.on(...)`): stream is + * undefined on every connection the allowHTTP1 lowering accepts — and + * on every HTTP/1.1 connection of Node's own allowHTTP1 server — so the + * call IS Node's member read on undefined: throws the exact catchable + * TypeError ("Cannot read properties of undefined (reading 'on')"). + * One arg: the read member's name (a string literal). Never returns. */ + "http2.streamUndefCall": { argTypes: [STRING], result: VOID }, + // The REAL h2c surface (scr_http2.c). Callback slots are null (the + // emitter picks the thunk); pairs slots are string arrays; the + // endStream tri-state and event flags ride as f64. + /** node:http2, the REAL h2c surface (scr_http2.c — frame codec + HPACK + * over the net loop; the design note atop that file has the story). + * Sessions and streams are first-class handle kinds; 'stream'/'response' + * payloads cross as flat [name, value, ...] pairs arrays and an EMITTED + * adapter closure builds the program-side headers record (the response + * :status rides separately as a number). */ + "http2.createServer": { argTypes: [], result: NETSERVER_T }, + "http2.createServerReq": { argTypes: [null], result: NETSERVER_T }, + "http2.serverOnStream": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http2.serverOnSession": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + // connect(authority, reject, ca[, cb]): the TLS client knobs an https + // authority reads (inert on http ones, exactly Node); ca is a PEM + // string/Buffer ("" = the system anchors). + /** connect(authority[, listener]) — h2c prior knowledge; the listener + * closure (if any) is the 'connect' once-listener. */ + "http2.connect": { argTypes: [STRING, BOOL, null], result: HTTP2SESSION_T }, + "http2.connectCb": { argTypes: [STRING, BOOL, null, null], result: HTTP2SESSION_T }, + /** session.request(pairs?, endStream) — endStream is a tri-state f64: + * -1 the method's payload-meaningless default, 0/1 explicit. */ + "http2.sessionRequest": { + argTypes: [HTTP2SESSION_T, arrayOf(STRING), F64], + result: HTTP2STREAM_T, + }, + "http2.sessionClose": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionCloseCb": { argTypes: [HTTP2SESSION_T, null], result: VOID }, + "http2.sessionDestroy": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionOnClose": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnError": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnConnect": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnStream": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnGoaway": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionClosed": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionDestroyed": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionEncrypted": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionType": { argTypes: [HTTP2SESSION_T], result: F64 }, + "http2.sessionAlpn": { argTypes: [HTTP2SESSION_T], result: STRING }, + "http2.sessionSocket": { argTypes: [HTTP2SESSION_T], result: NETSOCKET_T }, + /** stream.respond(pairs?, endStream) — the server answer. */ + "http2.streamRespond": { argTypes: [HTTP2STREAM_T, arrayOf(STRING), BOOL], result: VOID }, + "http2.streamWrite": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamWriteBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, + "http2.streamEnd": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamEndStr": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamEndBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, + "http2.streamClose": { argTypes: [HTTP2STREAM_T, F64], result: VOID }, + "http2.streamCloseCb": { argTypes: [HTTP2STREAM_T, F64, null], result: VOID }, + "http2.streamDestroy": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.sessionSettings0": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionSettings": { argTypes: [HTTP2SESSION_T, DYN], result: VOID }, + "http2.sessionSettingsDynCb": { argTypes: [HTTP2SESSION_T, DYN, DYN], result: VOID }, + "http2.sessionSettingsCb0": { + argTypes: [HTTP2SESSION_T, DYN, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http2.sessionOnSettingsDyn": { argTypes: [HTTP2SESSION_T, DYN, BOOL, BOOL], result: VOID }, + "http2.sessionOnSettings0": { + argTypes: [HTTP2SESSION_T, { kind: "func", params: [], ret: VOID }, BOOL, BOOL], + result: VOID, + }, + "http2.sessionSettingsGet": { argTypes: [HTTP2SESSION_T, BOOL], result: DYN }, + "http2.sessionPendingSettingsAck": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.getDefaultSettings": { argTypes: [], result: DYN }, + "http2.streamSetEncoding": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamSetEncodingRet": { argTypes: [HTTP2STREAM_T, STRING], result: HTTP2STREAM_T }, + "http2.streamResume": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamPause": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamOnData": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnEnd": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnClose": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnAborted": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnError": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnResponse": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamId": { argTypes: [HTTP2STREAM_T], result: F64 }, + "http2.streamRstCode": { argTypes: [HTTP2STREAM_T], result: F64 }, + "http2.streamDestroyed": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamClosed": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamAborted": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamPending": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamSession": { argTypes: [HTTP2STREAM_T], result: HTTP2SESSION_T }, + // The createConnection forms: arg 0 is the dialer closure (() => + // Socket — the libCall case checks), then path/method/timeout/headers/ + // autoEnd like http.request. + /** The createConnection form (the proxy's own dialer): args are + * (connCb, path, method, timeout, headers, autoEnd[, cb]) — connCb is + * a `() => net.Socket` closure the runtime invokes ONCE, synchronously + * (Node's onSocket timing); everything else matches http.request. The + * Host header defaults to "localhost" — a headers.host entry wins + * verbatim, the proxy shape. */ + "http.requestConn": { + argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL], + result: HTTPCLIENTREQ_T, + }, + "http.requestConnCb": { + argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.request": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.requestCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], + result: HTTPCLIENTREQ_T, + }, + /** The URL-string first argument, the http.requestUrl row over TLS: + * no options means Node's defaults (verification on, default trust + * anchors), and a non-https scheme is ERR_INVALID_PROTOCOL. */ + "https.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestAgent": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN], + result: HTTPCLIENTREQ_T, + }, + "https.requestAgentCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN, null], + result: HTTPCLIENTREQ_T, + }, + // The requestFn binding's runtime-secure rows: https.request's shape + // with the leading `secure` bool. + /** A call through a `const requestFn = tls ? https.request : + * http.request` binding — the module-function-as-value ternary between + * the two known clients: the https.request row with a leading `secure` + * bool that picks the dial at RUNTIME (true = the TLS client, exactly + * https.request; false = the plain client, exactly http.request — + * rejectUnauthorized/ca ignored there like Node ignores TLS options on + * http.request). The "https." prefix keeps the TLS unit linked. */ + "https.requestFn": { + argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.requestFnCb": { + argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], + result: HTTPCLIENTREQ_T, + }, + "http.clientWrite": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientSetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING, STRING], result: VOID }, + "http.clientGetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientHasHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: BOOL }, + "http.clientRemoveHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientGetHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, + "http.clientGetRawHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, + "http.clientGetHeaders": { argTypes: [HTTPCLIENTREQ_T], result: DYN }, + "http.clientWriteBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, + "http.clientEnd": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientEndStr": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientEndBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, + /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ + "http.clientWriteDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + "http.clientEndDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + "http.clientFlushHeaders": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAddTrailers": { argTypes: [HTTPCLIENTREQ_T, arrayOf(STRING)], result: VOID }, + "http.clientCork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientUncork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientWritableCorked": { argTypes: [HTTPCLIENTREQ_T], result: F64 }, + "http.clientMethod": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientPath": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientHost": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientWritableFinished": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientSocket": { argTypes: [HTTPCLIENTREQ_T], result: NETSOCKET_T }, + "http.clientReusedSocket": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientSetNoDelay": { argTypes: [HTTPCLIENTREQ_T, BOOL], result: VOID }, + "http.clientSetSocketKeepAlive": { argTypes: [HTTPCLIENTREQ_T, BOOL, F64], result: VOID }, + "http.clientSetTimeout": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, + "http.clientSetTimeoutCb": { + argTypes: [HTTPCLIENTREQ_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.statusCodes": { argTypes: [], result: DYN }, + "http.methods": { argTypes: [], result: arrayOf(STRING) }, + "http.reqSetTimeout": { argTypes: [HTTPREQ_T, F64], result: VOID }, + "http.reqSetTimeoutCb": { + argTypes: [HTTPREQ_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAbort": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAborted": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnSocket": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnFinish": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnError": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnTimeout": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnClose": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnAbort": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnContinue": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** execFileSync/execSync as ONE entry (args: cmd, argv, shell, input, + * cwd, hasEnv, envPairs, timeoutMs, stdoutMode, stderrMode, buffers — + * see scr_child.h). Throws Node's exact errors: "Command failed: " + * (+ captured stderr) on non-zero exit or signal death, "spawnSync + * ENOENT" on spawn failure, "spawnSync ETIMEDOUT" after + * the SIGTERM timeout, with Node's status, signal, output, pid, stdout + * and stderr properties (Buffer outputs when buffers is set: no + * encoding option). Result is the captured utf8 stdout (+1). */ + "cp.execSync": { + argTypes: [ + STRING, + arrayOf(STRING), + BOOL, + STRING, + BOOL, + STRING, + BOOL, + arrayOf(STRING), + F64, + F64, + F64, + BOOL, + ], + result: STRING, + }, + /** The promisified-execFile/exec capture (args: cmd, argv, shell, cwd, + * hasEnv, envPairs, timeoutMs; shell marks exec's /bin/sh -c argv, as + * for execSync, so failures display the command string): the same exec + * core in the async shape — both streams captured, no echo, Node's ASYNC messages on the throw paths + * ("Command failed: \n" with the unconditional newline, + * "spawn ENOENT" with .code, timeouts reporting as ordinary + * SIGTERM command failures — never ETIMEDOUT). Result reuses the + * ScrSpawnRes container (+1; stdout/stderr strings, status unused). + * Called only from the frontend's interned %execFileAsync and + * %execAsync ASYNC helpers, whose fiber turns the throw into the + * rejection. */ + "cp.execCapture": { + argTypes: [STRING, arrayOf(STRING), BOOL, STRING, BOOL, arrayOf(STRING), F64], + result: SPAWNRES_T, + }, + /** child_process.spawn (scr_child.c + the scr_async.c loop): posix_spawnp + * with per-slot ignore/inherit/pipe modes (and number output fds), the + * all-piped Node default, and the child registered with the event loop, + * which polls waitpid(WNOHANG) at quiescence like timers. + * NEVER throws: spawn failure defers to the "error" event, Node-exact + * (the error message is Node's "spawn "; an "error" + * event with no listener prints it and exits 1 like an EventEmitter). + * cmd/args borrowed; result an owned (+1) child handle. The loop will + * not exhaust while any spawned child is unreaped — Node's keep-alive. + * + * child.onExit / child.onClose / child.onError — terminal listener + * registration through child.on/once. Close uses the exit adapter shape + * but fires only after every piped stdio handle reaches EOF. The + * receiver is borrowed, the CALLBACK MOVES into the child's listener + * registry (released after the terminal event fires, or at reap for + * the event that never fires). Both are void (chaining is fenced). + * onExit's third emitted ingredient is an ADAPTER the backend interns + * per callback shape: the runtime invokes adapter(cb, has_code, code) + * and the adapter builds the `number | null` union (tags are program- + * dependent) or ignores the code for a zero-param callback. onError's + * adapters are runtime-provided (zero-param, or the %Error one-param + * shape — scr_error_new needs no program types). "exit" fires once + * with the code (f64 arm) or null (signal death); "error" fires only + * for spawn failure, exactly Node's split. */ + "cp.spawn": { argTypes: [STRING, arrayOf(STRING)], result: CHILD_T }, + /** Static-native fork startup and IPC. process.forkTarget initializes a + * re-executed child's inherited channel and returns its embedded target id + * (-1 in the parent). The remaining entries mirror Node's JSON channel. */ + "process.forkTarget": { argTypes: [F64], result: F64 }, + "cp.fork": { + argTypes: [F64, arrayOf(STRING), F64, F64, F64, BOOL, arrayOf(STRING), STRING], + result: CHILD_T, + }, + "child.connected": { argTypes: [CHILD_T], result: BOOL }, + "child.hasChannel": { argTypes: [CHILD_T], result: BOOL }, + "child.send": { argTypes: [CHILD_T, STRING], result: BOOL }, + "child.sendCb": { argTypes: [CHILD_T, STRING, null], result: BOOL }, + "child.disconnect": { argTypes: [CHILD_T], result: VOID }, + "child.onMessage": { argTypes: [CHILD_T, null, BOOL], result: VOID }, + "child.onDisconnect": { argTypes: [CHILD_T, null, BOOL], result: VOID }, + "child.onSpawn": { argTypes: [CHILD_T, null], result: VOID }, + /** Rest listeners use the checked function bridge to pack event arguments. */ + "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, + // Own properties JavaScript adds to children (handle-properties.ts). + "child.propsInstall": { argTypes: [], result: VOID }, + "child.propsDefine": { argTypes: [DYN, DYN, DYN], result: DYN }, + "child.propsDescriptor": { argTypes: [DYN, DYN], result: DYN }, + "child.propsResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "child.propsOnDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, + "process.connected": { argTypes: [], result: BOOL }, + /** Whether this fork child opened its channel; part of process.send's + * presence in programs with fork targets. Never throws. */ + "process.ipcOpen": { argTypes: [], result: BOOL }, + "process.send": { argTypes: [STRING], result: BOOL }, + "process.sendCb": { argTypes: [STRING, null], result: BOOL }, + "process.disconnect": { argTypes: [], result: VOID }, + "process.onMessage": { argTypes: [null, BOOL], result: VOID }, + "process.onDisconnect": { argTypes: [null, BOOL], result: VOID }, + /** child_process.execFile's callback slice: starts an all-piped child, + * captures stdout/stderr, and moves the error-first callback into the + * child registry. The callback shape is program-dependent and checked + * by the validator/backends. The last argument is exec's shell flag: + * the argv is ["-c", command] and failure messages show the command. */ + "cp.execFile": { argTypes: [STRING, arrayOf(STRING), null, BOOL], result: CHILD_T }, + // spawn's options form: per-slot stdio modes (0 ignore / 1 inherit / + // 2 fd) with the out/err fds for mode 2, detached, env replacement + // pairs, cwd ("" = inherit). + /** spawn with options (scr_child.c): the cp.spawn core plus PER-SLOT + * stdio — args are (cmd, argv, inMode, outMode, errMode, outFd, errFd, + * detached, hasEnv, envPairs, cwd); modes 0 ignore (/dev/null), 1 + * inherit, 2 fd (out/err only — the fd dup2s into the child's slot, + * Node's stdio fd form; the daemon-log idiom ["ignore", logFd, logFd]). + * detached is POSIX_SPAWN_SETSID (the child gets its own session and + * process group, Node's semantics), env is a REPLACEMENT ([k,v,...] + * pairs like cp.execSync's), cwd ""=inherit. Same event/loop story as + * cp.spawn. */ + "cp.spawnOpts": { + argTypes: [ + STRING, + arrayOf(STRING), + F64, + F64, + F64, + F64, + F64, + BOOL, + BOOL, + BOOL, + arrayOf(STRING), + STRING, + ], + result: CHILD_T, + }, + /** Runtime options normalized into the native spawn core; unsupported + * process features retain explicit refusals. (command, args, options). */ + "cp.spawnDynamic": { argTypes: [STRING, arrayOf(STRING), DYN], result: CHILD_T }, + // The callback's func type is program-dependent (zero params, or the + // `number | null` union / the %Error class) — the libCall case checks + // the shape; the slot here only pins arity and the child receiver. + "child.onExit": { argTypes: [CHILD_T, null], result: VOID }, + "child.onClose": { argTypes: [CHILD_T, null], result: VOID }, + "child.onError": { argTypes: [CHILD_T, null], result: VOID }, + // Like process.envGet, spawnRes.status's result type is program-dependent + // (the interned `number | null` union) — the libCall case checks the arms. + "spawnRes.status": { argTypes: [SPAWNRES_T], result: VOID }, + // spawnRes.error's result is the interned `Error | undefined` union — + // the libCall case checks the arms, the spawnRes.status pattern. + /** Node's spawn-failure carrier `error?: Error`: a fresh +1 %Error + * ("spawnSync ENOENT", `code` stamped) when the spawn itself + * failed, the interned undefined arm otherwise — the result type is the + * call site's `Error | undefined` union, constructed type-directedly in + * the backend (the envGet convention). Never throws. */ + "spawnRes.error": { argTypes: [SPAWNRES_T], result: VOID }, + // spawnRes.signal's result is the interned `string | null` union — + // same pattern. + "spawnRes.signal": { argTypes: [SPAWNRES_T], result: VOID }, + // The ChildProcess lifecycle members: pid/exitCode are program-dependent + // unions (`number | undefined` / `number | null` — the libCall case + // checks the arms, the spawnRes.status pattern). + /** The ChildProcess lifecycle members (scr_child.c), Node's shapes + * exactly (SEMANTICS.md has the pinned matrix). child.pid is the + * checker's `number | undefined` (undefined = spawn failure) and + * child.exitCode its `number | null` (null while running and after a + * signal death; -errno once a spawn failure settled) — both unions are + * type-directed constructions in the backend over a has/get runtime + * pair, the spawnRes.status pattern. child.killed is Node's + * sent-a-signal flag. child.kill sends while the child is un-reaped + * (false after — Node's null-handle answer) and THROWS the + * Unknown-signal TypeError on bad names (may-throw); killNum passes + * numbers through (0 probes; never throws). child.unref drops the + * child from the loop's keep-alive set — it is still reaped while the + * loop runs for other reasons, and one the loop never reaps is left to + * the OS at exit. All receivers borrowed. */ + "child.pid": { argTypes: [CHILD_T], result: VOID }, + "child.exitCode": { argTypes: [CHILD_T], result: VOID }, + // The close-override pair: the bound close's result func type and the + // override's func argument are program-dependent (the callback union's + // tags) — the slots pin the server receiver; the libCall case checks + // the bound value's shape. + /** The close-override pair (the portless close-proxy idiom). + * serverCloseBind is `wrapper.close.bind(wrapper)` as a VALUE: a + * compiler-emitted closure over the server whose invocation runs the + * REAL close (scr_net_server_close_direct — never the override), so + * the override body's `origClose(cb)` cannot recurse; its callback + * argument (the `((err?: Error) => void) | undefined` union) registers + * as a once-'close' listener, a one-param callback wrapped by an + * emitted zero-arg trampoline firing the undefined arm (a clean close + * carries no error). serverSetCloseOverride is `wrapper.close = fn`: + * the override MOVES in behind an emitted zero-arg wrapper (it invokes + * the user function with the undefined-arm callback — tags are program + * data), and server.close() consults it before closing. */ + "net.serverCloseBind": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverSetCloseOverride": { argTypes: [NETSERVER_T, null], result: VOID }, + // The piped-output stream reads: `Readable | null` unions (the libCall + // case checks the arms — the child.pid pattern with a ref arm). + /** The piped-output streams (stdio mode 3 — scr_child.c's stream + * slice). child.stdout/child.stderr answer the checker's + * `Readable | null` union (type-directed construction in the backend + * over the +1-or-NULL runtime pair — the child.pid pattern with a ref + * arm). stream.onData/onEnd register 'data'/'end' listeners (receiver + * borrowed, CALLBACK MOVES, trailing once-flag, void — chaining + * fenced): 'data' fires one Buffer chunk per read (zero-param and + * Buffer/string adapters are runtime-provided; setEncoding threads + * split multibyte sequences through the shared StringDecoder core. A + * union-param listener — + * ngrok's `Buffer | string` — gets a compiler-emitted adapter wrapping + * the chunk at its Buffer arm), 'end' fires once at EOF, always BEFORE + * the child's 'exit' (the pinned ordering). A flowing stream keeps the + * loop alive: usesTimers. */ + "child.stdout": { argTypes: [CHILD_T], result: VOID }, + "child.stderr": { argTypes: [CHILD_T], result: VOID }, + /** The piped-input writer (stdio mode 3 on fd 0). child.stdin answers + * `Writable | null`; writes copy borrowed data into the nonblocking + * queue, end/destroy settle it, writable is a pure state read, and + * listener callbacks move into the writer registry. */ + "child.stdin": { argTypes: [CHILD_T], result: VOID }, + /** Under the general stream representation (general-streams.ts), a + * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream + * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge + * throws when the program lacks the stream unit's boxing helpers. */ + "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, + // Listener registrations: callback func shapes are program-dependent + // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, + // the stream receiver, and the once-flag. + "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.onEnd": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.childSetEncoding": { argTypes: [CHILDSTREAM_T, STRING], result: CHILDSTREAM_T }, + "writer.writeString": { argTypes: [CHILDWRITER_T, STRING], result: BOOL }, + "writer.writeBytes": { argTypes: [CHILDWRITER_T, BYTES_U8], result: BOOL }, + "writer.end": { argTypes: [CHILDWRITER_T], result: VOID }, + "writer.destroy": { argTypes: [CHILDWRITER_T], result: VOID }, + "writer.writable": { argTypes: [CHILDWRITER_T], result: BOOL }, + "writer.onDrain": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + "writer.onFinish": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + "writer.onError": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + /** The first-class WritableStream write (`output.write(line)` — the + * prefixStream idiom): the receiver IS the fd scalar (process.stdout/ + * stderr reads mint 1/2), dispatched onto the exact stdoutWrite/ + * stderrWrite paths so buffering and ordering stay identical. Data + * borrowed; the bool is Node's always-true backpressure signal. */ + "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, + /** process.stdout/stderr flowing into a runtime Writable slot under the + * general stream representation: an fd-backed Writable (+1) whose + * writes take process.stdout.write's synchronous path. */ + /** A pipeline() stage that is a checked-dynamic value: a boxed native + * stream unwraps by identity (+1); anything else throws the given + * not-supported message. Typed as a Duplex so every stage position + * accepts it; the runtime pipeline checks the stream's real sides. */ + "stream.unboxDyn": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, + /** Whether pipeline() pumps a checked-dynamic first stage as an + * iterable (a string, or a value with an iterator method that is not a + * stream with pipe() and on()) instead of piping it as a stream. */ + "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, + /** The Transform a Cipheriv or Decipheriv reads and writes as a stream + * (+1, created on first use and owned by the cipher). */ + "stream.handleStream": { + argTypes: [DYN], + result: { kind: "object", className: "%Transform" }, + }, + /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a + * checked-dynamic value boxes (+1) — a runtime stream with the readable + * (1) or writable (2) side — or null for any other value. Never throws; + * the result union is `%Readable | null` or `%Writable | null`. */ + "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, + /** stream.dynNative for a pipe or unpipe destination: also a + * constructor-function stream's runtime stream, in programs that link + * that unit, unless it overrides write() or end(). */ + "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, + /** readable.pipe(destination, end) into a checked-dynamic destination + * that is not a native stream: [source, the source boxed, destination, + * end]. Node's pipe listener protocol over the destination's members + * (scr_stream_dyn.c); throws what those members throw. */ + "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, + /** readable.unpipe(destination) for a checked-dynamic destination that + * is not a native stream: [source, destination]. Unpipes the pipe + * stream.pipeDyn registered into it, if any (scr_stream_dyn.c). */ + "stream.unpipeDyn": { argTypes: [null, DYN], result: VOID }, + "stream.stdioWritable": { + argTypes: [PROCSTREAM_T], + result: { kind: "object", className: "%Writable" }, + }, + "child.killed": { argTypes: [CHILD_T], result: BOOL }, + "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, + "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, + "child.unref": { argTypes: [CHILD_T], result: VOID }, + "child.ref": { argTypes: [CHILD_T], result: VOID }, + "spawnRes.stdout": { argTypes: [SPAWNRES_T], result: STRING }, + "spawnRes.stderr": { argTypes: [SPAWNRES_T], result: STRING }, + /** fs/promises (scr_lib.c over scr_async.c's settled minting): the SAME + * sync syscalls, wrapped in an ALREADY-SETTLED promise — failure + * REJECTS (catchable at the await) instead of throwing, so none of + * these are in the may-throw seed. readFile is utf8-fenced like + * readFileSync. The non-interleaving divergence is documented in + * SEMANTICS.md. */ + /** node:crypto randomness: randomUUID plus randomBytes as either a real + * Buffer or the fused randomBytes(n).toString("hex"|"base64") path. + * The size-taking forms throw Node's RangeError on invalid values. */ + "crypto.randomUUID": { argTypes: [], result: STRING }, + "crypto.randomBytesToString": { argTypes: [F64, STRING], result: STRING }, + /** crypto.randomBytes(n) → a real u8 Buffer (+1). THROWS Node's + * RangeError on out-of-range sizes, exactly like the composed + * randomBytesToString (which keeps its one-libCall lowering — the two + * coexist: the composed form never materializes the Buffer). */ + "crypto.randomBytes": { argTypes: [F64], result: BYTES_U8 }, + /** The fused createHash(alg).update(data).digest(enc) fast path and the + * one-shot crypto.hash implementation. First-class Hash/Hmac handles use + * the entries below; all paths share the incremental runtime core. */ + "crypto.hashDigestStr": { argTypes: [STRING, STRING, STRING], result: STRING }, + "crypto.hashDigestBytes": { argTypes: [STRING, BYTES_U8, STRING], result: STRING }, + /** First-class static Hash/Hmac handles. Constructors validate the + * runtime algorithm string (every digest Node 24 lists); update returns the same + * handle by retained identity, copy snapshots Hash state, and digest + * finalizes the handle and returns either a Buffer or encoded string. */ + "crypto.native": { argTypes: [], result: DYN }, + "crypto.hashNew": { argTypes: [STRING], result: CRYPTOHASH_T }, + /** The Transform a Hash or Hmac reads and writes as a stream (+1, + * created on first use and owned by the value; scr_stream.c). The Piped + * form, for pipe destinations and pipeline stages, also keeps the value + * alive until the stream flushes or is destroyed. */ + "crypto.hashStream": { argTypes: [null], result: { kind: "object", className: "%Transform" } }, + "crypto.hashStreamPiped": { + argTypes: [null], + result: { kind: "object", className: "%Transform" }, + }, + /** createCipheriv (false) / createDecipheriv (true) over checked-dynamic + * cipher, key, iv and options arguments (scr_cipher.c): a native + * Cipheriv/Decipheriv handle whose methods dispatch through dynInvoke. */ + "crypto.cipherNew": { argTypes: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + /** createECDH(curveName) over a checked-dynamic curve (scr_ecdh.c): a + * native ECDH handle whose methods dispatch through dynInvoke. */ + "crypto.ecdhNew": { argTypes: [DYN], result: DYN }, + /** ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format): + * the converted point as a Buffer or string. */ + "crypto.ecdhConvertKey": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: DYN }, + /** createDiffieHellman(sizeOrKey, keyEncoding, generator, + * generatorEncoding) over checked-dynamic arguments (scr_dh.c): a native + * DiffieHellman handle whose methods dispatch through dynInvoke. */ + "crypto.dhNew": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + /** getDiffieHellman(groupName) and createDiffieHellmanGroup(name): a + * native DiffieHellmanGroup handle. */ + "crypto.dhGroupNew": { argTypes: [DYN], result: DYN }, + /** checkPrimeSync(candidate, options) and generatePrimeSync(size, + * options) over checked-dynamic arguments (scr_dh.c): a boolean, and an + * ArrayBuffer or bigint, as checked-dynamic results. The Cb forms take + * the callback and whether its error parameter takes null for Node's + * undefined, and call it in a later macrotask; Node's argument errors + * throw (may-throw). */ + "crypto.checkPrimeSync": { argTypes: [DYN, DYN], result: DYN }, + /** getCipherInfo(nameOrNid, options) over checked-dynamic arguments + * (scr_cipher_info.c): the info object or undefined. */ + "crypto.getCipherInfo": { argTypes: [DYN, DYN], result: DYN }, + "crypto.checkPrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "crypto.generatePrimeSync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generatePrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "crypto.hmacNewStr": { argTypes: [STRING, STRING], result: CRYPTOHMAC_T }, + "crypto.hmacNewBytes": { argTypes: [STRING, BYTES_U8], result: CRYPTOHMAC_T }, + "crypto.hashUpdateStr": { argTypes: [CRYPTOHASH_T, STRING], result: CRYPTOHASH_T }, + "crypto.hashUpdateBytes": { argTypes: [CRYPTOHASH_T, BYTES_U8], result: CRYPTOHASH_T }, + "crypto.hmacUpdateStr": { argTypes: [CRYPTOHMAC_T, STRING], result: CRYPTOHMAC_T }, + "crypto.hmacUpdateBytes": { argTypes: [CRYPTOHMAC_T, BYTES_U8], result: CRYPTOHMAC_T }, + "crypto.hashCopy": { argTypes: [CRYPTOHASH_T], result: CRYPTOHASH_T }, + "crypto.hashDigestString": { argTypes: [CRYPTOHASH_T, STRING], result: STRING }, + "crypto.hashDigestBuffer": { argTypes: [CRYPTOHASH_T], result: BYTES_U8 }, + "crypto.hmacDigestString": { argTypes: [CRYPTOHMAC_T, STRING], result: STRING }, + "crypto.hmacDigestBuffer": { argTypes: [CRYPTOHMAC_T], result: BYTES_U8 }, + /** The option and any-value forms, with Node's checks at run time. The + * Dyn digests return a string or a Buffer, and crypto.hash's Dyn form + * either one by its outputEncoding. */ + "crypto.hashNewOpts": { argTypes: [STRING, DYN], result: CRYPTOHASH_T }, + "crypto.hmacNewDyn": { argTypes: [STRING, DYN, DYN], result: CRYPTOHMAC_T }, + "crypto.hashCopyOpts": { argTypes: [CRYPTOHASH_T, DYN], result: CRYPTOHASH_T }, + "crypto.hashUpdateEnc": { argTypes: [CRYPTOHASH_T, STRING, STRING], result: CRYPTOHASH_T }, + "crypto.hmacUpdateEnc": { argTypes: [CRYPTOHMAC_T, STRING, STRING], result: CRYPTOHMAC_T }, + "crypto.hashUpdateDyn": { argTypes: [CRYPTOHASH_T, DYN, DYN], result: CRYPTOHASH_T }, + "crypto.hmacUpdateDyn": { argTypes: [CRYPTOHMAC_T, DYN, DYN], result: CRYPTOHMAC_T }, + "crypto.hashDigestDyn": { argTypes: [CRYPTOHASH_T, DYN], result: DYN }, + "crypto.hmacDigestDyn": { argTypes: [CRYPTOHMAC_T, DYN], result: DYN }, + "crypto.hashOneshot": { argTypes: [STRING, DYN, DYN], result: DYN }, + /** Views of any element kind (the validator checks both are views). */ + "crypto.timingSafeEqual": { argTypes: [null, null], result: BOOL }, + "crypto.timingSafeEqualDyn": { argTypes: [DYN, DYN], result: BOOL }, + "crypto.randomFill": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, + "crypto.randomFillRest": { argTypes: [BYTES_U8, F64], result: BYTES_U8 }, + "crypto.randomInt": { argTypes: [F64, F64], result: F64 }, + "crypto.randomIntMax": { argTypes: [F64], result: F64 }, + /** randomFillSync over any view returns that view at its own type (the + * validator checks the result against the first argument). The Dyn form + * takes ArrayBuffers and other values. The callback forms carry + * randomFill's argument count before the callback; randomIntCb carries + * whether min was given. */ + "crypto.randomFillView": { argTypes: [null, F64, F64], result: BYTES_U8 }, + "crypto.randomFillViewRest": { argTypes: [null, F64], result: BYTES_U8 }, + /** crypto.getRandomValues: a view returns itself at its own type (the + * float/size DOMExceptions are thrown at run time); any other value + * takes the checked form. */ + "crypto.getRandomValues": { argTypes: [null], result: BYTES_U8 }, + "crypto.getRandomValuesDyn": { argTypes: [DYN], result: DYN }, + "crypto.randomFillSyncDyn": { argTypes: [DYN, DYN, DYN], result: DYN }, + "crypto.randomFillCb": { argTypes: [null, F64, F64, F64, null], result: VOID }, + "crypto.randomFillCbDyn": { argTypes: [DYN, DYN, DYN, F64, null], result: VOID }, + "crypto.randomIntCb": { argTypes: [F64, F64, F64, null], result: VOID }, + "crypto.randomUUIDOpts": { argTypes: [DYN], result: STRING }, + /** Secret KeyObjects: native handles whose getters and methods dispatch + * through the handle. keyObjectInput reads a key's bytes for hkdf. */ + "crypto.createSecretKey": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeySync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeyCb": { argTypes: [DYN, DYN, null], result: VOID }, + "crypto.keyObjectInput": { argTypes: [DYN], result: BYTES_U8 }, + "crypto.isKeyObject": { argTypes: [DYN], result: BOOL }, + /** WebCrypto (scr_webcrypto.c): each method takes its argument vector as + * one dyn array and answers a promise that argument and validation + * failures reject. CryptoKey values are native handles. */ + "webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.wrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.unwrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.deriveBits": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.deriveKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.sign": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.verify": { argTypes: [DYN], result: { kind: "promise", inner: BOOL } }, + "webcrypto.digest": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.isCryptoKey": { argTypes: [DYN], result: BOOL }, + "webcrypto.object": { argTypes: [], result: DYN }, + "crypto.randomUUIDv7": { argTypes: [DYN], result: STRING }, + "crypto.pbkdf2": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: BYTES_U8 }, + "crypto.randomBytesCb": { argTypes: [F64, null], result: VOID }, + "crypto.pbkdf2Cb": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING, null], result: VOID }, + "crypto.hkdf": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: DYN }, + "crypto.hkdfCb": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64, null], result: VOID }, + "crypto.scrypt": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, + "crypto.scryptCb": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN, null], result: VOID }, + /** util.promisify over the callback forms: the callback form's + * arguments without the callback → a promise the callback form settles, + * or rejected with an argument error; never throws. The result's value + * type is the declared one (a Buffer, the filled view, a number, or a + * checked-dynamic value); the validator checks it. */ + "crypto.randomBytesPromise": { argTypes: [F64], result: VOID }, + "crypto.pbkdf2Promise": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: VOID }, + "crypto.scryptPromise": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: VOID }, + "crypto.hkdfPromise": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: VOID }, + "crypto.randomFillPromise": { argTypes: [null, F64, F64, F64], result: VOID }, + "crypto.randomFillPromiseDyn": { argTypes: [DYN, DYN, DYN, F64], result: VOID }, + "crypto.randomIntPromise": { argTypes: [F64, F64, F64], result: VOID }, + "crypto.generateKeyPromise": { argTypes: [DYN, DYN], result: VOID }, + /** util.promisify(dns.lookup) and util.promisify(dns.lookupService): + * [hostname, options] and [address, port] → a promise of the + * checked-dynamic result object (the address array with options.all), + * or rejected with the error; never throws. */ + "dns.lookupPromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.lookupServicePromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + // The Buffer statics and the fs/zlib Buffer forms: fixed always-u8 + // signatures (Buffer IS a Uint8Array — one bytes kind). + /** The Buffer statics with fixed (always-u8) signatures. fromStr is + * `Buffer.from(string, enc)` — the frontend completes an omitted + * encoding to "utf8" and fences non-literal/unsupported ones; hex and + * base64 decode Node-leniently, so it never throws. concat takes ONE + * bytes[] arg (the list) and returns a fresh copy. `Buffer.from(u8)` + * and `Buffer.alloc(n)` need no libFn — they lower to bytesNew. */ + "buffer.fromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.fromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.brand": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** Buffer.isBuffer over a typed view: whether the view carries the + * Buffer brand (any element kind; only Buffers do). Never throws. */ + "buffer.isBranded": { argTypes: [null], result: BOOL }, + /** Buffer.from on checked-native strings, bytes, arrays and data-only + * array-like/Buffer-JSON objects. The encoding is a normalized literal; + * non-string inputs ignore it. Copies the input and may throw during + * element coercion or argument validation. Custom object valueOf and + * opaque reference inputs retain explicit runtime refusals. */ + "buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 }, + "buffer.concatDyn": { argTypes: [DYN], result: BYTES_U8 }, + /** The pooled Buffer factories (Node's allocate/fromStringFast/ + * fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays, + * number[] and checked values, and concat return views into the + * per-thread pool slab below Buffer.poolSize >>> 1 bytes. alloc and + * allocUnsafeSlow never pool. The size forms validate like Node + * (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */ + "buffer.alloc": { argTypes: [F64], result: BYTES_U8 }, + /** Buffer.alloc as a function value: the size, fill and encoding arrive + * unchecked, and the runtime fills as Node's alloc does. */ + "buffer.allocValue": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 }, + "buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 }, + "buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.poolFromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 }, + "buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + /** poolFromDyn that also copies an array crossing by reference. */ + "buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "buffer.poolFromDynOffset": { argTypes: [DYN, DYN], result: BYTES_U8 }, + /** Buffer.from(value, byteOffset, length): an ArrayBuffer's range with + * Node's fromArrayBuffer checks; other values as poolFromDynOffset. */ + "buffer.poolFromDynRange": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "buffer.poolSize": { argTypes: [], result: F64 }, + "buffer.setPoolSize": { argTypes: [F64], result: VOID }, + /** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like + * fromStr's — and Buffer.isEncoding(name) over a runtime string + * (case-insensitive against Node's alias set). Pure; never throw. */ + "buffer.byteLenDyn": { argTypes: [DYN, STRING], result: F64 }, + /** Buffer.byteLength as a function value: the encoding arrives at run time. */ + "buffer.byteLenValue": { argTypes: [DYN, DYN], result: F64 }, + "buffer.byteLenStr": { argTypes: [STRING, STRING], result: F64 }, + "buffer.isEncoding": { argTypes: [STRING], result: BOOL }, + /** Buffer.concat(list, totalLength): the concatenation truncated or + * zero-padded to the total. THROWS Node's 'length' RangeError on a + * negative/non-integer total (may-throw seed). */ + "buffer.concatLen": { argTypes: [arrayOf(BYTES_U8), F64], result: BYTES_U8 }, + "buffer.concatLenDyn": { argTypes: [DYN, F64], result: BYTES_U8 }, + // The checked-dynamic compare/equals validators (Node's argument + // ladders over dyn-boxed invalid-input probes). + /** The checked-dynamic compare/equals validators (scr_bytes_io.c) — + * the lowered form when an argument is NOT statically bytes (the + * invalid-input probes; a dyn from an untyped JS helper): Node's own + * argument ladders run at runtime — ERR_INVALID_ARG_TYPE with the + * API's argument name ("buf1"/"buf2", "otherBuffer", "target"; offsets + * "of type number"), validateOffset's ERR_OUT_OF_RANGE for bad + * numbers, undefined offsets taking their Node defaults — and a + * well-typed value still computes the real answer. All args borrowed + * dyn; compareChk's four offset slots pass the undefined dyn when + * syntactically absent. May-throw seeds. */ + "buffer.compareChk": { argTypes: [DYN, DYN], result: F64 }, + "bytes.equalsChk": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "bytes.compareChk": { argTypes: [BYTES_U8, DYN, DYN, DYN, DYN, DYN], result: F64 }, + /** The deprecated `new Buffer(number, 'enc')` string-arm rejection: + * always throws Node's ERR_INVALID_ARG_TYPE ("The \"string\" argument + * must be of type string. Received ..."). Borrowed dyn; may-throw. */ + "buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 }, + "buffer.deprecation": { argTypes: [], result: VOID }, + "buffer.deprecationCount": { argTypes: [], result: VOID }, + "buffer.constructor": { argTypes: [], result: DYN }, + "buffer.installValue": { argTypes: [], result: VOID }, + "buffer.staticValue": { argTypes: [STRING], result: DYN }, + "buffer.prototypeMember": { argTypes: [DYN], result: DYN }, + /** fs._toUnixTimestamp(time) over a dyn value: numeric strings and + * finite numbers coerce (negatives answer now/1000, Node's shape); + * everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */ + "fs.toUnixTimestamp": { argTypes: [DYN], result: F64 }, + /** The fs argument-validation ladders (checked-dynamic lane): each Chk + * replicates its API's Node-order validation over dyn values (Node's + * exact typed errors — ERR_INVALID_ARG_TYPE/VALUE, ERR_OUT_OF_RANGE), + * and a full pass meets the trailing compiler-rendered SC2020 fence + * string — so the ALWAYS-THROW forms take the error.nodeThrow + * polymorphic-result carve-out. mkdtempSyncChk and the lchmod sync/ + * promise pair run the REAL operation on a validated pass instead + * (macOS lchmod(2); non-APPLE answers Node's not-a-function / + * ERR_METHOD_NOT_IMPLEMENTED shapes). May-throw seeds, all of them. */ + "fs.existsChk": { argTypes: [DYN, DYN], result: DYN }, + "fs.mkdtempChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.mkdtempSyncChk": { argTypes: [DYN, DYN, STRING], result: STRING }, + "fs.readFileChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, + "fs.opendirChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.watchFileChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.lchmodChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, + "fs.lchmodSyncChk": { argTypes: [DYN, DYN], result: DYN }, + "fsp.lchmodChk": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fs.readChk": { argTypes: [DYN, DYN, DYN, DYN, DYN, STRING], result: VOID }, + "fs.streamOptsChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + /** fs.ReadStream / fs.WriteStream (scr_fs_stream.c): the constructors + * take the path and options as dyn values and throw Node's option + * errors; the members read bytesRead/bytesWritten, pending and the path + * value, and close() / close(cb) (a `() => void` callback). */ + "fs.createReadStream": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%Readable" }, + }, + "fs.createWriteStream": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%Writable" }, + }, + "fs.fsStreamBytes": { argTypes: [null], result: F64 }, + "fs.fsStreamPending": { argTypes: [null], result: BOOL }, + "fs.fsStreamPath": { argTypes: [null], result: DYN }, + "fs.fsStreamPropDyn": { argTypes: [null, STRING], result: DYN }, + "fileHandle.createReadStream": { + argTypes: [FILEHANDLE_T, DYN], + result: { kind: "object", className: "%Readable" }, + }, + "fileHandle.createWriteStream": { + argTypes: [FILEHANDLE_T, DYN], + result: { kind: "object", className: "%Writable" }, + }, + "fs.fsStreamClose": { argTypes: [null], result: VOID }, + "fs.fsStreamCloseCb": { argTypes: [null, funcOf([], VOID)], result: VOID }, + /** The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME- + * rendered Received tail: args [argname, "of type ..." clause, the + * offending dyn value]. ALWAYS THROWS; polymorphic result (the + * error.nodeThrow pattern). May-throw seed. */ + "error.argTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, + /** The property flavor of argTypeThrow ("The \"options.x\" property + * must be ..."): the option-bag ladders' provably-invalid arms. ALWAYS + * THROWS; polymorphic result. May-throw seed. */ + "error.propTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, + /** The checked-dynamic max-listeners ladders: setMaxChk is the + * instance form over a dyn n (non-numbers ERR_INVALID_ARG_TYPE, + * negatives/NaN ERR_OUT_OF_RANGE; +1 receiver back — chaining); + * setDefaultMaxChk is the static/property form, its second argument + * naming the message slot ("setMaxListeners" for the static call, + * "defaultMaxListeners" for the module-property assignment). */ + "emitter.setMaxChk": { argTypes: [null, DYN], result: VOID }, + "emitter.setDefaultMaxChk": { argTypes: [DYN, STRING], result: VOID }, + /** The Buffer forms of fs: readFileSync(path) with NO + * encoding → bytes (+1), writeFileSync/appendFileSync(path, bytes), and the + * fs/promises readFile(path) no-encoding form (an already-settled + * promise, rejecting on failure like the other fsp members). The sync + * forms THROW catchably on failure exactly like the utf8 forms. */ + "fs.readFileSyncBytes": { argTypes: [STRING], result: BYTES_U8 }, + "fs.writeFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, + "fs.appendFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, + "fsp.readFileBytes": { argTypes: [STRING], result: { kind: "promise", inner: BYTES_U8 } }, + /** node:zlib (scr_zlib.c — native-toolchain.ts compiles/links it ONLY when these + * appear on the IR, the regex/libcurl gating precedent): one-shot zlib, + * raw-DEFLATE, gzip, and auto-detect codecs over u8 bytes with Node's + * default options, or a validated literal compression level from -1 to 9. + * Compression never throws (OOM aborts); decompression + * of corrupt input THROWS Node's error catchably. */ + "zlib.deflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.inflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.deflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** bytes, mode (0 zlib / 1 raw / 2 gzip), validated compression level. */ + "zlib.deflateLevelSync": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, + "zlib.inflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.gzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.gunzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.unzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** The default-options callback twins run on the executable's shared + * native work pool and deliver (Error | null, Buffer) on a later loop + * turn. crc32 validates its optional uint32 seed synchronously. */ + "zlib.deflateCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.inflateCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.deflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.inflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.gzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.gunzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.unzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.crc32": { argTypes: [BYTES_U8, F64], result: F64 }, + /** The one-shot forms with an options value: node_zlib_mode, the input, + * the options (validated at runtime with Node's ladder). The Cb form + * takes the program-shaped (Error | null, Buffer) callback, which moves. */ + "zlib.syncOpts": { argTypes: [F64, BYTES_U8, DYN], result: BYTES_U8 }, + "zlib.cbOpts": { argTypes: [F64, BYTES_U8, DYN, null], result: VOID }, + /** The checked-dynamic input twins (the JS lane's untyped data). */ + "zlib.syncOptsDyn": { argTypes: [F64, DYN, DYN], result: BYTES_U8 }, + "zlib.cbOptsDyn": { argTypes: [F64, DYN, DYN, null], result: VOID }, + /** util.promisify over the callback one-shots: [mode, data, options] + * like zlib.cbOpts → a promise the worker-pool run settles with the + * Buffer or the callback form's Error. An options or input error rejects + * it; never throws. The Dyn twin takes the JS lane's untyped data. */ + "zlib.promise": { + argTypes: [F64, BYTES_U8, DYN], + result: { kind: "promise", inner: BYTES_U8 }, + }, + "zlib.promiseDyn": { argTypes: [F64, DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, + /** The stream classes (scr_zlib_stream.c, linked with the stream unit): + * streamNew takes Node's node_zlib_mode number and the options value + * (undefined when absent), validates it with Node's ladder, and answers + * the +1 %Transform stream. flush takes the kind value, params the level + * and strategy values; the Cb forms move their () => void callback. The + * receivers are borrowed. */ + /** Connects scr_brotli.c to the zlib engine (%main, before any module). */ + "zlib.brotliInstall": { argTypes: [], result: VOID }, + "zlib.streamNew": { argTypes: [F64, DYN], result: VOID }, + "zlib.streamBytesWritten": { argTypes: [null], result: F64 }, + /** `stream instanceof zlib.Gzip`: the borrowed stream and the class's + * node_zlib_mode; true when the stream is a zlib stream of that mode. */ + "zlib.streamIs": { argTypes: [null, F64], result: BOOL }, + "zlib.streamFlush": { argTypes: [null, DYN], result: VOID }, + "zlib.streamFlushCb": { argTypes: [null, DYN, null], result: VOID }, + "zlib.streamParams": { argTypes: [null, DYN, DYN], result: VOID }, + "zlib.streamParamsCb": { argTypes: [null, DYN, DYN, null], result: VOID }, + "zlib.streamClose": { argTypes: [null], result: VOID }, + "zlib.streamCloseCb": { argTypes: [null, null], result: VOID }, + "zlib.streamReset": { argTypes: [null], result: VOID }, + /** The Buffer overloads of the raw stream writes — same promptly + * submitted streams as process.stdoutWrite/stderrWrite, constantly true. + * The encoding arg is evaluated but ignored for bytes, like Node. The Cb + * forms move their program-shaped completion callback to the tick queue. */ + "process.stdoutWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, + "process.stderrWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, + // Completion callback is program-dependent: zero params, checked-dynamic, + // or an optional Error | null slot (same success shape as fs.rename). + "process.stdoutWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, + "process.stderrWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, + /** The value of a FULFILLED promise, read without a microtask turn (+1 + * for references): the tuple form of Promise.all reads its entries this + * way once the combinator fulfills, each entry widened to Promise + * and read at the type it was built with (program-dependent). */ + "promise.fulfilledValue": { argTypes: [null], result: VOID }, + /** Promise.all's tuple form as a value: the entries (each widened to + * Promise) join the runtime combinator, and when it fulfills the + * function reads the fulfilled entries into the tuple inside that + * settlement, so the result settles on the combinator's turn; a + * rejection settles it the same way. The result is the tuple's promise + * (program-dependent). */ + "promise.allTuple": { argTypes: [null, null], result: VOID }, + "fsp.readFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, + "fsp.writeFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + /** fs.promises.writeFile(path, data, { mode }): the settled-promise + * twin of fs.writeFileModeSync. */ + "fsp.writeFileMode": { + argTypes: [STRING, STRING, F64], + result: { kind: "promise", inner: VOID }, + }, + "fsp.mkdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + /** The fs/promises option/member tail the certs pipeline uses: mkdir's + * literal { recursive?, mode? } options (the mkdirSync matrix behind + * settled promises), unlink, chmod. Failures REJECT (catchable at the + * await), like the rest of the fsp family. */ + "fsp.mkdirMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.mkdirRecursive": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.mkdirRecursiveMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.unlink": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.chmod": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.chown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, + "fsp.lchown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, + "fsp.access": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.truncate": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.appendFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.appendFileBytes": { + argTypes: [STRING, BYTES_U8], + result: { kind: "promise", inner: VOID }, + }, + "fsp.copyFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.mkdtemp": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, + /** readFile(path, encoding | { encoding }) for any runtime encoding name. */ + "fsp.readFileEncoded": { + argTypes: [STRING, STRING], + result: { kind: "promise", inner: STRING }, + }, + "fsp.rmdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.link": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.symlink": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.cp": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.readlink": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "fsp.statfs": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "fsp.readlinkStr": { argTypes: [DYN, DYN], result: { kind: "promise", inner: STRING } }, + "fsp.readlinkBuffer": { argTypes: [DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "fsp.utimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.lutimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.rename": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.readdir": { argTypes: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } }, + // Result is promise; the libCall case + // validates the program-dependent record shape in validate.ts. + /** `fs.promises.readdir(path, { withFileTypes: true })` — the settled- + * promise twin of fs.readdirTypesSync. The backends assemble the same + * call-site-shaped Dirent rows, then move the array into a promise; + * scandir failure becomes its rejection. */ + "fsp.readdirTypes": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.rm": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + /** `fs.promises.rm(path, options)`: fs.rmOptsSync and fs.rmRetrySync + * behind a settled promise; their errors become its rejection. */ + "fsp.rmOpts": { argTypes: [STRING, BOOL, BOOL], result: { kind: "promise", inner: VOID } }, + "fsp.rmRetry": { + argTypes: [STRING, BOOL, BOOL, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + "fsp.stat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, + "fsp.realpath": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, + "fsp.lstat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, + /** fs/promises.open and the statically represented FileHandle surface. + * Every operation returns an already-settled promise; syscall failures + * become rejections rather than escaping synchronously. read/write + * results are call-site record shapes ({ bytesRead/bytesWritten, + * buffer }) assembled by the backends around the fixed runtime ABI. */ + "fsp.open": { + argTypes: [STRING, STRING, F64], + result: { kind: "promise", inner: FILEHANDLE_T }, + }, + "fileHandle.fd": { argTypes: [FILEHANDLE_T], result: F64 }, + "fileHandle.close": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.sync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.datasync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.truncate": { + argTypes: [FILEHANDLE_T, F64], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.chmod": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } }, + "fileHandle.utimes": { + argTypes: [FILEHANDLE_T, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.readv": { + argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writev": { + argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], + result: { kind: "promise", inner: VOID }, + }, + // read/write carry call-site result record shapes; the validator checks + // those in validate.ts, so promise is only a table sentinel. + "fileHandle.read": { + argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeBytes": { + argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeStr": { + argTypes: [FILEHANDLE_T, STRING, F64, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.readFile": { + argTypes: [FILEHANDLE_T, STRING], + result: { kind: "promise", inner: STRING }, + }, + "fileHandle.readFileBytes": { + argTypes: [FILEHANDLE_T, STRING], + result: { kind: "promise", inner: BYTES_U8 }, + }, + "fileHandle.writeFile": { + argTypes: [FILEHANDLE_T, STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeFileBytes": { + argTypes: [FILEHANDLE_T, BYTES_U8, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.stat": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: STATS_T } }, + "process.argv": { argTypes: [], result: arrayOf(STRING) }, + // The Node runtime flag a node:test binary took out of process.argv + // (--test-update-snapshots), or [] — a fresh array per read. + "process.execArgv": { argTypes: [], result: arrayOf(STRING) }, + "process.platform": { argTypes: [], result: STRING }, + // The one libCall whose result type is program-dependent (union ids are + // per-module): the `result` here is a placeholder — the libCall case + // checks the union's ARMS ([string, undefinedT] in canonical order) + // against the module's registry instead. + /** getenv(3): one string key arg → the interned `string | undefined` + * union (present: +1 string wrapped into the string arm; absent: the + * interned undefined-arm instance). BOTH source forms — `process.env.FOO` + * and `process.env[expr]` — lower here. Purely static; never throws. */ + "process.envGet": { argTypes: [STRING], result: VOID }, + /** setenv(3): (name, value) string args → void. Later envGet reads and + * spawned children observe the write, like Node (values are strings — + * the frontend fences non-string RHS). Never throws. */ + "process.envSet": { argTypes: [STRING, STRING], result: VOID }, + /** `delete process.env[NAME]` — unsetenv(3): the mutation is visible to + * every later read (process.envGet asks getenv fresh) and inherited by + * spawned children, exactly Node. Statement position only (JS's boolean + * result is constant true there). Borrows the name; never throws. */ + "process.envUnset": { argTypes: [STRING], result: VOID }, + /** The whole environment as alternating [k0, v0, k1, v1, ...] strings in + * environ order — the raw material of the process.env SNAPSHOT record + * (the frontend's interned %env.snapshot helper keyed-writes the pairs + * into a fresh `{ [k: string]: string | undefined }` record). Fresh +1 + * array; never throws. */ + "process.envPairs": { argTypes: [], result: arrayOf(STRING) }, + "process.exit": { argTypes: [F64], result: VOID }, + /** Numeric process.exitCode write (integer validation, implicit exit status). */ + "process.setExitCode": { argTypes: [F64], result: VOID }, + /** The process.exitCode write of a program that also reads the + * property: the same validation, and the code counts as assigned. */ + "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, + /** The code for process.exit() with no argument, or zero when unset. */ + "process.currentExitCode": { argTypes: [], result: F64 }, + /** Whether process.exitCode reads a code rather than undefined. Never throws. */ + "process.hasExitCode": { argTypes: [], result: BOOL }, + "process.cwd": { argTypes: [], result: STRING }, + /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both + * always answer (the checker's `getuid?` optionality covers Windows — + * `process.getuid?.()` lowers as the plain call). Never throw. */ + "process.pid": { argTypes: [], result: F64 }, + "dyn.this": { argTypes: [], result: DYN }, + "dyn.generatorThis": { argTypes: [], result: DYN }, + "process.getuid": { argTypes: [], result: F64 }, + "process.getgid": { argTypes: [], result: F64 }, + /** geteuid(2) / getegid(2) / getgroups(2) (the effective gid appended + * when the list lacks it, like Node). Windows has no such members: the + * calls throw the TypeError a missing member does (MAY THROW). */ + "process.geteuid": { argTypes: [], result: F64 }, + "process.getegid": { argTypes: [], result: F64 }, + "process.getgroups": { argTypes: [], result: arrayOf(F64) }, + /** process.ppid: the parent's pid at the read. Never throws. */ + "process.ppid": { argTypes: [], result: F64 }, + /** process.title: libuv's process title (argv[0] until set); setTitle + * writes it (truncated to the original argument area on POSIX, the + * console title on Windows). Never throw. */ + "process.title": { argTypes: [], result: STRING }, + "process.setTitle": { argTypes: [STRING], result: VOID }, + /** process.argv0: the original argv[0]. Never throws. */ + "process.argv0": { argTypes: [], result: STRING }, + /** process.release: the compatibility target's stable release object + * (name, lts, sourceUrl, headersUrl, and libUrl on Windows). +1. */ + "process.release": { argTypes: [], result: DYN }, + /** process.execPath: the compiled binary's own resolved absolute path + * (one interned string, +1 per read) — the honest answer where Node's + * is the node executable's (SEMANTICS.md divergence 12). Never throws. */ + "process.execPath": { argTypes: [], result: STRING }, + /** process.arch: the compiled binary's OWN architecture ("arm64", + * "x64") — Node's answer for its own build on the same machine. + * Interned; +1 per read. Never throws. */ + "process.arch": { argTypes: [], result: STRING }, + /** Stable native version dictionary. node/openssl identify compatibility + * targets; components absent from the native runtime are omitted. */ + "process.versions": { argTypes: [], result: DYN }, + "process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING }, + "process.builtinModule": { argTypes: [STRING, DYN], result: DYN }, + "process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN }, + /** A module namespace object as a checked-dynamic value + * (scr_module_namespace_value): the module id, its sorted export names + * and the compiled getter. One object per module. Never throws. */ + "module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN }, + "fs.callbackValue": { argTypes: [STRING], result: DYN }, + "fs.callbackCall": { argTypes: [STRING, DYN], result: DYN }, + "process.hrtimeValue": { argTypes: [], result: DYN }, + /** process.versions.node: the runtime's Node COMPATIBILITY TARGET — + * there is no Node under the binary, so this reports the version whose + * semantics the runtime implements (SEMANTICS.md divergence 60, the + * execPath stance). Interned; +1 per read. Never throws. */ + "process.versionsNode": { argTypes: [], result: STRING }, + "process.versionsOpenssl": { argTypes: [], result: STRING }, + /** kill(2) with Node's exact semantics and error shapes: the pid must be + * an int32 (else the ERR_INVALID_ARG_TYPE TypeError text), the named + * form resolves Node's signal-name table (unknown names throw the + * ERR_UNKNOWN_SIGNAL TypeError), an omitted signal completes to + * "SIGTERM" in the frontend, signal 0 probes, and a kill(2) failure + * throws Node's `kill ESRCH`/`kill EPERM` Error. Result is Node's + * constant true. */ + "process.kill": { argTypes: [F64, STRING], result: BOOL }, + "process.killNum": { argTypes: [F64, F64], result: BOOL }, + /** The raw byte writes: one borrowed string arg → bool (constantly true + * — this synchronous runtime never queues backpressure). stdoutWrite + * shares console.log's stream, each call is submitted promptly, and + * interleaved output keeps source order. No newline, no formatting, + * never throws. */ + "process.stdoutWrite": { argTypes: [STRING], result: BOOL }, + "process.stdio": { argTypes: [F64], result: DYN }, + "process.stderrWrite": { argTypes: [STRING], result: BOOL }, + // error.new's result and the receiver slots are builtin-error classes — + // program-dependent object types, checked in the libCall case. + /** The runtime-provided Error hierarchy's entry points (scr_error.c). + * error.new: one borrowed string arg (the message), result an owned (+1) + * builtin error instance — the result TYPE names which builtin class + * (backends derive the runtime kind from it). error.ctor: the + * super(message) call of an `extends Error` constructor — borrowed + * receiver (already allocated by the derived class's new) + borrowed + * message, void; the receiver's type names the builtin class whose name + * field to stamp. error.toString: borrowed `%Error`-typed receiver, +1 + * string in Node's "name: message" shape. Formatting can invoke a user + * message accessor, which can throw. */ + "error.new": { argTypes: [STRING], result: VOID }, + /** ECMAScript constructors with raw checked-dynamic message/options. + * Options retain cause presence; constructor calls borrow their args. + * Message coercion may throw before the cause is installed. */ + "error.newOptions": { argTypes: [DYN, DYN], result: VOID }, + "error.ctorOptions": { argTypes: [null, DYN, DYN], result: VOID }, + /** Borrowed Error receiver. cause returns an owned dyn value (undefined + * when absent); hasCause distinguishes absence from present undefined. */ + "error.cause": { argTypes: [null], result: DYN }, + "error.hasCause": { argTypes: [null], result: BOOL }, + /** Assignment borrows both operands and retains the new cause. */ + "error.setCause": { argTypes: [null, DYN], result: VOID }, + /** `err.code = s` on an error-hierarchy receiver: the code slot that + * error.code reads and coded throws stamp. Borrows both; never throws. */ + "error.setCode": { argTypes: [null, STRING], result: VOID }, + /** `err.code = v` with a value that is not statically a string: strings + * take the code slot, anything else clears it and sets the dynamic own + * property. Borrows both; never throws. */ + "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, + /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver + * (args: the error, a dyn array of the sources): the own-member copy + * onto its checked-dynamic node, then the typed slots (name, message, + * stack, cause, code) re-read from it. Throws what the copy throws. */ + "error.assign": { argTypes: [null, DYN], result: VOID }, + /** `key in err` for a key no compiled class of the receiver declares + * (args: the error, the key): a runtime-set own property (Object.assign, + * throw-site fields, dynamic writes) or an Object.prototype member. */ + "error.hasKey": { argTypes: [null, STRING], result: BOOL }, + "error.hasCode": { argTypes: [null], result: BOOL }, + /** Whether the error's own `name` or `message` (the key) is an accessor + * a reflective definition installed. Borrows both; never throws. */ + "error.ownAccessor": { argTypes: [null, STRING], result: BOOL }, + /** That accessor's getter called on the error (owned result), and its + * setter called with the value (V8's TypeError without one). Borrow + * their arguments; throw what the accessor throws. */ + "error.accessorGet": { argTypes: [null, STRING], result: DYN }, + "error.accessorSet": { argTypes: [null, STRING, DYN], result: VOID }, + "error.defineCause": { argTypes: [null, DYN], result: VOID }, + "error.deleteCause": { argTypes: [null], result: VOID }, + /** The compiler-resolved Node-parity throw for always-throwing lowered + * arms (ERR_INVALID_THIS receivers, ERR_MISSING_ARGS arity ladders, + * the symbol-to-string TypeError): args are [error-kind f64 (the + * SCR_ERR_* index: 0 Error, 1 TypeError, 2 RangeError), code (empty = + * no code slot), message]. ALWAYS THROWS catchably; the result type is + * the replaced expression's own (never materialized — the + * global.undefRead pattern). May-throw seed. */ + "error.nodeThrow": { argTypes: [F64, STRING, STRING], result: VOID }, + /** JS ToString over a dyn value WITH the object protocol (a user + * toString/valueOf member is CALLED and its throw propagates; + * exhaustion throws "Cannot convert object to primitive value"; units + * render "null"/"undefined") — the WHATWG USVString conversions + * (URLSearchParams names/values). Borrowed dyn; +1 string. May-throw. */ + "dyn.toStringCoerce": { argTypes: [DYN], result: STRING }, + "dyn.stringConstructor": { argTypes: [DYN], result: STRING }, + "dyn.propertyKey": { argTypes: [DYN], result: DYN }, + "dyn.toNumeric": { argTypes: [DYN], result: DYN }, + "dyn.increment": { argTypes: [DYN, BOOL], result: DYN }, + /** JS ToNumber over a dyn value WITH the object protocol (number-hint + * valueOf/toString ordering; user throws propagate). Borrowed dyn; + * f64 result, or a throw. Used by statically lowered numeric coercions + * whose checker type remained any. */ + "dyn.numberConstructor": { argTypes: [DYN], result: F64 }, + "dyn.bigintConstructor": { argTypes: [DYN], result: BIGINT_T }, + "dyn.toNumberCoerce": { argTypes: [DYN], result: F64 }, + "dyn.add": { argTypes: [DYN, DYN], result: DYN }, + "dyn.arithmetic": { argTypes: [DYN, DYN, STRING], result: DYN }, + "dyn.compare": { argTypes: [DYN, DYN, STRING], result: BOOL }, + "dyn.bitwise": { argTypes: [DYN, DYN, STRING], result: DYN }, + "dyn.proxyNew": { argTypes: [DYN, DYN], result: DYN }, + /* Dynamic-tier values (jsval; two-tier design stage S3). The jsval pass + * (ir/jsval.ts) introduces these in dynamic-tier functions only; the + * runtime is scr_js_value.c. Arguments are borrowed and results owned, + * like every libCall. Boundary conversions: js.fromDyn (a cell keeps its + * identity), js.toDyn (an immediate is boxed), js.fromF64/fromBool/ + * fromStr/fromBigInt (the design's toJs: immediates, and strings and + * BigInts under a tag without a copy) and js.toF64 (fromJs to number: dynCheck's + * number check and TypeError). The operators are the dyn.* operators + * over jsvals, with identical JS semantics. */ + "js.fromDyn": { argTypes: [DYN], result: JSVAL }, + "js.toDyn": { argTypes: [JSVAL], result: DYN }, + "js.fromF64": { argTypes: [F64], result: JSVAL }, + "js.fromBool": { argTypes: [BOOL], result: JSVAL }, + "js.fromStr": { argTypes: [STRING], result: JSVAL }, + "js.fromBigInt": { argTypes: [BIGINT_T], result: JSVAL }, + "js.toF64": { argTypes: [JSVAL], result: F64 }, + "js.add": { argTypes: [JSVAL, JSVAL], result: JSVAL }, + "js.arithmetic": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, + "js.bitwise": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, + "js.compare": { argTypes: [JSVAL, JSVAL, STRING], result: BOOL }, + "js.toNumeric": { argTypes: [JSVAL], result: JSVAL }, + "js.increment": { argTypes: [JSVAL, BOOL], result: JSVAL }, + "js.toNumberCoerce": { argTypes: [JSVAL], result: F64 }, + // Always throws; the result is the READ's declared type (a typed dummy + // the unwind abandons) — the libCall case skips the result check. + /** A read of a `declare`d const NOTHING defines (the bundler-define + * pattern — __VERSION__): always throws the catchable ReferenceError + * Node raises at the access (" is not defined"). args[0] is the + * name; the result type is the read's declared type (a typed dummy the + * unwind abandons — the value never exists). */ + "global.undefRead": { argTypes: [STRING], result: VOID }, + "console.native": { argTypes: [], result: DYN }, + /** node:console Console instances (scr_console_native.c): new mints the + * handle over the stdout/stderr fds (1/2; 0 = a stream binds next) and + * the ignoreErrors flag; bind attaches a borrowed writable stream to + * target 0 (stdout) or 1 (stderr); write prints `text\n` to the + * selected target of an instance or the global console handle. */ + "console.new": { argTypes: [F64, F64, BOOL], result: DYN }, + "console.bind": { argTypes: [DYN, F64, null], result: VOID }, + "console.write": { argTypes: [DYN, STRING, BOOL], result: VOID }, + /** The global console's remaining members (scr_console_native.c; the + * group indentation itself lives with the writers in scr_console.c). + * Arguments are borrowed; string results are owned. noop is the + * emitter-dropped tail of a lowering whose effects sit in statements. + * label is `${label}` over a defaulted label (undefined -> "default"; + * a Symbol throws Node's TypeError). count prints "label: n". + * countReset answers the warning text for an unknown (or non-string) + * label, "" otherwise. time answers the duplicate-label warning text + * or ""; timeEnd/timeLog answer Node's formatted elapsed time, or "" + * for an unknown label (timeEnd drops the timer). group adds (+1) or + * removes (-1) one two-space indentation step. clear writes the + * cursor-home and erase-down sequences when stdout is a terminal. + * table renders and prints Node's table for a dyn value (undefined + * properties = all). dirDyn is console.dir's inspect of a dyn value + * (custom inspection off: a Buffer anywhere inside throws the fence). */ + "console.noop": { argTypes: [], result: VOID }, + "console.label": { argTypes: [DYN], result: STRING }, + "console.count": { argTypes: [STRING], result: VOID }, + "console.countReset": { argTypes: [DYN], result: STRING }, + "console.time": { argTypes: [STRING], result: STRING }, + "console.timeEnd": { argTypes: [STRING], result: STRING }, + "console.timeLog": { argTypes: [STRING], result: STRING }, + "console.group": { argTypes: [F64], result: VOID }, + "console.clear": { argTypes: [], result: VOID }, + "console.table": { argTypes: [DYN, DYN], result: VOID }, + "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, + /** A native reference to the global object; known names retain value fences. */ + "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, + // `X.name` through a class value: the arg is a program-dependent + // classval (a null slot; the libCall case checks the kind). + /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed + * classval; the result is the class object's stored .name string, + * retained (+1 — the string is an interned immortal, so the retain is a + * no-op, kept for ownership uniformity). Never throws. A direct + * `C.name` on the class name itself folds to a strLit instead. */ + "class.name": { argTypes: [null], result: STRING }, + "error.ctor": { argTypes: [null, STRING], result: VOID }, + "error.toString": { argTypes: [null], result: STRING }, + /** SameValue over checked values, including NaN, signed zero and reference identity. */ + "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, + "error.stack": { argTypes: [null], result: STRING }, + "error.setStack": { argTypes: [null, STRING], result: VOID }, + "error.stackLimitGet": { argTypes: [], result: F64 }, + "error.stackLimitSet": { argTypes: [F64], result: VOID }, + // Receiver (any error-hierarchy object) and the program-dependent + // `string | undefined` result are checked in the libCall case. + /** NodeJS.ErrnoException's `.code` read: borrowed error-hierarchy + * receiver → the interned `string | undefined` union (type-directed + * construction in the backend, the process.envGet pattern) — the errno + * name where a throw site stamped one (fs, exec spawn/timeout, + * process.kill, the spawn 'error' event), the undefined arm everywhere + * else. Never throws. */ + "error.code": { argTypes: [null], result: VOID }, + // DOMException: construction takes the two dyn args (WebIDL's + // resolution runs in the runtime); the %DOMException result is a + // program-dependent object type, checked in the libCall case. The + // read surface takes the %DOMException receiver (a null slot — the + // libCall case checks the class name). + /** `new DOMException(message?, nameOrOptions?)` (scr_error.c): both args + * are borrowed dyn values (the lowering passes the dyn undefined for an + * absent argument, so WebIDL's optionality lives in one place). The + * runtime ToStrings the message ("" for absent/undefined), resolves the + * name — absent/undefined → "Error", a non-null object → ToString of its + * `name` member plus the `cause` own-property record, anything else → + * ToString — and stamps the legacy code from the WebIDL name table (0 + * off-table). Result is an owned (+1) %DOMException. Never throws (dyn + * ToString is total). */ + "error.newDom": { argTypes: [DYN, DYN], result: VOID }, + /** DOMException's own read surface (scr_error.c; %DOMException receivers + * only — borrowed). domCode: the legacy numeric code. domHasCause: the + * options form's own-property record (`'cause' in e`). domCause: the + * cause value, +1 (the dyn undefined when absent — matching Node's + * undefined read). None throws. */ + "error.domCode": { argTypes: [null], result: F64 }, + "error.domHasCause": { argTypes: [null], result: BOOL }, + "error.domCause": { argTypes: [null], result: DYN }, + /** structuredClone of a %DOMException receiver (scr_error.c): WebIDL + * serialization — name/message copy, the legacy code re-derives, cause + * does not serialize. args are the borrowed receiver and the borrowed + * options dyn value (the dyn undefined when absent — the shared + * validation throws Node's exact option errors; any non-empty transfer + * list throws DataCloneError, nothing static is transferable). Result + * +1 %DOMException. */ + "error.domClone": { argTypes: [null, DYN], result: VOID }, + /** `new AggregateError(errors, message?, options?)` (scr_json.c), in + * spec order: newAggregate installs the message and cause (both dyn, + * borrowed — the message conversion can throw) and returns an owned + * %AggregateError; aggregateSetErrors then stores the iterated list + * (borrowed receiver, the dyn array borrowed and retained). + * aggregateErrors reads the list (+1, the same array every read). */ + "error.newAggregate": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%AggregateError" }, + }, + "error.aggregateSetErrors": { argTypes: [null, DYN], result: VOID }, + "error.aggregateErrors": { argTypes: [null], result: DYN }, + /** `new SuppressedError(error, suppressed, message?)` (scr_json.c): all + * three dyn and borrowed; the message conversion can throw. Returns an + * owned %SuppressedError. */ + "error.newSuppressed": { + argTypes: [DYN, DYN, DYN], + result: { kind: "object", className: "%SuppressedError" }, + }, + /** `d instanceof TypeError` (and the other BUILTIN error classes) on a + * checked-dynamic value (scr_json.c): the from_error cache holds the + * dyn↔error identity edge, so the test resolves the runtime error and + * asks its vtable's stamped preorder interval — exact for every error + * that crossed the boundary. A dyn object that never came from an + * error (a hand-built {%error} literal) answers false: subclass + * identity is unknowable there (the root keeps dynTest's marker + * answer). args are the borrowed dyn and the SCR_ERR_* kind literal. + * Never throws. */ + "dyn.errInstanceof": { argTypes: [DYN, F64], result: BOOL }, + "weakMap.is": { argTypes: [DYN], result: BOOL }, + "weakSet.is": { argTypes: [DYN], result: BOOL }, + "weakMap.new": { argTypes: [DYN], result: DYN }, + "weakSet.new": { argTypes: [DYN], result: DYN }, + "finalizationRegistry.new": { argTypes: [DYN], result: DYN }, + "finalizationRegistry.is": { argTypes: [DYN], result: BOOL }, + "dyn.fromEntries": { argTypes: [DYN], result: DYN }, + "bytes.constructor": { argTypes: [STRING], result: DYN }, + "bytes.instanceOf": { argTypes: [DYN, DYN, STRING], result: BOOL }, + "bytes.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, + /** The ambient receiver — JS `this` in a plain (non-method) function + * body: the innermost binding the current firing/dispatch window + * pushed (Node's listener receiver, a dyn OBJ method's object, an + * apply/call thisArg), or the undefined dyn singleton with none bound + * (the strict-mode plain-call answer, the old constant). Zero args → + * dyn (+1). Never throws. */ + "dyn.dataViewIs": { argTypes: [DYN], result: BOOL }, + "dyn.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, + "arrayBuffer.constructor": { argTypes: [], result: DYN }, + "sharedArrayBuffer.new": { argTypes: [DYN], result: DYN }, + "sharedArrayBuffer.is": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.new": { argTypes: [DYN], result: DYN }, + "ffi.argument": { argTypes: [DYN, STRING], result: DYN }, + "ffi.memoryModule": { argTypes: [DYN], result: DYN }, + "intl.segmenterNew": { argTypes: [], result: DYN }, + "intl.segmenterRefusing": { argTypes: [STRING], result: DYN }, + "arrayBuffer.is": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.isView": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 }, + "arrayBuffer.byteLengthDescriptor": { argTypes: [DYN], result: DYN }, + "arrayBuffer.viewU8C": { argTypes: [DYN, DYN, DYN], result: bytesOf("u8c") }, + "arrayBuffer.viewI8": { argTypes: [DYN, DYN, DYN], result: bytesOf("i8") }, + "arrayBuffer.viewU16": { argTypes: [DYN, DYN, DYN], result: bytesOf("u16") }, + "arrayBuffer.viewI16": { argTypes: [DYN, DYN, DYN], result: bytesOf("i16") }, + "arrayBuffer.viewU8": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "arrayBuffer.viewU32": { argTypes: [DYN, DYN, DYN], result: bytesOf("u32") }, + "arrayBuffer.viewI32": { argTypes: [DYN, DYN, DYN], result: bytesOf("i32") }, + "arrayBuffer.viewF16": { argTypes: [DYN, DYN, DYN], result: bytesOf("f16") }, + "arrayBuffer.viewF32": { argTypes: [DYN, DYN, DYN], result: bytesOf("f32") }, + "arrayBuffer.viewF64": { argTypes: [DYN, DYN, DYN], result: bytesOf("f64") }, + "arrayBuffer.viewDV": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + /** Object.keys/values/entries over a CHECKED-DYNAMIC receiver + * (scr_json.c): the runtime walks the dyn node's own members in JS + * own-key order (array-index keys ascending first, then insertion + * order) and answers a dyn array (entries: an array of [key, value] + * pairs; values RETAIN the member nodes — reference semantics, like + * JS). Strings/arrays/bytes answer their index keys; other scalars an + * empty array; null/undefined throw Node's catchable TypeError + * ("Cannot convert undefined or null to object"). */ + "dyn.objKeys": { argTypes: [DYN], result: DYN }, + "dyn.forInKeys": { argTypes: [DYN], result: DYN }, + "dyn.forInKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, + "dyn.forInStable": { argTypes: [DYN], result: BOOL }, + "dyn.objKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, + /** Object.entries as a native [string, any][] (VOID: the tuple row's + * shape is program-dependent; the validator checks it). */ + "dyn.objEntryPairs": { argTypes: [DYN], result: VOID }, + "dyn.hasOwn": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.propertyIsEnumerable": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.assign": { argTypes: [DYN, DYN], result: DYN }, + "dyn.copyDataProperties": { argTypes: [DYN, DYN], result: DYN }, + "dyn.objectRest": { argTypes: [DYN, DYN], result: DYN }, + /** Variadic Object.assign over CHECKED-DYNAMIC targets (`Object.assign( + * {}, ...arr.map(f), tail)` — the option-table merge): the lowering + * builds one fresh dyn pack of sources (packPush retains a plain source + * in; packPushSpread flattens a spread source through the spread-call + * walk — V8's exact TypeError texts, the string arg spelling the spread + * expression for the nullish form), so every source evaluates and + * flattens BEFORE any copying (JS's ArgumentListEvaluation), then + * assignAll copies each pack element's own enumerable keys onto the + * target left to right and answers the TARGET (+1) — identity, like JS. + * assignAll throws Node's ToObject TypeError on a nullish target. */ + "dyn.packPush": { argTypes: [DYN, DYN], result: VOID }, + "dyn.packPushSpread": { argTypes: [DYN, DYN, STRING], result: VOID }, + /** The iterated-path spread twin: V8 spells the optimized apply-path + * texts (packPushSpread's — the expression named for nullish sources) + * only for the SINGLE LAST argument's spread; any other spread position + * drives the real iterator protocol, whose failure describes the VALUE + * ("object null is not iterable (cannot read property + * Symbol(Symbol.iterator))"). The frontend picks by position. */ + "dyn.packPushSpreadIter": { argTypes: [DYN, DYN], result: VOID }, + "dyn.reflectApply": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.reflectGet": { argTypes: [DYN, DYN, DYN], result: DYN }, + /** `using` / `await using` with a dyn value (scr_json.c): usingMethod + * runs the declaration's checks and answers what usingDispose calls at + * scope exit (undefined for a null or undefined value). The bool selects + * `await using`. Both throw Node's TypeErrors or the method's error. */ + "dyn.usingMethod": { argTypes: [DYN, BOOL], result: DYN }, + "dyn.usingDispose": { argTypes: [DYN, DYN], result: DYN }, + /** DisposableStack (scr_symbol.c): a new stack handle; whether a value is + * one; and `stack.use(value)` for a class instance, registering the + * compiler's disposal function for it. The last throws Node's + * ReferenceError on a disposed stack. */ + "disposable.stackNew": { argTypes: [], result: DYN }, + "disposable.stackIs": { argTypes: [DYN], result: BOOL }, + "disposable.stackUseCallback": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "dyn.reflectSet": { argTypes: [DYN, DYN, DYN, DYN], result: BOOL }, + "dyn.reflectDefine": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "dyn.abstractEq": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.assignAll": { argTypes: [DYN, DYN], result: DYN }, - "dyn.objCreateNullProto": { argTypes: [], result: DYN }, - "dyn.arrayPrototype": { argTypes: [], result: DYN }, - "dyn.arrayConstructor": { argTypes: [], result: DYN }, - "dyn.objectPrototype": { argTypes: [], result: DYN }, - "dyn.errorPrototype": { argTypes: [], result: DYN }, - "dyn.inheritsErrorPrototype": { argTypes: [DYN], result: BOOL }, - "dyn.errorCaptureStackTrace": { argTypes: [DYN], result: DYN }, - "dyn.errorCaptureStackTraceFunction": { argTypes: [], result: DYN }, - "dyn.functionApply": { argTypes: [], result: DYN }, - "dyn.builtinMethod": { argTypes: [STRING, STRING], result: DYN }, - "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, - "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, - "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, - /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), - * Transform (5) and PassThrough (6) as untyped constructor values, one - * per thread (scr_dyn_builtin_class.c). */ - "dyn.builtinClass": { argTypes: [F64], result: DYN }, - /** The stream classes (2-6 above) with their stream half: constructor - * functions over them get native stream state - * (scr_dyn_builtin_stream.c). */ - "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, - /** `value instanceof C` for those constructors, over the value's - * prototype chain. */ - "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, - /** `value instanceof f` for an untyped constructor function: the - * prototype chain against f.prototype. */ - "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, - "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.bagGet": { argTypes: [DYN, STRING, DYN], result: DYN }, - /** A class field's creation in its instance's property bag: adds the - * string key as a default data property when absent, answering false - * (nothing changed) when the bag needs the general defineProperty path. */ - "dyn.bagCreateField": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.bagSet": { argTypes: [DYN, STRING, DYN, DYN], result: VOID }, - "dyn.objCreate": { argTypes: [DYN], result: DYN }, - // Names a compiled class's prototype object (args[0]) after the class - // (args[1]) for util.inspect. - "dyn.nameClassPrototype": { argTypes: [DYN, STRING], result: VOID }, - "dyn.objCreateWithProperties": { argTypes: [DYN, DYN], result: DYN }, - "dyn.getPrototype": { argTypes: [DYN], result: DYN }, - // dyn.getPrototype for walks that compare each prototype with a program - // class's prototype: native builtin values end the walk (null). - "dyn.getPrototypeForIdentity": { argTypes: [DYN], result: DYN }, - "dyn.setPrototype": { argTypes: [DYN, DYN], result: DYN }, - "dyn.getOwnPropertyNames": { argTypes: [DYN], result: DYN }, - "dyn.ownKeys": { argTypes: [DYN], result: DYN }, - "dyn.getOwnPropertySymbols": { argTypes: [DYN], result: DYN }, - "dyn.getOwnPropertyDescriptors": { argTypes: [DYN], result: DYN }, - "dyn.preventExtensions": { argTypes: [DYN], result: DYN }, - "dyn.isExtensible": { argTypes: [DYN], result: BOOL }, - "dyn.seal": { argTypes: [DYN], result: DYN }, - "dyn.isSealed": { argTypes: [DYN], result: BOOL }, - "dyn.objValues": { argTypes: [DYN], result: DYN }, - /** Object.values as a native vector of checked values, for results the - * program consumes directly (for-of, index reads, read-only methods). */ - "dyn.objValueList": { argTypes: [DYN], result: arrayOf(DYN) }, - "dyn.objEntries": { argTypes: [DYN], result: DYN }, - /** structuredClone over the checked-dynamic tree (scr_json.c): the JSON-safe subset plus - * bytes (a fresh copy — a Buffer clones as a plain Uint8Array, like - * Node), deep. Functions and handle kinds throw the spec's catchable - * DataCloneError; CYCLES throw the scriptc fence (the checked-dynamic tree cannot - * represent them — Node clones cycles; documented divergence). The - * options dyn value validates with Node's exact errors (dictionary - * conversion, the transfer-sequence member; any non-empty transfer - * list throws DataCloneError). dyn.cloneMissing is the zero-argument - * call: always throws Node's TypeError [ERR_MISSING_ARGS] with Node's - * own (verbatim, doubly-wrapped) message. */ - "dyn.structuredClone": { argTypes: [DYN, DYN], result: DYN }, - /** The declaration-typed boundary's adoption (decl-projection.ts): - * `source` is the dynamic value a boundary cast just converted, and - * `capsule` a live capsule of the native value built from it. Every - * object or array of `source` that something else still references - * becomes, in place, a live capsule of the native value built from it. - * 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.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 - * catchable DataCloneError ("Found invalid value in transferList.") — - * lowered directly (the list's values need no dyn representation to - * fail). */ - "dyn.cloneTransferFail": { argTypes: [], result: DYN }, - /** The same refusal given the transfer list's first member, an - * ArrayBuffer: a Buffer pool slab gets Node's untransferable message. */ - "dyn.cloneTransferFailWith": { argTypes: [DYN], result: DYN }, - /** `new RegExp(pattern, flags?)` (scr_regex.c): a heap regex over the - * same libregexp engine the literals use. The pattern compiles EAGERLY - * so an invalid pattern (or an unknown flag letter) throws Node's - * catchable SyntaxError at construction — Node's message shape with - * libregexp's detail text (approximate fidelity; e.name exact). An - * empty pattern stores the spec's "(?:)" source. Both args borrowed - * strings (the lowering completes an absent flags to ""); result +1. - * The result TYPE is the regex kind, so the link switch pulls the - * engine exactly like a literal. */ - "regex.new": { argTypes: [STRING, STRING], result: REGEX }, - "regex.resetLastIndex": { argTypes: [REGEX, F64], result: VOID }, - "regex.newChecked": { argTypes: [DYN, DYN], result: REGEX }, - "regex.replaceCallback": { argTypes: [STRING, REGEX, DYN, BOOL], result: STRING }, - // Reads of capture args[1] of an execSpans/matchGroups result: - // `capture === args[2]`, Number(capture) and parseInt(capture, - // args[2]). Borrowed; never throw. - "regex.groupEquals": { argTypes: [arrayOf(STRING), F64, STRING], result: BOOL }, - "regex.groupNumber": { argTypes: [arrayOf(STRING), F64], result: F64 }, - "regex.groupParseInt": { argTypes: [arrayOf(STRING), F64, F64], result: F64 }, - // `.index` / `.input` (args[1]) of a match result. Borrowed; never throws. - "regex.resultProp": { argTypes: [arrayOf(STRING), STRING], result: DYN }, - // The string keys `for...in` visits on an array (args[0], any element - // type) after its indices. Borrowed; never throws. - "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, - // Whether the array (args[0]) still has the key (args[1]) from its - // namedKeys list. Borrowed; never throws. - "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, - // Whether an array (args[0], any element type) has properties besides its - // elements: noncanonical numeric keys, match metadata or named - // properties. Borrowed; never throws. - "array.hasProperties": { argTypes: [null], result: BOOL }, - // The key of an element access whose index is undefined (the property - // named "undefined"; frontend/lowering/undefined-index.ts). - "array.undefinedKey": { argTypes: [], result: F64 }, - // node:events EventEmitter: receivers are emitter-hierarchy objects and - // the chaining forms (on/off/removeAll/setMax) return the receiver's - // own class — program-dependent object types, checked in the libCall - // case. emitter.emit is the one VARIADIC libCall: args beyond - // (recv, name) are the event's frontend-unified tuple, any borrowable - // kind — the count check admits a longer list for it alone. - /** node:events EventEmitter (scr_events_emitter.c, link-gated by - * moduleUsesEmitter). The receiver of every instance form is a borrowed - * emitter-hierarchy object (`%EventEmitter` or a user subclass — the - * backend reinterprets to ScrEmitter*, the identical prefix); event - * names are borrowed strings (compile-time literals — the frontend - * fences non-literals and unifies each event's argument tuple program- - * wide). The chaining forms (on/off/removeAll/setMax) return the - * receiver +1 typed as its static class, Node's `return this`. - * - * emitter.new: `new EventEmitter()` → a +1 bare emitter. emitter.ctor: - * super() into the prefix of an emitted subclass (borrowed receiver, - * void — allocation already initialized the prefix). emitter.on: - * (recv, name, cb /moves/, once, prepend) — the backend synthesizes the - * per-signature va_list invoke adapter from the cb's func type. - * emitter.emit: (recv, name, ...tuple) — VARIADIC, the one libCall - * whose arg count exceeds its signature; args are borrowed, result is - * the had-listeners bool. emitter.emitError: emit('error', err) — - * throws err when unhandled. count/countFn/names/listeners/getMax/ - * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ - "emitter.new": { argTypes: [], result: VOID }, - /** `x.constructor.name` of an emitter or stream (a borrowed object of - * EventEmitter's hierarchy): the +1 class name the instance records. */ - "emitter.className": { argTypes: [null], result: STRING }, - "emitter.ctor": { argTypes: [null], result: VOID }, - "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, - "emitter.off": { argTypes: [null, STRING, null], result: VOID }, - // The dyn-adapted registration family (JS-lane checked-dynamic - // listeners): checkListener has NO receiver (it validates the listener - // argument alone); onDyn carries (recv, name, cb dyn, adapter func, - // once, prepend), offDyn (recv, name, cb dyn). - "emitter.checkListener": { argTypes: [DYN], result: VOID }, - "emitter.onDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, - "emitter.onFlex": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - /** on/once/prepend* and emit through an untyped value (the class - * dispatch of compiled emitters): onFlex/emitFlex plus runtime typed - * adapters under 'error' and the meta events, and Node's unhandled-'error' - * contract. emitAny is VARIADIC like emitFlex. */ - "emitter.onAny": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - "emitter.offDyn": { argTypes: [null, STRING, DYN], result: VOID }, - "emitter.removeAll": { argTypes: [null, STRING, BOOL], result: VOID }, - "emitter.emit": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitFlex": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitAny": { argTypes: [null, STRING], result: BOOL }, - /** An untyped emit of an event with a fixed tuple: (recv, name, k, then - * k checked-dynamic arguments as given, then the event's converted - * tuple) — VARIADIC like emitter.emit. hasTyped answers whether a - * listener of the name needs the converted tuple. */ - "emitter.emitMixed": { argTypes: [null, STRING, F64], result: BOOL }, - "emitter.hasTyped": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitError": { argTypes: [null, STRING, null], result: BOOL }, - "emitter.count": { argTypes: [null, STRING], result: F64 }, - "emitter.countFn": { argTypes: [null, STRING, null], result: F64 }, - "emitter.countDyn": { argTypes: [null, STRING, DYN], result: F64 }, - "emitter.names": { argTypes: [null], result: VOID }, - "emitter.listeners": { argTypes: [null, STRING], result: VOID }, - /** listeners()/rawListeners() through an untyped value: a checked- - * dynamic array (throws when a listener has typed parameters). */ - "emitter.listenersDyn": { argTypes: [null, STRING], result: DYN }, - /** Stream-'data' registration twins of emitter.on/onDyn (same args): - * chosen at registration sites whose receiver is stream-rooted, so the - * backend emits DATA thunks — the runtime's 'data' emission carries - * BOTH payload slots (bytes, string; exactly one non-NULL — encoded - * streams deliver strings), and the thunk unwraps the listener's - * declared side (typed) or boxes by tag (dyn). emitter.emitData is the - * user-emit form: (recv, name, chunk) with a bytes OR string chunk. */ - "emitter.onData": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, - "emitter.onDataDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, - "emitter.emitData": { argTypes: [null, STRING, null], result: BOOL }, - "emitter.setMax": { argTypes: [null, F64], result: VOID }, - "emitter.getMax": { argTypes: [null], result: F64 }, - "emitter.setDefaultMax": { argTypes: [F64], result: VOID }, - "emitter.getDefaultMax": { argTypes: [], result: F64 }, - // The ES module namespace's copy of defaultMaxListeners, and - // module.syncBuiltinESMExports() refreshing it. - "emitter.esmDefaultMax": { argTypes: [], result: F64 }, - "emitter.esmSync": { argTypes: [], result: VOID }, - /** The events.errorMonitor event key: an immortal string the runtime - * registry matches by identity, so no string event name reaches it. */ - "emitter.errorMonitor": { argTypes: [], result: STRING }, - // node:stream: receivers are stream-hierarchy objects and several - // results are program-dependent (the receiver's class, unions) — the - // libCall case owns those checks. The constructors and a few methods - // are VARIADIC (trailing option callbacks / the optional chunk+cb tail): - // argTypes here is the fixed prefix, VARIADIC_LIB_FNS admits the rest. - /** node:stream (scr_stream.c, link-gated by moduleUsesStream — which - * implies the emitter unit: stream events dispatch through the embedded - * ScrEmitter registry). Receivers are borrowed stream-class objects - * (`%Readable`/`%Writable`/`%Duplex`/`%Transform`/`%PassThrough` — one - * runtime layout, reinterpreted by side). - * - * Constructors (`readable.new` et al): args are [hwmR, hwmW, flags] - * followed by the PRESENT user callbacks in canonical order (read, - * write, final, destroy, transform, flush — the flags f64 is a bitmask - * naming which follow; absent ones emit NULL). Every callback closure - * MOVES and carries a leading `this` param (the stream), invoked - * through compiler-emitted adapters. Results are +1. - * - * readable.push / readable.pushStr / readable.pushNull: Node's push — - * buffers or delivers (bytes chunk borrowed; string converted utf8); - * returns the below-hwm answer. readable.read: (recv, size — -1 for - * absent) → Buffer|null union. pause/resume return recv +1 (`this`); - * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, - * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off - * when dst is the converted process.stdout / process.stderr; - * readable.unpipe (recv[, dst]) → recv +1. - * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb - * MOVES when present, called after the user write completes). - * writable.writeEnc: (recv, flags, chunk[, encoding][, cb]) — flags 1 - * an encoding argument, 2 a callback (the encoding is then null, as - * Node's write shifts it). - * writable.end: (recv, flags[, chunk][, encoding][, cb]) → recv +1; - * flags 16: an encoding argument follows the dyn chunk. cork/uncork are - * void. stream.destroy/destroyErr: (recv[, err]) → recv +1. - * stream.prop: (recv, name-literal) → the flag/number the name asks - * for; stream.errored → Error|null union; readable.flowing → - * bool|null union. All may leave a listener's exception pending - * (dispatch runs user code synchronously, like emit). */ - "readable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, - "writable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, - "duplex.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "transform.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "passthrough.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - // The subclass-initialization twins: the borrowed receiver leads, then - // the .new tail (variadic callbacks likewise). - // The dyn-options twins: newDyn takes the record, initDyn the borrowed - // receiver + record + fallback flags (trailing wrapper closures ride - // the variadic tail like the static forms). - /** The underscore-method assignment surface (`r._read = fn` after - * construction — Node's own-property shadow of the prototype method): - * args [stream receiver (borrowed), callback closure (+1 moves)]. The - * runtime slot the matching option callback fills swaps its closure - * and invoke thunk; the next dispatch uses it (Node's timing). The - * setters themselves never throw. */ - "stream.setRead": { argTypes: [null, null], result: VOID }, - "stream.setWrite": { argTypes: [null, null], result: VOID }, - "stream.setFinal": { argTypes: [null, null], result: VOID }, - "stream.setDestroy": { argTypes: [null, null], result: VOID }, - "stream.setTransform": { argTypes: [null, null], result: VOID }, - "stream.setFlush": { argTypes: [null, null], result: VOID }, - "stream.setConstruct": { argTypes: [null, null], result: VOID }, - "stream.replaceConstruct": { argTypes: [null, null], result: VOID }, - "stream.setWritev": { argTypes: [null, null], result: VOID }, - "stream.setWritevDefault": { argTypes: [null, null], result: VOID }, - /** The dyn-options twins (a checked-dynamic options record — the JS - * lane's `super(options)` forwarding and `new Readable(dynVar)`): the - * option walk runs at RUNTIME with Node's reading rules. newDyn: - * (optsDyn) → the fresh stream; initDyn: (recv, optsDyn, flags, - * ...fallback wrapper closures in canonical order — the flags literal - * names which ride, exactly the .init callback ABI). MAY THROW (a - * consumed-but-unlowered option is the compile fence's runtime twin). */ - /** stream.finished(s, cb) — the callback form: the watcher fires once - * at the terminal point with the finish status; the result is the +1 - * cleanup closure. stream.pipeline(count, s1..sn, cb): chains pipes, - * propagates the first error by destroying the rest, calls cb after the - * last 'close'; answers the destination +1. The Dyn twins take the - * callback as a checked-dynamic VALUE (mustCall wrappers). */ - /** stream.addAbortSignal: [stream, signal] — the constructors' signal - * option, chained over the fresh stream (falsy signals are ignored); - * stream.addAbortSignalFn: [signal, stream] — addAbortSignal(signal, - * stream). Both answer the stream (+1) and throw ERR_INVALID_ARG_TYPE - * for a value that is not an AbortSignal; they link the event-target - * unit (addAbortListener). */ - "stream.addAbortSignal": { argTypes: [null, DYN], result: VOID }, - "stream.addAbortSignalFn": { argTypes: [DYN, null], result: VOID }, - "stream.finished": { argTypes: [null, null], result: VOID }, - "stream.finishedDyn": { argTypes: [null, DYN], result: VOID }, - "stream.pipeline": { argTypes: [F64], result: VOID }, - "stream.pipelineDyn": { argTypes: [F64], result: VOID }, - "stream.pipelineStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - "stream.composeStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - "stream.pipelineStagesDyn": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - /** stream.compose(count, s1..sn): the Duplex over a pipeline of the - * streams (+1). MAY THROW (ERR_INVALID_ARG_VALUE for a stream that is - * no longer readable or writable). */ - "stream.compose": { argTypes: [F64], result: VOID }, - /** node:stream/promises — the promise forms over the same machinery: - * sp.finished: (s) → a pending void promise the terminal watcher - * settles; sp.pipeline: (count, s1..sn) → the callback pipeline's - * chaining/destroyer semantics settling a void promise (fulfilled on a - * clean finish, rejected with the finish status otherwise). */ - "sp.finished": { argTypes: [null], result: { kind: "promise", inner: VOID } }, - /** The options forms: finished(s, { readable, writable, error, cleanup, - * signal }) as [s, readable, writable, error, cleanup, signal?] — - * readable/writable -1 (absent), 0 or 1 — and pipeline(...streams, - * { signal, end }) as [count, s1..sn, end, signal?]. The Signal forms - * link the event-target unit (addAbortListener). The callback form - * stream.finishedOpts: [s, readable, writable, error, signal?, cb] → - * the cleanup closure. */ - "sp.finishedOpts": { - argTypes: [null, F64, F64, BOOL, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "sp.finishedOptsSignal": { - argTypes: [null, F64, F64, BOOL, BOOL, DYN], - result: { kind: "promise", inner: VOID }, - }, - "sp.pipelineOpts": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "sp.pipelineOptsSignal": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "stream.finishedOpts": { argTypes: [null, F64, F64, BOOL, null], result: VOID }, - "stream.finishedOptsSignal": { argTypes: [null, F64, F64, BOOL, DYN, null], result: VOID }, - "sp.pipeline": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - /** pipeline with function stages: [n, k, positions, flags, mask, - * s1..sn, stage1..stagek, helpers..., box?, (signal, abort)?] (+ the - * callback for the stream.* forms): the user's stage functions at the - * positions the bitmask names, the frontend's (next, read, return) - * helpers for each pumped generator (pipelinePumpedRuns) and a promise - * destination's box, and the pipeline's AbortSignal with its abort - * closure; flags bits 1-2 give the destination kind and bit 5 the - * signal group (scr_stream_pipeline_stages). A promise destination's - * promise type is sp.pipelineStages' result. */ - "sp.pipelineStages": { - argTypes: [F64, F64, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - /** sp.pipelineStages with the options argument: the same head, then - * end (a bool) and, with flags bit 6, the options' signal (dyn). */ - "sp.pipelineStagesOpts": { - argTypes: [F64, F64, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - /** node:stream/consumers — the promise consumers over the readable - * machinery: (s) → a pending promise settled at the terminal point - * with the accumulated result (sc.text: the utf8 decode, sc.json: the - * parsed dyn — malformed input rejects with the parse's SyntaxError, - * sc.buffer: the concatenated bytes, sc.arrayBuffer: an ArrayBuffer - * dyn over them) or rejected with the stream's - * error / ERR_STREAM_PREMATURE_CLOSE. */ - "sc.text": { argTypes: [null], result: { kind: "promise", inner: STRING } }, - "sc.json": { argTypes: [null], result: { kind: "promise", inner: DYN } }, - "sc.buffer": { argTypes: [null], result: { kind: "promise", inner: BYTES_U8 } }, - "sc.arrayBuffer": { argTypes: [null], result: { kind: "promise", inner: DYN } }, - "readable.newDyn": { argTypes: [DYN], result: VOID }, - "writable.newDyn": { argTypes: [DYN], result: VOID }, - "duplex.newDyn": { argTypes: [DYN], result: VOID }, - "transform.newDyn": { argTypes: [DYN], result: VOID }, - "passthrough.newDyn": { argTypes: [DYN], result: VOID }, - "readable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "writable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "duplex.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "transform.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "passthrough.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - /** Subclass initialization (`super(options?)` in a user `extends - * Readable` constructor): same tail as the `.new` forms, prefixed with - * the BORROWED receiver (the emitted subclass allocation — vtable and - * display name stamped, state NULL until here). Overridden underscore - * methods arrive as synthesized wrapper closures dispatching through - * the vtable. Void result. */ - "readable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, - "writable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, - "duplex.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "transform.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "passthrough.init": { - argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], - result: VOID, - }, - "readable.push": { argTypes: [null, BYTES_U8], result: BOOL }, - "readable.pushStr": { argTypes: [null, STRING], result: BOOL }, - "readable.pushNull": { argTypes: [null], result: BOOL }, - "readable.pushU": { argTypes: [null, null], result: BOOL }, - "readable.pushDyn": { argTypes: [null, DYN], result: BOOL }, - "readable.unshift": { argTypes: [null, BYTES_U8], result: VOID }, - "readable.unshiftDyn": { argTypes: [null, DYN], result: VOID }, - "readable.unshiftStr": { argTypes: [null, STRING], result: VOID }, - "stream.onDyn": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - "readable.readDyn": { argTypes: [null, DYN], result: DYN }, - "readable.read": { argTypes: [null, F64], result: VOID }, - "readable.pause": { argTypes: [null], result: VOID }, - "readable.setEncoding": { argTypes: [null, STRING], result: VOID }, - /** push(chunk, enc) with a literal non-utf8 encoding, and the - * defaultEncoding option's push side (how push(string) decodes — - * Buffer.from(chunk, enc)); both carry the CANONICAL literal. */ - "readable.pushStrEnc": { argTypes: [null, STRING, STRING], result: BOOL }, - "readable.pushEncoding": { argTypes: [null, STRING], result: VOID }, - /** The objectMode option's sides (readable, writable), applied right - * after construction; answers the receiver (+1, the chaining shape). */ - "stream.objectMode": { argTypes: [null, BOOL, BOOL], result: VOID }, - "stream.decodeStrings": { argTypes: [null, BOOL], result: VOID }, - /** Runtime decodeStrings and defaultEncoding option values, applied - * after construction (answers the receiver +1); the encoding was - * validated by stream.checkEncodingOption before construction. */ - "stream.optionValues": { argTypes: [null, DYN, DYN], result: VOID }, - "stream.checkEncodingOption": { argTypes: [DYN], result: VOID }, - "stream.duplexFromGen": { argTypes: [null, null, null, null, null, null], result: VOID }, - "stream.duplexFromDyn": { argTypes: [DYN], result: VOID }, - /** Duplex.from over a lone Readable or Writable, or a { readable, - * writable } pair of streams (duplexify.js's _duplexify). */ - "stream.duplexifyReadable": { argTypes: [null], result: VOID }, - "stream.duplexifyWritable": { argTypes: [null], result: VOID }, - "stream.duplexifyPair": { argTypes: [null, null], result: VOID }, - /** Duplex.from(promise): the promise and its box helper. */ - "stream.duplexFromPromise": { argTypes: [null, null], result: VOID }, - /** Duplex.from(asyncFunction): the stage, its source helper and the - * promise's box helper. */ - "stream.duplexFromAsyncFn": { argTypes: [null, null, null], result: VOID }, - /** The same for a body that declares the options argument: then the - * AbortController's abort() and its signal. */ - "stream.duplexFromGenSignal": { - argTypes: [null, null, null, null, null, null, null, DYN], - result: VOID, - }, - "stream.duplexFromAsyncFnSignal": { argTypes: [null, null, null, null, DYN], result: VOID }, - "writable.writeEncoding": { argTypes: [null, STRING], result: VOID }, - "writable.setDefaultEncoding": { argTypes: [null, DYN], result: VOID }, - /** Whether a readable.nextChunkDyn step answered the end (the done - * marker; an object-mode stream can yield undefined). */ - "stream.chunkDone": { argTypes: [DYN], result: BOOL }, - /** for-await over a readable (the desugared loop's per-pass promise): - * +1 promise of the next chunk — buffered content, the EOF sentinel - * (empty Buffer / dyn undefined), or a rejection with the stream's - * error. The Dyn twin boxes chunks by runtime tag (the JS lane). - * readable.fromArr is Readable.from over a typed array, string or - * Buffer: [array, SCR_FROM_* flags, highWaterMark]. readable.fromDyn is - * Readable.from over a checked-dynamic source: [source, options]. - * readable.fromIter is Readable.from over a class iterator, already - * opened: [checked-dynamic { next } object, flags, highWaterMark]; - * readable.fromAsyncIter is the async class iterator's twin. */ - "readable.nextChunk": { argTypes: [null], result: VOID }, - "readable.nextChunkDyn": { argTypes: [null], result: VOID }, - "readable.fromArr": { argTypes: [null, F64, F64], result: VOID }, - "readable.fromDyn": { argTypes: [DYN, DYN], result: VOID }, - "readable.fromIter": { argTypes: [DYN, F64, F64], result: VOID }, - "readable.fromAsyncIter": { argTypes: [DYN, F64, F64], result: VOID }, - /** Readable.from over a compiled generator: [generator, next, return, - * throw, flags, highWaterMark] (sync) or [generator, next, read, - * return, throw, flags, highWaterMark] (async), the helpers being the - * frontend's interned closures. readable.fromDone is the done marker - * the helpers answer. */ - "readable.fromGen": { argTypes: [null, null, null, null, F64, F64], result: VOID }, - "readable.fromAsyncGen": { argTypes: [null, null, null, null, null, F64, F64], result: VOID }, - "readable.fromDone": { argTypes: [], result: DYN }, - /** The Readable operators (scr_stream_ops.c): [stream, ...] with every - * other argument a checked-dynamic value (the callback, the count, the - * options). map/filter/flatMap/drop/take answer the object-mode output - * stream (validated as a stream class; THROW Node's validation errors); - * toArray settles a dyn array, find a dyn value, some/every a boolean, - * forEach nothing; reduce: [stream, fn, init, hasInit, options, kind] - * with the payload kind (0 dyn, 1 number, 2 boolean, 3 string). The - * promise-returning ones reject instead of throwing. */ - "readable.opMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - "readable.opFilter": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - "readable.opFlatMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - /** flatMap whose callback returns a compiled generator: the helpers - * over its type follow (async: next, read, return, throw; sync: next, - * return, throw). */ - "readable.opFlatMapAsyncGen": { - argTypes: [null, DYN, DYN, DYN, null, null, null, null], - result: VOID, - }, - "readable.opFlatMapSyncGen": { argTypes: [null, DYN, DYN, DYN, null, null, null], result: VOID }, - "readable.opDrop": { argTypes: [null, DYN, DYN], result: VOID }, - "readable.opTake": { argTypes: [null, DYN, DYN], result: VOID }, - "readable.opToArray": { argTypes: [null, DYN], result: { kind: "promise", inner: DYN } }, - "readable.opForEach": { - argTypes: [null, DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - "readable.opSome": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: BOOL } }, - "readable.opEvery": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: BOOL } }, - "readable.opFind": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** The map family's last argument is the callback's { signal } (or - * undefined). reduceSignal adds ac.abort() (a closure) and - * { signal: ac.signal } for a reducer that reads them. */ - "readable.opReduce": { argTypes: [null, DYN, DYN, BOOL, DYN, F64], result: VOID }, - "readable.opReduceSignal": { - argTypes: [null, DYN, DYN, BOOL, DYN, F64, null, DYN], - result: VOID, - }, - "readable.opSignalList": { argTypes: [DYN], result: DYN }, - "readable.opSignalOpts": { argTypes: [DYN], result: DYN }, - "readable.opSignalOf": { argTypes: [DYN], result: DYN }, - "readable.opOnceOptions": { argTypes: [], result: DYN }, - "readable.resume": { argTypes: [null], result: VOID }, - "readable.isPaused": { argTypes: [null], result: BOOL }, - "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, - "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, - "readable.unpipe": { argTypes: [null], result: VOID }, - "readable.flowing": { argTypes: [null], result: VOID }, - "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, - "writable.writeStr": { argTypes: [null, STRING], result: BOOL }, - "writable.writeU": { argTypes: [null, null], result: BOOL }, - "writable.writeDyn": { argTypes: [null, DYN], result: BOOL }, - "writable.writeEnc": { argTypes: [null, F64, DYN], result: BOOL }, - "writable.end": { argTypes: [null, F64], result: VOID }, - "writable.cork": { argTypes: [null], result: VOID }, - "writable.uncork": { argTypes: [null], result: VOID }, - "stream.destroy": { argTypes: [null], result: VOID }, - "stream.destroyErr": { argTypes: [null, null], result: VOID }, - /** AsyncIteratorClose for Readable[Symbol.asyncIterator](): destroy with - * Node's AbortError/ABORT_ERR payload while the iterator's internal error - * consumer prevents an unhandled-error crash. */ - "stream.iteratorClose": { argTypes: [null], result: VOID }, - "stream.prop": { argTypes: [null, STRING], result: VOID }, - "stream.errored": { argTypes: [null], result: VOID }, - // node:assert: pass/negated/deep/hasMsg are frontend-computed bools; the - // message slot always carries a string ("" when hasMsg is false). - /** node:assert (scr_assert.c; assert.match in scr_regex.c — every call - * site carries a regex value, so the regex link switch is already on). - * Failures throw a catchable AssertionError — a runtime %Error whose - * name is "AssertionError" and whose code slot is "ERR_ASSERTION" — so - * `instanceof Error`, `.name`, `.message`, and `.code` all answer like - * Node's. Generated messages are Node's assertion_error.js scalar forms - * byte-exactly (the short `a !== b` form, the stacked `+ actual - * - expected` diff with the string `^` indicator, the inline-vs-block - * not-equal split); composite deep failures carry the header line - * without the rendered inspect diff (documented divergence). - * - * assert.ok: (pass, message) — the frontend computed the truthiness AND - * the full message (the user's, or the compile-time source-text form — - * assert.fail lowers here too with pass=false). assert.eqF64/eqBigInt/ - * eqStr/eqBool: (a, b, negated, deep, msg, hasMsg) — Object.is comparison, - * covering strictEqual/notStrictEqual and the scalar deepStrictEqual - * pair; msg is a typed dummy ("" literal) when hasMsg is false (Node - * distinguishes an omitted message from an empty one per operator). - * assert.deepResult: (equal, negated, msg, hasMsg) — the verdict of a - * frontend-synthesized structural comparison, turned into Node's throw. - * assert.sameValue: Object.is over doubles (the deep-equal helpers' - * number leaf; never throws). assert.match: (s, regex, negated, msg, - * hasMsg) — a fresh exec from index 0. assert.throwsNone: (rejection, - * ename, hasEname, msg, hasMsg) — the "Missing expected - * exception|rejection" throw of assert.throws/rejects whose callback - * returned (fulfilled) normally, with Node's ` (${expected.name})` - * detail when the expected class or shape carries a name. - * assert.throwsMismatch: (expectedName, error) — the wrong-class throw - * of the assert.throws(fn, ErrorClass) form. assert.eqSym: - * (a, b, negated, deep, msg, hasMsg) — strict equality over symbol - * values, pointer identity with v24's "Symbol(desc)" stacked-diff - * messages (scr_symbol.c, the assert.match pattern — symbol-typed - * slots already flip the symbol link switch). assert.eqDyn: - * (a, b, negated, deep, msg, hasMsg) — the whole quartet over - * checked-dynamic operands (both slots dyn; the frontend boxes a - * static side with dynFrom first): SameValue for the strict pair over - * the dyn kinds (boxed-closure identity for functions), the structural - * dyn walk for the deep pair, with assertion_error.js's messages — - * scalar forms byte-exact, composites rendered compact:false/sorted - * through the checked-dynamic tree and diffed with the real myers line printer. - * - * The assert.throws(fn, {name/code/message}) shape check - * (expectedException over the static error surface): shapeBegin - * stashes the caught error, shapeStr/shapeRe add one expected key each - * (key ids 0 code / 1 message / 2 name; shapeRe lives in scr_regex.c — - * its regex argument flips the regex link switch — and tests eagerly - * so scr_assert.c stays libregexp-free), then shapeEnd throws Node's - * deep-equal Comparison diff BYTE-EXACTLY (the bounded key set makes - * the rendering enumerable) or the custom message. - * assert.throwsRegex: (regex, error, msg, hasMsg) — the - * assert.throws(fn, /re/) check over String(error), Node's - * regex-mismatch message. assert.regexErrTest: doesNotReject's - * regex predicate over String(error) (never throws). - * assert.unwantedRejection: (error, msg, hasMsg) — doesNotReject's - * "Got unwanted rejection" throw. - * assert.ifErrorErr/F64/Str/Bool: assert.ifError's per-type throws - * ("ifError got unwanted exception: " + the error's message/name or - * the value's inspection) — the frontend routes null/undefined to a - * no-op and everything else here (Node throws for falsy values too). - * All arguments are borrowed. */ - "assert.ok": { argTypes: [BOOL, STRING, BOOL], result: VOID }, - /** The next AssertionError's actual, expected and operator, recorded on - * a failure branch. */ - "assert.record": { argTypes: [DYN, DYN, STRING], result: VOID }, - "assert.errorNew": { argTypes: [DYN], result: { kind: "object", className: "%AssertionError" } }, - "assert.eqF64": { argTypes: [F64, F64, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqBigInt": { argTypes: [BIGINT_T, BIGINT_T, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqStr": { argTypes: [STRING, STRING, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqBool": { argTypes: [BOOL, BOOL, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.deepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.looseDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.looseDeepDyn": { argTypes: [DYN, DYN, BOOL, STRING, BOOL], result: VOID }, - "assert.partialDeepDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, - "assert.partialDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.partialEqDyn": { argTypes: [DYN, DYN], result: BOOL }, - "assert.looseF64Eq": { argTypes: [F64, F64], result: BOOL }, - "assert.sameValue": { argTypes: [F64, F64], result: BOOL }, - // The deep-equality pair memo: both slots are program-dependent - // (any cycle-capable record/array/map type). - /* deepStrictEqual's pair memo over cycle-capable types: enter answers - * true for a pair already being compared (Node's memo — equal cyclic - * structures compare true); leave pops. */ - "assert.deqEnter": { argTypes: [null, null], result: BOOL }, - "assert.deqLeave": { argTypes: [], result: VOID }, - "assert.match": { argTypes: [STRING, REGEX, BOOL, STRING, BOOL], result: VOID }, - "assert.throwsNone": { argTypes: [BOOL, STRING, BOOL, STRING, BOOL], result: VOID }, - "assert.throwsMismatch": { - argTypes: [STRING, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], - result: VOID, - }, - "assert.throwsRegex": { - argTypes: [REGEX, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], - result: VOID, - }, - // Symbol strict equality (pointer identity; scr_symbol.c). - "assert.eqSym": { - argTypes: [{ kind: "symbol" }, { kind: "symbol" }, BOOL, BOOL, STRING, BOOL], - result: VOID, - }, - // The equality quartet over checked-dynamic operands (the frontend - // boxes a static side into the checked-dynamic tree first). - "assert.eqDyn": { argTypes: [DYN, DYN, BOOL, BOOL, STRING, BOOL], result: VOID }, - /** A message argument that is not a string, registered for the next - * failing assertion (scr_assert_message_value). */ - "assert.messageValue": { argTypes: [DYN, BOOL], result: VOID }, - /** An assert method as a function value, by canonical name. */ - "assert.methodValue": { argTypes: [STRING], result: DYN }, - "assert.messageClear": { argTypes: [], result: VOID }, - "assert.looseResult": { argTypes: [BOOL, BOOL, STRING, STRING, STRING, BOOL], result: VOID }, - // The throws(fn, {shape}) accumulator: begin/slot calls never throw; - // shapeEnd throws the Comparison diff. The error slot is the - // %Error-narrowed caught value. - "assert.shapeBegin": { argTypes: [{ kind: "object", className: "%Error" }], result: VOID }, - "assert.shapeStr": { argTypes: [F64, STRING], result: VOID }, - "assert.shapeRe": { argTypes: [F64, REGEX], result: VOID }, - "assert.shapeEnd": { argTypes: [STRING, BOOL, BOOL], result: VOID }, - "assert.regexErrTest": { - argTypes: [REGEX, { kind: "object", className: "%Error" }], - result: BOOL, - }, - "assert.unwantedRejection": { - argTypes: [{ kind: "object", className: "%Error" }, STRING, BOOL], - result: VOID, - }, - "assert.unwantedError": { argTypes: [STRING, BOOL, STRING, BOOL], result: VOID }, - "assert.noErrorPredicate": { argTypes: [DYN, DYN], result: BOOL }, - "assert.regexDynTest": { argTypes: [REGEX, DYN], result: BOOL }, - /** Node's expectsError over an error-INSTANCE expected (assert.throws/ - * rejects second argument): walk the expected dyn error's keys (name, - * message, code — the %error marker skipped) and deep-compare each - * against the caught value's; a mismatch throws the deep-equal - * AssertionError (scr_assert.c). MAY THROW by design. */ - "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, - /** A validation function against the caught value (Node's expectedException). */ - "assert.throwsValidate": { argTypes: [DYN, DYN, STRING, STRING, BOOL], result: VOID }, - // assert.ifError's typed entries (always throw; unit args never - // lower). The error slot is program-dependent — any %Error-hierarchy - // class, root or subclass (the insp.error precedent, null slot). - "assert.ifErrorErr": { argTypes: [null], result: VOID }, - "assert.ifErrorF64": { argTypes: [F64], result: VOID }, - "assert.ifErrorStr": { argTypes: [STRING], result: VOID }, - "assert.ifErrorBool": { argTypes: [BOOL], result: VOID }, - "assert.ifErrorDyn": { argTypes: [DYN], result: VOID }, - // Bytes equality: the two value slots are any bytes kind (the frontend - // gates both sides to ONE static bytes type; the libCall case checks - // the kind and the pair below). - "assert.refEqBytes": { argTypes: [null, null, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.refEqFn": { argTypes: [null, null, BOOL, STRING, BOOL], result: VOID }, - "assert.bytesDeepEq": { argTypes: [null, null, BOOL], result: BOOL }, - "assert.looseBytesEq": { argTypes: [null, null], result: BOOL }, - // util.inspect: the error receiver is program-dependent (a builtin or - // user error class — the error.toString precedent, null slot). + "dyn.objCreateNullProto": { argTypes: [], result: DYN }, + "dyn.arrayPrototype": { argTypes: [], result: DYN }, + "dyn.arrayConstructor": { argTypes: [], result: DYN }, + "dyn.objectPrototype": { argTypes: [], result: DYN }, + "dyn.errorPrototype": { argTypes: [], result: DYN }, + "dyn.inheritsErrorPrototype": { argTypes: [DYN], result: BOOL }, + "dyn.errorCaptureStackTrace": { argTypes: [DYN], result: DYN }, + "dyn.errorCaptureStackTraceFunction": { argTypes: [], result: DYN }, + "dyn.functionApply": { argTypes: [], result: DYN }, + "dyn.builtinMethod": { argTypes: [STRING, STRING], result: DYN }, + "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, + "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, + "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, + /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), + * Transform (5) and PassThrough (6) as untyped constructor values, one + * per thread (scr_dyn_builtin_class.c). */ + "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** The stream classes (2-6 above) with their stream half: constructor + * functions over them get native stream state + * (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, + /** `value instanceof C` for those constructors, over the value's + * prototype chain. */ + "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, + /** `value instanceof f` for an untyped constructor function: the + * prototype chain against f.prototype. */ + "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, + "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.bagGet": { argTypes: [DYN, STRING, DYN], result: DYN }, + /** A class field's creation in its instance's property bag: adds the + * string key as a default data property when absent, answering false + * (nothing changed) when the bag needs the general defineProperty path. */ + "dyn.bagCreateField": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.bagSet": { argTypes: [DYN, STRING, DYN, DYN], result: VOID }, + "dyn.objCreate": { argTypes: [DYN], result: DYN }, + // Names a compiled class's prototype object (args[0]) after the class + // (args[1]) for util.inspect. + "dyn.nameClassPrototype": { argTypes: [DYN, STRING], result: VOID }, + "dyn.objCreateWithProperties": { argTypes: [DYN, DYN], result: DYN }, + "dyn.getPrototype": { argTypes: [DYN], result: DYN }, + // dyn.getPrototype for walks that compare each prototype with a program + // class's prototype: native builtin values end the walk (null). + "dyn.getPrototypeForIdentity": { argTypes: [DYN], result: DYN }, + "dyn.setPrototype": { argTypes: [DYN, DYN], result: DYN }, + "dyn.getOwnPropertyNames": { argTypes: [DYN], result: DYN }, + "dyn.ownKeys": { argTypes: [DYN], result: DYN }, + "dyn.getOwnPropertySymbols": { argTypes: [DYN], result: DYN }, + "dyn.getOwnPropertyDescriptors": { argTypes: [DYN], result: DYN }, + "dyn.preventExtensions": { argTypes: [DYN], result: DYN }, + "dyn.isExtensible": { argTypes: [DYN], result: BOOL }, + "dyn.seal": { argTypes: [DYN], result: DYN }, + "dyn.isSealed": { argTypes: [DYN], result: BOOL }, + "dyn.objValues": { argTypes: [DYN], result: DYN }, + /** Object.values as a native vector of checked values, for results the + * program consumes directly (for-of, index reads, read-only methods). */ + "dyn.objValueList": { argTypes: [DYN], result: arrayOf(DYN) }, + "dyn.objEntries": { argTypes: [DYN], result: DYN }, + /** structuredClone over the checked-dynamic tree (scr_json.c): the JSON-safe subset plus + * bytes (a fresh copy — a Buffer clones as a plain Uint8Array, like + * Node), deep. Functions and handle kinds throw the spec's catchable + * DataCloneError; CYCLES throw the scriptc fence (the checked-dynamic tree cannot + * represent them — Node clones cycles; documented divergence). The + * options dyn value validates with Node's exact errors (dictionary + * conversion, the transfer-sequence member; any non-empty transfer + * list throws DataCloneError). dyn.cloneMissing is the zero-argument + * call: always throws Node's TypeError [ERR_MISSING_ARGS] with Node's + * own (verbatim, doubly-wrapped) message. */ + "dyn.structuredClone": { argTypes: [DYN, DYN], result: DYN }, + /** The declaration-typed boundary's adoption (decl-projection.ts): + * `source` is the dynamic value a boundary cast just converted, and + * `capsule` a live capsule of the native value built from it. Every + * object or array of `source` that something else still references + * becomes, in place, a live capsule of the native value built from it. + * 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.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 + * catchable DataCloneError ("Found invalid value in transferList.") — + * lowered directly (the list's values need no dyn representation to + * fail). */ + "dyn.cloneTransferFail": { argTypes: [], result: DYN }, + /** The same refusal given the transfer list's first member, an + * ArrayBuffer: a Buffer pool slab gets Node's untransferable message. */ + "dyn.cloneTransferFailWith": { argTypes: [DYN], result: DYN }, + /** `new RegExp(pattern, flags?)` (scr_regex.c): a heap regex over the + * same libregexp engine the literals use. The pattern compiles EAGERLY + * so an invalid pattern (or an unknown flag letter) throws Node's + * catchable SyntaxError at construction — Node's message shape with + * libregexp's detail text (approximate fidelity; e.name exact). An + * empty pattern stores the spec's "(?:)" source. Both args borrowed + * strings (the lowering completes an absent flags to ""); result +1. + * The result TYPE is the regex kind, so the link switch pulls the + * engine exactly like a literal. */ + "regex.new": { argTypes: [STRING, STRING], result: REGEX }, + "regex.resetLastIndex": { argTypes: [REGEX, F64], result: VOID }, + "regex.newChecked": { argTypes: [DYN, DYN], result: REGEX }, + "regex.replaceCallback": { argTypes: [STRING, REGEX, DYN, BOOL], result: STRING }, + // Reads of capture args[1] of an execSpans/matchGroups result: + // `capture === args[2]`, Number(capture) and parseInt(capture, + // args[2]). Borrowed; never throw. + "regex.groupEquals": { argTypes: [arrayOf(STRING), F64, STRING], result: BOOL }, + "regex.groupNumber": { argTypes: [arrayOf(STRING), F64], result: F64 }, + "regex.groupParseInt": { argTypes: [arrayOf(STRING), F64, F64], result: F64 }, + // `.index` / `.input` (args[1]) of a match result. Borrowed; never throws. + "regex.resultProp": { argTypes: [arrayOf(STRING), STRING], result: DYN }, + // The string keys `for...in` visits on an array (args[0], any element + // type) after its indices. Borrowed; never throws. + "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, + // Whether the array (args[0]) still has the key (args[1]) from its + // namedKeys list. Borrowed; never throws. + "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, + // Whether an array (args[0], any element type) has properties besides its + // elements: noncanonical numeric keys, match metadata or named + // properties. Borrowed; never throws. + "array.hasProperties": { argTypes: [null], result: BOOL }, + // The key of an element access whose index is undefined (the property + // named "undefined"; frontend/lowering/undefined-index.ts). + "array.undefinedKey": { argTypes: [], result: F64 }, + // node:events EventEmitter: receivers are emitter-hierarchy objects and + // the chaining forms (on/off/removeAll/setMax) return the receiver's + // own class — program-dependent object types, checked in the libCall + // case. emitter.emit is the one VARIADIC libCall: args beyond + // (recv, name) are the event's frontend-unified tuple, any borrowable + // kind — the count check admits a longer list for it alone. + /** node:events EventEmitter (scr_events_emitter.c, link-gated by + * moduleUsesEmitter). The receiver of every instance form is a borrowed + * emitter-hierarchy object (`%EventEmitter` or a user subclass — the + * backend reinterprets to ScrEmitter*, the identical prefix); event + * names are borrowed strings (compile-time literals — the frontend + * fences non-literals and unifies each event's argument tuple program- + * wide). The chaining forms (on/off/removeAll/setMax) return the + * receiver +1 typed as its static class, Node's `return this`. + * + * emitter.new: `new EventEmitter()` → a +1 bare emitter. emitter.ctor: + * super() into the prefix of an emitted subclass (borrowed receiver, + * void — allocation already initialized the prefix). emitter.on: + * (recv, name, cb /moves/, once, prepend) — the backend synthesizes the + * per-signature va_list invoke adapter from the cb's func type. + * emitter.emit: (recv, name, ...tuple) — VARIADIC, the one libCall + * whose arg count exceeds its signature; args are borrowed, result is + * the had-listeners bool. emitter.emitError: emit('error', err) — + * throws err when unhandled. count/countFn/names/listeners/getMax/ + * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ + "emitter.new": { argTypes: [], result: VOID }, + /** `x.constructor.name` of an emitter or stream (a borrowed object of + * EventEmitter's hierarchy): the +1 class name the instance records. */ + "emitter.className": { argTypes: [null], result: STRING }, + "emitter.ctor": { argTypes: [null], result: VOID }, + "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, + "emitter.off": { argTypes: [null, STRING, null], result: VOID }, + // The dyn-adapted registration family (JS-lane checked-dynamic + // listeners): checkListener has NO receiver (it validates the listener + // argument alone); onDyn carries (recv, name, cb dyn, adapter func, + // once, prepend), offDyn (recv, name, cb dyn). + "emitter.checkListener": { argTypes: [DYN], result: VOID }, + "emitter.onDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, + "emitter.onFlex": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + /** on/once/prepend* and emit through an untyped value (the class + * dispatch of compiled emitters): onFlex/emitFlex plus runtime typed + * adapters under 'error' and the meta events, and Node's unhandled-'error' + * contract. emitAny is VARIADIC like emitFlex. */ + "emitter.onAny": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + "emitter.offDyn": { argTypes: [null, STRING, DYN], result: VOID }, + "emitter.removeAll": { argTypes: [null, STRING, BOOL], result: VOID }, + "emitter.emit": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitFlex": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitAny": { argTypes: [null, STRING], result: BOOL }, + /** An untyped emit of an event with a fixed tuple: (recv, name, k, then + * k checked-dynamic arguments as given, then the event's converted + * tuple) — VARIADIC like emitter.emit. hasTyped answers whether a + * listener of the name needs the converted tuple. */ + "emitter.emitMixed": { argTypes: [null, STRING, F64], result: BOOL }, + "emitter.hasTyped": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitError": { argTypes: [null, STRING, null], result: BOOL }, + "emitter.count": { argTypes: [null, STRING], result: F64 }, + "emitter.countFn": { argTypes: [null, STRING, null], result: F64 }, + "emitter.countDyn": { argTypes: [null, STRING, DYN], result: F64 }, + "emitter.names": { argTypes: [null], result: VOID }, + "emitter.listeners": { argTypes: [null, STRING], result: VOID }, + /** listeners()/rawListeners() through an untyped value: a checked- + * dynamic array (throws when a listener has typed parameters). */ + "emitter.listenersDyn": { argTypes: [null, STRING], result: DYN }, + /** Stream-'data' registration twins of emitter.on/onDyn (same args): + * chosen at registration sites whose receiver is stream-rooted, so the + * backend emits DATA thunks — the runtime's 'data' emission carries + * BOTH payload slots (bytes, string; exactly one non-NULL — encoded + * streams deliver strings), and the thunk unwraps the listener's + * declared side (typed) or boxes by tag (dyn). emitter.emitData is the + * user-emit form: (recv, name, chunk) with a bytes OR string chunk. */ + "emitter.onData": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, + "emitter.onDataDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, + "emitter.emitData": { argTypes: [null, STRING, null], result: BOOL }, + "emitter.setMax": { argTypes: [null, F64], result: VOID }, + "emitter.getMax": { argTypes: [null], result: F64 }, + "emitter.setDefaultMax": { argTypes: [F64], result: VOID }, + "emitter.getDefaultMax": { argTypes: [], result: F64 }, + // The ES module namespace's copy of defaultMaxListeners, and + // module.syncBuiltinESMExports() refreshing it. + "emitter.esmDefaultMax": { argTypes: [], result: F64 }, + "emitter.esmSync": { argTypes: [], result: VOID }, + /** The events.errorMonitor event key: an immortal string the runtime + * registry matches by identity, so no string event name reaches it. */ + "emitter.errorMonitor": { argTypes: [], result: STRING }, + // node:stream: receivers are stream-hierarchy objects and several + // results are program-dependent (the receiver's class, unions) — the + // libCall case owns those checks. The constructors and a few methods + // are VARIADIC (trailing option callbacks / the optional chunk+cb tail): + // argTypes here is the fixed prefix, VARIADIC_LIB_FNS admits the rest. + /** node:stream (scr_stream.c, link-gated by moduleUsesStream — which + * implies the emitter unit: stream events dispatch through the embedded + * ScrEmitter registry). Receivers are borrowed stream-class objects + * (`%Readable`/`%Writable`/`%Duplex`/`%Transform`/`%PassThrough` — one + * runtime layout, reinterpreted by side). + * + * Constructors (`readable.new` et al): args are [hwmR, hwmW, flags] + * followed by the PRESENT user callbacks in canonical order (read, + * write, final, destroy, transform, flush — the flags f64 is a bitmask + * naming which follow; absent ones emit NULL). Every callback closure + * MOVES and carries a leading `this` param (the stream), invoked + * through compiler-emitted adapters. Results are +1. + * + * readable.push / readable.pushStr / readable.pushNull: Node's push — + * buffers or delivers (bytes chunk borrowed; string converted utf8); + * returns the below-hwm answer. readable.read: (recv, size — -1 for + * absent) → Buffer|null union. pause/resume return recv +1 (`this`); + * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, + * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off + * when dst is the converted process.stdout / process.stderr; + * readable.unpipe (recv[, dst]) → recv +1. + * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb + * MOVES when present, called after the user write completes). + * writable.writeEnc: (recv, flags, chunk[, encoding][, cb]) — flags 1 + * an encoding argument, 2 a callback (the encoding is then null, as + * Node's write shifts it). + * writable.end: (recv, flags[, chunk][, encoding][, cb]) → recv +1; + * flags 16: an encoding argument follows the dyn chunk. cork/uncork are + * void. stream.destroy/destroyErr: (recv[, err]) → recv +1. + * stream.prop: (recv, name-literal) → the flag/number the name asks + * for; stream.errored → Error|null union; readable.flowing → + * bool|null union. All may leave a listener's exception pending + * (dispatch runs user code synchronously, like emit). */ + "readable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, + "writable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, + "duplex.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "transform.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "passthrough.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + // The subclass-initialization twins: the borrowed receiver leads, then + // the .new tail (variadic callbacks likewise). + // The dyn-options twins: newDyn takes the record, initDyn the borrowed + // receiver + record + fallback flags (trailing wrapper closures ride + // the variadic tail like the static forms). + /** The underscore-method assignment surface (`r._read = fn` after + * construction — Node's own-property shadow of the prototype method): + * args [stream receiver (borrowed), callback closure (+1 moves)]. The + * runtime slot the matching option callback fills swaps its closure + * and invoke thunk; the next dispatch uses it (Node's timing). The + * setters themselves never throw. */ + "stream.setRead": { argTypes: [null, null], result: VOID }, + "stream.setWrite": { argTypes: [null, null], result: VOID }, + "stream.setFinal": { argTypes: [null, null], result: VOID }, + "stream.setDestroy": { argTypes: [null, null], result: VOID }, + "stream.setTransform": { argTypes: [null, null], result: VOID }, + "stream.setFlush": { argTypes: [null, null], result: VOID }, + "stream.setConstruct": { argTypes: [null, null], result: VOID }, + "stream.replaceConstruct": { argTypes: [null, null], result: VOID }, + "stream.setWritev": { argTypes: [null, null], result: VOID }, + "stream.setWritevDefault": { argTypes: [null, null], result: VOID }, + /** The dyn-options twins (a checked-dynamic options record — the JS + * lane's `super(options)` forwarding and `new Readable(dynVar)`): the + * option walk runs at RUNTIME with Node's reading rules. newDyn: + * (optsDyn) → the fresh stream; initDyn: (recv, optsDyn, flags, + * ...fallback wrapper closures in canonical order — the flags literal + * names which ride, exactly the .init callback ABI). MAY THROW (a + * consumed-but-unlowered option is the compile fence's runtime twin). */ + /** stream.finished(s, cb) — the callback form: the watcher fires once + * at the terminal point with the finish status; the result is the +1 + * cleanup closure. stream.pipeline(count, s1..sn, cb): chains pipes, + * propagates the first error by destroying the rest, calls cb after the + * last 'close'; answers the destination +1. The Dyn twins take the + * callback as a checked-dynamic VALUE (mustCall wrappers). */ + /** stream.addAbortSignal: [stream, signal] — the constructors' signal + * option, chained over the fresh stream (falsy signals are ignored); + * stream.addAbortSignalFn: [signal, stream] — addAbortSignal(signal, + * stream). Both answer the stream (+1) and throw ERR_INVALID_ARG_TYPE + * for a value that is not an AbortSignal; they link the event-target + * unit (addAbortListener). */ + "stream.addAbortSignal": { argTypes: [null, DYN], result: VOID }, + "stream.addAbortSignalFn": { argTypes: [DYN, null], result: VOID }, + "stream.finished": { argTypes: [null, null], result: VOID }, + "stream.finishedDyn": { argTypes: [null, DYN], result: VOID }, + "stream.pipeline": { argTypes: [F64], result: VOID }, + "stream.pipelineDyn": { argTypes: [F64], result: VOID }, + "stream.pipelineStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + "stream.composeStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + "stream.pipelineStagesDyn": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + /** stream.compose(count, s1..sn): the Duplex over a pipeline of the + * streams (+1). MAY THROW (ERR_INVALID_ARG_VALUE for a stream that is + * no longer readable or writable). */ + "stream.compose": { argTypes: [F64], result: VOID }, + /** node:stream/promises — the promise forms over the same machinery: + * sp.finished: (s) → a pending void promise the terminal watcher + * settles; sp.pipeline: (count, s1..sn) → the callback pipeline's + * chaining/destroyer semantics settling a void promise (fulfilled on a + * clean finish, rejected with the finish status otherwise). */ + "sp.finished": { argTypes: [null], result: { kind: "promise", inner: VOID } }, + /** The options forms: finished(s, { readable, writable, error, cleanup, + * signal }) as [s, readable, writable, error, cleanup, signal?] — + * readable/writable -1 (absent), 0 or 1 — and pipeline(...streams, + * { signal, end }) as [count, s1..sn, end, signal?]. The Signal forms + * link the event-target unit (addAbortListener). The callback form + * stream.finishedOpts: [s, readable, writable, error, signal?, cb] → + * the cleanup closure. */ + "sp.finishedOpts": { + argTypes: [null, F64, F64, BOOL, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "sp.finishedOptsSignal": { + argTypes: [null, F64, F64, BOOL, BOOL, DYN], + result: { kind: "promise", inner: VOID }, + }, + "sp.pipelineOpts": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "sp.pipelineOptsSignal": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "stream.finishedOpts": { argTypes: [null, F64, F64, BOOL, null], result: VOID }, + "stream.finishedOptsSignal": { argTypes: [null, F64, F64, BOOL, DYN, null], result: VOID }, + "sp.pipeline": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + /** pipeline with function stages: [n, k, positions, flags, mask, + * s1..sn, stage1..stagek, helpers..., box?, (signal, abort)?] (+ the + * callback for the stream.* forms): the user's stage functions at the + * positions the bitmask names, the frontend's (next, read, return) + * helpers for each pumped generator (pipelinePumpedRuns) and a promise + * destination's box, and the pipeline's AbortSignal with its abort + * closure; flags bits 1-2 give the destination kind and bit 5 the + * signal group (scr_stream_pipeline_stages). A promise destination's + * promise type is sp.pipelineStages' result. */ + "sp.pipelineStages": { + argTypes: [F64, F64, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + /** sp.pipelineStages with the options argument: the same head, then + * end (a bool) and, with flags bit 6, the options' signal (dyn). */ + "sp.pipelineStagesOpts": { + argTypes: [F64, F64, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + /** node:stream/consumers — the promise consumers over the readable + * machinery: (s) → a pending promise settled at the terminal point + * with the accumulated result (sc.text: the utf8 decode, sc.json: the + * parsed dyn — malformed input rejects with the parse's SyntaxError, + * sc.buffer: the concatenated bytes, sc.arrayBuffer: an ArrayBuffer + * dyn over them) or rejected with the stream's + * error / ERR_STREAM_PREMATURE_CLOSE. */ + "sc.text": { argTypes: [null], result: { kind: "promise", inner: STRING } }, + "sc.json": { argTypes: [null], result: { kind: "promise", inner: DYN } }, + "sc.buffer": { argTypes: [null], result: { kind: "promise", inner: BYTES_U8 } }, + "sc.arrayBuffer": { argTypes: [null], result: { kind: "promise", inner: DYN } }, + "readable.newDyn": { argTypes: [DYN], result: VOID }, + "writable.newDyn": { argTypes: [DYN], result: VOID }, + "duplex.newDyn": { argTypes: [DYN], result: VOID }, + "transform.newDyn": { argTypes: [DYN], result: VOID }, + "passthrough.newDyn": { argTypes: [DYN], result: VOID }, + "readable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "writable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "duplex.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "transform.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "passthrough.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + /** Subclass initialization (`super(options?)` in a user `extends + * Readable` constructor): same tail as the `.new` forms, prefixed with + * the BORROWED receiver (the emitted subclass allocation — vtable and + * display name stamped, state NULL until here). Overridden underscore + * methods arrive as synthesized wrapper closures dispatching through + * the vtable. Void result. */ + "readable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, + "writable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, + "duplex.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "transform.init": { + argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], + result: VOID, + }, + "passthrough.init": { + argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], + result: VOID, + }, + "readable.push": { argTypes: [null, BYTES_U8], result: BOOL }, + "readable.pushStr": { argTypes: [null, STRING], result: BOOL }, + "readable.pushNull": { argTypes: [null], result: BOOL }, + "readable.pushU": { argTypes: [null, null], result: BOOL }, + "readable.pushDyn": { argTypes: [null, DYN], result: BOOL }, + "readable.unshift": { argTypes: [null, BYTES_U8], result: VOID }, + "readable.unshiftDyn": { argTypes: [null, DYN], result: VOID }, + "readable.unshiftStr": { argTypes: [null, STRING], result: VOID }, + "stream.onDyn": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + "readable.readDyn": { argTypes: [null, DYN], result: DYN }, + "readable.read": { argTypes: [null, F64], result: VOID }, + "readable.pause": { argTypes: [null], result: VOID }, + "readable.setEncoding": { argTypes: [null, STRING], result: VOID }, + /** push(chunk, enc) with a literal non-utf8 encoding, and the + * defaultEncoding option's push side (how push(string) decodes — + * Buffer.from(chunk, enc)); both carry the CANONICAL literal. */ + "readable.pushStrEnc": { argTypes: [null, STRING, STRING], result: BOOL }, + "readable.pushEncoding": { argTypes: [null, STRING], result: VOID }, + /** The objectMode option's sides (readable, writable), applied right + * after construction; answers the receiver (+1, the chaining shape). */ + "stream.objectMode": { argTypes: [null, BOOL, BOOL], result: VOID }, + "stream.decodeStrings": { argTypes: [null, BOOL], result: VOID }, + /** Runtime decodeStrings and defaultEncoding option values, applied + * after construction (answers the receiver +1); the encoding was + * validated by stream.checkEncodingOption before construction. */ + "stream.optionValues": { argTypes: [null, DYN, DYN], result: VOID }, + "stream.checkEncodingOption": { argTypes: [DYN], result: VOID }, + "stream.duplexFromGen": { argTypes: [null, null, null, null, null, null], result: VOID }, + "stream.duplexFromDyn": { argTypes: [DYN], result: VOID }, + /** Duplex.from over a lone Readable or Writable, or a { readable, + * writable } pair of streams (duplexify.js's _duplexify). */ + "stream.duplexifyReadable": { argTypes: [null], result: VOID }, + "stream.duplexifyWritable": { argTypes: [null], result: VOID }, + "stream.duplexifyPair": { argTypes: [null, null], result: VOID }, + /** Duplex.from(promise): the promise and its box helper. */ + "stream.duplexFromPromise": { argTypes: [null, null], result: VOID }, + /** Duplex.from(asyncFunction): the stage, its source helper and the + * promise's box helper. */ + "stream.duplexFromAsyncFn": { argTypes: [null, null, null], result: VOID }, + /** The same for a body that declares the options argument: then the + * AbortController's abort() and its signal. */ + "stream.duplexFromGenSignal": { + argTypes: [null, null, null, null, null, null, null, DYN], + result: VOID, + }, + "stream.duplexFromAsyncFnSignal": { argTypes: [null, null, null, null, DYN], result: VOID }, + "writable.writeEncoding": { argTypes: [null, STRING], result: VOID }, + "writable.setDefaultEncoding": { argTypes: [null, DYN], result: VOID }, + /** Whether a readable.nextChunkDyn step answered the end (the done + * marker; an object-mode stream can yield undefined). */ + "stream.chunkDone": { argTypes: [DYN], result: BOOL }, + /** for-await over a readable (the desugared loop's per-pass promise): + * +1 promise of the next chunk — buffered content, the EOF sentinel + * (empty Buffer / dyn undefined), or a rejection with the stream's + * error. The Dyn twin boxes chunks by runtime tag (the JS lane). + * readable.fromArr is Readable.from over a typed array, string or + * Buffer: [array, SCR_FROM_* flags, highWaterMark]. readable.fromDyn is + * Readable.from over a checked-dynamic source: [source, options]. + * readable.fromIter is Readable.from over a class iterator, already + * opened: [checked-dynamic { next } object, flags, highWaterMark]; + * readable.fromAsyncIter is the async class iterator's twin. */ + "readable.nextChunk": { argTypes: [null], result: VOID }, + "readable.nextChunkDyn": { argTypes: [null], result: VOID }, + "readable.fromArr": { argTypes: [null, F64, F64], result: VOID }, + "readable.fromDyn": { argTypes: [DYN, DYN], result: VOID }, + "readable.fromIter": { argTypes: [DYN, F64, F64], result: VOID }, + "readable.fromAsyncIter": { argTypes: [DYN, F64, F64], result: VOID }, + /** Readable.from over a compiled generator: [generator, next, return, + * throw, flags, highWaterMark] (sync) or [generator, next, read, + * return, throw, flags, highWaterMark] (async), the helpers being the + * frontend's interned closures. readable.fromDone is the done marker + * the helpers answer. */ + "readable.fromGen": { argTypes: [null, null, null, null, F64, F64], result: VOID }, + "readable.fromAsyncGen": { argTypes: [null, null, null, null, null, F64, F64], result: VOID }, + "readable.fromDone": { argTypes: [], result: DYN }, + /** The Readable operators (scr_stream_ops.c): [stream, ...] with every + * other argument a checked-dynamic value (the callback, the count, the + * options). map/filter/flatMap/drop/take answer the object-mode output + * stream (validated as a stream class; THROW Node's validation errors); + * toArray settles a dyn array, find a dyn value, some/every a boolean, + * forEach nothing; reduce: [stream, fn, init, hasInit, options, kind] + * with the payload kind (0 dyn, 1 number, 2 boolean, 3 string). The + * promise-returning ones reject instead of throwing. */ + "readable.opMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + "readable.opFilter": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + "readable.opFlatMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + /** flatMap whose callback returns a compiled generator: the helpers + * over its type follow (async: next, read, return, throw; sync: next, + * return, throw). */ + "readable.opFlatMapAsyncGen": { + argTypes: [null, DYN, DYN, DYN, null, null, null, null], + result: VOID, + }, + "readable.opFlatMapSyncGen": { + argTypes: [null, DYN, DYN, DYN, null, null, null], + result: VOID, + }, + "readable.opDrop": { argTypes: [null, DYN, DYN], result: VOID }, + "readable.opTake": { argTypes: [null, DYN, DYN], result: VOID }, + "readable.opToArray": { argTypes: [null, DYN], result: { kind: "promise", inner: DYN } }, + "readable.opForEach": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + "readable.opSome": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: BOOL }, + }, + "readable.opEvery": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: BOOL }, + }, + "readable.opFind": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** The map family's last argument is the callback's { signal } (or + * undefined). reduceSignal adds ac.abort() (a closure) and + * { signal: ac.signal } for a reducer that reads them. */ + "readable.opReduce": { argTypes: [null, DYN, DYN, BOOL, DYN, F64], result: VOID }, + "readable.opReduceSignal": { + argTypes: [null, DYN, DYN, BOOL, DYN, F64, null, DYN], + result: VOID, + }, + "readable.opSignalList": { argTypes: [DYN], result: DYN }, + "readable.opSignalOpts": { argTypes: [DYN], result: DYN }, + "readable.opSignalOf": { argTypes: [DYN], result: DYN }, + "readable.opOnceOptions": { argTypes: [], result: DYN }, + "readable.resume": { argTypes: [null], result: VOID }, + "readable.isPaused": { argTypes: [null], result: BOOL }, + "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, + "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, + "readable.unpipe": { argTypes: [null], result: VOID }, + "readable.flowing": { argTypes: [null], result: VOID }, + "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, + "writable.writeStr": { argTypes: [null, STRING], result: BOOL }, + "writable.writeU": { argTypes: [null, null], result: BOOL }, + "writable.writeDyn": { argTypes: [null, DYN], result: BOOL }, + "writable.writeEnc": { argTypes: [null, F64, DYN], result: BOOL }, + "writable.end": { argTypes: [null, F64], result: VOID }, + "writable.cork": { argTypes: [null], result: VOID }, + "writable.uncork": { argTypes: [null], result: VOID }, + "stream.destroy": { argTypes: [null], result: VOID }, + "stream.destroyErr": { argTypes: [null, null], result: VOID }, + /** AsyncIteratorClose for Readable[Symbol.asyncIterator](): destroy with + * Node's AbortError/ABORT_ERR payload while the iterator's internal error + * consumer prevents an unhandled-error crash. */ + "stream.iteratorClose": { argTypes: [null], result: VOID }, + "stream.prop": { argTypes: [null, STRING], result: VOID }, + "stream.errored": { argTypes: [null], result: VOID }, + // node:assert: pass/negated/deep/hasMsg are frontend-computed bools; the + // message slot always carries a string ("" when hasMsg is false). + /** node:assert (scr_assert.c; assert.match in scr_regex.c — every call + * site carries a regex value, so the regex link switch is already on). + * Failures throw a catchable AssertionError — a runtime %Error whose + * name is "AssertionError" and whose code slot is "ERR_ASSERTION" — so + * `instanceof Error`, `.name`, `.message`, and `.code` all answer like + * Node's. Generated messages are Node's assertion_error.js scalar forms + * byte-exactly (the short `a !== b` form, the stacked `+ actual + * - expected` diff with the string `^` indicator, the inline-vs-block + * not-equal split); composite deep failures carry the header line + * without the rendered inspect diff (documented divergence). + * + * assert.ok: (pass, message) — the frontend computed the truthiness AND + * the full message (the user's, or the compile-time source-text form — + * assert.fail lowers here too with pass=false). assert.eqF64/eqBigInt/ + * eqStr/eqBool: (a, b, negated, deep, msg, hasMsg) — Object.is comparison, + * covering strictEqual/notStrictEqual and the scalar deepStrictEqual + * pair; msg is a typed dummy ("" literal) when hasMsg is false (Node + * distinguishes an omitted message from an empty one per operator). + * assert.deepResult: (equal, negated, msg, hasMsg) — the verdict of a + * frontend-synthesized structural comparison, turned into Node's throw. + * assert.sameValue: Object.is over doubles (the deep-equal helpers' + * number leaf; never throws). assert.match: (s, regex, negated, msg, + * hasMsg) — a fresh exec from index 0. assert.throwsNone: (rejection, + * ename, hasEname, msg, hasMsg) — the "Missing expected + * exception|rejection" throw of assert.throws/rejects whose callback + * returned (fulfilled) normally, with Node's ` (${expected.name})` + * detail when the expected class or shape carries a name. + * assert.throwsMismatch: (expectedName, error) — the wrong-class throw + * of the assert.throws(fn, ErrorClass) form. assert.eqSym: + * (a, b, negated, deep, msg, hasMsg) — strict equality over symbol + * values, pointer identity with v24's "Symbol(desc)" stacked-diff + * messages (scr_symbol.c, the assert.match pattern — symbol-typed + * slots already flip the symbol link switch). assert.eqDyn: + * (a, b, negated, deep, msg, hasMsg) — the whole quartet over + * checked-dynamic operands (both slots dyn; the frontend boxes a + * static side with dynFrom first): SameValue for the strict pair over + * the dyn kinds (boxed-closure identity for functions), the structural + * dyn walk for the deep pair, with assertion_error.js's messages — + * scalar forms byte-exact, composites rendered compact:false/sorted + * through the checked-dynamic tree and diffed with the real myers line printer. + * + * The assert.throws(fn, {name/code/message}) shape check + * (expectedException over the static error surface): shapeBegin + * stashes the caught error, shapeStr/shapeRe add one expected key each + * (key ids 0 code / 1 message / 2 name; shapeRe lives in scr_regex.c — + * its regex argument flips the regex link switch — and tests eagerly + * so scr_assert.c stays libregexp-free), then shapeEnd throws Node's + * deep-equal Comparison diff BYTE-EXACTLY (the bounded key set makes + * the rendering enumerable) or the custom message. + * assert.throwsRegex: (regex, error, msg, hasMsg) — the + * assert.throws(fn, /re/) check over String(error), Node's + * regex-mismatch message. assert.regexErrTest: doesNotReject's + * regex predicate over String(error) (never throws). + * assert.unwantedRejection: (error, msg, hasMsg) — doesNotReject's + * "Got unwanted rejection" throw. + * assert.ifErrorErr/F64/Str/Bool: assert.ifError's per-type throws + * ("ifError got unwanted exception: " + the error's message/name or + * the value's inspection) — the frontend routes null/undefined to a + * no-op and everything else here (Node throws for falsy values too). + * All arguments are borrowed. */ + "assert.ok": { argTypes: [BOOL, STRING, BOOL], result: VOID }, + /** The next AssertionError's actual, expected and operator, recorded on + * a failure branch. */ + "assert.record": { argTypes: [DYN, DYN, STRING], result: VOID }, + "assert.errorNew": { + argTypes: [DYN], + result: { kind: "object", className: "%AssertionError" }, + }, + "assert.eqF64": { argTypes: [F64, F64, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqBigInt": { argTypes: [BIGINT_T, BIGINT_T, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqStr": { argTypes: [STRING, STRING, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqBool": { argTypes: [BOOL, BOOL, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.deepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.looseDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.looseDeepDyn": { argTypes: [DYN, DYN, BOOL, STRING, BOOL], result: VOID }, + "assert.partialDeepDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, + "assert.partialDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.partialEqDyn": { argTypes: [DYN, DYN], result: BOOL }, + "assert.looseF64Eq": { argTypes: [F64, F64], result: BOOL }, + "assert.sameValue": { argTypes: [F64, F64], result: BOOL }, + // The deep-equality pair memo: both slots are program-dependent + // (any cycle-capable record/array/map type). + /* deepStrictEqual's pair memo over cycle-capable types: enter answers + * true for a pair already being compared (Node's memo — equal cyclic + * structures compare true); leave pops. */ + "assert.deqEnter": { argTypes: [null, null], result: BOOL }, + "assert.deqLeave": { argTypes: [], result: VOID }, + "assert.match": { argTypes: [STRING, REGEX, BOOL, STRING, BOOL], result: VOID }, + "assert.throwsNone": { argTypes: [BOOL, STRING, BOOL, STRING, BOOL], result: VOID }, + "assert.throwsMismatch": { + argTypes: [STRING, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], + result: VOID, + }, + "assert.throwsRegex": { + argTypes: [REGEX, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], + result: VOID, + }, + // Symbol strict equality (pointer identity; scr_symbol.c). + "assert.eqSym": { + argTypes: [{ kind: "symbol" }, { kind: "symbol" }, BOOL, BOOL, STRING, BOOL], + result: VOID, + }, + // The equality quartet over checked-dynamic operands (the frontend + // boxes a static side into the checked-dynamic tree first). + "assert.eqDyn": { argTypes: [DYN, DYN, BOOL, BOOL, STRING, BOOL], result: VOID }, + /** A message argument that is not a string, registered for the next + * failing assertion (scr_assert_message_value). */ + "assert.messageValue": { argTypes: [DYN, BOOL], result: VOID }, + /** An assert method as a function value, by canonical name. */ + "assert.methodValue": { argTypes: [STRING], result: DYN }, + "assert.messageClear": { argTypes: [], result: VOID }, + "assert.looseResult": { argTypes: [BOOL, BOOL, STRING, STRING, STRING, BOOL], result: VOID }, + // The throws(fn, {shape}) accumulator: begin/slot calls never throw; + // shapeEnd throws the Comparison diff. The error slot is the + // %Error-narrowed caught value. + "assert.shapeBegin": { argTypes: [{ kind: "object", className: "%Error" }], result: VOID }, + "assert.shapeStr": { argTypes: [F64, STRING], result: VOID }, + "assert.shapeRe": { argTypes: [F64, REGEX], result: VOID }, + "assert.shapeEnd": { argTypes: [STRING, BOOL, BOOL], result: VOID }, + "assert.regexErrTest": { + argTypes: [REGEX, { kind: "object", className: "%Error" }], + result: BOOL, + }, + "assert.unwantedRejection": { + argTypes: [{ kind: "object", className: "%Error" }, STRING, BOOL], + result: VOID, + }, + "assert.unwantedError": { argTypes: [STRING, BOOL, STRING, BOOL], result: VOID }, + "assert.noErrorPredicate": { argTypes: [DYN, DYN], result: BOOL }, + "assert.regexDynTest": { argTypes: [REGEX, DYN], result: BOOL }, + /** Node's expectsError over an error-INSTANCE expected (assert.throws/ + * rejects second argument): walk the expected dyn error's keys (name, + * message, code — the %error marker skipped) and deep-compare each + * against the caught value's; a mismatch throws the deep-equal + * AssertionError (scr_assert.c). MAY THROW by design. */ + "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, + /** A validation function against the caught value (Node's expectedException). */ + "assert.throwsValidate": { argTypes: [DYN, DYN, STRING, STRING, BOOL], result: VOID }, + // assert.ifError's typed entries (always throw; unit args never + // lower). The error slot is program-dependent — any %Error-hierarchy + // class, root or subclass (the insp.error precedent, null slot). + "assert.ifErrorErr": { argTypes: [null], result: VOID }, + "assert.ifErrorF64": { argTypes: [F64], result: VOID }, + "assert.ifErrorStr": { argTypes: [STRING], result: VOID }, + "assert.ifErrorBool": { argTypes: [BOOL], result: VOID }, + "assert.ifErrorDyn": { argTypes: [DYN], result: VOID }, + // Bytes equality: the two value slots are any bytes kind (the frontend + // gates both sides to ONE static bytes type; the libCall case checks + // the kind and the pair below). + "assert.refEqBytes": { argTypes: [null, null, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.refEqFn": { argTypes: [null, null, BOOL, STRING, BOOL], result: VOID }, + "assert.bytesDeepEq": { argTypes: [null, null, BOOL], result: BOOL }, + "assert.looseBytesEq": { argTypes: [null, null], result: BOOL }, + // util.inspect: the error receiver is program-dependent (a builtin or + // user error class — the error.toString precedent, null slot). - "insp.f64": { argTypes: [F64], result: STRING }, - /** util.format %j over a checked-dynamic argument: the runtime dyn - * walk (JS-exact stringify; root undefined/function prints - * "undefined"; a handle in the tree throws the "not supported yet" - * Error). */ - "insp.jsonDyn": { argTypes: [DYN], result: STRING }, - "insp.str": { argTypes: [STRING], result: STRING }, - "insp.regex": { argTypes: [REGEX], result: STRING }, - /** A Date's inspect text: toISOString(), or "Invalid Date". */ - "insp.date": { argTypes: [DATE_T], result: STRING }, - "insp.buffer": { argTypes: [BYTES_U8], result: STRING }, - /** A numeric typed array that is not a Buffer (any element kind). */ - "insp.typedArray": { argTypes: [null, F64, F64], result: STRING }, - /** A u8 byte view in a union arm, element or field: a DataView, Buffer - * or Uint8Array by its own flags. */ - "insp.bytes": { argTypes: [null, F64, F64], result: STRING }, - "insp.error": { argTypes: [null, F64, F64], result: STRING }, - "insp.errorHasCause": { argTypes: [null], result: BOOL }, - "insp.errorBagHasProperties": { argTypes: [DYN], result: BOOL }, - "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, - "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, - "insp.dynS": { argTypes: [DYN, F64], result: STRING }, - /** util.format's %o over a checked-dynamic value: inspect with - * showHidden. Kinds whose hidden entries have no rendering throw the - * 'not supported yet' Error. */ - "insp.dynHidden": { argTypes: [DYN, F64], result: STRING }, - "insp.begin": { argTypes: [F64], result: VOID }, - /** util.inspect's breakLength, compact (0 for false) and sorted for the - * renders that follow; the call site restores 80, 3 and false afterwards. */ - "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, - "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, - // Circular references: the receiver slot is program-dependent (any - // cycle-capable record/array/map/class type — the insp.error precedent). - /* Circular references over cycle-capable composites (recursive record/ - * class types): circCheck answers a value's circular id when it is - * already on the traversal stack (0 otherwise), seenPush/refWrap - * bracket the frame (insp.end adds Node's " " prefix to values - * the walk found circular), circular renders "[Circular *N]". */ - "insp.circCheck": { argTypes: [null], result: F64 }, - "insp.seenPush": { argTypes: [null], result: VOID }, - "insp.refWrap": { argTypes: [null, STRING], result: STRING }, - "insp.circular": { argTypes: [F64], result: STRING }, - /* Whether a closed declared-field record kept the undeclared properties - * of its checked source (scr_record_source_note): reflective surfaces - * (inspect, Object.keys/values/entries) then answer through its live - * checked view. The receiver is any such record type. Never throws. */ - "record.sourceHas": { argTypes: [null], result: BOOL }, - /** `record.sourceSpread(built, value)`: the record a literal starting - * with `...value` just built keeps the order of value's kept source - * (key-order-witness.ts ordered spreads). Both are record types. Never - * throws. */ - "record.sourceSpread": { argTypes: [null, null], result: VOID }, - "insp.key": { argTypes: [STRING], result: STRING }, - "insp.moreItems": { argTypes: [F64], result: STRING }, - "insp.end": { argTypes: [STRING, STRING, STRING, F64, BOOL, BOOL], result: STRING }, - /** A nested inspect inside a custom rendering (PerformanceEntry's - * `${name} ${inspect(toJSON)}`): nestedBegin starts a fresh inspect - * context, nestedEnd(prefix, inner) restores the caller's and answers - * `prefix inner` re-indented at the caller's level (+1). */ - "insp.nestedBegin": { argTypes: [], result: VOID }, - "insp.nestedEnd": { argTypes: [STRING, STRING], result: STRING }, - /** Around a top-level render that an inspection refusal can end: mark - * records the inspector's frame and nesting depth, unwind (in the - * render's finally) closes what the render left open above it. */ - "insp.mark": { argTypes: [], result: VOID }, - "insp.unwind": { argTypes: [], result: VOID }, - /** node:string_decoder's utf8 StringDecoder (scr_string.c): the decoder - * value is a one-field record whose f64 PACKS the pending partial - * sequence (count + up to 3 raw bytes — Node buffers at most 3 for - * every encoding); the frontend's interned %strdec helpers thread it, - * with the decoder's CANONICAL encoding name first, through these pure - * functions. write: (enc, pending, chunk) → the decoded complete - * prefix of pending+chunk (+1); next: (enc, pending, chunk) → the - * packed NEW pending; end: (enc, pending) → the buffered partial's - * flush (+1). Node-exact per encoding (oracle-pinned); none throws. */ - "strdec.write": { argTypes: [STRING, F64, BYTES_U8], result: STRING }, - "strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 }, - "strdec.end": { argTypes: [STRING, F64], result: STRING }, - /** A runtime encoding argument's canonical name (Node's normalizeEncoding: - * nullish and "" are utf8); an unknown name throws ERR_UNKNOWN_ENCODING. */ - "strdec.encoding": { argTypes: [DYN], result: STRING }, - // node:readline: the interface handle is f64; the callbacks' func types - // are program-dependent (the emitter picks the adapter by parameter - // count), so those slots are null like child.onExit's. - /** node:readline and node:readline/promises (scr_readline.c, linked - * under the events gate — these fns imply moduleUsesProcessEvents; a - * stream input or output adds scr_stream.c's bridge). The interface - * value is an f64 handle (the Timeout-id precedent); an open interface - * on stdin keeps the loop alive until close/EOF. - * rl.create: [mode, input?, output?, crlfDelay, prompt?] → f64, mode - * bits 1 stream input, 2 stream output, 4 stdout output, 16 prompt - * (lowerReadlineCreateCall); rl.createSignal: the same plus the - * trailing AbortSignal (mode bit 8), which links the event-target unit - * for addAbortListener. rl.question: [handle, query, cb] — the callback - * form; THROWS ERR_USE_AFTER_CLOSE on a closed interface (may-throw). - * rl.questionAsync(Signal): [handle, query, signal?] → promise, - * rejecting instead of throwing. rl.close/pause/resume/prompt: - * [handle] (pause/resume/prompt throw ERR_USE_AFTER_CLOSE when - * closed); rl.setPrompt/getPrompt; rl.write: [handle, data] (throws - * when closed). rl.on: [handle, event, cb, once] — the event number - * (0 'line', 1 'close', 2 'pause', 3 'resume', 4 'error') and the - * listener (moves). rl.iterOpen/iterNext/iterReturn: the async - * iterator — iterNext answers promise of the line or undefined. */ - "rl.create": { argTypes: [F64], result: F64 }, - "rl.createSignal": { argTypes: [F64], result: F64 }, - "rl.question": { argTypes: [F64, STRING, null], result: VOID }, - "rl.questionAsync": { argTypes: [F64, STRING], result: { kind: "promise", inner: STRING } }, - "rl.questionAsyncSignal": { - argTypes: [F64, STRING, DYN], - result: { kind: "promise", inner: STRING }, - }, - /** The question forms with a checked-dynamic query (a JavaScript value - * that need not be a string). */ - "rl.questionDyn": { argTypes: [F64, DYN, null], result: VOID }, - "rl.questionAsyncDyn": { argTypes: [F64, DYN], result: { kind: "promise", inner: STRING } }, - "rl.questionAsyncSignalDyn": { - argTypes: [F64, DYN, DYN], - result: { kind: "promise", inner: STRING }, - }, - "rl.close": { argTypes: [F64], result: VOID }, - "rl.pause": { argTypes: [F64], result: VOID }, - "rl.resume": { argTypes: [F64], result: VOID }, - "rl.prompt": { argTypes: [F64], result: VOID }, - /** prompt(preserveCursor): [handle, preserveCursor] */ - "rl.promptWith": { argTypes: [F64, BOOL], result: VOID }, - "rl.setPrompt": { argTypes: [F64, STRING], result: VOID }, - "rl.setPromptDyn": { argTypes: [F64, DYN], result: VOID }, - "rl.getPrompt": { argTypes: [F64], result: STRING }, - "rl.write": { argTypes: [F64, STRING], result: VOID }, - /** write(data, key): [handle, hasData, data, hasKey, hasName, name, - * ctrl, meta, shift] */ - "rl.writeKey": { - argTypes: [F64, BOOL, STRING, BOOL, BOOL, STRING, BOOL, BOOL, BOOL], - result: VOID, - }, - "rl.on": { argTypes: [F64, F64, null, BOOL], result: VOID }, - "rl.iterOpen": { argTypes: [F64], result: VOID }, - "rl.iterNext": { argTypes: [F64], result: { kind: "promise", inner: DYN } }, - "rl.iterReturn": { argTypes: [F64], result: VOID }, - /** createInterface's history and editing options (scr_rl_check_options: - * [flags, tabSize, escapeCodeTimeout, historySize], may throw; - * rl.setOptions: [handle, flags, historySize, removeHistoryDuplicates, - * tabSize, escapeCodeTimeout]; rl.setHistory: [handle, history]) and - * the member reads (rl.history answers the array, rl.numProp the - * numeric member by number). */ - "rl.checkOptions": { argTypes: [F64, F64, F64, F64], result: VOID }, - "rl.checkCompleter": { argTypes: [DYN], result: VOID }, - "rl.setOptions": { argTypes: [F64, F64, F64, BOOL, F64, F64], result: VOID }, - "rl.setHistory": { argTypes: [F64, arrayOf(STRING)], result: VOID }, - "rl.history": { argTypes: [F64], result: arrayOf(STRING) }, - "rl.numProp": { argTypes: [F64, F64], result: F64 }, - /** Terminal mode (scr_readline_tty.c): rl.terminal: [handle, mode (0 - * the option's value, 2 output.isTTY of process.stdout), terminal, - * completer (undefined when absent), readline/promises], emitted with - * scr_insp_dyn when there is a completer; the line, terminal and cursor - * members. */ - "rl.terminal": { argTypes: [F64, F64, BOOL, DYN, BOOL], result: VOID }, - "rl.line": { argTypes: [F64], result: STRING }, - "rl.isTerminal": { argTypes: [F64], result: BOOL }, - "rl.isPromises": { argTypes: [F64], result: BOOL }, - /** createInterface's input or output as a checked-dynamic value: - * [value, output] — the stream; refused when it is not one. */ - "rl.inputFromDyn": { argTypes: [DYN, BOOL], result: { kind: "object", className: "%Readable" } }, - "rl.outputFromDyn": { - argTypes: [DYN, BOOL], - result: { kind: "object", className: "%Writable" }, - }, - "rl.cursor": { argTypes: [F64], result: F64 }, - /** readline.cursorTo / moveCursor / clearLine / clearScreenDown: rl.seq - * [kind (0-3), the first and second numbers, whether the second is - * given] answers the data to write after the argument checks (may - * throw); rl.moves [dx, dy] says whether moveCursor writes. */ - "rl.seq": { argTypes: [F64, F64, F64, BOOL], result: STRING }, - "rl.moves": { argTypes: [F64, F64], result: BOOL }, - /** getCursorPos() (scr_readline_tty.c, linked through the regex unit - * like rl.terminal): [handle, 0 rows | 1 cols] */ - "rl.cursorPos": { argTypes: [F64, F64], result: F64 }, - /** readline.emitKeypressEvents (scr_readline_tty.c): rl.keypressStdin - * [interface (0: none), escapeCodeTimeout (NaN: the interface's or - * 500)], rl.keypressStream [stream, interface, timeout]. */ - "rl.keypressStdin": { argTypes: [F64, F64], result: VOID }, - "rl.keypressStream": { argTypes: [null, F64, F64], result: VOID }, - /** readline/promises Readline over an f64 handle: rl.uiOpen [fd (1 - * stdout, 2 stderr), autoCommit], rl.uiOpenStream [stream, autoCommit] - * (isWritable, may throw); the actions take [handle, ...numbers] (the - * checks may throw; cursorTo: [handle, x, whether y is given, y]); - * rl.uiCommit answers a promise. */ - "rl.uiOpen": { argTypes: [F64, BOOL], result: F64 }, - "rl.uiOpenStream": { argTypes: [null, BOOL], result: F64 }, - "rl.uiCursorTo": { argTypes: [F64, F64, BOOL, F64], result: VOID }, - "rl.uiMoveCursor": { argTypes: [F64, F64, F64], result: VOID }, - "rl.uiClearLine": { argTypes: [F64, F64], result: VOID }, - "rl.uiClearScreenDown": { argTypes: [F64], result: VOID }, - "rl.uiRollback": { argTypes: [F64], result: VOID }, - "rl.uiCommit": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - /** node:timers/promises — the promisified pair (scr_async.c, beside - * the timer heap they ride): tp.setTimeout: [ms] → a pending void - * promise a one-shot heap timer fulfills (the loop's timer phase, FIFO - * against equal deadlines like Node); tp.setImmediate: [] → the same - * through the immediate queue (fires before due timers of later loop - * turns, Node's check phase). Neither throws. */ - "tp.setTimeout": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - // scheduler.wait(delay): tp.setTimeout's promise. It throws Node's - // ERR_INVALID_THIS once mock.timers has restored the real scheduler.wait. - "tp.schedulerWait": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "tp.setImmediate": { argTypes: [], result: { kind: "promise", inner: VOID } }, - /** events.once (scr_events_emitter.c): [emitter, name, boxer, - * errorBoxer, listenForError] → a pending promise of any[]; the boxers - * turn the event's and the 'error' tuple into that array. */ - "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, - /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): - * events.once's arguments plus the checked-dynamic signal (an - * AbortSignal or undefined). */ - "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, - /** The resolve-value forms: [ms?, value, ref] → a pending promise of the - * value's type that copies the borrowed already-resolved `value` promise - * when the timer (or immediate) fires; ref false unrefs it. The result - * type follows the value promise. */ - "tp.setTimeoutValue": { argTypes: [F64, null, BOOL], result: VOID }, - "tp.setImmediateValue": { argTypes: [null, BOOL], result: VOID }, - /** The same with options.signal (a checked-dynamic AbortSignal or - * undefined) last: an abort clears the timer and rejects with an - * AbortError (scr_event_target.c). */ - "tp.setTimeoutSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, - "tp.setImmediateSignal": { argTypes: [null, BOOL, DYN], result: VOID }, - /** scheduler.wait(delay, options): tp.setTimeoutValue's and - * tp.setTimeoutSignal's shapes, with tp.schedulerWait's throws. */ - "tp.schedulerWaitValue": { argTypes: [F64, null, BOOL], result: VOID }, - "tp.schedulerWaitSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, - /** node:diagnostics_channel (scr_dc.c, linked when any dc.* appears — - * the zlib gating precedent): a process-global name→channel registry; - * channel values are f64 handles (type-mapper.ts maps Channel to F64, the - * readline.Interface pattern). Subscribers are dyn function values — - * identity-compared by unsubscribe, called (message, name) by publish - * over a SNAPSHOT of the list (a subscriber unsubscribing itself - * mid-publish still lets its siblings fire, Node's behavior). dc.publish - * MAY THROW: a subscriber's throw propagates out of publish (catchable - * there) where Node routes it to triggerUncaughtException — the - * documented divergence. subscribe/unsubscribe throw Node's - * ERR_INVALID_ARG_TYPE TypeError for non-function subscribers. */ - "dc.channel": { argTypes: [STRING], result: F64 }, - "dc.subscribe": { argTypes: [STRING, DYN], result: VOID }, - "dc.unsubscribe": { argTypes: [STRING, DYN], result: BOOL }, - "dc.hasSubscribers": { argTypes: [STRING], result: BOOL }, - "dc.publish": { argTypes: [F64, DYN], result: VOID }, - "dc.chanSubscribe": { argTypes: [F64, DYN], result: VOID }, - "dc.chanUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, - "dc.chanHasSubscribers": { argTypes: [F64], result: BOOL }, - "dc.chanName": { argTypes: [F64], result: STRING }, - /** TracingChannel (dc.tracingChannel): a registry entry of the five - * event channels, an f64 handle like Channel (type-mapper.ts). tcSubscribe/ - * tcUnsubscribe walk a dyn handlers object's five event keys (truthy - * non-function slots throw the per-channel ERR_INVALID_ARG_TYPE); - * tcTraceSync/tcTraceCallback/tcTracePromise run Node's publish choreography in C over - * dyn values (fn, ctx, thisArg, args-array) with thisArg bound as the - * ambient receiver — the traced call's throw and any subscriber throw - * both propagate (MAY THROW). tcTraceCallback wraps args[position] in a - * native error/result + asyncStart/asyncEnd publisher and throws Node's - * TypeError when that slot is not callable. tracingChannelOf is the - * five-Channel collection form of the constructor. */ - /** setImmediate as a first-class dyn value (scr_async.c): a minted dyn - * callable scheduling args[0](args[1..]) on the immediate queue — the - * Node-suite traceCallback shape (`traceCallback(setImmediate, ...)`). - * Calling it validates the callback (the dyn call machinery); minting - * never throws. */ - "timers.setImmediateFnValue": { argTypes: [], result: DYN }, - /** `new Promise(setImmediate)` (the Node-suite early-exit shape): a - * fresh promise an immediate fulfills with the undefined dyn value — - * the executor IS setImmediate, so resolve rides the immediate queue - * (scr_async.c). Never throws. */ - "timers.immediatePromise": { argTypes: [], result: { kind: "promise", inner: DYN } }, - "dc.tracingChannel": { argTypes: [STRING], result: F64 }, - "dc.tracingChannelOf": { argTypes: [F64, F64, F64, F64, F64], result: F64 }, - "dc.tcChannel": { argTypes: [F64, F64], result: F64 }, - "dc.tcHasSubscribers": { argTypes: [F64], result: BOOL }, - "dc.tcSubscribe": { argTypes: [F64, DYN], result: VOID }, - "dc.tcUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, - "dc.tcTraceSync": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, - "dc.tcTraceCallback": { argTypes: [F64, DYN, F64, DYN, DYN, DYN], result: DYN }, - /** tracePromise (scr_dc.c): start publish, the traced call, a wrap of - * non-promise results, the end publish, and a REACTION FIBER that - * awaits the traced promise and publishes asyncStart/asyncEnd (error - * first on rejection) before settling the returned promise with - * the passed-through outcome. MAY THROW (the traced call and the - * synchronous publishes). */ - "dc.tcTracePromise": { - argTypes: [F64, DYN, DYN, DYN, DYN], - result: { kind: "promise", inner: DYN }, - }, - /** process.on/once('unhandledRejection', fn): registers a dyn listener - * the checkpoint report dispatches per never-observed rejection — - * (reason, promise) — instead of printing and exiting 1 (scr_async.c). - * The bool second arg is `once` (auto-removed after one delivery, - * Node's once); off/removeListener remove by closure identity, the - * offWarning stance. Throws Node's ERR_INVALID_ARG_TYPE on a - * non-function. */ - /** Uncaught exception handlers and monitors: [callback dyn, once, monitor]. */ - "process.onUncaughtException": { argTypes: [DYN, BOOL, BOOL], result: VOID }, - "process.offUncaughtException": { argTypes: [DYN, BOOL], result: VOID }, - "process.onUnhandledRejection": { argTypes: [DYN, BOOL], result: VOID }, - "process.offUnhandledRejection": { argTypes: [DYN], result: VOID }, - /** process.on/once/off('rejectionHandled', fn): the sibling registry. - * A handler attached after unhandledRejection delivery fires the event - * once. Dispatch is synchronous at the attach, with the promise, - * Node's payload. */ - "process.onRejectionHandled": { argTypes: [DYN, BOOL], result: VOID }, - "process.offRejectionHandled": { argTypes: [DYN], result: VOID }, - /** process warnings (scr_async_dyn.c): onWarning/offWarning register - * dyn listeners; emitWarning applies Node's full argument grammar over - * the call's dyn argument vector (ERR_INVALID_ARG_TYPE TypeErrors — MAY - * THROW) and emits on the next tick, like Node: Node's stderr report, - * then the listeners. */ - "process.onWarning": { argTypes: [DYN], result: VOID }, - "process.offWarning": { argTypes: [DYN], result: VOID }, - "process.emitWarning": { argTypes: [DYN], result: VOID }, - /** A Warning-typed process warning with this message (the console - * members' warnings), emitted on the next tick as Node's emitWarning - * does (so the loop runs); an empty message emits nothing. */ - "process.emitWarningText": { argTypes: [STRING], result: VOID }, - /** process.noDeprecation (0) / process.throwDeprecation (1): the stored - * value (undefined until assigned) and its assignment. util.deprecate's - * warnings and DeprecationWarning emissions read their truthiness. */ - "process.deprecationFlag": { argTypes: [F64], result: DYN }, - "process.setDeprecationFlag": { argTypes: [F64, DYN], result: VOID }, - "process.setEmitWarning": { argTypes: [DYN], result: VOID }, - /** AsyncLocalStorage (node:async_hooks — scr_async.c): stores are f64 - * handles; contexts are immutable fiber-carried snapshots (spawned - * fibers inherit the spawner's — Node's init-time capture). run/exit - * enter (or clear) the store, call the dyn function with forwarded - * arguments, and restore (the finally); getStore answers the current - * dyn value or undefined; enterWith installs with no restore point. - * run/exit MAY THROW (the callback's own throws propagate). */ - /** `await v` over a checked-dynamic VALUE (scr_async.c): a dyn promise - * adopts (rejections re-throw — MAY THROW), anything else takes JS's - * one-microtask non-thenable await and answers itself. Only emitted - * inside async bodies (the frontend's isAsync gate). */ - "async.awaitDyn": { argTypes: [DYN], result: DYN }, - /** The bare one-microtask hop (scr_await_hop): `await v` over a typed - * NON-promise value — JS awaits non-thenables through exactly one - * microtask turn and yields the value itself. Never throws. */ - "async.hop": { argTypes: [], result: VOID }, - "als.new": { argTypes: [], result: F64 }, - "als.get": { argTypes: [F64], result: DYN }, - "als.run": { argTypes: [F64, DYN, DYN, DYN], result: DYN }, - "als.exitRun": { argTypes: [F64, DYN, DYN], result: DYN }, - "als.enterWith": { argTypes: [F64, DYN], result: VOID }, - "als.disable": { argTypes: [F64], result: VOID }, - /** new AsyncLocalStorage(options) (Node's object check throws; MAY - * THROW) and the store's name option (dyn; "" without one). */ - "als.newOptions": { argTypes: [DYN], result: F64 }, - "als.name": { argTypes: [F64], result: DYN }, - /** A function bound to the active context: the completion callbacks of - * callback-style APIs (always false: an empty context leaves the - * callback unbound) and AsyncLocalStorage.bind (always true). The typed - * form answers its closure argument's type; the dyn form throws Node's - * ERR_INVALID_ARG_TYPE for a non-function when always (MAY THROW). */ - "als.bindCallback": { argTypes: [null, BOOL], result: VOID }, - "als.bindCallbackDyn": { argTypes: [DYN, BOOL], result: DYN }, - /** AsyncLocalStorage.snapshot(): a dyn function (cb, ...args) that runs - * cb in the context active at the snapshot. */ - "als.snapshot": { argTypes: [], result: DYN }, - /** Channel.bindStore/unbindStore/runStores (scr_dc.c): the - * AsyncLocalStorage integration — set-semantics bindings per store, - * runStores entering every bound store with transform(data) around the - * publish and the callback (MAY THROW: transforms, subscribers, and - * the callback all run). */ - "dc.chanBindStore": { argTypes: [F64, F64, DYN], result: VOID }, - "dc.chanUnbindStore": { argTypes: [F64, F64], result: BOOL }, - "dc.chanRunStores": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, - /** The Number statics with a static C implementation (scr_lib.c): one - * f64 arg → bool, JS-exact BY CONSTRUCTION — Number.isFinite/isNaN/ - * isInteger/isSafeInteger never coerce, and the frontend routes only - * f64-typed arguments here (other static types fence). None throws. */ - "number.isFinite": { argTypes: [F64], result: BOOL }, - "number.isNaN": { argTypes: [F64], result: BOOL }, - "number.isInteger": { argTypes: [F64], result: BOOL }, - "number.isSafeInteger": { argTypes: [F64], result: BOOL }, - /** Date (scr_lib.c). Values are TimeClip'd epoch-millisecond scalars: - * construction/store/pass/getters are exact while identity and mutation - * remain fenced. date.now is Node's integer milliseconds since epoch; - * date.toISOString formats one raw f64 millisecond time value and - * date.toISOStringValue formats a stored Date, both with Node's rules (UTC, - * YYYY-MM-DDTHH:mm:ss.sssZ, expanded ±YYYYYY years outside 0–9999, - * ToInteger truncation of fractional ms) and THROWS Node's "Invalid - * time value" RangeError on NaN / out-of-range input (may-throw seed). - * Results: f64 / owned (+1) string. */ - "date.now": { argTypes: [], result: F64 }, - "date.newNow": { argTypes: [], result: DATE_T }, - "date.newMs": { argTypes: [F64], result: DATE_T }, - "date.newString": { argTypes: [STRING], result: DATE_T }, - /** Date.parse(dateString), with its own reach witness for exact fences. */ - "date.parse": { argTypes: [STRING], result: F64 }, - "date.getTime": { argTypes: [DATE_T], result: F64 }, - "date.valueOf": { argTypes: [DATE_T], result: F64 }, - "date.toISOString": { argTypes: [F64], result: STRING }, - "date.toISOStringValue": { argTypes: [DATE_T], result: STRING }, - /** Date.prototype.toUTCString over a stored Date ("Invalid Date" for - * NaN; never throws). */ - "date.toUTCString": { argTypes: [DATE_T], result: STRING }, - "date.getFullYear": { argTypes: [DATE_T], result: F64 }, - "date.getUTCFullYear": { argTypes: [DATE_T], result: F64 }, - "date.getMonth": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMonth": { argTypes: [DATE_T], result: F64 }, - "date.getDate": { argTypes: [DATE_T], result: F64 }, - "date.getUTCDate": { argTypes: [DATE_T], result: F64 }, - "date.getDay": { argTypes: [DATE_T], result: F64 }, - "date.getUTCDay": { argTypes: [DATE_T], result: F64 }, - "date.getHours": { argTypes: [DATE_T], result: F64 }, - "date.getUTCHours": { argTypes: [DATE_T], result: F64 }, - "date.getMinutes": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMinutes": { argTypes: [DATE_T], result: F64 }, - "date.getSeconds": { argTypes: [DATE_T], result: F64 }, - "date.getUTCSeconds": { argTypes: [DATE_T], result: F64 }, - "date.getMilliseconds": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, - "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, - /** A Date object for a time value a local holds (date-values.ts): a - * lib-call argument passes the time value itself, and any other use - * creates the Date. */ - "date.fromTime": { argTypes: [F64], result: DATE_T }, - /** Date.prototype's field setters over a time value - * (scr_date_set_fields): time value, setter code, argument count and four - * arguments; answers the new time value. */ - "date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, - /** TimeClip of a number (setTime's argument). */ - "date.timeClip": { argTypes: [F64], result: F64 }, - /** Stores a time value into a Date object and answers it (a setter's - * write). */ - "date.store": { argTypes: [DATE_T, F64], result: F64 }, - /** Date.prototype.toString / toDateString / toTimeString and String(date): - * V8's local-time formats with the host zone's long name, "Invalid Date" - * for an invalid date. Owned (+1) string; never throws. */ - "date.toString": { argTypes: [DATE_T], result: STRING }, - "date.toDateString": { argTypes: [DATE_T], result: STRING }, - "date.toTimeString": { argTypes: [DATE_T], result: STRING }, - /** The composed `new Date(dateString).getTime()` read: one borrowed - * string, f64 milliseconds since epoch, with V8's date-string grammar - * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, - * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 - * rejects is NaN, Node's invalid-date getTime. Never throws. */ - "date.parseGetTime": { argTypes: [STRING], result: F64 }, - /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the - * spec's defaults for omitted trailing parts: month 0, date 1, time - * parts 0), the spec's MakeDay/MakeTime/TimeClip exactly: 0–99 years - * map to 1900+year, out-of-range months/dates roll over, non-finite - * parts and out-of-range results answer NaN. Never throws. */ - "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, - /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — - * the same seven-f64 completion as date.utc, but the fields are LOCAL - * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps - * read with the offset before the transition, overlaps answer the - * earlier instant), then TimeClip. Never throws. */ - "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, - /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with - * default options — the same maximal-subpart replacement decode as - * Buffer.toString("utf8"), with the leading BOM stripped (the one - * behavioral difference; ignoreBOM defaults to false). Inline calls - * lower directly; stored decoder records dispatch by encoding id. - * TextEncoder.encode needs no libFn: its matching forms - * lower to buffer.fromStr(s, "utf8") (identical bytes — both write each - * lone surrogate as U+FFFD). Borrowed arg; owned (+1) string; never throws. */ - "text.decode": { argTypes: [BYTES_U8], result: STRING }, - "bytes.bufferSource": { argTypes: [DYN], result: BYTES_U8 }, - /** Whether an error-first callback's Buffer result is absent: the zlib - * and crypto callback adapters pass NULL after an error. Borrowed arg; - * never throws. */ - "bytes.isAbsent": { argTypes: [BYTES_U8], result: BOOL }, - /** Whether two typed arrays share no byte: their element ranges in - * memory do not overlap. Borrowed args of any element kind; never throws. */ - "bytes.disjoint": { argTypes: [null, null], result: BOOL }, - /** The absent Buffer result itself (NULL), where an error-path callback - * body passes its undefined result on into a Buffer slot. Owned (+1); - * never throws. */ - "bytes.absent": { argTypes: [], result: BYTES_U8 }, - /** node:crypto asymmetric keys and signatures (scr_pki.c): checked- - * dynamic arguments, borrowed; KeyObject, Sign and Verify handles, - * Buffers and booleans as checked-dynamic results (+1). Node's argument - * and decoder errors throw (may-throw); generateKeyPairCb's callback - * receives (null, publicKey, privateKey) in a later macrotask. */ - "crypto.createPrivateKey": { argTypes: [DYN], result: DYN }, - "crypto.createPublicKey": { argTypes: [DYN], result: DYN }, - "crypto.generateKeyPairSync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeyPairCb": { argTypes: [DYN, DYN, DYN], result: VOID }, - "crypto.signOneshot": { argTypes: [DYN, DYN, DYN], result: DYN }, - "crypto.diffieHellman": { argTypes: [DYN, DYN], result: DYN }, - "crypto.verifyOneshot": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - "crypto.createSign": { argTypes: [DYN], result: DYN }, - "crypto.createVerify": { argTypes: [DYN], result: DYN }, - /** The Buffer brand of a Uint8Array source onto a fresh Uint8Array - * result (map and filter of a Buffer return a Buffer). */ - "bytes.copyBrand": { argTypes: [BYTES_U8, BYTES_U8], result: VOID }, - "text.decodeBufferSource": { argTypes: [DYN], result: STRING }, - "text.decodeOptions": { argTypes: [BYTES_U8, BOOL, BOOL], result: STRING }, - "text.decodeStream": { argTypes: [DYN, BYTES_U8, F64, BOOL, BOOL, BOOL], result: STRING }, - "text.decodeLegacyOptions": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: STRING }, - /** TextDecoder.decode for a compile-time non-UTF-8 WHATWG label. The - * second f64 is the frontend-owned encoding id consumed by scr_bytes.c; - * label aliases/case/ASCII whitespace are canonicalized before IR. The - * mapping tables are generated from the pinned Node 24 oracle, keeping - * output portable without an ICU/iconv dependency. Borrowed args; owned - * (+1) string; never throws. */ - "text.decodeLegacy": { argTypes: [BYTES_U8, F64], result: STRING }, - "text.decoderEncoding": { argTypes: [DYN], result: F64 }, - "text.decoderName": { argTypes: [F64], result: STRING }, - "text.encodeInto": { argTypes: [DYN, DYN], result: DYN }, - /** `value instanceof TextDecoder` (true) or `TextEncoder` (false) on a - * checked-dynamic value: a codec record's handle box (scr_text_codec_dyn.c). - * Borrowed; never throws. */ - "text.codecIs": { argTypes: [DYN, BOOL], result: BOOL }, - "buffer.isAscii": { argTypes: [DYN], result: BOOL }, - "buffer.isUtf8": { argTypes: [DYN], result: BOOL }, - "buffer.transcode": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - /** The fs option forms and friends (scr_lib.c), all throwing catchably - * like the rest of sync fs. mkdirRecursiveSync is Node's recursive - * algorithm (try mkdir, EEXIST-dir is fine, ENOENT creates the parent - * first — errors report Node's errno at Node's path, EEXIST at a file - * target, ENOTDIR at the full path past a file); the first-created-dir - * return value has no lowering (statement position only, frontend- - * fenced). rmOptsSync is rmSync with (recursive, force) bools: force - * swallows ENOENT, recursive removes trees post-order, a directory - * without recursive throws Node's ERR_FS_EISDIR SystemError. mkdtempSync appends the six X's and returns the - * created path (+1). accessSync takes the F_OK/R_OK/W_OK/X_OK bits as - * one f64 (the frontend bakes fs.constants.* as literals and completes - * an omitted mode to 0). readFdSync/readFdSyncBytes are the - * readFileSync(fd[, "utf8"]) forms — a read(2) loop to EOF on the fd - * (the stdin path); errors carry Node's no-path message shape. */ - "fs.mkdirRecursiveSync": { argTypes: [STRING], result: VOID }, - "fs.rmOptsSync": { argTypes: [STRING, BOOL, BOOL], result: VOID }, - /** rmOptsSync's maxRetries/retryDelay form (path, recursive, force, - * maxRetries, retryDelay): Node's linear-backoff retry on - * EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM — the tmpdir-harness shape - * rmSync(p, { maxRetries: 3, recursive: true, force: true }). Throws - * ERR_OUT_OF_RANGE for a retryDelay outside int32 >= 0 or a maxRetries - * outside uint32, before checking the path. */ - "fs.rmRetrySync": { argTypes: [STRING, BOOL, BOOL, F64, F64], result: VOID }, - "fs.mkdtempSync": { argTypes: [STRING], result: STRING }, - "fs.accessSync": { argTypes: [STRING, F64], result: VOID }, - "fs.readFdSync": { argTypes: [F64, STRING], result: STRING }, - "fs.readFdSyncBytes": { argTypes: [F64], result: BYTES_U8 }, - /** Validated isatty(3) probe. Stream property lowering maps false to - * undefined; tty.isatty exposes the boolean result directly. */ - "process.isTTY": { argTypes: [F64], result: BOOL }, - // Like process.envGet: the result is the module's interned - // `number | undefined` union — checked by arms in the libCall case. - /** Terminal width over an fd literal (1/2 — process.stdout/stderr - * .columns): ioctl(TIOCGWINSZ). The result is the module's interned - * `number | undefined` union — a non-TTY stream (or an ioctl refusal) - * yields the undefined arm, exactly Node's missing `.columns`. Never - * throws. */ - "process.columns": { argTypes: [F64], result: VOID }, - /** Terminal height, with the same fd and optional-number contract. */ - "process.rows": { argTypes: [F64], result: VOID }, - /** process.stdin.destroy(): a deliberate no-op (no stream machinery - * exists to tear down; documented in SEMANTICS.md). */ - "process.stdinDestroy": { argTypes: [], result: VOID }, - /** process.stdin.setRawMode(mode) — one borrowed bool arg, void. On a - * TTY stdin: termios raw mode on/off (libuv's UV_TTY_MODE_RAW flag set, - * the mode Node's setRawMode(true) applies; false restores the entry - * state). On a NON-TTY stdin Node's process.stdin has no such method at - * all, so the call throws Node's exact catchable TypeError - * ("process.stdin.setRawMode is not a function") — the may-throw seed - * carries it. */ - "process.stdinSetRawMode": { argTypes: [BOOL], result: VOID }, - // Arg 0 is a packed f64[] OR a bytes value (the spread-typed-array - // form) — checked in the libCall case. - /** fromCharCode takes one packed f64[] or bytes arg and builds a string - * from UTF-16 code units. Adjacent surrogate pairs combine; lone - * surrogates follow the runtime's replacement policy. */ - "string.fromCharCode": { argTypes: [null], result: STRING }, - /** Numeric code points, with catchable RangeError for non-integers or - * values outside 0..0x10ffff. Uses the same UTF-8 surrogate policy. */ - "string.fromCodePoint": { argTypes: [null], result: STRING }, - /** lastIndexOf returns the last UTF-16 start index, or -1. The two-arg - * form searches at or before its numeric position; NaN starts at the end. - * String arguments are borrowed and neither form throws. */ - "string.lastIndexOf": { argTypes: [STRING, STRING], result: F64 }, - "string.lastIndexOfFrom": { argTypes: [STRING, STRING, F64], result: F64 }, - /** String.raw(template, ...subs): the raw literals array (a string[] - * read off the template record) interleaved with the PRE-STRINGIFIED - * substitutions (the frontend applies the static ToString and packs - * them into one string[] literal) — extra substitutions drop, missing - * ones skip, the spec's loop exactly (scr_array.c). Borrowed args; - * +1 string; never throws. */ - "string.raw": { argTypes: [arrayOf(STRING), arrayOf(STRING)], result: STRING }, -}); + "insp.f64": { argTypes: [F64], result: STRING }, + /** util.format %j over a checked-dynamic argument: the runtime dyn + * walk (JS-exact stringify; root undefined/function prints + * "undefined"; a handle in the tree throws the "not supported yet" + * Error). */ + "insp.jsonDyn": { argTypes: [DYN], result: STRING }, + "insp.str": { argTypes: [STRING], result: STRING }, + "insp.regex": { argTypes: [REGEX], result: STRING }, + /** A Date's inspect text: toISOString(), or "Invalid Date". */ + "insp.date": { argTypes: [DATE_T], result: STRING }, + "insp.buffer": { argTypes: [BYTES_U8], result: STRING }, + /** A numeric typed array that is not a Buffer (any element kind). */ + "insp.typedArray": { argTypes: [null, F64, F64], result: STRING }, + /** A u8 byte view in a union arm, element or field: a DataView, Buffer + * or Uint8Array by its own flags. */ + "insp.bytes": { argTypes: [null, F64, F64], result: STRING }, + "insp.error": { argTypes: [null, F64, F64], result: STRING }, + "insp.errorHasCause": { argTypes: [null], result: BOOL }, + "insp.errorBagHasProperties": { argTypes: [DYN], result: BOOL }, + "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, + "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, + "insp.dynS": { argTypes: [DYN, F64], result: STRING }, + /** util.format's %o over a checked-dynamic value: inspect with + * showHidden. Kinds whose hidden entries have no rendering throw the + * 'not supported yet' Error. */ + "insp.dynHidden": { argTypes: [DYN, F64], result: STRING }, + "insp.begin": { argTypes: [F64], result: VOID }, + /** util.inspect's breakLength, compact (0 for false) and sorted for the + * renders that follow; the call site restores 80, 3 and false afterwards. */ + "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, + "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, + // Circular references: the receiver slot is program-dependent (any + // cycle-capable record/array/map/class type — the insp.error precedent). + /* Circular references over cycle-capable composites (recursive record/ + * class types): circCheck answers a value's circular id when it is + * already on the traversal stack (0 otherwise), seenPush/refWrap + * bracket the frame (insp.end adds Node's " " prefix to values + * the walk found circular), circular renders "[Circular *N]". */ + "insp.circCheck": { argTypes: [null], result: F64 }, + "insp.seenPush": { argTypes: [null], result: VOID }, + "insp.refWrap": { argTypes: [null, STRING], result: STRING }, + "insp.circular": { argTypes: [F64], result: STRING }, + /* Whether a closed declared-field record kept the undeclared properties + * of its checked source (scr_record_source_note): reflective surfaces + * (inspect, Object.keys/values/entries) then answer through its live + * checked view. The receiver is any such record type. Never throws. */ + "record.sourceHas": { argTypes: [null], result: BOOL }, + /** `record.sourceSpread(built, value)`: the record a literal starting + * with `...value` just built keeps the order of value's kept source + * (key-order-witness.ts ordered spreads). Both are record types. Never + * throws. */ + "record.sourceSpread": { argTypes: [null, null], result: VOID }, + "insp.key": { argTypes: [STRING], result: STRING }, + "insp.moreItems": { argTypes: [F64], result: STRING }, + "insp.end": { argTypes: [STRING, STRING, STRING, F64, BOOL, BOOL], result: STRING }, + /** A nested inspect inside a custom rendering (PerformanceEntry's + * `${name} ${inspect(toJSON)}`): nestedBegin starts a fresh inspect + * context, nestedEnd(prefix, inner) restores the caller's and answers + * `prefix inner` re-indented at the caller's level (+1). */ + "insp.nestedBegin": { argTypes: [], result: VOID }, + "insp.nestedEnd": { argTypes: [STRING, STRING], result: STRING }, + /** Around a top-level render that an inspection refusal can end: mark + * records the inspector's frame and nesting depth, unwind (in the + * render's finally) closes what the render left open above it. */ + "insp.mark": { argTypes: [], result: VOID }, + "insp.unwind": { argTypes: [], result: VOID }, + /** node:string_decoder's utf8 StringDecoder (scr_string.c): the decoder + * value is a one-field record whose f64 PACKS the pending partial + * sequence (count + up to 3 raw bytes — Node buffers at most 3 for + * every encoding); the frontend's interned %strdec helpers thread it, + * with the decoder's CANONICAL encoding name first, through these pure + * functions. write: (enc, pending, chunk) → the decoded complete + * prefix of pending+chunk (+1); next: (enc, pending, chunk) → the + * packed NEW pending; end: (enc, pending) → the buffered partial's + * flush (+1). Node-exact per encoding (oracle-pinned); none throws. */ + "strdec.write": { argTypes: [STRING, F64, BYTES_U8], result: STRING }, + "strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 }, + "strdec.end": { argTypes: [STRING, F64], result: STRING }, + /** A runtime encoding argument's canonical name (Node's normalizeEncoding: + * nullish and "" are utf8); an unknown name throws ERR_UNKNOWN_ENCODING. */ + "strdec.encoding": { argTypes: [DYN], result: STRING }, + // node:readline: the interface handle is f64; the callbacks' func types + // are program-dependent (the emitter picks the adapter by parameter + // count), so those slots are null like child.onExit's. + /** node:readline and node:readline/promises (scr_readline.c, linked + * under the events gate — these fns imply moduleUsesProcessEvents; a + * stream input or output adds scr_stream.c's bridge). The interface + * value is an f64 handle (the Timeout-id precedent); an open interface + * on stdin keeps the loop alive until close/EOF. + * rl.create: [mode, input?, output?, crlfDelay, prompt?] → f64, mode + * bits 1 stream input, 2 stream output, 4 stdout output, 16 prompt + * (lowerReadlineCreateCall); rl.createSignal: the same plus the + * trailing AbortSignal (mode bit 8), which links the event-target unit + * for addAbortListener. rl.question: [handle, query, cb] — the callback + * form; THROWS ERR_USE_AFTER_CLOSE on a closed interface (may-throw). + * rl.questionAsync(Signal): [handle, query, signal?] → promise, + * rejecting instead of throwing. rl.close/pause/resume/prompt: + * [handle] (pause/resume/prompt throw ERR_USE_AFTER_CLOSE when + * closed); rl.setPrompt/getPrompt; rl.write: [handle, data] (throws + * when closed). rl.on: [handle, event, cb, once] — the event number + * (0 'line', 1 'close', 2 'pause', 3 'resume', 4 'error') and the + * listener (moves). rl.iterOpen/iterNext/iterReturn: the async + * iterator — iterNext answers promise of the line or undefined. */ + "rl.create": { argTypes: [F64], result: F64 }, + "rl.createSignal": { argTypes: [F64], result: F64 }, + "rl.question": { argTypes: [F64, STRING, null], result: VOID }, + "rl.questionAsync": { argTypes: [F64, STRING], result: { kind: "promise", inner: STRING } }, + "rl.questionAsyncSignal": { + argTypes: [F64, STRING, DYN], + result: { kind: "promise", inner: STRING }, + }, + /** The question forms with a checked-dynamic query (a JavaScript value + * that need not be a string). */ + "rl.questionDyn": { argTypes: [F64, DYN, null], result: VOID }, + "rl.questionAsyncDyn": { argTypes: [F64, DYN], result: { kind: "promise", inner: STRING } }, + "rl.questionAsyncSignalDyn": { + argTypes: [F64, DYN, DYN], + result: { kind: "promise", inner: STRING }, + }, + "rl.close": { argTypes: [F64], result: VOID }, + "rl.pause": { argTypes: [F64], result: VOID }, + "rl.resume": { argTypes: [F64], result: VOID }, + "rl.prompt": { argTypes: [F64], result: VOID }, + /** prompt(preserveCursor): [handle, preserveCursor] */ + "rl.promptWith": { argTypes: [F64, BOOL], result: VOID }, + "rl.setPrompt": { argTypes: [F64, STRING], result: VOID }, + "rl.setPromptDyn": { argTypes: [F64, DYN], result: VOID }, + "rl.getPrompt": { argTypes: [F64], result: STRING }, + "rl.write": { argTypes: [F64, STRING], result: VOID }, + /** write(data, key): [handle, hasData, data, hasKey, hasName, name, + * ctrl, meta, shift] */ + "rl.writeKey": { + argTypes: [F64, BOOL, STRING, BOOL, BOOL, STRING, BOOL, BOOL, BOOL], + result: VOID, + }, + "rl.on": { argTypes: [F64, F64, null, BOOL], result: VOID }, + "rl.iterOpen": { argTypes: [F64], result: VOID }, + "rl.iterNext": { argTypes: [F64], result: { kind: "promise", inner: DYN } }, + "rl.iterReturn": { argTypes: [F64], result: VOID }, + /** createInterface's history and editing options (scr_rl_check_options: + * [flags, tabSize, escapeCodeTimeout, historySize], may throw; + * rl.setOptions: [handle, flags, historySize, removeHistoryDuplicates, + * tabSize, escapeCodeTimeout]; rl.setHistory: [handle, history]) and + * the member reads (rl.history answers the array, rl.numProp the + * numeric member by number). */ + "rl.checkOptions": { argTypes: [F64, F64, F64, F64], result: VOID }, + "rl.checkCompleter": { argTypes: [DYN], result: VOID }, + "rl.setOptions": { argTypes: [F64, F64, F64, BOOL, F64, F64], result: VOID }, + "rl.setHistory": { argTypes: [F64, arrayOf(STRING)], result: VOID }, + "rl.history": { argTypes: [F64], result: arrayOf(STRING) }, + "rl.numProp": { argTypes: [F64, F64], result: F64 }, + /** Terminal mode (scr_readline_tty.c): rl.terminal: [handle, mode (0 + * the option's value, 2 output.isTTY of process.stdout), terminal, + * completer (undefined when absent), readline/promises], emitted with + * scr_insp_dyn when there is a completer; the line, terminal and cursor + * members. */ + "rl.terminal": { argTypes: [F64, F64, BOOL, DYN, BOOL], result: VOID }, + "rl.line": { argTypes: [F64], result: STRING }, + "rl.isTerminal": { argTypes: [F64], result: BOOL }, + "rl.isPromises": { argTypes: [F64], result: BOOL }, + /** createInterface's input or output as a checked-dynamic value: + * [value, output] — the stream; refused when it is not one. */ + "rl.inputFromDyn": { + argTypes: [DYN, BOOL], + result: { kind: "object", className: "%Readable" }, + }, + "rl.outputFromDyn": { + argTypes: [DYN, BOOL], + result: { kind: "object", className: "%Writable" }, + }, + "rl.cursor": { argTypes: [F64], result: F64 }, + /** readline.cursorTo / moveCursor / clearLine / clearScreenDown: rl.seq + * [kind (0-3), the first and second numbers, whether the second is + * given] answers the data to write after the argument checks (may + * throw); rl.moves [dx, dy] says whether moveCursor writes. */ + "rl.seq": { argTypes: [F64, F64, F64, BOOL], result: STRING }, + "rl.moves": { argTypes: [F64, F64], result: BOOL }, + /** getCursorPos() (scr_readline_tty.c, linked through the regex unit + * like rl.terminal): [handle, 0 rows | 1 cols] */ + "rl.cursorPos": { argTypes: [F64, F64], result: F64 }, + /** readline.emitKeypressEvents (scr_readline_tty.c): rl.keypressStdin + * [interface (0: none), escapeCodeTimeout (NaN: the interface's or + * 500)], rl.keypressStream [stream, interface, timeout]. */ + "rl.keypressStdin": { argTypes: [F64, F64], result: VOID }, + "rl.keypressStream": { argTypes: [null, F64, F64], result: VOID }, + /** readline/promises Readline over an f64 handle: rl.uiOpen [fd (1 + * stdout, 2 stderr), autoCommit], rl.uiOpenStream [stream, autoCommit] + * (isWritable, may throw); the actions take [handle, ...numbers] (the + * checks may throw; cursorTo: [handle, x, whether y is given, y]); + * rl.uiCommit answers a promise. */ + "rl.uiOpen": { argTypes: [F64, BOOL], result: F64 }, + "rl.uiOpenStream": { argTypes: [null, BOOL], result: F64 }, + "rl.uiCursorTo": { argTypes: [F64, F64, BOOL, F64], result: VOID }, + "rl.uiMoveCursor": { argTypes: [F64, F64, F64], result: VOID }, + "rl.uiClearLine": { argTypes: [F64, F64], result: VOID }, + "rl.uiClearScreenDown": { argTypes: [F64], result: VOID }, + "rl.uiRollback": { argTypes: [F64], result: VOID }, + "rl.uiCommit": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + /** node:timers/promises — the promisified pair (scr_async.c, beside + * the timer heap they ride): tp.setTimeout: [ms] → a pending void + * promise a one-shot heap timer fulfills (the loop's timer phase, FIFO + * against equal deadlines like Node); tp.setImmediate: [] → the same + * through the immediate queue (fires before due timers of later loop + * turns, Node's check phase). Neither throws. */ + "tp.setTimeout": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + // scheduler.wait(delay): tp.setTimeout's promise. It throws Node's + // ERR_INVALID_THIS once mock.timers has restored the real scheduler.wait. + "tp.schedulerWait": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "tp.setImmediate": { argTypes: [], result: { kind: "promise", inner: VOID } }, + /** events.once (scr_events_emitter.c): [emitter, name, boxer, + * errorBoxer, listenForError] → a pending promise of any[]; the boxers + * turn the event's and the 'error' tuple into that array. */ + "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): + * events.once's arguments plus the checked-dynamic signal (an + * AbortSignal or undefined). */ + "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, + /** The resolve-value forms: [ms?, value, ref] → a pending promise of the + * value's type that copies the borrowed already-resolved `value` promise + * when the timer (or immediate) fires; ref false unrefs it. The result + * type follows the value promise. */ + "tp.setTimeoutValue": { argTypes: [F64, null, BOOL], result: VOID }, + "tp.setImmediateValue": { argTypes: [null, BOOL], result: VOID }, + /** The same with options.signal (a checked-dynamic AbortSignal or + * undefined) last: an abort clears the timer and rejects with an + * AbortError (scr_event_target.c). */ + "tp.setTimeoutSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, + "tp.setImmediateSignal": { argTypes: [null, BOOL, DYN], result: VOID }, + /** scheduler.wait(delay, options): tp.setTimeoutValue's and + * tp.setTimeoutSignal's shapes, with tp.schedulerWait's throws. */ + "tp.schedulerWaitValue": { argTypes: [F64, null, BOOL], result: VOID }, + "tp.schedulerWaitSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, + /** node:diagnostics_channel (scr_dc.c, linked when any dc.* appears — + * the zlib gating precedent): a process-global name→channel registry; + * channel values are f64 handles (type-mapper.ts maps Channel to F64, the + * readline.Interface pattern). Subscribers are dyn function values — + * identity-compared by unsubscribe, called (message, name) by publish + * over a SNAPSHOT of the list (a subscriber unsubscribing itself + * mid-publish still lets its siblings fire, Node's behavior). dc.publish + * MAY THROW: a subscriber's throw propagates out of publish (catchable + * there) where Node routes it to triggerUncaughtException — the + * documented divergence. subscribe/unsubscribe throw Node's + * ERR_INVALID_ARG_TYPE TypeError for non-function subscribers. */ + "dc.channel": { argTypes: [STRING], result: F64 }, + "dc.subscribe": { argTypes: [STRING, DYN], result: VOID }, + "dc.unsubscribe": { argTypes: [STRING, DYN], result: BOOL }, + "dc.hasSubscribers": { argTypes: [STRING], result: BOOL }, + "dc.publish": { argTypes: [F64, DYN], result: VOID }, + "dc.chanSubscribe": { argTypes: [F64, DYN], result: VOID }, + "dc.chanUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, + "dc.chanHasSubscribers": { argTypes: [F64], result: BOOL }, + "dc.chanName": { argTypes: [F64], result: STRING }, + /** TracingChannel (dc.tracingChannel): a registry entry of the five + * event channels, an f64 handle like Channel (type-mapper.ts). tcSubscribe/ + * tcUnsubscribe walk a dyn handlers object's five event keys (truthy + * non-function slots throw the per-channel ERR_INVALID_ARG_TYPE); + * tcTraceSync/tcTraceCallback/tcTracePromise run Node's publish choreography in C over + * dyn values (fn, ctx, thisArg, args-array) with thisArg bound as the + * ambient receiver — the traced call's throw and any subscriber throw + * both propagate (MAY THROW). tcTraceCallback wraps args[position] in a + * native error/result + asyncStart/asyncEnd publisher and throws Node's + * TypeError when that slot is not callable. tracingChannelOf is the + * five-Channel collection form of the constructor. */ + /** setImmediate as a first-class dyn value (scr_async.c): a minted dyn + * callable scheduling args[0](args[1..]) on the immediate queue — the + * Node-suite traceCallback shape (`traceCallback(setImmediate, ...)`). + * Calling it validates the callback (the dyn call machinery); minting + * never throws. */ + "timers.setImmediateFnValue": { argTypes: [], result: DYN }, + /** `new Promise(setImmediate)` (the Node-suite early-exit shape): a + * fresh promise an immediate fulfills with the undefined dyn value — + * the executor IS setImmediate, so resolve rides the immediate queue + * (scr_async.c). Never throws. */ + "timers.immediatePromise": { argTypes: [], result: { kind: "promise", inner: DYN } }, + "dc.tracingChannel": { argTypes: [STRING], result: F64 }, + "dc.tracingChannelOf": { argTypes: [F64, F64, F64, F64, F64], result: F64 }, + "dc.tcChannel": { argTypes: [F64, F64], result: F64 }, + "dc.tcHasSubscribers": { argTypes: [F64], result: BOOL }, + "dc.tcSubscribe": { argTypes: [F64, DYN], result: VOID }, + "dc.tcUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, + "dc.tcTraceSync": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, + "dc.tcTraceCallback": { argTypes: [F64, DYN, F64, DYN, DYN, DYN], result: DYN }, + /** tracePromise (scr_dc.c): start publish, the traced call, a wrap of + * non-promise results, the end publish, and a REACTION FIBER that + * awaits the traced promise and publishes asyncStart/asyncEnd (error + * first on rejection) before settling the returned promise with + * the passed-through outcome. MAY THROW (the traced call and the + * synchronous publishes). */ + "dc.tcTracePromise": { + argTypes: [F64, DYN, DYN, DYN, DYN], + result: { kind: "promise", inner: DYN }, + }, + /** process.on/once('unhandledRejection', fn): registers a dyn listener + * the checkpoint report dispatches per never-observed rejection — + * (reason, promise) — instead of printing and exiting 1 (scr_async.c). + * The bool second arg is `once` (auto-removed after one delivery, + * Node's once); off/removeListener remove by closure identity, the + * offWarning stance. Throws Node's ERR_INVALID_ARG_TYPE on a + * non-function. */ + /** Uncaught exception handlers and monitors: [callback dyn, once, monitor]. */ + "process.onUncaughtException": { argTypes: [DYN, BOOL, BOOL], result: VOID }, + "process.offUncaughtException": { argTypes: [DYN, BOOL], result: VOID }, + "process.onUnhandledRejection": { argTypes: [DYN, BOOL], result: VOID }, + "process.offUnhandledRejection": { argTypes: [DYN], result: VOID }, + /** process.on/once/off('rejectionHandled', fn): the sibling registry. + * A handler attached after unhandledRejection delivery fires the event + * once. Dispatch is synchronous at the attach, with the promise, + * Node's payload. */ + "process.onRejectionHandled": { argTypes: [DYN, BOOL], result: VOID }, + "process.offRejectionHandled": { argTypes: [DYN], result: VOID }, + /** process warnings (scr_async_dyn.c): onWarning/offWarning register + * dyn listeners; emitWarning applies Node's full argument grammar over + * the call's dyn argument vector (ERR_INVALID_ARG_TYPE TypeErrors — MAY + * THROW) and emits on the next tick, like Node: Node's stderr report, + * then the listeners. */ + "process.onWarning": { argTypes: [DYN], result: VOID }, + "process.offWarning": { argTypes: [DYN], result: VOID }, + "process.emitWarning": { argTypes: [DYN], result: VOID }, + /** A Warning-typed process warning with this message (the console + * members' warnings), emitted on the next tick as Node's emitWarning + * does (so the loop runs); an empty message emits nothing. */ + "process.emitWarningText": { argTypes: [STRING], result: VOID }, + /** process.noDeprecation (0) / process.throwDeprecation (1): the stored + * value (undefined until assigned) and its assignment. util.deprecate's + * warnings and DeprecationWarning emissions read their truthiness. */ + "process.deprecationFlag": { argTypes: [F64], result: DYN }, + "process.setDeprecationFlag": { argTypes: [F64, DYN], result: VOID }, + "process.setEmitWarning": { argTypes: [DYN], result: VOID }, + /** AsyncLocalStorage (node:async_hooks — scr_async.c): stores are f64 + * handles; contexts are immutable fiber-carried snapshots (spawned + * fibers inherit the spawner's — Node's init-time capture). run/exit + * enter (or clear) the store, call the dyn function with forwarded + * arguments, and restore (the finally); getStore answers the current + * dyn value or undefined; enterWith installs with no restore point. + * run/exit MAY THROW (the callback's own throws propagate). */ + /** `await v` over a checked-dynamic VALUE (scr_async.c): a dyn promise + * adopts (rejections re-throw — MAY THROW), anything else takes JS's + * one-microtask non-thenable await and answers itself. Only emitted + * inside async bodies (the frontend's isAsync gate). */ + "async.awaitDyn": { argTypes: [DYN], result: DYN }, + /** The bare one-microtask hop (scr_await_hop): `await v` over a typed + * NON-promise value — JS awaits non-thenables through exactly one + * microtask turn and yields the value itself. Never throws. */ + "async.hop": { argTypes: [], result: VOID }, + "als.new": { argTypes: [], result: F64 }, + "als.get": { argTypes: [F64], result: DYN }, + "als.run": { argTypes: [F64, DYN, DYN, DYN], result: DYN }, + "als.exitRun": { argTypes: [F64, DYN, DYN], result: DYN }, + "als.enterWith": { argTypes: [F64, DYN], result: VOID }, + "als.disable": { argTypes: [F64], result: VOID }, + /** new AsyncLocalStorage(options) (Node's object check throws; MAY + * THROW) and the store's name option (dyn; "" without one). */ + "als.newOptions": { argTypes: [DYN], result: F64 }, + "als.name": { argTypes: [F64], result: DYN }, + /** A function bound to the active context: the completion callbacks of + * callback-style APIs (always false: an empty context leaves the + * callback unbound) and AsyncLocalStorage.bind (always true). The typed + * form answers its closure argument's type; the dyn form throws Node's + * ERR_INVALID_ARG_TYPE for a non-function when always (MAY THROW). */ + "als.bindCallback": { argTypes: [null, BOOL], result: VOID }, + "als.bindCallbackDyn": { argTypes: [DYN, BOOL], result: DYN }, + /** AsyncLocalStorage.snapshot(): a dyn function (cb, ...args) that runs + * cb in the context active at the snapshot. */ + "als.snapshot": { argTypes: [], result: DYN }, + /** Channel.bindStore/unbindStore/runStores (scr_dc.c): the + * AsyncLocalStorage integration — set-semantics bindings per store, + * runStores entering every bound store with transform(data) around the + * publish and the callback (MAY THROW: transforms, subscribers, and + * the callback all run). */ + "dc.chanBindStore": { argTypes: [F64, F64, DYN], result: VOID }, + "dc.chanUnbindStore": { argTypes: [F64, F64], result: BOOL }, + "dc.chanRunStores": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, + /** The Number statics with a static C implementation (scr_lib.c): one + * f64 arg → bool, JS-exact BY CONSTRUCTION — Number.isFinite/isNaN/ + * isInteger/isSafeInteger never coerce, and the frontend routes only + * f64-typed arguments here (other static types fence). None throws. */ + "number.isFinite": { argTypes: [F64], result: BOOL }, + "number.isNaN": { argTypes: [F64], result: BOOL }, + "number.isInteger": { argTypes: [F64], result: BOOL }, + "number.isSafeInteger": { argTypes: [F64], result: BOOL }, + /** Date (scr_lib.c). Values are TimeClip'd epoch-millisecond scalars: + * construction/store/pass/getters are exact while identity and mutation + * remain fenced. date.now is Node's integer milliseconds since epoch; + * date.toISOString formats one raw f64 millisecond time value and + * date.toISOStringValue formats a stored Date, both with Node's rules (UTC, + * YYYY-MM-DDTHH:mm:ss.sssZ, expanded ±YYYYYY years outside 0–9999, + * ToInteger truncation of fractional ms) and THROWS Node's "Invalid + * time value" RangeError on NaN / out-of-range input (may-throw seed). + * Results: f64 / owned (+1) string. */ + "date.now": { argTypes: [], result: F64 }, + "date.newNow": { argTypes: [], result: DATE_T }, + "date.newMs": { argTypes: [F64], result: DATE_T }, + "date.newString": { argTypes: [STRING], result: DATE_T }, + /** Date.parse(dateString), with its own reach witness for exact fences. */ + "date.parse": { argTypes: [STRING], result: F64 }, + "date.getTime": { argTypes: [DATE_T], result: F64 }, + "date.valueOf": { argTypes: [DATE_T], result: F64 }, + "date.toISOString": { argTypes: [F64], result: STRING }, + "date.toISOStringValue": { argTypes: [DATE_T], result: STRING }, + /** Date.prototype.toUTCString over a stored Date ("Invalid Date" for + * NaN; never throws). */ + "date.toUTCString": { argTypes: [DATE_T], result: STRING }, + "date.getFullYear": { argTypes: [DATE_T], result: F64 }, + "date.getUTCFullYear": { argTypes: [DATE_T], result: F64 }, + "date.getMonth": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMonth": { argTypes: [DATE_T], result: F64 }, + "date.getDate": { argTypes: [DATE_T], result: F64 }, + "date.getUTCDate": { argTypes: [DATE_T], result: F64 }, + "date.getDay": { argTypes: [DATE_T], result: F64 }, + "date.getUTCDay": { argTypes: [DATE_T], result: F64 }, + "date.getHours": { argTypes: [DATE_T], result: F64 }, + "date.getUTCHours": { argTypes: [DATE_T], result: F64 }, + "date.getMinutes": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMinutes": { argTypes: [DATE_T], result: F64 }, + "date.getSeconds": { argTypes: [DATE_T], result: F64 }, + "date.getUTCSeconds": { argTypes: [DATE_T], result: F64 }, + "date.getMilliseconds": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, + "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, + /** A Date object for a time value a local holds (date-values.ts): a + * lib-call argument passes the time value itself, and any other use + * creates the Date. */ + "date.fromTime": { argTypes: [F64], result: DATE_T }, + /** Date.prototype's field setters over a time value + * (scr_date_set_fields): time value, setter code, argument count and four + * arguments; answers the new time value. */ + "date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** TimeClip of a number (setTime's argument). */ + "date.timeClip": { argTypes: [F64], result: F64 }, + /** Stores a time value into a Date object and answers it (a setter's + * write). */ + "date.store": { argTypes: [DATE_T, F64], result: F64 }, + /** Date.prototype.toString / toDateString / toTimeString and String(date): + * V8's local-time formats with the host zone's long name, "Invalid Date" + * for an invalid date. Owned (+1) string; never throws. */ + "date.toString": { argTypes: [DATE_T], result: STRING }, + "date.toDateString": { argTypes: [DATE_T], result: STRING }, + "date.toTimeString": { argTypes: [DATE_T], result: STRING }, + /** The composed `new Date(dateString).getTime()` read: one borrowed + * string, f64 milliseconds since epoch, with V8's date-string grammar + * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, + * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 + * rejects is NaN, Node's invalid-date getTime. Never throws. */ + "date.parseGetTime": { argTypes: [STRING], result: F64 }, + /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the + * spec's defaults for omitted trailing parts: month 0, date 1, time + * parts 0), the spec's MakeDay/MakeTime/TimeClip exactly: 0–99 years + * map to 1900+year, out-of-range months/dates roll over, non-finite + * parts and out-of-range results answer NaN. Never throws. */ + "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — + * the same seven-f64 completion as date.utc, but the fields are LOCAL + * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps + * read with the offset before the transition, overlaps answer the + * earlier instant), then TimeClip. Never throws. */ + "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, + /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with + * default options — the same maximal-subpart replacement decode as + * Buffer.toString("utf8"), with the leading BOM stripped (the one + * behavioral difference; ignoreBOM defaults to false). Inline calls + * lower directly; stored decoder records dispatch by encoding id. + * TextEncoder.encode needs no libFn: its matching forms + * lower to buffer.fromStr(s, "utf8") (identical bytes — both write each + * lone surrogate as U+FFFD). Borrowed arg; owned (+1) string; never throws. */ + "text.decode": { argTypes: [BYTES_U8], result: STRING }, + "bytes.bufferSource": { argTypes: [DYN], result: BYTES_U8 }, + /** Whether an error-first callback's Buffer result is absent: the zlib + * and crypto callback adapters pass NULL after an error. Borrowed arg; + * never throws. */ + "bytes.isAbsent": { argTypes: [BYTES_U8], result: BOOL }, + /** Whether two typed arrays share no byte: their element ranges in + * memory do not overlap. Borrowed args of any element kind; never throws. */ + "bytes.disjoint": { argTypes: [null, null], result: BOOL }, + /** The absent Buffer result itself (NULL), where an error-path callback + * body passes its undefined result on into a Buffer slot. Owned (+1); + * never throws. */ + "bytes.absent": { argTypes: [], result: BYTES_U8 }, + /** node:crypto asymmetric keys and signatures (scr_pki.c): checked- + * dynamic arguments, borrowed; KeyObject, Sign and Verify handles, + * Buffers and booleans as checked-dynamic results (+1). Node's argument + * and decoder errors throw (may-throw); generateKeyPairCb's callback + * receives (null, publicKey, privateKey) in a later macrotask. */ + "crypto.createPrivateKey": { argTypes: [DYN], result: DYN }, + "crypto.createPublicKey": { argTypes: [DYN], result: DYN }, + "crypto.generateKeyPairSync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeyPairCb": { argTypes: [DYN, DYN, DYN], result: VOID }, + "crypto.signOneshot": { argTypes: [DYN, DYN, DYN], result: DYN }, + "crypto.diffieHellman": { argTypes: [DYN, DYN], result: DYN }, + "crypto.verifyOneshot": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + "crypto.createSign": { argTypes: [DYN], result: DYN }, + "crypto.createVerify": { argTypes: [DYN], result: DYN }, + /** The Buffer brand of a Uint8Array source onto a fresh Uint8Array + * result (map and filter of a Buffer return a Buffer). */ + "bytes.copyBrand": { argTypes: [BYTES_U8, BYTES_U8], result: VOID }, + "text.decodeBufferSource": { argTypes: [DYN], result: STRING }, + "text.decodeOptions": { argTypes: [BYTES_U8, BOOL, BOOL], result: STRING }, + "text.decodeStream": { argTypes: [DYN, BYTES_U8, F64, BOOL, BOOL, BOOL], result: STRING }, + "text.decodeLegacyOptions": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: STRING }, + /** TextDecoder.decode for a compile-time non-UTF-8 WHATWG label. The + * second f64 is the frontend-owned encoding id consumed by scr_bytes.c; + * label aliases/case/ASCII whitespace are canonicalized before IR. The + * mapping tables are generated from the pinned Node 24 oracle, keeping + * output portable without an ICU/iconv dependency. Borrowed args; owned + * (+1) string; never throws. */ + "text.decodeLegacy": { argTypes: [BYTES_U8, F64], result: STRING }, + "text.decoderEncoding": { argTypes: [DYN], result: F64 }, + "text.decoderName": { argTypes: [F64], result: STRING }, + "text.encodeInto": { argTypes: [DYN, DYN], result: DYN }, + /** `value instanceof TextDecoder` (true) or `TextEncoder` (false) on a + * checked-dynamic value: a codec record's handle box (scr_text_codec_dyn.c). + * Borrowed; never throws. */ + "text.codecIs": { argTypes: [DYN, BOOL], result: BOOL }, + "buffer.isAscii": { argTypes: [DYN], result: BOOL }, + "buffer.isUtf8": { argTypes: [DYN], result: BOOL }, + "buffer.transcode": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + /** The fs option forms and friends (scr_lib.c), all throwing catchably + * like the rest of sync fs. mkdirRecursiveSync is Node's recursive + * algorithm (try mkdir, EEXIST-dir is fine, ENOENT creates the parent + * first — errors report Node's errno at Node's path, EEXIST at a file + * target, ENOTDIR at the full path past a file); the first-created-dir + * return value has no lowering (statement position only, frontend- + * fenced). rmOptsSync is rmSync with (recursive, force) bools: force + * swallows ENOENT, recursive removes trees post-order, a directory + * without recursive throws Node's ERR_FS_EISDIR SystemError. mkdtempSync appends the six X's and returns the + * created path (+1). accessSync takes the F_OK/R_OK/W_OK/X_OK bits as + * one f64 (the frontend bakes fs.constants.* as literals and completes + * an omitted mode to 0). readFdSync/readFdSyncBytes are the + * readFileSync(fd[, "utf8"]) forms — a read(2) loop to EOF on the fd + * (the stdin path); errors carry Node's no-path message shape. */ + "fs.mkdirRecursiveSync": { argTypes: [STRING], result: VOID }, + "fs.rmOptsSync": { argTypes: [STRING, BOOL, BOOL], result: VOID }, + /** rmOptsSync's maxRetries/retryDelay form (path, recursive, force, + * maxRetries, retryDelay): Node's linear-backoff retry on + * EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM — the tmpdir-harness shape + * rmSync(p, { maxRetries: 3, recursive: true, force: true }). Throws + * ERR_OUT_OF_RANGE for a retryDelay outside int32 >= 0 or a maxRetries + * outside uint32, before checking the path. */ + "fs.rmRetrySync": { argTypes: [STRING, BOOL, BOOL, F64, F64], result: VOID }, + "fs.mkdtempSync": { argTypes: [STRING], result: STRING }, + "fs.accessSync": { argTypes: [STRING, F64], result: VOID }, + "fs.readFdSync": { argTypes: [F64, STRING], result: STRING }, + "fs.readFdSyncBytes": { argTypes: [F64], result: BYTES_U8 }, + /** Validated isatty(3) probe. Stream property lowering maps false to + * undefined; tty.isatty exposes the boolean result directly. */ + "process.isTTY": { argTypes: [F64], result: BOOL }, + // Like process.envGet: the result is the module's interned + // `number | undefined` union — checked by arms in the libCall case. + /** Terminal width over an fd literal (1/2 — process.stdout/stderr + * .columns): ioctl(TIOCGWINSZ). The result is the module's interned + * `number | undefined` union — a non-TTY stream (or an ioctl refusal) + * yields the undefined arm, exactly Node's missing `.columns`. Never + * throws. */ + "process.columns": { argTypes: [F64], result: VOID }, + /** Terminal height, with the same fd and optional-number contract. */ + "process.rows": { argTypes: [F64], result: VOID }, + /** process.stdin.destroy(): a deliberate no-op (no stream machinery + * exists to tear down; documented in SEMANTICS.md). */ + "process.stdinDestroy": { argTypes: [], result: VOID }, + /** process.stdin.setRawMode(mode) — one borrowed bool arg, void. On a + * TTY stdin: termios raw mode on/off (libuv's UV_TTY_MODE_RAW flag set, + * the mode Node's setRawMode(true) applies; false restores the entry + * state). On a NON-TTY stdin Node's process.stdin has no such method at + * all, so the call throws Node's exact catchable TypeError + * ("process.stdin.setRawMode is not a function") — the may-throw seed + * carries it. */ + "process.stdinSetRawMode": { argTypes: [BOOL], result: VOID }, + // Arg 0 is a packed f64[] OR a bytes value (the spread-typed-array + // form) — checked in the libCall case. + /** fromCharCode takes one packed f64[] or bytes arg and builds a string + * from UTF-16 code units. Adjacent surrogate pairs combine; lone + * surrogates follow the runtime's replacement policy. */ + "string.fromCharCode": { argTypes: [null], result: STRING }, + /** Numeric code points, with catchable RangeError for non-integers or + * values outside 0..0x10ffff. Uses the same UTF-8 surrogate policy. */ + "string.fromCodePoint": { argTypes: [null], result: STRING }, + /** lastIndexOf returns the last UTF-16 start index, or -1. The two-arg + * form searches at or before its numeric position; NaN starts at the end. + * String arguments are borrowed and neither form throws. */ + "string.lastIndexOf": { argTypes: [STRING, STRING], result: F64 }, + "string.lastIndexOfFrom": { argTypes: [STRING, STRING, F64], result: F64 }, + /** String.raw(template, ...subs): the raw literals array (a string[] + * read off the template record) interleaved with the PRE-STRINGIFIED + * substitutions (the frontend applies the static ToString and packs + * them into one string[] literal) — extra substitutions drop, missing + * ones skip, the spec's loop exactly (scr_array.c). Borrowed args; + * +1 string; never throws. */ + "string.raw": { argTypes: [arrayOf(STRING), arrayOf(STRING)], result: STRING }, + }); +} + +let libFnSignatureTable: ReturnType | undefined; + +/** The runtime call slots of every native builtin, keyed by name. The table + * is built on the first call, so runs that neither lower nor validate a + * module do not construct it. */ +export function libFnSignatures(): ReturnType { + libFnSignatureTable ??= buildLibFnSignatures(); + return libFnSignatureTable; +} /** A builtin name exists exactly when its runtime signature exists. */ -export type IrLibFn = keyof typeof LIB_FN_SIGS; +export type IrLibFn = keyof ReturnType; /** The function-stage runs of a pipeline of `total` stages that a stream * follows (positions bit j: stage j is a function), and an async diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 251d14904..655288945 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -3940,7 +3940,7 @@ export type IrExpr = } /** Standard-library call (`process` members, node:fs functions). `fn` is a * closed union; arg/result types are fixed per member (validated against - * LIB_FN_SIGS). Property READS (`process.argv`, `process.platform`) are + * libFnSignatures()). Property READS (`process.argv`, `process.platform`) are * zero-arg libCalls. Args are BORROWED by the operation (frame temps * release at statement end); refcounted results come back owned (+1) — * `process.argv` returns +1 on ONE interned array (JS identity: diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index eba319f85..f27d11f5b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -66,7 +66,7 @@ import { import { unionWideningTags } from "./analysis.js"; import { alwaysReturns } from "./control-flow.js"; import { dynamicOperationOf, functionTier } from "./tiers.js"; -import { LIB_FN_SIGS, pipelinePumpedRuns } from "./builtin-signatures.js"; +import { libFnSignatures, pipelinePumpedRuns } from "./builtin-signatures.js"; import { ALWAYS_THROW_LIB_FNS } from "./builtin-effects.js"; /** The promisified crypto calls' value kinds besides checked-dynamic. */ @@ -4104,7 +4104,7 @@ function validateFunction( } if (e.fn === "worker.new" && !workers) errors.push({ message: "Worker construction requires worker execution mode", loc: e.loc }); - const sig = LIB_FN_SIGS[e.fn]; + const sig = libFnSignatures()[e.fn]; if (!sig) { err(`libCall of unknown library function "${e.fn as string}"`, e.loc); return; diff --git a/packages/compiler/src/library/fence-eval.ts b/packages/compiler/src/library/fence-eval.ts index 8b0450857..a950b3990 100644 --- a/packages/compiler/src/library/fence-eval.ts +++ b/packages/compiler/src/library/fence-eval.ts @@ -56,9 +56,9 @@ import { } from "../coverage/surface-manifest.js"; import { libFenceDiag, type ScrDiagnostic } from "../diagnostics/diagnostic.js"; import { - AMBIENT_SURFACE_FNS, + ambientSurfaceFns, BUILTIN_MODULE_CONSTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, BUILTIN_MODULE_FN_ALIASES, STATIC_MATH_FNS, STATIC_MATH_PROPS, @@ -125,7 +125,7 @@ interface FenceTaxonomy { /** node-builtin constant ids: reads fold to literals at compile time. */ foldedIds: Set; /** Ambient dedicated-path surfaces (Date/perf_hooks/process): manifest - * id → the libCall spellings witnessing reach (AMBIENT_SURFACE_FNS — + * id → the libCall spellings witnessing reach (ambientSurfaceFns() — * the same rows the manifest projects the entries from). */ ambientFns: Map; } @@ -148,7 +148,7 @@ function fenceTaxonomy(): FenceTaxonomy { const builtinFns = new Map(); const builtinRoots = new Map(); const foldedIds = new Set(); - for (const [mod, members] of Object.entries(BUILTIN_MODULE_FNS)) { + for (const [mod, members] of Object.entries(builtinModuleFns())) { const root = new Set(); for (const [member, entry] of Object.entries(members!)) { if (entry === undefined) continue; @@ -156,7 +156,7 @@ function fenceTaxonomy(): FenceTaxonomy { // The bare-path and url tables rebind per TARGET platform // (builtinModuleFnsOf): a fence on the bare id covers both flavors. if (mod === "path") { - const w = BUILTIN_MODULE_FNS["path/win32"]?.[member]; + const w = builtinModuleFns()["path/win32"]?.[member]; if (w !== undefined) fns.add(w.fn); } if (mod === "url" && member === "pathToFileURL") fns.add("url.pathToFileURLWin32"); @@ -173,7 +173,7 @@ function fenceTaxonomy(): FenceTaxonomy { } for (const member of Object.keys(STATIC_MATH_PROPS)) foldedIds.add(`stdlib.math.${member}`); const ambientFns = new Map( - AMBIENT_SURFACE_FNS.map((row) => [row.id, row.fns as readonly string[]]), + ambientSurfaceFns().map((row) => [row.id, row.fns as readonly string[]]), ); taxonomy = { byId, diff --git a/packages/compiler/test/compat-exports.test.ts b/packages/compiler/test/compat-exports.test.ts new file mode 100644 index 000000000..57ee41339 --- /dev/null +++ b/packages/compiler/test/compat-exports.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from "vitest"; +import { + LIB_FN_SIGS, + libFnSignatures, + NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, +} from "../src/index.js"; + +test("the table constants name the tables their functions build", () => { + expect(LIB_FN_SIGS).toBe(libFnSignatures()); + expect(NODE24_FETCH_COMPAT_PROFILE).toBe(node24FetchCompatProfile()); +}); diff --git a/packages/compiler/test/node-pin.test.ts b/packages/compiler/test/node-pin.test.ts index 7f032c69e..4d4c4a7e8 100644 --- a/packages/compiler/test/node-pin.test.ts +++ b/packages/compiler/test/node-pin.test.ts @@ -5,7 +5,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { expect, test } from "vitest"; -import { NODE24_FETCH_COMPAT_PROFILE } from "../src/compat/fetch-profile.js"; +import { node24FetchCompatProfile } from "../src/compat/fetch-profile.js"; import { NODE_COMPAT_VERSION } from "../src/frontend/lowering/builtins/process.js"; import { CRYPTO_CONSTANTS } from "../src/frontend/lowering/crypto-tables.js"; import { ZLIB_CONSTANTS } from "../src/frontend/lowering/zlib-constants.js"; @@ -23,7 +23,7 @@ test("the Node pins agree", () => { expect(compat.tag).toBe(`v${pinned}`); expect(read("Dockerfile.sandbox")).toMatch(new RegExp(`^ARG NODE_VERSION=${pinned}$`, "m")); expect(NODE_COMPAT_VERSION).toBe(pinned); - expect(NODE24_FETCH_COMPAT_PROFILE.target.node).toBe(pinned); + expect(node24FetchCompatProfile().target.node).toBe(pinned); expect(read("packages/runtime/src/scr_lib.c")).toContain( `#define SCR_NODE_COMPAT_VERSION "${pinned}"`, ); diff --git a/tests/harness/README.md b/tests/harness/README.md index 3922c72cc..66a528f4e 100644 --- a/tests/harness/README.md +++ b/tests/harness/README.md @@ -52,7 +52,7 @@ that may be newer than the installed declarations). Every item is classified: The selected adjacent interface families excluded from the census carry reasons too. A Node upgrade that adds/removes a public member or dictionary key fails the focused suite until that member is classified. The static and unsupported rows project into the shipped surface manifest; -filter `NODE24_FETCH_COMPAT_PROFILE.inventory.entries` by `status`/`owner` for +filter `node24FetchCompatProfile().inventory.entries` by `status`/`owner` for the next cohesive implementation queue. When Node changes, update `.node-version` and the profile's Node/Undici tuple diff --git a/tests/harness/code-quality.test.ts b/tests/harness/code-quality.test.ts index deff9afe0..9a683e50a 100644 --- a/tests/harness/code-quality.test.ts +++ b/tests/harness/code-quality.test.ts @@ -93,7 +93,7 @@ test("IR imports and the explicit parser islands remain usable", () => { source( work, "packages/compiler/src/frontend/lowering/probe.ts", - 'export { LIB_FN_SIGS } from "../../ir/builtin-signatures.js";\nexport { STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js";\n', + 'export { libFnSignatures } from "../../ir/builtin-signatures.js";\nexport { STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js";\n', ); source( work, diff --git a/tests/harness/fetch-conformance-program.ts b/tests/harness/fetch-conformance-program.ts index 0858f5bfe..322443e73 100644 --- a/tests/harness/fetch-conformance-program.ts +++ b/tests/harness/fetch-conformance-program.ts @@ -429,7 +429,7 @@ export function generateFetchConformanceProgram( const traceCount = options.traceCount ?? 12; const scenarios = generatedScenarioIds(profile); const sections: string[] = [ - `// Generated from NODE24_FETCH_COMPAT_PROFILE. Seed: 0x${seed.toString(16)}.`, + `// Generated from node24FetchCompatProfile(). Seed: 0x${seed.toString(16)}.`, ]; for (const scenario of scenarios) { if (!Object.hasOwn(SCENARIO_GENERATORS, scenario)) { diff --git a/tests/harness/fetch-conformance.test.ts b/tests/harness/fetch-conformance.test.ts index 5b6d62f08..abeac58ae 100644 --- a/tests/harness/fetch-conformance.test.ts +++ b/tests/harness/fetch-conformance.test.ts @@ -17,7 +17,7 @@ import { promisify } from "node:util"; import { beforeAll, describe, expect, test } from "vitest"; import { compile, - NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, renderDiagnostics, type FetchCompatEvidence, } from "@scriptc/compiler"; @@ -32,7 +32,7 @@ const execFileAsync = promisify(execFile); const repoRoot = join(import.meta.dirname, "../.."); const fixturesRoot = join(repoRoot, "tests/fixtures/fetch"); const sanitize = process.env["SCRIPTC_SAN"] === "1"; -const profile = NODE24_FETCH_COMPAT_PROFILE; +const profile = node24FetchCompatProfile(); function configuredInteger(name: string, fallback: number, maximum: number): number { const raw = process.env[name]; if (raw === undefined || raw === "") return fallback; diff --git a/tests/harness/surface-manifest.test.ts b/tests/harness/surface-manifest.test.ts index 58958c102..8a838aac6 100644 --- a/tests/harness/surface-manifest.test.ts +++ b/tests/harness/surface-manifest.test.ts @@ -6,7 +6,7 @@ import { analyze, generateSurfaceManifest, ir, - LIB_FN_SIGS, + libFnSignatures, renderSurfaceManifest, resolveLibraryFences, type SurfaceManifest, @@ -563,7 +563,7 @@ describe("determinism attestation ↔ fence parity", () => { // initializer is the Date expression the fences witness. "date.fromTime", ]); - const missing = Object.keys(LIB_FN_SIGS).filter( + const missing = Object.keys(libFnSignatures()).filter( (fn) => ir.LIB_NONDETERMINISTIC_PREFIXES.some(([prefix]) => fn.startsWith(prefix)) && !covered.has(fn) && @@ -571,7 +571,7 @@ describe("determinism attestation ↔ fence parity", () => { ); expect( missing, - "attestation-demoting spellings no declarable fence can deny — add a manifest projection (AMBIENT_SURFACE_FNS) or a member alias (BUILTIN_MODULE_FN_ALIASES)", + "attestation-demoting spellings no declarable fence can deny — add a manifest projection (ambientSurfaceFns()) or a member alias (BUILTIN_MODULE_FN_ALIASES)", ).toEqual([]); // The excuse list stays honest in the other direction too: an excused // spelling that gains detector coverage must leave the list. From 0e92be2bf79e9e244e32ffd3aef52d05cb9e5a93 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:41:44 -0500 Subject: [PATCH 211/395] Poll worker cancellation between blocks of typed-array loop iterations In a worker program, every loop without a pending test on each iteration decremented a cancellation budget in its body and polled the exception flag every 64 iterations. The poll's early return made each loop an early-exit loop, which LLVM's loop vectorizer does not vectorize. A counted for loop (integer counter, invariant limit), or a loop to a typed array's length, whose body reads or writes a typed-array element outside nested loops and closures, now runs as blocks of at most 64 iterations: the outer header tests the counter against the limit, the inner loop runs to the nearer of the limit and 64 steps on, and the poll follows each block. A typed array's length is fixed, so each block takes it as its limit. The loop polls as often as the budget did, and the inner loop's only exit besides break and return is its bound. The block bound lives in one slot shared by the loop's speculative copies, which resume iterations in each other's bodies. Other loops keep the budget, which costs less in loops of a few iterations. An elementwise Float64Array loop in a worker-enabled program vectorizes again: 949 ms to 691 ms. A byte-classifying loop over a 100M-byte Uint8Array takes 13 ms instead of 141 ms. New corpus program: native-worker-terminate-counted-loops.ts. --- .../src/backend/llvm/emit-statements.ts | 157 +++++++++++++++++- packages/compiler/src/backend/llvm/emitter.ts | 27 ++- packages/compiler/src/backend/loop-polls.ts | 31 ++++ .../test/counted-loop-emission.test.ts | 80 ++++++++- ...ive-worker-terminate-counted-loops.ts.json | 6 + .../native-worker-terminate-counted-loops.ts | 66 ++++++++ ...ive-worker-terminate-counted-loops.ts.json | 1 + 7 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json create mode 100644 tests/corpus/native-worker-terminate-counted-loops.ts create mode 100644 tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index cbdeb0558..2efe2cf3a 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -91,6 +91,7 @@ import { } from "./string-construction.js"; import { emitStringInputs } from "./string-lifetimes.js"; import { LOOPS } from "./loop-state.js"; +import { loopAccessesTypedArrays } from "../loop-polls.js"; /** A callback loop version copies the loop; keep the copies small. */ const MAX_CALLBACK_VERSION_COST = 400; @@ -1211,9 +1212,67 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.line(`store ptr ${fresh}, ptr ${slot}`); } }; - const workerBudget = emitter.workerLoopBudget(s); + // A counted loop over typed arrays, or a loop to a typed array's + // length, polls between blocks of WORKER_POLL_BLOCK iterations instead + // of counting a budget in its body: the inner loop over a block then + // has no exit besides its own bound, which LLVM can vectorize. A typed + // array's length is fixed, so each block can take it as its limit. + const stripMined = + capturedLets.length === 0 && + integerSlot !== null && + ((countedLoop !== null && + countedLimit !== null && + Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || + (integerLoop !== null && integerArrayLoop === null)) && + emitter.workerLoopPolls(s) && + loopAccessesTypedArrays(s); + const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); + // One slot for every copy of the loop: a speculative copy resumes an + // iteration in another copy's body, which then reads this bound. + let blockBound: string | null = null; + if (stripMined) { + blockBound = B.slot(); + B.entryAllocas.push( + `${blockBound} = alloca ${countedLoop ? "i64" : emitter.sizeType} ; cancellation poll block bound`, + ); + } freshenBindings(); emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { + if (stripMined) { + const counter: StripMinedCounter = countedLoop + ? { + slot: integerSlot!, + type: "i64", + signed: true, + step: countedLoop.step, + followers: countedLoop.followers.map((f, i) => ({ + slot: followerSlots[i]!, + step: f.step, + })), + limit: () => countedLimit!, + } + : { + slot: integerSlot!, + type: emitter.sizeType, + signed: false, + step: 1, + followers: [], + limit: () => { + const receiver = emitter.emitStableReceiver(integerLoop!.limitReceiver, []); + const lenPtr = B.tmp(); + const len = B.tmp(); + B.line( + `${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`, + ); + B.line( + `${len} = load ${emitter.sizeType}, ptr ${lenPtr}${bytesHeaderAlias(emitter)}`, + ); + return len; + }, + }; + emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit); + return; + } const lc = B.newLabel("loop.c"); const lb = B.newLabel("loop.b"); const lu = @@ -2549,6 +2608,102 @@ function emitInt32KindLoop( /** At the top of a loop copy's header: lets a speculative loop find its * ordinary copy's header, and keeps its third copy out of the loop. */ +/** Iterations between cancellation polls of a strip-mined counted loop. */ +const WORKER_POLL_BLOCK = 64; + +/** 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. */ +interface StripMinedCounter { + slot: string; + type: string; + signed: boolean; + step: number; + followers: { slot: string; step: number }[]; + limit: () => string; +} + +/** A counted `for` loop of a worker program as blocks of WORKER_POLL_BLOCK + * iterations, with the cancellation poll between blocks: the outer header + * tests the counter against the loop's limit, each block runs the body up + * to the nearer of the limit and WORKER_POLL_BLOCK steps on, and the poll + * follows the block. It polls as often as the budget it replaces, and the + * inner loop's only exit besides `break` and `return` is its bound. */ +function emitStripMinedCountedLoop( + emitter: LlEmitter, + s: Extract, + counter: StripMinedCounter, + boundSlot: string, + exit: string, +): void { + const B = emitter.B; + const { slot, type, signed, step } = counter; + const ascending = step > 0; + const compare = !signed ? "ult" : ascending ? "slt" : "sgt"; + const saturating = `llvm.${signed ? "sadd" : "uadd"}.sat.${type}`; + const nearer = `llvm.${!signed ? "umin" : ascending ? "smin" : "smax"}.${type}`; + const wrap = signed ? "nsw" : "nuw"; + const lc = B.newLabel("loop.c"); + const block = B.newLabel("loop.block"); + const bc = B.newLabel("loop.bc"); + const lb = B.newLabel("loop.b"); + const lu = B.newLabel("loop.u"); + const poll = B.newLabel("loop.poll"); + emitter.declare(`declare ${type} @${saturating}(${type}, ${type})`); + emitter.declare(`declare ${type} @${nearer}(${type}, ${type})`); + B.br(lc); + B.startBlock(lc); + enterLoopHeader(emitter, lc); + const start = B.tmp(); + const open = B.tmp(); + B.line(`${start} = load ${type}, ptr ${slot}`); + const limit = counter.limit(); + B.line(`${open} = icmp ${compare} ${type} ${start}, ${limit}`); + B.condBr(open, block, exit); + B.startBlock(block); + const stepped = B.tmp(); + const bound = B.tmp(); + B.line( + `${stepped} = call ${type} @${saturating}(${type} ${start}, ${type} ${step * WORKER_POLL_BLOCK})`, + ); + B.line(`${bound} = call ${type} @${nearer}(${type} ${stepped}, ${type} ${limit})`); + B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + B.br(bc); + B.startBlock(bc); + const index = B.tmp(); + const end = B.tmp(); + const inBlock = B.tmp(); + B.line(`${index} = load ${type}, ptr ${slot}`); + B.line(`${end} = load ${type}, ptr ${boundSlot}`); + B.line(`${inBlock} = icmp ${compare} ${type} ${index}, ${end}`); + B.condBr(inBlock, lb, poll); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: exit, + contLabel: lu, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(lu); + B.startBlock(lu); + for (const { slot: stepSlot, step: by } of [{ slot, step }, ...counter.followers]) { + const old = B.tmp(); + const next = B.tmp(); + B.line(`${old} = load ${type}, ptr ${stepSlot}`); + B.line(`${next} = add ${wrap} ${type} ${old}, ${by}`); + B.line(`store ${type} ${next}, ptr ${stepSlot}`); + } + B.br(bc); + B.startBlock(poll); + emitter.emitWorkerPoll(); + B.br(lc); +} + function enterLoopHeader(emitter: LlEmitter, header: string): void { const dispatch = emitter.speculationHeader; emitter.speculationHeader = null; diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index d1f4ebcca..c1ea7bf35 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -3932,13 +3932,19 @@ export class LlEmitter { } } + /** Whether `loop` needs a cancellation poll: in a worker program, unless + * the loop already runs a pending test on every iteration, which observes + * a terminate request itself (a separate poll would only add work). */ + workerLoopPolls(loop: Extract): boolean { + return ( + this.mod.workers === true && !loopTestsEveryIteration(loop, (e) => this.callTestsPending(e)) + ); + } + workerLoopBudget( loop: Extract, ): string | null { - if (!this.mod.workers) return null; - // A loop that runs a pending test on every iteration observes a - // terminate request through it; a separate poll would only add work. - if (loopTestsEveryIteration(loop, (e) => this.callTestsPending(e))) return null; + if (!this.workerLoopPolls(loop)) return null; const slot = this.B.slot(); this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`); this.B.line(`store i32 0, ptr ${slot}`); @@ -4056,14 +4062,19 @@ export class LlEmitter { B.condBr(due, poll, next); B.startBlock(poll); B.line(`store i32 63, ptr ${slot}`); - // Calls and throwing operations retain their own checks. Amortize the - // extra cancellation poll across bounded loop batches so scalar work - // does not cross the runtime ABI on every iteration. - this.emitPendingCheck(undefined, true); + this.emitWorkerPoll(); B.br(next); B.startBlock(next); } + /** The cancellation poll itself. Calls and throwing operations retain + * their own checks. Loops amortize this extra poll across bounded + * batches of iterations so scalar work does not cross the runtime ABI on + * every iteration. */ + emitWorkerPoll(): void { + this.emitPendingCheck(undefined, true); + } + /** Moves an already-evaluated value into the runtime's exception cell — * the `throw` statement's kind dispatch (stmts.ts's), shared with * every synthetic thrower. Ownership of a refcounted payload must have diff --git a/packages/compiler/src/backend/loop-polls.ts b/packages/compiler/src/backend/loop-polls.ts index 14c2b049c..c582a4974 100644 --- a/packages/compiler/src/backend/loop-polls.ts +++ b/packages/compiler/src/backend/loop-polls.ts @@ -106,3 +106,34 @@ export function loopTestsEveryIteration(loop: Loop, tested: (call: IrExpr) => bo } return false; } + +/** True when the body of `loop`, outside its nested loops and closures, + * reads or writes a typed-array element: the loops whose cancellation poll + * the emitter moves between blocks of iterations, so that LLVM can + * vectorize them. Other loops keep the budget in their body, which costs + * less in a loop of a few iterations. */ +export function loopAccessesTypedArrays(loop: Loop): boolean { + const inExpr = (e: IrExpr): boolean => { + if (e.kind === "bytesIntrinsic" && e.method === "get") return true; + let found = false; + everyExprChild( + e, + (child) => !(found = inExpr(child)), + (child) => !(found = inStmt(child)), + ); + return found; + }; + const inStmt = (s: IrStmt): boolean => { + if (s.kind === "bytesSet") return true; + if (s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf") + return false; + let found = false; + everyStmtChild( + s, + (child) => !(found = inExpr(child)), + (child) => !(found = inStmt(child)), + ); + return found; + }; + return loop.body.some(inStmt); +} diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 7a3fc1f44..206a3c57e 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; @@ -45,3 +45,81 @@ test("counted loops guard exact induction and preserve wide remainders on both p await rm(dir, { recursive: true, force: true }); } }); + +test("worker programs poll counted loops over typed arrays between blocks of iterations", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-counted-poll-")); + try { + const source = (workers: boolean): string => + [ + workers ? '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 mix(n: number): number {", + " let t = 0;", + " for (let i = 0; i < n; i++) t = (t * 31 + i) % 1000003;", + " return t;", + "}", + "const a = new Float64Array(8).fill(1);", + "const b = new Float64Array(8);", + "scale(a, b, 8);", + "const c = new Uint8Array(8).fill(97);", + "codes(c);", + "console.log(b[7], mix(8), c[0]);", + workers ? "if (!isMainThread) new Worker(new URL(import.meta.url));" : "", + ].join("\n"); + const emit = async (workers: boolean): Promise => { + const entry = join(dir, `${workers ? "worker" : "plain"}.ts`); + await writeFile(entry, source(workers)); + const outPath = join(dir, `${workers ? "worker" : "plain"}.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")); + return emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + }; + // Every emitted version of a function (the number and int32 entries). + const bodies = (ll: string, 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"); + const workerModule = await emit(true); + const worker = bodies(workerModule, "scale"); + const blocks = worker.split(/\n(?=[\w.]+:)/); + // No budget in the body: the inner loop's latch only steps the counter, + // the poll runs after each block, and the block's bound is the nearer of + // the limit and 64 steps on. + expect(worker).not.toContain("worker cancellation budget"); + expect(worker).toContain("@llvm.sadd.sat.i64(i64 %"); + expect(worker).toMatch(/@llvm\.smin\.i64\(i64 %\w+, i64 %\w+\)/); + for (const latch of blocks.filter((block) => block.startsWith("loop.u"))) + expect(latch).not.toContain("scr_exc_flag"); + const polls = blocks.filter((block) => block.startsWith("loop.poll")); + expect(polls.length).toBeGreaterThan(0); + for (const poll of polls) expect(poll).toContain("@scr_exc_flag"); + // A counted loop without typed-array accesses keeps the budget. + // A loop to a typed array's length is strip-mined the same way, with the + // length as each block's limit. + const codes = bodies(workerModule, "codes"); + expect(codes).not.toContain("worker cancellation budget"); + expect(codes).toMatch(/@llvm\.uadd\.sat\.i64\(i64 %\w+, i64 64\)/); + expect(codes).toMatch(/@llvm\.umin\.i64\(i64 %\w+, i64 %\w+\)/); + expect(codes).toMatch(/^loop\.poll\d+:$/m); + const mix = bodies(workerModule, "mix"); + expect(mix).toContain("worker cancellation budget"); + expect(mix).not.toContain("loop.poll"); + const plain = bodies(await emit(false), "scale"); + expect(plain).not.toContain("loop.poll"); + expect(plain).not.toContain("@scr_exc_flag"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json new file mode 100644 index 000000000..5f7c54075 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-terminate-counted-loops.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-terminate-counted-loops.ts b/tests/corpus/native-worker-terminate-counted-loops.ts new file mode 100644 index 000000000..55972c4f6 --- /dev/null +++ b/tests/corpus/native-worker-terminate-counted-loops.ts @@ -0,0 +1,66 @@ +// worker.terminate() stops a worker in counted loops over a typed array +// with long bounds: ascending and descending counters, a step above one, a +// second counter that steps with the first, and loops to a typed array's +// length run again and again. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +function ascending(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = 0; i < limit; i++) { + acc = (acc + values[i & 1023]!) % 1000003; + if (i === 5000) parentPort!.postMessage("ready"); + } + return acc; +} + +function descending(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = limit; i > 0; i -= 3) { + acc = (acc * 31 + i + values[i & 1023]!) % 1000003; + if (i === limit - 15000) parentPort!.postMessage("ready"); + } + return acc; +} + +function paired(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = 0, j = 7; i < limit; i++, j += 2) { + acc = (acc + i * j + values[j & 1023]!) % 1000003; + if (i === 5000) parentPort!.postMessage("ready"); + } + return acc; +} + +function lengths(bytes: Uint8Array): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < bytes.length; ++i) bytes[i] = bytes[i] === 97 ? 0 : (bytes[i]! + round) & 127; + for (let i = 0; i < bytes.length; i++) acc = (acc + bytes[i]!) % 1000003; + if (round === 50) parentPort!.postMessage("ready"); + } + return acc; +} + +if (isMainThread) { + for (const kind of ["ascending", "descending", "paired", "lengths"]) { + await new Promise((resolve) => { + const worker = new Worker(new URL(import.meta.url), { workerData: kind }); + worker.once("message", () => { + void worker.terminate().then((code: number) => console.log(kind, "terminate", code)); + }); + worker.once("exit", (code: number) => { + console.log(kind, "exit", code); + resolve(); + }); + }); + } +} else { + const values = new Float64Array(1024).map((_, i) => i); + const limit = 1e15; + let result = 0; + if (workerData === "ascending") result = ascending(values, limit); + else if (workerData === "descending") result = descending(values, limit); + else if (workerData === "paired") result = paired(values, limit); + else result = lengths(new Uint8Array(1 << 16).fill(97)); + console.log("unexpected result", result); +} diff --git a/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json b/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json new file mode 100644 index 000000000..66a146275 --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json @@ -0,0 +1 @@ +{ "plain": 3594, "san": 23593 } From 1e9afec1d412240843aa865a84173f7dafd82f1c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:42:02 -0500 Subject: [PATCH 212/395] Reclaim completed socket writes before growing the pending-write queue The queue of writes with buffered bytes dropped completed entries only when the buffer emptied. On a socket whose buffer stays nonempty while old writes complete and new ones arrive, it kept every write since the buffer last emptied. Appending to a full queue now first moves the pending entries to the front, so its size follows the writes still pending. --- packages/runtime/src/scr_net.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 0fe06cedc..85c7b7db7 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -1139,6 +1139,13 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len, s } memcpy(s->wbuf + s->wlen, data, len); s->wlen += len; + if (s->wq_n == s->wq_cap && s->wq_head > 0) { + /* reclaim the completed writes before growing: a buffer that never + * empties completes old writes while new ones arrive */ + memmove(s->wq, s->wq + s->wq_head, (s->wq_n - s->wq_head) * sizeof *s->wq); + s->wq_n -= s->wq_head; + s->wq_head = 0; + } if (s->wq_n == s->wq_cap) { s->wq_cap = s->wq_cap ? s->wq_cap * 2 : 8; s->wq = realloc(s->wq, s->wq_cap * sizeof *s->wq); From b3c98139e40d5b2c4bd57ec2dd9b06768baaf6d8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:48:40 -0500 Subject: [PATCH 213/395] Move small number-key maps to the direct index from the second key A number-keyed Map or Set with keys the direct index allows now moves to it at its second entry rather than its fifth, so a map over a few small integer keys (a histogram over 0..3) reads through the inline direct index instead of the out-of-line linear scan. --- ...6310-small-number-map-direct-index.ts.json | 6 ++ packages/runtime/src/scr_map.c | 8 +- packages/runtime/src/scr_map.test.c | 8 +- packages/runtime/test/test_map.c | 23 +++--- .../6310-small-number-map-direct-index.ts | 77 +++++++++++++++++++ 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json create mode 100644 tests/corpus/6310-small-number-map-direct-index.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json new file mode 100644 index 000000000..72472aa95 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6310-small-number-map-direct-index.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index 1cb77b0f6..ccfd4b907 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -1205,9 +1205,11 @@ static __attribute__((noinline)) void scr_map_set_num(ScrMap *m, uint64_t hash, } /* A number-key map with at least this many entries moves to the direct - * index when its keys allow it, so small integer keys are indexed before - * the linear scan ends. */ -#define SCR_MAP_DENSE_AFTER 4 + * index when its keys allow it: from its second key, so a map of a few + * small integer keys (a histogram over 0..3) reads through the inline + * direct index rather than the out-of-line linear scan. A map with one + * key keeps its linear storage. */ +#define SCR_MAP_DENSE_AFTER 1 /* Whether appending one more entry would grow the lookup index rather than * reuse tombstones: scr_map_reserve_append compacts a linear map with a diff --git a/packages/runtime/src/scr_map.test.c b/packages/runtime/src/scr_map.test.c index 9916c7406..e04e0dac9 100644 --- a/packages/runtime/src/scr_map.test.c +++ b/packages/runtime/src/scr_map.test.c @@ -184,9 +184,11 @@ static void iterate_while_mutating(void) { static void modes(void) { ScrMap *map = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_F64, NULL); - for (int i = 1; i <= 4; i++) scr_map_set_f64_f64(map, i, i); - assert(!map->ndense && !map->nbuckets); /* still a linear scan */ - scr_map_set_f64_f64(map, 5, 5); + scr_map_set_f64_f64(map, 1, 1); + assert(!map->ndense && !map->nbuckets); /* one key: still a linear scan */ + scr_map_set_f64_f64(map, 2, 2); + assert(map->ndense >= 16 && !map->nbuckets); /* from the second small integer key */ + for (int i = 3; i <= 5; i++) scr_map_set_f64_f64(map, i, i); assert(map->ndense >= 16 && !map->nbuckets); for (int i = 6; i < 1000; i++) scr_map_set_f64_f64(map, i, i); assert(map->ndense >= 1000 && map->ndense <= 1000 + 1000 / 4 + 64); /* near the largest key */ diff --git a/packages/runtime/test/test_map.c b/packages/runtime/test/test_map.c index 46805da1e..e23618f77 100644 --- a/packages/runtime/test/test_map.c +++ b/packages/runtime/test/test_map.c @@ -422,12 +422,14 @@ static void test_live_iteration_indices(void) { /* A small (linear) map that must grow its lookup index while an iteration * is active may not compact: entry indices, tombstones and the append * position stay put. Integer keys in a compact range move to the direct - * index (dense) from the fifth entry, any other number keys to the bucket + * index (dense) from the second entry, any other number keys to the bucket * table from the ninth; both transitions are covered by running the same - * script with `offset` 0 and 0.5. */ + * script with `offset` 0 and 0.5. The walk deletes `victim`, an existing + * key, then appends the key that makes the index grow. */ static void run_small_storage_transitions(double offset) { bool dense = offset == 0; - int n = dense ? 4 : 8; /* entries a linear map holds before its index grows */ + int n = dense ? 1 : 8; /* entries a linear map holds before its index grows */ + int victim = dense ? 0 : 1; ScrMap *m = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_F64, NULL); for (int i = 0; i < n; i++) scr_map_set_f64_f64(m, i + offset, i + 10); check(m->nbuckets == 0 && m->buckets == NULL && m->ndense == 0, @@ -441,22 +443,23 @@ static void run_small_storage_transitions(double offset) { ScrMap *copy = scr_map_clone(m, false, NULL); check(copy->nbuckets == 0 && copy->ndense == 0, "small clone preserves compact storage"); scr_map_iter_enter(m); - scr_map_delete_f64(m, 1 + offset); + scr_map_delete_f64(m, victim + offset); scr_map_set_f64_f64(m, n + offset, 14); check(dense ? m->ndense > 0 && m->nbuckets == 0 : m->nbuckets > 0 && m->ndense == 0, "live iteration promotes without moving entry indices"); - check(m->nentries == (uint32_t)n + 1 && !scr_map_iter_live(m, 1) && + check(m->nentries == (uint32_t)n + 1 && !scr_map_iter_live(m, victim) && scr_map_iter_key_f64(m, n) == n + offset, "promotion preserves tombstones and append position"); for (int i = n + 1; i < 64; i++) scr_map_set_f64_f64(m, i + offset, i + 10); - check(m->nentries == 64 && !scr_map_iter_live(m, 1) && scr_map_iter_key_f64(m, 63) == 63 + offset, + check(m->nentries == 64 && !scr_map_iter_live(m, victim) && scr_map_iter_key_f64(m, 63) == 63 + offset, "growth during iteration keeps entry indices"); double out = 0; - check(scr_map_get_f64_f64(m, offset, &out) && out == 10, - "original key survives promotion and growth"); - check(!scr_map_has_f64(m, 1 + offset), "deleted small key stays absent after rehash"); + check(dense ? scr_map_get_f64_f64(m, n + offset, &out) && out == 14 + : scr_map_get_f64_f64(m, offset, &out) && out == 10, + "keys written before the promotion survive it and the growth"); + check(!scr_map_has_f64(m, victim + offset), "deleted small key stays absent after rehash"); scr_map_iter_exit(m); - check(scr_map_has_f64(copy, 1 + offset) && !scr_map_has_f64(copy, n + offset), + check(scr_map_has_f64(copy, victim + offset) && !scr_map_has_f64(copy, n + offset), "small clone remains independent"); scr_map_clear(copy); scr_map_iter_enter(copy); diff --git a/tests/corpus/6310-small-number-map-direct-index.ts b/tests/corpus/6310-small-number-map-direct-index.ts new file mode 100644 index 000000000..5614a56a1 --- /dev/null +++ b/tests/corpus/6310-small-number-map-direct-index.ts @@ -0,0 +1,77 @@ +// Number-keyed Maps and Sets with two to four small integer keys use the +// direct index. Results must match Node through overwrites, deletes and +// re-adds, a fractional, negative or large key that moves the map to +// hashing, clear, and mutation during a for-of or forEach walk. + +const hist = new Map(); +const codes = [0, 3, 1, 3, 2, 0, 3, 3, 1]; +for (const c of codes) hist.set(c, (hist.get(c) ?? 0) + 1); +console.log([...hist].map(([k, v]) => `${k}:${v}`).join(" "), hist.size, hist.has(4), hist.get(-0)); + +// Delete and re-add: the re-added key moves to the end. +hist.delete(3); +hist.set(3, 9); +hist.delete(0); +console.log([...hist.keys()].join(","), hist.get(3), hist.has(0)); + +// A key the direct index cannot hold moves the map to hashing. +for (const odd of [0.5, -1, 2 ** 40]) { + const m = new Map([ + [1, "a"], + [2, "b"], + ]); + m.set(odd, "c"); + m.set(1, "A"); + console.log([...m].map(([k, v]) => `${k}=${v}`).join(" "), m.get(2), m.get(odd), m.size); +} + +// Two keys, then clear, then reuse. +const pair = new Map(); +pair.set(5, 50); +pair.set(7, 70); +pair.clear(); +pair.set(7, 1); +pair.set(1, 2); +console.log([...pair].map(([k, v]) => `${k}=${v}`).join(" "), pair.size); + +// A walk that deletes ahead and adds keys. +const walk = new Map([ + [0, 0], + [1, 10], + [2, 20], +]); +const seen: number[] = []; +for (const [k, v] of walk) { + seen.push(v); + if (k === 0) { + walk.delete(2); + walk.set(3, 30); + } + if (k === 3) walk.set(0, -1); +} +console.log(seen.join(","), [...walk].map(([k, v]) => `${k}=${v}`).join(" ")); + +// Sets of small integers, forEach with deletion and addition. +const set = new Set([2, 0]); +set.add(1); +set.add(2); +const order: number[] = []; +set.forEach((v) => { + order.push(v); + if (v === 2) { + set.delete(1); + set.add(3); + } +}); +console.log(order.join(","), [...set].join(","), set.has(1), set.size); + +// Many small maps, each with a few keys. +let total = 0; +for (let i = 0; i < 1000; i++) { + const m = new Map(); + m.set(i % 3, i); + m.set((i + 1) % 3, 1); + if (i % 5 === 0) m.delete(i % 3); + total += (m.get(i % 3) ?? 100) + (m.get((i + 1) % 3) ?? 0) + m.size; +} +console.log(total); From 4eb3a16a0b0de657f7b4de5253e2545f1a0a2682 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:52:34 -0500 Subject: [PATCH 214/395] Match the Promise.all tuple adapter by its key-derived name in the chunked-emission test The adapter is named from its key since the helpers moved to contentSymbol, so the test no longer finds the counter name sc_all_tuple_0. It now matches any sc_all_tuple_ name; the serial and chunked emissions are still compared in full. --- packages/compiler/test/chunked-emission.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 6d2b2e06a..703669648 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -95,7 +95,8 @@ test("a Promise.all tuple adapter requested in a range is defined", async () => throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); const ir = await readFile(outPath, "utf8"); const serial = text(emitLlvmModuleSource(deserializeModule(ir), {})); - expect(serial).toContain("define internal void @sc_all_tuple_0("); + // The adapter is named from its key (contentSymbol). + expect(serial).toMatch(/define internal void @sc_all_tuple_\d+\(/); const chunked = text(emitLlvmModuleSource(deserializeModule(ir), { functionChunks: 2 })); expect(chunked === serial).toBe(true); } finally { From cac5a19fc07a7cb13a8085645723a37d1a110d52 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:58:42 -0500 Subject: [PATCH 215/395] Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time - fill with a string or Buffer value longer than one byte: writes the value once, then doubles the filled prefix with memcpy, as Node does. The value is copied with memmove, so a value that is a view of the buffer being filled gives Node's result; the previous per-byte loop read such a view while overwriting it. The loop also computed a division per byte. - swap16, swap32 and swap64: one load, byte swap and store per group, through a local data pointer. - toString("utf16le" | "ucs2"): four units below 0x80 copy as four bytes, and other units outside the surrogate range are written inline (a three-byte sequence as one four-byte store). Surrogates keep the previous path. The result records its UTF-16 length (one unit per input unit), or is marked ASCII, so `.length` does not walk it. --- .../8007-buffer-fill-swap-utf16le.ts.json | 6 + packages/runtime/src/scr_bytes.c | 85 ++++++++++++-- packages/runtime/src/scr_string.c | 13 +++ packages/runtime/src/scr_string.h | 3 + tests/corpus/8007-buffer-fill-swap-utf16le.ts | 110 ++++++++++++++++++ .../8007-buffer-fill-swap-utf16le.ts.json | 1 + 6 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json create mode 100644 tests/corpus/8007-buffer-fill-swap-utf16le.ts create mode 100644 tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json new file mode 100644 index 000000000..f629dac24 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8007-buffer-fill-swap-utf16le.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 398e6eb86..df64b2ab2 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2495,14 +2495,57 @@ static uint32_t scr_bytes_utf16le_cp(const uint8_t *in, size_t units, size_t *u) return cu; } +/* Four units below 0x80 copy as four bytes (one word holds them), and + * other units outside the surrogate range are written inline; surrogates + * take scr_bytes_utf16le_cp. The string has one UTF-16 unit per input + * unit, which is recorded so its length needs no walk. */ static ScrStr *scr_bytes_decode_utf16le(const uint8_t *in, size_t n) { size_t units = n / 2; if (units > SIZE_MAX / 3) scr_bytes_oom(); ScrStr *s = scr_str_alloc_raw(0, units * 3); - size_t o = 0; - for (size_t u = 0; u < units;) o = scr_bytes_put_cp(s->data, o, scr_bytes_utf16le_cp(in, units, &u)); + uint8_t *out = (uint8_t *)s->data; + size_t o = 0, u = 0; + while (u < units) { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + if (units - u >= 4) { + uint64_t w; + memcpy(&w, in + u * 2, 8); + if (!(w & UINT64_C(0xff80ff80ff80ff80))) { + out[o] = (uint8_t)w; + out[o + 1] = (uint8_t)(w >> 16); + out[o + 2] = (uint8_t)(w >> 32); + out[o + 3] = (uint8_t)(w >> 48); + o += 4; + u += 4; + continue; + } + } +#endif + uint32_t cu = (uint32_t)in[u * 2] | ((uint32_t)in[u * 2 + 1] << 8); + if (cu >= 0x800 && (cu < 0xd800 || cu > 0xdfff)) { + /* Three bytes in one four-byte store; the next write (or the + * terminator) replaces the fourth. */ + uint8_t seq[4] = {(uint8_t)(0xe0 | (cu >> 12)), (uint8_t)(0x80 | ((cu >> 6) & 0x3f)), + (uint8_t)(0x80 | (cu & 0x3f)), 0}; + memcpy(out + o, seq, 4); + o += 3; + u++; + } else if (cu < 0x80) { + out[o++] = (uint8_t)cu; + u++; + } else if (cu < 0x800) { + out[o] = (uint8_t)(0xc0 | (cu >> 6)); + out[o + 1] = (uint8_t)(0x80 | (cu & 0x3f)); + o += 2; + u++; + } else { + o = scr_bytes_put_cp(s->data, o, scr_bytes_utf16le_cp(in, units, &u)); + } + } s->len = o; s->data[o] = '\0'; + if (o == units) scr_str_note_ascii(s); + else scr_str_note_utf16_len(s, units); return s; } @@ -3479,7 +3522,15 @@ static ScrBytes *scr_bytes_fill_core(ScrBytes *b, const uint8_t *pat, size_t pat } else if (patn == 1) { memset(dst, pat[0], e - o); } else { - for (size_t i = o; i < e; i++) dst[i - o] = pat[(i - o) % patn]; + /* Node's fill: the pattern's first bytes (memmove: the pattern may + * be a view of this buffer), then the filled prefix doubled. */ + size_t len = e - o, done = patn < len ? patn : len; + memmove(dst, pat, done); + while (done < len) { + size_t step = done < len - done ? done : len - done; + memcpy(dst + done, dst, step); + done += step; + } } if (b->shared) { scr_shared_store(b->data + o, dst, e - o); @@ -3558,11 +3609,29 @@ ScrBytes *scr_bytes_swap(ScrBytes *b, double width) { scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_INVALID_BUFFER_SIZE"); return NULL; } - for (size_t g = 0; g < b->len; g += w) { - for (size_t i = 0; i < w / 2; i++) { - uint8_t t = b->data[g + i]; - b->data[g + i] = b->data[g + w - 1 - i]; - b->data[g + w - 1 - i] = t; + /* One load, byte swap and store per group. */ + uint8_t *p = b->data; + size_t len = b->len; + if (w == 2) { + for (size_t g = 0; g < len; g += 2) { + uint16_t v; + memcpy(&v, p + g, 2); + v = __builtin_bswap16(v); + memcpy(p + g, &v, 2); + } + } else if (w == 4) { + for (size_t g = 0; g < len; g += 4) { + uint32_t v; + memcpy(&v, p + g, 4); + v = __builtin_bswap32(v); + memcpy(p + g, &v, 4); + } + } else { + for (size_t g = 0; g < len; g += 8) { + uint64_t v; + memcpy(&v, p + g, 8); + v = __builtin_bswap64(v); + memcpy(p + g, &v, 8); } } return scr_bytes_retain(b); diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index d2d397e25..dfd0d23f8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -2006,6 +2006,19 @@ double scr_str_utf16_len(ScrStr *s) { return (double)(int64_t)units; } +/* Records the UTF-16 length of a string whose producer counted it (latin1 + * text has one unit per byte), so scr_str_utf16_len does not walk it. Only + * strings that scr_str_utf16_len would otherwise record are kept. */ +void scr_str_note_utf16_len(ScrStr *s, size_t units) { + if (s->ascii || s->len <= 64 || s->rc == SIZE_MAX) return; + if (!scr_u16len_tab) { + scr_u16len_tab = calloc(SCR_U16LEN_N, sizeof *scr_u16len_tab); + if (!scr_u16len_tab) return; + scr_sidx_register_cleanup(); + } + *scr_u16len_slot(s) = (ScrU16Len){s, units}; +} + double scr_str_char_code_at(ScrStr *s, double i) { /* A sequential scan of a non-ASCII receiver stays inside the bounded * ASCII span cached at its last resolved position. Its unit offset is an diff --git a/packages/runtime/src/scr_string.h b/packages/runtime/src/scr_string.h index fc2f15562..e269498b1 100644 --- a/packages/runtime/src/scr_string.h +++ b/packages/runtime/src/scr_string.h @@ -83,6 +83,9 @@ static inline void scr_str_hash_forget(ScrStr *s) { /* True only when every byte is proven below 0x80 (see above). */ static inline bool scr_str_known_ascii(const ScrStr *s) { return s->ascii; } +/* Record the UTF-16 length a string's producer counted (scr_string.c). */ +void scr_str_note_utf16_len(ScrStr *s, size_t units); + /* Record a proven-ASCII fact. Immortal strings may live in read-only * memory and are shared across threads, so they are never written. */ static inline void scr_str_note_ascii(ScrStr *s) { diff --git a/tests/corpus/8007-buffer-fill-swap-utf16le.ts b/tests/corpus/8007-buffer-fill-swap-utf16le.ts new file mode 100644 index 000000000..850c5b964 --- /dev/null +++ b/tests/corpus/8007-buffer-fill-swap-utf16le.ts @@ -0,0 +1,110 @@ +// Buffer fill with values of more than one byte, swap16/32/64, and +// toString("utf16le"): pattern lengths that do and do not divide the +// filled range, offsets and ends, patterns that are views of the buffer +// being filled, string encodings, sizes around word and pool boundaries, +// and utf16le text with ASCII runs, other BMP characters, surrogate pairs, +// lone surrogates and an odd last byte. + +function show(buf: Uint8Array): string { + return Buffer.from(buf).toString("hex"); +} + +const fills: string[] = []; +for (const size of [0, 1, 2, 3, 7, 8, 9, 31, 32, 33]) { + const buf = Buffer.alloc(size); + buf.fill("ab"); + fills.push(buf.toString("latin1")); + buf.fill(Buffer.from([1, 2, 3])); + fills.push(show(buf)); +} +console.log(fills.join(" ")); + +const ranged = Buffer.alloc(20); +ranged.fill("xyz", 3, 17); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill(Buffer.from("0123456789"), 5); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill("long pattern longer than the range", 2, 6); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill("é中", 0, 11); +console.log(show(ranged)); +ranged.fill("6869", 0, 7, "hex"); +ranged.fill("aGk=", 10, 15, "base64"); +console.log(show(ranged)); +ranged.fill("éa", 0, 20, "latin1"); +console.log(show(ranged)); +ranged.fill("ab", 0, 9, "utf16le"); +console.log(show(ranged)); + +// Patterns that are views of the buffer being filled. +const self = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +self.fill(self.subarray(1, 3)); +console.log(show(self)); +const later = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +later.fill(later.subarray(6, 9), 2); +console.log(show(later)); +const whole = Buffer.from([9, 8, 7, 6]); +whole.fill(whole); +console.log(show(whole)); +const typed = new Uint8Array(12); +typed.set([5, 6, 7], 0); +Buffer.from(typed.buffer).fill(typed.subarray(0, 3), 3); +console.log(show(typed)); + +const big = Buffer.alloc(70001); +big.fill("0123456"); +console.log(big.length, big.toString("latin1", 0, 10), big.toString("latin1", 69990), big.toString("latin1", 35000, 35010)); +try { + Buffer.alloc(4).fill(Buffer.alloc(0)); +} catch (error) { + console.log((error as NodeJS.ErrnoException).code); +} +console.log(show(Buffer.alloc(5).fill(""))); + +// swap16/32/64. +for (const size of [0, 8, 16, 24, 64, 1000]) { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) buf[i] = (i * 37 + 11) & 255; + const a = Buffer.from(buf).swap16(); + const b = Buffer.from(buf).swap32(); + const c = Buffer.from(buf).swap64(); + console.log(size, show(a.subarray(0, 8)), show(b.subarray(0, 8)), show(c.subarray(0, 8)), show(c.subarray(c.length - 8)), a.swap16().equals(buf)); +} +for (const [size, width] of [[3, 16], [6, 32], [12, 64]] as const) { + try { + const buf = Buffer.alloc(size); + if (width === 16) buf.swap16(); + else if (width === 32) buf.swap32(); + else buf.swap64(); + } catch (error) { + console.log((error as NodeJS.ErrnoException).code, (error as Error).message); + } +} +const view = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).subarray(1, 9); +view.swap32(); +console.log(show(view)); + +// toString("utf16le"). +const texts = [ + "plain ascii text", + "abcéÿĀ߿ࠀ中￿", + "ascii run then 中文 and more ascii after it", + "\u{1F600}\u{10FFFF}a\u{10000}", + "lone \ud800 high and \udc00 low and \ud83d", + "x".repeat(4097) + "é" + "y".repeat(3), +]; +for (const text of texts) { + const bytes = Buffer.from(text, "utf16le"); + const back = bytes.toString("utf16le"); + console.log(back === text, back.length, bytes.length, JSON.stringify(back.slice(0, 24)), back.charCodeAt(back.length - 1)); +} +const odd = Buffer.from([0x61, 0, 0x62, 0, 0x63]); +console.log(JSON.stringify(odd.toString("utf16le")), odd.toString("utf16le").length); +const pairSplit = Buffer.from([0x3d, 0xd8, 0x00, 0xde, 0x3d, 0xd8]); +const split = pairSplit.toString("utf16le"); +console.log(split.length, split.codePointAt(0), split.charCodeAt(2)); +const units = Buffer.alloc(40); +for (let i = 0; i < 40; i++) units[i] = (i * 53 + 7) & 255; +const decoded = units.toString("utf16le"); +console.log(decoded.length, [...decoded].map((c) => c.codePointAt(0)).join(",")); +console.log(units.toString("utf16le", 3, 21).length, units.toString("ucs2", 0, 8) === units.toString("utf16le", 0, 8)); diff --git a/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json b/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json new file mode 100644 index 000000000..2c773a1b8 --- /dev/null +++ b/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json @@ -0,0 +1 @@ +{ "plain": 4406, "san": 2190 } From 872c12e247952524485022a9e1af90c89ce05c3e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:59:55 -0500 Subject: [PATCH 216/395] Emit dev builds' function bodies on the emit workers --- .../compiler/src/backend/llvm/debug-info.ts | 104 ++++++++++++++++- .../compiler/src/backend/llvm/emit-journal.ts | 44 ++++++- .../compiler/src/backend/llvm/emit-module.ts | 8 +- packages/compiler/src/backend/llvm/emitter.ts | 32 ++++-- .../compiler/test/chunked-emission.test.ts | 108 +++++++++++------- 5 files changed, 232 insertions(+), 64 deletions(-) diff --git a/packages/compiler/src/backend/llvm/debug-info.ts b/packages/compiler/src/backend/llvm/debug-info.ts index 3a75c6263..aaa35d666 100644 --- a/packages/compiler/src/backend/llvm/debug-info.ts +++ b/packages/compiler/src/backend/llvm/debug-info.ts @@ -13,9 +13,37 @@ import { import { SourceLocations } from "../source-locations.js"; import { mangleFunction } from "../mangle.js"; -/** DWARF describes source bindings at their actual native storage locations. */ +/** A request a range emitter made of its debug info that added nodes + * (LlvmDebugInfo.record), with the ids it answered. */ +export type DebugRequest = + | { readonly op: "function"; readonly fn: string; readonly result: string } + | { + readonly op: "local"; + readonly local: IrLocal; + readonly fn: string; + readonly arg: number; + readonly captured: boolean; + readonly result: { variable: string; location: string }; + } + | { + readonly op: "location"; + readonly loc: SrcLoc; + readonly fn: string; + readonly result: string; + }; + +/** DWARF describes source bindings at their actual native storage locations. + * + * Nodes are numbered in request order. A range emitter (emit-module.ts) + * records the requests that added nodes; the emitter that merges the range + * replays them in order, so every node gets the number serial emission + * gives it, and renames the range's text from the ids the range answered to + * its own. A request that added no node answered an id that existed before + * the range started or that an earlier recorded request created. */ export class LlvmDebugInfo { private readonly nodes: string[] = []; + /** The requests that added nodes, while recording (record). */ + private requests: DebugRequest[] | null = null; private readonly files = new Map(); private readonly locations = new Map(); private readonly scopes = new Map(); @@ -73,7 +101,53 @@ export class LlvmDebugInfo { return unit; } + /** Records the requests that add nodes from here on. */ + record(): void { + this.requests = []; + } + + /** The requests recorded since record. */ + recorded(): readonly DebugRequest[] { + return this.requests ?? []; + } + + /** Replays a range's recorded requests in order and adds to `ids` the + * rename from the ids the range answered to this debug info's. */ + replay( + requests: readonly DebugRequest[], + functionNamed: (name: string) => IrFunction, + ids: Map, + ): void { + const own = (id: string, to: string | null | undefined): void => { + if (to === null || to === undefined) + throw new Error(`llvm emitter bug: debug request for ${id} added no node on replay`); + if (to !== id) ids.set(id, to); + }; + for (const request of requests) { + if (request.op === "function") { + own(request.result, this.function(functionNamed(request.fn))); + continue; + } + const fn = ids.get(request.fn) ?? request.fn; + if (request.op === "location") { + own(request.result, this.location(request.loc, fn)); + continue; + } + const local = this.local(request.local, fn, request.arg, request.captured); + own(request.result.variable, local?.variable); + own(request.result.location, local?.location); + } + } + function(fn: IrFunction): string | null { + const before = this.nodes.length; + const id = this.functionNode(fn); + if (this.requests !== null && id !== null && this.nodes.length > before) + this.requests.push({ op: "function", fn: fn.name, result: id }); + return id; + } + + private functionNode(fn: IrFunction): string | null { const pos = this.source.position(fn.loc); if (pos === null) return null; const file = this.file(pos.file); @@ -230,6 +304,26 @@ export class LlvmDebugInfo { fn: string | null, arg = 0, captured = false, + ): { variable: string; expression: string; location: string } | null { + const before = this.nodes.length; + const result = this.localNodes(local, fn, arg, captured); + if (this.requests !== null && fn !== null && result !== null && this.nodes.length > before) + this.requests.push({ + op: "local", + local, + fn, + arg, + captured, + result: { variable: result.variable, location: result.location }, + }); + return result; + } + + private localNodes( + local: IrLocal, + fn: string | null, + arg: number, + captured: boolean, ): { variable: string; expression: string; location: string } | null { if (fn === null || local.source === undefined) return null; const pos = this.source.position(local.source.loc); @@ -279,6 +373,14 @@ export class LlvmDebugInfo { } location(loc: SrcLoc, fn: string | null): string | null { + const before = this.nodes.length; + const id = this.locationNode(loc, fn); + if (this.requests !== null && fn !== null && id !== null && this.nodes.length > before) + this.requests.push({ op: "location", loc, fn, result: id }); + return id; + } + + private locationNode(loc: SrcLoc, fn: string | null): string | null { if (fn === null) return null; const pos = this.source.position(loc); if (pos === null) return null; diff --git a/packages/compiler/src/backend/llvm/emit-journal.ts b/packages/compiler/src/backend/llvm/emit-journal.ts index ad89d2671..d6fed03a6 100644 --- a/packages/compiler/src/backend/llvm/emit-journal.ts +++ b/packages/compiler/src/backend/llvm/emit-journal.ts @@ -5,6 +5,7 @@ * replay calls the same entry point with the same arguments, so every * helper gets the number and the definition the serial emission gives it, * and the range's text is renamed to those numbers. */ +import type { DebugRequest } from "./debug-info.js"; import type { LlEmitter } from "./emitter.js"; /** One event of a range's emission, in order. */ @@ -97,6 +98,8 @@ export class EmitJournal { * worker process can write it. */ export interface ChunkResult { readonly entries: readonly JournalEntry[]; + /** The requests that added debug metadata nodes (LlvmDebugInfo.record). */ + readonly debugRequests: readonly DebugRequest[]; readonly flags: { readonly usesCoroutines: boolean; readonly usesTimers: boolean; @@ -152,23 +155,56 @@ export function replayJournal( } /** The rename of a range's text: numbered names (`names`, without the - * sigil) and field alias tags (`tags`). One scan for each kind of name: - * a numbered name starts `sc_` after a character that cannot be part of a - * name, and ends at the last digit after its counter prefix. */ + * sigil), field alias tags (`tags`) and debug metadata ids (`debugIds`, + * `!N`). One scan for each kind of name: a numbered name starts `sc_` + * after a character that cannot be part of a name, and ends at the last + * digit after its counter prefix. */ export function renameChunk( names: ReadonlyMap, tags: ReadonlyMap, + debugIds: ReadonlyMap = new Map(), ): (text: string) => string { const prefixes = new Set(); for (const name of names.keys()) prefixes.add(name.replace(/\d+$/, "")); - if (prefixes.size === 0 && tags.size === 0) return (text) => text; + if (prefixes.size === 0 && tags.size === 0 && debugIds.size === 0) return (text) => text; return (text) => { let out = prefixes.size === 0 ? text : renameNames(text, names, prefixes); if (tags.size > 0) out = renameTags(out, tags); + if (debugIds.size > 0) out = renameDebugIds(out, debugIds); return out; }; } +/** Renames metadata references `!N` outside string literals. Debug builds + * attach no alias tags, so every such reference in a function's text is a + * debug metadata id. */ +function renameDebugIds(text: string, ids: ReadonlyMap): string { + const parts: string[] = []; + let copied = 0; + // The next quote at or after the scan, and whether the scan is inside a + // string literal. + let quote = text.indexOf('"'); + let quoted = false; + for (let at = text.indexOf("!"); at >= 0; at = text.indexOf("!", at + 1)) { + while (quote >= 0 && quote < at) { + quoted = !quoted; + quote = text.indexOf('"', quote + 1); + } + if (quoted) continue; + if (at > 0 && isNameChar(text.charCodeAt(at - 1))) continue; + let end = at + 1; + while (end < text.length && isDigit(text.charCodeAt(end))) end++; + if (end === at + 1 || (end < text.length && isNameChar(text.charCodeAt(end)))) continue; + const own = ids.get(text.slice(at, end)); + if (own === undefined) continue; + parts.push(text.slice(copied, at), own); + copied = end; + } + if (copied === 0) return text; + parts.push(text.slice(copied)); + return parts.join(""); +} + function renameNames( text: string, names: ReadonlyMap, diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index dfdd95451..6a33924c3 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -134,6 +134,7 @@ function emitFunctionsOnWorkers(emitter: LlEmitter, workers: number): string[] | let code = 1; try { emitter.journal = new EmitJournal(); + emitter.debug?.record(); const texts: string[] = []; for (const fn of functions.slice(range * size, (range + 1) * size)) { texts.push(emitter.emitFunction(fn)); @@ -230,13 +231,10 @@ const PARTITION_GROUP_END = "; scriptc-group"; 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. - // Debug metadata is numbered per emitter and not journaled, so a debug - // build emits every body here. - const ranges = emitter.debug === null; const chunks = emitter.targetOptions.functionChunks ?? 1; const fnDefs = - (ranges && chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ?? - (ranges ? emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) : null) ?? + (chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ?? + emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) ?? []; if (fnDefs.length === 0) for (const fn of emitter.mod.functions) { diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index d1f4ebcca..8dc1f912a 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -476,14 +476,13 @@ export interface LlvmTargetOptions { * earlier emission found breaking its rules (UnwindAbiViolation). */ unwindExcluded?: ReadonlySet; /** Emit the function bodies in this many contiguous ranges, each by its - * own emitter, and merge them in function order (emit-journal.ts). The - * output is the same as one emitter's. Debug builds (`debugSources`) - * emit in one emitter. */ + * own emitter, and merge them in function order (emit-journal.ts, and + * debug-info.ts for debug metadata). The output is the same as one + * emitter's. */ functionChunks?: number; /** Emit the function bodies of a large module on this many forked worker * processes at a time (emit-module.ts), forked with `forkPrimitives`. The - * output is the same as one emitter's. Debug builds (`debugSources`) - * emit in one emitter. */ + * output is the same as one emitter's. */ functionWorkers?: number; /** The host's process primitives, where it can fork (the native * compiler's). */ @@ -1889,9 +1888,7 @@ export class LlEmitter { this.journal = journal; this.journaled = journal !== null || - ((options.functionWorkers ?? 1) > 1 && - options.forkPrimitives !== undefined && - options.debugSources === undefined); + ((options.functionWorkers ?? 1) > 1 && options.forkPrimitives !== undefined); // One walk of every body, which the analyses below read instead of // walking the bodies themselves. this.nodeIndex = int32Analysis?.nodeIndex ?? new IrNodeIndex(mod); @@ -5264,6 +5261,7 @@ export class LlEmitter { chunkResult(): ChunkResult { return { entries: this.journal!.entries, + debugRequests: this.debug?.recorded() ?? [], flags: { usesCoroutines: this.usesCoroutines, usesTimers: this.usesTimers, @@ -5299,6 +5297,8 @@ export class LlEmitter { // emission left them (a helper set guards the declarations its first // request adds). replayJournal(this, chunk.entries, names, tags); + const debugIds = new Map(); + this.debug?.replay(chunk.debugRequests, (name) => this.functionNamed(name), debugIds); const flags = chunk.flags; this.usesCoroutines ||= flags.usesCoroutines; this.usesTimers ||= flags.usesTimers; @@ -5319,7 +5319,13 @@ export class LlEmitter { for (const name of chunk.adoptCallees) this.adoptCallees.add(name); for (const name of chunk.directAwaitCallees) this.directAwaitCallees.add(name); for (const name of chunk.inlineAwaitCallees) this.inlineAwaitCallees.add(name); - return renameChunk(names, tags); + return renameChunk(names, tags, debugIds); + } + + private functionNamed(name: string): IrFunction { + const fn = this.fnByName.get(name); + if (fn === undefined) throw new InternalCompilerError(`llvm emitter bug: no function ${name}`); + return fn; } /** Adds a content-named constant a chunk interned. Its symbol is this @@ -5447,7 +5453,13 @@ export class LlEmitter { /** An emitter for a range of this module's functions: the same facts, * empty module-level tables, and a journal of what the range adds. */ chunkEmitter(): LlEmitter { - return new LlEmitter(this.mod, { ...this.targetOptions, functionChunks: 1 }, new EmitJournal()); + const chunk = new LlEmitter( + this.mod, + { ...this.targetOptions, functionChunks: 1 }, + new EmitJournal(), + ); + chunk.debug?.record(); + return chunk; } /** The double-ABI entry of a function with an i32 entry: every caller diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 6d2b2e06a..567a082ef 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -103,57 +103,77 @@ test("a Promise.all tuple adapter requested in a range is defined", async () => } }, 60_000); -// Debug metadata is numbered per emitter (debug-info.ts) and not journaled: -// a debug build emits its function bodies in one emitter. Its one fork -// computes the int32 slot analysis; when the fork fails, the emitter -// computes it itself. -test("debug builds emit function bodies serially, with every metadata reference defined", async () => { +// Debug builds journal their metadata requests (debug-info.ts): ranges +// merged in function order number every node as one emitter does. +const DEBUG_PROGRAMS: { entry: string; sources: string[] }[] = [ + { + entry: "3066-source-debugging/main.ts", + sources: ["3066-source-debugging/main.ts", "3066-source-debugging/helper.ts"], + }, + ...[ + "1020-async-basics.ts", + "7103-fiberless-plain-calls.ts", + "7110-als-completion-callbacks.ts", + "1301-errors-subclass.ts", + "1529-class-with-statics.ts", + "1591-js-closures.js", + "2010-generators-basics.ts", + "2013-generators-sent-values.ts", + "4451-int32-field-storage.ts", + ].map((program) => ({ entry: program, sources: [program] })), +]; + +test("debug builds emitted in ranges merge into the text one emitter gives", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-")); const minFunctions = process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; try { - const corpus = join(import.meta.dirname, "../../../tests/corpus/3066-source-debugging"); - const entry = join(corpus, "main.ts"); - const outPath = join(dir, "main.ir.json"); - const result = await compile(entry, { - outDir: dir, - outPath, - outputKind: "ir", - optimization: "dev", - }); - if (!result.ok) - throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); - const ir = await readFile(outPath, "utf8"); - const debugSources = new Map(); - for (const name of ["main.ts", "helper.ts"]) - debugSources.set(join(corpus, name), await readFile(join(corpus, name), "utf8")); - const options: LlvmTargetOptions = { debugSources }; - const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); - expect(serial).toContain("!DILocation"); + const corpus = join(import.meta.dirname, "../../../tests/corpus"); + for (const program of DEBUG_PROGRAMS) { + const outPath = join(dir, "main.ir.json"); + const result = await compile(join(corpus, program.entry), { + outDir: dir, + outPath, + outputKind: "ir", + optimization: "dev", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ir = await readFile(outPath, "utf8"); + const debugSources = new Map(); + for (const name of program.sources) + debugSources.set(join(corpus, name), await readFile(join(corpus, name), "utf8")); + const options: LlvmTargetOptions = { debugSources }; + const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); + expect(serial, program.entry).toContain("!DILocation"); - let forks = 0; - process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = "1"; - const forkPrimitives = { - fork: (): number => { - forks++; - return -1; - }, - exit: (): void => {}, - wait: (): number => -1, - }; - for (const variant of [ - { functionChunks: 3 }, - { functionWorkers: 4, forkPrimitives }, - ] satisfies LlvmTargetOptions[]) { - const emitted = text(emitLlvmModuleSource(deserializeModule(ir), { ...options, ...variant })); - expect(emitted === serial, JSON.stringify(Object.keys(variant))).toBe(true); - const defined = new Set([...emitted.matchAll(/^(![0-9]+) = /gm)].map((m) => m[1])); - const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1])); - expect([...referenced].filter((id) => !defined.has(id))).toEqual([]); + // A fork that fails leaves every range to the merging emitter. + process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = "1"; + const forkPrimitives = { + fork: (): number => -1, + exit: (): void => {}, + wait: (): number => -1, + }; + for (const variant of [ + { functionChunks: 2 }, + { functionChunks: 5 }, + { functionWorkers: 4, forkPrimitives }, + ] satisfies LlvmTargetOptions[]) { + const emitted = text( + emitLlvmModuleSource(deserializeModule(ir), { ...options, ...variant }), + ); + expect(emitted === serial, `${program.entry} ${JSON.stringify(Object.keys(variant))}`).toBe( + true, + ); + const defined = new Set([...emitted.matchAll(/^(![0-9]+) = /gm)].map((m) => m[1])); + const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1])); + expect([...referenced].filter((id) => !defined.has(id))).toEqual([]); + } + if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; + else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions; } - expect(forks).toBe(1); } finally { if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions; await rm(dir, { recursive: true, force: true }); } -}, 120_000); +}, 300_000); From 627ebd86a51546f2fd6d40a82caab8191ceee667 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:16:32 -0500 Subject: [PATCH 217/395] Call replace callbacks with unannotated parameters directly TypeScript types the parameters of an inline replace callback after the match `any`. An unannotated parameter that is never reassigned, whose every read means the same for a string or number as for `any`, and that receives a capture every match sets, the offset or the subject now takes that value's type. String-search callbacks get the same treatment for the offset and the subject. scr_regex_replace_fn calls the closure directly in more cases: - checked-dynamic parameters, which receive the boxed value Node passes at their position (a capture or undefined, the offset, the subject, the groups object, then undefined); - `string | undefined` capture parameters, which receive NULL when the match leaves the capture unset; - a checked-dynamic result, converted with ToString; - up to six parameters. Callbacks of the earlier typed form compile to the same call, with a kinds argument of 0, and run the same per-match work. --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/regex-replacers.json | 6 + .../runtime/workloads/regex-replacers.ts | 60 ++++ .../compiler/src/backend/llvm/lib-dispatch.ts | 77 +++-- .../containers/replacer-params.test.ts | 63 ++++ .../lowering/containers/replacer-params.ts | 292 ++++++++++++++++++ .../lowering/containers/string-and-regexp.ts | 7 +- .../lowering/containers/string-replacement.ts | 14 +- .../compiler/test/replacer-params.test.ts | 84 +++++ .../tests/corpus/8118-replacer-params.ts.json | 6 + ...45-replace-callback-untyped-params.ts.json | 6 + ...6-replace-callback-untyped-params.mjs.json | 6 + packages/runtime/src/scr_regex.c | 203 +++++++++--- packages/runtime/src/scr_regex.h | 26 +- tests/corpus/8118-replacer-params.ts | 43 +++ .../8345-replace-callback-untyped-params.ts | 85 +++++ .../8346-replace-callback-untyped-params.mjs | 32 ++ .../corpus-costs/8118-replacer-params.ts.json | 1 + ...45-replace-callback-untyped-params.ts.json | 1 + ...6-replace-callback-untyped-params.mjs.json | 1 + 20 files changed, 941 insertions(+), 73 deletions(-) create mode 100644 benchmarks/runtime/workloads/regex-replacers.json create mode 100644 benchmarks/runtime/workloads/regex-replacers.ts create mode 100644 packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts create mode 100644 packages/compiler/src/frontend/lowering/containers/replacer-params.ts create mode 100644 packages/compiler/test/replacer-params.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json create mode 100644 tests/corpus/8118-replacer-params.ts create mode 100644 tests/corpus/8345-replace-callback-untyped-params.ts create mode 100644 tests/corpus/8346-replace-callback-untyped-params.mjs create mode 100644 tests/harness/corpus-costs/8118-replacer-params.ts.json create mode 100644 tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json create mode 100644 tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 520964cf3..af3d3d239 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -30,6 +30,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `record-boundaries` | cpu | records built from unchecked array reads passed to and returned from annotated functions | | `records-sort` | cpu | comparator sorts, group-by, `reduce`/`filter` | | `regex-logs` | cpu | regex `exec` with groups, `test`, global `replace` | +| `regex-replacers` | cpu | regex `replace`/`replaceAll` with inline callbacks whose parameters have no annotations: dictionary placeholders, snake_case to camelCase, date reordering, optional captures, capitalization, and a string search that reads the offset | | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | | `template-render` | cpu | template literals, escaping, `map`/`join` | | `throw-chains` | cpu | exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and through a callback, and a backtracking parser that throws on a failed alternative | diff --git a/benchmarks/runtime/workloads/regex-replacers.json b/benchmarks/runtime/workloads/regex-replacers.json new file mode 100644 index 000000000..c01c6c272 --- /dev/null +++ b/benchmarks/runtime/workloads/regex-replacers.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/regex-replacers.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/regex-replacers.ts b/benchmarks/runtime/workloads/regex-replacers.ts new file mode 100644 index 000000000..be3820b89 --- /dev/null +++ b/benchmarks/runtime/workloads/regex-replacers.ts @@ -0,0 +1,60 @@ +// Regex replace and replaceAll with inline callbacks, written as they +// usually are, without parameter annotations: template placeholders looked +// up in a dictionary, snake_case to camelCase, date reordering, optional +// version parts, capitalization and a string search with its offset. +let seed = 7; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +function pick(items: readonly T[]): T { + return items[Math.floor(random() * items.length)]!; +} + +const words = ["user", "order", "item", "cart", "price", "total", "status", "created", "updated", "id"]; +const names = ["Ada", "Grace", "Linus", "Barbara", "Ken", "Margaret"]; +const vars: Record = { + name: "world", + city: "Lisbon", + day: "Tuesday", + plan: "pro", + count: "12", +}; +const keys = [...Object.keys(vars), "missing"]; + +const scale = Number(process.argv[2] ?? "1"); +const count = Math.floor(20000 * scale); +const templates: string[] = []; +const identifiers: string[] = []; +const dates: string[] = []; +const versions: string[] = []; +const sentences: string[] = []; +for (let i = 0; i < count; i++) { + templates.push(`Hello {${pick(keys)}}, your {${pick(keys)}} plan renews on {${pick(keys)}} in {${pick(keys)}}.`); + identifiers.push(`${pick(words)}_${pick(words)}_${pick(words)}${random() < 0.5 ? "_" + pick(words) : ""}`); + const year = 1990 + Math.floor(random() * 40); + const month = String(1 + Math.floor(random() * 12)).padStart(2, "0"); + const day = String(1 + Math.floor(random() * 28)).padStart(2, "0"); + dates.push(`from ${year}-${month}-${day} to ${year + 1}-${month}-${day} (${pick(names)})`); + versions.push(`v${i % 9}.${i % 13}${random() < 0.5 ? "." + (i % 7) : ""} and v${i % 5}.${i % 3}`); + sentences.push(`${pick(names)} said the ${pick(words)} and the ${pick(words)} were ready for ${pick(names)}`); +} + +let length = 0; +let checksum = 0; +for (let round = 0; round < 4; round++) { + for (let i = 0; i < count; i++) { + const filled = templates[i]!.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`); + const camel = identifiers[i]!.replace(/_([a-z])/g, (_m, c) => c.toUpperCase()); + const reordered = dates[i]!.replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, m, d) => `${d}/${m}/${y}`); + const padded = versions[i]!.replace(/v(\d+)\.(\d+)(?:\.(\d+))?/g, (_m, major, minor, patch) => `v${major}.${minor}.${patch ?? "0"}`); + const titled = sentences[i]!.replace(/\b[a-z]/g, (c) => c.toUpperCase()); + const marked = sentences[i]!.replaceAll("the", (m, at) => (at % 2 === 0 ? m.toUpperCase() : m)); + length += filled.length + camel.length + reordered.length + padded.length + titled.length + marked.length; + checksum = (checksum * 31 + camel.charCodeAt(camel.length - 1) + padded.charCodeAt(padded.length - 1)) >>> 0; + } +} +console.log(templates[0]!.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log(identifiers[1]!.replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log(versions[2]!.replace(/v(\d+)\.(\d+)(?:\.(\d+))?/g, (_m, major, minor, patch) => `v${major}.${minor}.${patch ?? "0"}`)); +console.log("length", length, "checksum", checksum); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 8a98fb154..4af8182a7 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -843,35 +843,68 @@ function emitAssertFailing( * `a.name.localeCompare(b.name)` reads both fields without retaining). */ const READ_ONLY_STRING_LIB_FNS: ReadonlySet = new Set(["str.localeCompare"]); -/** The parameter count of a replace callback the runtime can call through - * the closure ABI (scr_regex_replace_fn), or null when it needs the dyn - * box. Each declared parameter must receive a value of its declared kind - * in every match: the match string at 0; with more than one parameter the +/** The most parameters scr_regex_replace_fn calls a replacer with + * (SCR_REPLACER_MAX_ARITY), the first bit of its string-or-undefined + * parameters (SCR_REPLACER_OPTIONAL), and its flag for a checked-dynamic + * result (SCR_REPLACER_BOXED_RESULT). */ +const REPLACER_MAX_ARITY = 6; +const REPLACER_OPTIONAL = 2 ** 8; +const REPLACER_BOXED_RESULT = 2 ** 31; + +/** `string | undefined` held as the string pointer, NULL for undefined. */ +function optionalStringType(host: LlvmEmitterContext, type: IrType): boolean { + if (type.kind !== "union") return false; + const nullable = host.nullableUnions.get(type.unionId); + if (nullable?.repr !== "single" || nullable.arm.kind !== "string" || nullable.nullTag >= 0) + return false; + return host.unionsById.get(type.unionId)?.arms[nullable.unitTag]?.kind === "undefinedT"; +} + +/** The parameter count and kinds (scr_regex_replace_fn's `kinds`) of a + * replace callback the runtime can call through the closure ABI, or null + * when it needs the dyn box. A checked-dynamic parameter receives whatever + * Node passes at its position. Each typed parameter must receive a value of + * its declared kind in every match: the match string at 0; past 0 the * pattern must be a literal whose captures are known, so position i is a - * capture that every match sets (a string), the offset (a number) right - * after the captures, or the subject (a string) after that. */ -function typedReplacerArity(callback: IrType, pattern: IrExpr): number | null { + * capture (a string when every match sets it, otherwise `string | + * undefined`), the offset (a number) right after the captures, or the + * subject (a string) after that. The result is a string or a + * checked-dynamic value. */ +function typedReplacerShape( + host: LlvmEmitterContext, + callback: IrType, + pattern: IrExpr, +): { arity: number; kinds: number } | null { if (callback.kind !== "func" || callback.rest || callback.argumentsAll) return null; - if (callback.ret.kind !== "string") return null; + if (callback.ret.kind !== "string" && callback.ret.kind !== "dyn") return null; const params = callback.params; - if (params.length > 4) return null; - if (params.length === 0) return 0; - if (params[0]!.kind !== "string") return null; - if (params.length === 1) return 1; - if (pattern.kind !== "regexLit") return null; - const captures = regexLiteralCaptures(pattern.pattern, pattern.flags); - if (!captures) return null; - for (let i = 1; i < params.length; i++) { + if (params.length > REPLACER_MAX_ARITY) return null; + let kinds = callback.ret.kind === "dyn" ? REPLACER_BOXED_RESULT : 0; + let captures: { count: number; always: boolean[] } | null | undefined; + for (let i = 0; i < params.length; i++) { const kind = params[i]!.kind; + if (kind === "dyn") { + kinds += 2 ** i; + continue; + } + const optional = optionalStringType(host, params[i]!); + if (i === 0) { + if (kind !== "string") return null; + continue; + } + if (pattern.kind !== "regexLit") return null; + captures ??= regexLiteralCaptures(pattern.pattern, pattern.flags); + if (!captures) return null; const ok = i < captures.count - ? kind === "string" && captures.always[i] === true + ? (kind === "string" && captures.always[i] === true) || optional : i === captures.count ? kind === "f64" : i === captures.count + 1 && kind === "string"; if (!ok) return null; + if (optional) kinds += REPLACER_OPTIONAL * 2 ** i; } - return params.length; + return { arity: params.length, kinds }; } /** `s.replace(re, fn)` / `replaceAll` with a typed callback the runtime can @@ -881,16 +914,16 @@ function typedReplacerArity(callback: IrType, pattern: IrExpr): number | null { function emitTypedRegexReplace(host: LlvmEmitterContext, e: LibCallExpr): LlValue | null { const [subject, pattern, callback, all] = e.args; if (!subject || !pattern || callback?.kind !== "dynFrom" || all?.kind !== "boolLit") return null; - const arity = typedReplacerArity(callback.value.type, pattern); - if (arity === null) return null; + const shape = typedReplacerShape(host, callback.value.type, pattern); + if (shape === null) return null; const B = host.B; const s = host.emitExpr(subject); const re = host.emitExpr(pattern); const fn = host.emitExpr(callback.value); - host.declare(`declare ptr @scr_regex_replace_fn(ptr, ptr, ptr, i32, i1 zeroext)`); + host.declare(`declare ptr @scr_regex_replace_fn(ptr, ptr, ptr, i32, i32, i1 zeroext)`); const result = B.tmp(); B.line( - `${result} = call ptr @scr_regex_replace_fn(ptr ${s.name}, ptr ${re.name}, ptr ${fn.name}, i32 ${arity}, i1 zeroext ${all.value})`, + `${result} = call ptr @scr_regex_replace_fn(ptr ${s.name}, ptr ${re.name}, ptr ${fn.name}, i32 ${shape.arity}, i32 ${shape.kinds | 0}, i1 zeroext ${all.value})`, ); const output = host.own({ name: result, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts new file mode 100644 index 000000000..1d5d376e9 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test } from "vitest"; +import { regexCaptureParticipation } from "./replacer-params.js"; + +describe("regex capture participation", () => { + test("counts captures and marks the ones every match sets", () => { + expect(regexCaptureParticipation("\\{(\\w+)\\}", "g")).toEqual({ + count: 2, + always: [true, true], + }); + expect(regexCaptureParticipation("(\\d{4})-(\\d{2})(?:-(\\d{2}))?", "g")).toEqual({ + count: 4, + always: [true, true, true, false], + }); + expect(regexCaptureParticipation("(a)|(b)", "")).toEqual({ + count: 3, + always: [true, false, false], + }); + expect(regexCaptureParticipation("((a)|b)", "")).toEqual({ + count: 3, + always: [true, true, false], + }); + expect(regexCaptureParticipation("(?:(a)|b)(c)", "")).toEqual({ + count: 3, + always: [true, false, true], + }); + expect(regexCaptureParticipation("(a)*(b)+(c){0,2}(d){1,3}(e){00}", "")).toEqual({ + count: 6, + always: [true, false, true, false, true, false], + }); + expect(regexCaptureParticipation("(?=(a))(?!(b))(?<=(c))(?e)", "")).toEqual({ + count: 6, + always: [true, false, false, false, false, true], + }); + }); + + test("skips escapes, classes and non-capturing groups", () => { + expect(regexCaptureParticipation("\\((a)\\)[(|)\\]](?:b)(?i:c)", "")).toEqual({ + count: 2, + always: [true, true], + }); + expect(regexCaptureParticipation("[](a)", "")).toEqual({ count: 2, always: [true, true] }); + expect(regexCaptureParticipation("(a)[", "")).toBeNull(); + expect(regexCaptureParticipation("(a", "")).toBeNull(); + expect(regexCaptureParticipation("a)", "")).toBeNull(); + expect(regexCaptureParticipation("(a)", "v")).toBeNull(); + }); + test("marks captures in alternations, optional groups and lookarounds", () => { + const always = (pattern: string): boolean[] | null => + regexCaptureParticipation(pattern, "")?.always ?? null; + expect(always("tag(\\d+)")).toEqual([true, true]); + expect(always("((a)b)+c")).toEqual([true, true, true]); + expect(always("(?\\d)(x)")).toEqual([true, true, true]); + expect(always("(a){2,3}")).toEqual([true, true]); + expect(always("(?:x|y)(a)")).toEqual([true, true]); + expect(always("(?:(a)|b)c")).toEqual([true, false]); + expect(always("(x)?(\\d)")).toEqual([true, false, true]); + expect(always("(?:(t)a)*g")).toEqual([true, false]); + expect(always("((a)?)")).toEqual([true, true, false]); + expect(always("(?<=(-))(\\w)")).toEqual([true, false, true]); + expect(always("[\\]|(](b)")).toEqual([true, true]); + expect(always("[^)](c)\\|")).toEqual([true, true]); + }); +}); diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts new file mode 100644 index 000000000..e84ca1461 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts @@ -0,0 +1,292 @@ +import * as ts from "../../ts7/adapter.js"; +import { F64, STRING, type IrExpr, type IrType } from "../../../ir/ir.js"; +import type { Lowerer } from "../lowerer.js"; +import { bindingNeverReassigned } from "../binding-analysis.js"; + +/** The captures of a regex literal's pattern (the whole match included) + * and, per capture, whether every successful match sets it. A capture may + * be unset when an enclosing group has alternatives, when an enclosing + * group or the capture itself is quantified with a minimum of zero, or + * when it sits inside a lookaround. Null for the `v` flag (nested + * classes) and for a pattern the scan does not finish. */ +export function regexCaptureParticipation( + pattern: string, + flags: string, +): { count: number; always: boolean[] } | null { + if (flags.includes("v")) return null; + const always: boolean[] = [true]; + /** Open groups: the group's own capture (-1 when it does not capture), + * the captures nested in it, whether it has alternatives, and whether it + * is a lookaround. */ + const frames: { own: number; nested: number[]; alternatives: boolean; lookaround: boolean }[] = [ + { own: -1, nested: [], alternatives: false, lookaround: false }, + ]; + let i = 0; + while (i < pattern.length) { + const c = pattern[i]!; + if (c === "\\") { + i += 2; + continue; + } + if (c === "[") { + // The first unescaped `]` ends the class (`[]` is an empty class). + i++; + while (i < pattern.length && pattern[i] !== "]") i += pattern[i] === "\\" ? 2 : 1; + if (i >= pattern.length) return null; + i++; + continue; + } + if (c === "|") { + frames[frames.length - 1]!.alternatives = true; + i++; + continue; + } + if (c === "(") { + let capture = false; + let lookaround = false; + if (pattern[i + 1] !== "?") { + capture = true; + i++; + } else if (pattern[i + 2] === "<" && pattern[i + 3] !== "=" && pattern[i + 3] !== "!") { + const gt = pattern.indexOf(">", i + 3); + if (gt < 0) return null; + capture = true; + i = gt + 1; + } else { + const kind = pattern[i + 2] === "<" ? pattern[i + 3] : pattern[i + 2]; + lookaround = kind === "=" || kind === "!"; + i += 2; + } + frames.push({ + own: capture ? always.length : -1, + nested: [], + alternatives: false, + lookaround, + }); + if (capture) always.push(true); + continue; + } + if (c === ")") { + if (frames.length === 1) return null; + const frame = frames.pop()!; + i++; + const next = pattern[i]; + const optional = + next === "?" || next === "*" || (next === "{" && /^\{0+[,}]/.test(pattern.slice(i))); + const parent = frames[frames.length - 1]!; + // Alternatives inside the group leave its nested captures unset, but + // not the group's own capture. + for (const index of frame.nested) { + if (optional || frame.lookaround || frame.alternatives) always[index] = false; + parent.nested.push(index); + } + if (frame.own >= 0) { + if (optional || frame.lookaround) always[frame.own] = false; + parent.nested.push(frame.own); + } + continue; + } + i++; + } + if (frames.length !== 1) return null; + if (frames[0]!.alternatives) for (const index of frames[0]!.nested) always[index] = false; + return { count: always.length, always }; +} + +/** String members a refined parameter may use: these lower from the + * receiver's type whatever the checker resolved for an `any` receiver. */ +const STRING_MEMBERS: ReadonlySet = new Set([ + "length", + "toUpperCase", + "toLowerCase", + "trim", + "charCodeAt", + "codePointAt", + "slice", + "substring", + "repeat", + "padStart", + "padEnd", + "includes", + "startsWith", + "endsWith", + "indexOf", +]); + +/** Whether every read of `symbol` in `body` is one that means the same + * for an `any` value and for a `kind` value, and lowers for both: a + * template span, a Number/String/parseInt/parseFloat argument, a listed + * string member, `+`, strict equality, arithmetic and comparisons of + * numbers, a returned value, and a string key of an element access on a + * string-indexed record or an `any` value. Anything else keeps the + * parameter `any`. */ +function readsFit( + lowerer: Lowerer, + body: ts.Node, + symbol: ts.Symbol, + kind: "string" | "f64", +): boolean { + const name = ts.symbolName(symbol); + const primitive = (e: ts.Expression): boolean => { + const flags = lowerer.checker.getTypeAtLocation(e).flags; + return (flags & (ts.TypeFlags.NumberLike | ts.TypeFlags.StringLike)) !== 0; + }; + const fits = (id: ts.Identifier): boolean => { + let node: ts.Node = id; + while (ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + if (ts.isTemplateSpan(parent) || ts.isReturnStatement(parent)) return true; + if (ts.isElementAccessExpression(parent)) { + if (kind !== "string" || parent.argumentExpression !== node) return false; + const receiver = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(parent.expression)); + if (receiver?.kind === "dyn") return true; + const shape = receiver?.kind === "record" ? lowerer.shapes.get(receiver.shapeId) : undefined; + return shape?.indexValue !== undefined && !shape.tuple; + } + if (ts.isArrowFunction(parent)) return parent.body === node; + if (ts.isCallExpression(parent)) + return ( + parent.expression !== node && + ["Number", "String", "parseInt", "parseFloat"].some((global) => + lowerer.isStdlibGlobal(parent.expression, global), + ) + ); + if (ts.isPropertyAccessExpression(parent)) { + const member = parent.name.text; + if (kind !== "string" || parent.expression !== node || !STRING_MEMBERS.has(member)) + return false; + return ( + member === "length" || + (ts.isCallExpression(parent.parent) && parent.parent.expression === parent) + ); + } + if (ts.isBinaryExpression(parent)) { + const op = parent.operatorToken.kind; + if ( + op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken + ) + return true; + const other = parent.left === node ? parent.right : parent.left; + if (op === ts.SyntaxKind.PlusToken) return kind === "string" || primitive(other); + return ( + kind === "f64" && + (op === ts.SyntaxKind.MinusToken || + op === ts.SyntaxKind.AsteriskToken || + op === ts.SyntaxKind.SlashToken || + op === ts.SyntaxKind.PercentToken || + op === ts.SyntaxKind.LessThanToken || + op === ts.SyntaxKind.GreaterThanToken || + op === ts.SyntaxKind.LessThanEqualsToken || + op === ts.SyntaxKind.GreaterThanEqualsToken) && + (lowerer.checker.getTypeAtLocation(other).flags & ts.TypeFlags.NumberLike) !== 0 + ); + } + return false; + }; + let ok = true; + const visit = (n: ts.Node): void => { + if (!ok) return; + if (ts.isIdentifier(n) && n.text === name && lowerer.resolveValueSymbol(n) === symbol) { + if (!fits(n)) ok = false; + return; + } + ts.forEachChild(n, visit); + }; + visit(body); + return ok; +} + +/** The capture layout of a string search: the match, then the offset and + * the subject. */ +export const PLAIN_SEARCH_CAPTURES: { count: number; always: boolean[] } = { + count: 1, + always: [true], +}; + +/** The captures of a regex literal argument (regexCaptureParticipation), + * or null for any other pattern expression. */ +export function regexLiteralCapturesOf( + pattern: ts.Expression, +): { count: number; always: boolean[] } | null { + let regex = pattern; + while (ts.isParenthesizedExpression(regex)) regex = regex.expression; + if (!ts.isRegularExpressionLiteral(regex)) return null; + const text = regex.text; + const slash = text.lastIndexOf("/"); + return regexCaptureParticipation(text.slice(1, slash), text.slice(slash + 1)); +} + +/** Lowers an inline replace callback. JavaScript passes it the match, the + * captures, the match offset and the subject (`captures` gives their + * layout; null lowers the callback as written). TypeScript types the + * parameters after the match `any`, so an unannotated parameter that is + * never reassigned, whose every read fits its value (readsFit), and that + * receives a capture every match sets, the offset or the subject takes + * that value's type: a string or a number. The closure can then be called + * directly (typed replacers in lib-dispatch.ts) with fewer boxed values. */ +export function lowerReplacerCallback( + lowerer: Lowerer, + callback: ts.Expression, + captures: { count: number; always: boolean[] } | null, +): IrExpr { + let node = callback; + while (ts.isParenthesizedExpression(node)) node = node.expression; + // A named function expression can call itself with other arguments. + const literal = + ts.isArrowFunction(node) || (ts.isFunctionExpression(node) && !node.name) ? node : null; + if (!literal || !captures) return lowerer.lowerExpr(callback); + const bound: { symbol: ts.Symbol; previous: IrType | undefined }[] = []; + const previousImplicit = lowerer.implicitParamTypes; + let implicit: Map | null = null; + const params = literal.parameters.nodes; + // A declared `this` parameter takes no argument position. + const first = + params[0] && ts.isIdentifier(params[0].name) && params[0].name.text === "this" ? 1 : 0; + params.slice(first).forEach((param, i) => { + const type = + i === 0 || i > captures.count + 1 + ? null + : i < captures.count + ? captures.always[i] === true + ? STRING + : null + : i === captures.count + ? F64 + : STRING; + if ( + type === null || + !ts.isIdentifier(param.name) || + param.type || + param.initializer || + param.dotDotDotToken || + param.questionToken + ) + return; + if ((lowerer.checker.getTypeAtLocation(param.name).flags & ts.TypeFlags.Any) === 0) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if ( + !symbol || + !bindingNeverReassigned(lowerer, symbol, param) || + !readsFit(lowerer, literal.body, symbol, type === STRING ? "string" : "f64") + ) + return; + bound.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) }); + lowerer.runtimeOptionalBindingTypes.set(symbol, type); + implicit ??= new Map(previousImplicit ?? []); + implicit.set( + symbol, + type === STRING ? lowerer.checker.getStringType() : lowerer.checker.getNumberType(), + ); + }); + if (implicit !== null) lowerer.implicitParamTypes = implicit; + try { + return lowerer.lowerExpr(callback); + } finally { + for (const { symbol, previous } of bound) { + if (previous === undefined) lowerer.runtimeOptionalBindingTypes.delete(symbol); + else lowerer.runtimeOptionalBindingTypes.set(symbol, previous); + } + lowerer.implicitParamTypes = previousImplicit; + } +} diff --git a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts index d55f71ff0..12885418b 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts @@ -33,6 +33,7 @@ import { isSafeToDiscard } from "../expressions/evaluation-safety.js"; import { STRING_INDEX_METHODS, STRING_REPLACE_METHODS, STR_METHODS } from "../surfaces.js"; import { execResultCaptureMask } from "./exec-result-uses.js"; import { lowerStringReplacement } from "./string-replacement.js"; +import { lowerReplacerCallback, regexLiteralCapturesOf } from "./replacer-params.js"; import { coerceStringSearchValue, defaultAfterUndefined, @@ -1025,7 +1026,11 @@ export function lowerRegexMethodCall( : [ lowerer.lowerExprExpecting(arg0, { kind: "regex" }), call.arguments.nodes[1] - ? lowerer.lowerExpr(call.arguments.nodes[1]) + ? lowerReplacerCallback( + lowerer, + call.arguments.nodes[1], + regexLiteralCapturesOf(arg0), + ) : dynUndefinedExpr(loc), ]; if (name !== "split") { diff --git a/packages/compiler/src/frontend/lowering/containers/string-replacement.ts b/packages/compiler/src/frontend/lowering/containers/string-replacement.ts index 156f74869..c21808464 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-replacement.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-replacement.ts @@ -18,6 +18,7 @@ import { import { dynUndefinedExpr, numLit, strLit, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import { locOf } from "../../program.js"; +import { PLAIN_SEARCH_CAPTURES, lowerReplacerCallback } from "./replacer-params.js"; import type { Lowerer } from "../lowerer.js"; import { coerceStringSearchValue, @@ -48,11 +49,18 @@ export function lowerStringReplacement( type.kind === "bool" || type.kind === "bigint" || isUnitType(type); - const argument = (node: ts.Expression | undefined, callback: boolean): IrExpr => { + const argument = ( + node: ts.Expression | undefined, + callback: boolean, + plainSearch = false, + ): IrExpr => { if (!node) return strLit("undefined", loc); const absent = lowerStaticallyUndefinedArgument(lowerer, node); if (absent) return defaultAfterUndefined(absent, strLit("undefined", loc)); - const value = lowerer.lowerExpr(node); + // A plain search passes (match, offset, subject). + const value = plainSearch + ? lowerReplacerCallback(lowerer, node, PLAIN_SEARCH_CAPTURES) + : lowerer.lowerExpr(node); if (value.type.kind === "dyn" || (callback && value.type.kind === "func")) return value; if (!primitive(value.type)) { lowerer.noLowering( @@ -64,7 +72,7 @@ export function lowerStringReplacement( return coerceStringSearchValue(lowerer, value, node, loc); }; const search = argument(args[0], false); - const replacement = argument(args[1], true); + const replacement = argument(args[1], true, search.type.kind !== "dyn"); if (search.type.kind === "dyn" || replacement.type.kind === "dyn") { // Evaluate all inputs once before choosing the search protocol. Regexes // retain their native pattern; plain values use the string-search helper. diff --git a/packages/compiler/test/replacer-params.test.ts b/packages/compiler/test/replacer-params.test.ts new file mode 100644 index 000000000..bb5549a86 --- /dev/null +++ b/packages/compiler/test/replacer-params.test.ts @@ -0,0 +1,84 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, test } from "vitest"; +import { compile } from "../src/index.js"; +import { regexLiteralCaptures } from "../src/backend/llvm/regex-matchers.js"; +import { regexCaptureParticipation } from "../src/frontend/lowering/containers/replacer-params.js"; + +// The frontend binds a replace callback parameter as a string only for a +// capture every match sets; the backend then calls the callback directly +// only when its own parse agrees. +describe("replace callback captures", () => { + test("never claims a capture the backend's parse can leave unset", () => { + const patterns = [ + "(\\w+)=(\\d)+", + "(?a)(b)?|(c)", + "x(?:(a)|(b))y(c)*(d){0,2}(e){1,3}", + "((a)(b)?)+", + "(a(b|c))d", + "(?:a|(b))(c)", + ]; + for (const pattern of patterns) { + const frontend = regexCaptureParticipation(pattern, ""); + const backend = regexLiteralCaptures(pattern, ""); + expect(frontend?.count).toBe(backend?.count); + frontend!.always.forEach((always, index) => { + if (always) expect(backend!.always[index]).toBe(true); + }); + } + }); +}); + +const directories: string[] = []; +afterEach(async () => { + await Promise.all(directories.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +async function emit(source: string): Promise { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-replacer-")); + directories.push(outDir); + const entry = join(outDir, "main.ts"); + await writeFile(entry, source); + const outPath = join(outDir, "program.ll"); + const result = await compile(entry, { entry, outDir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result)).toBe(true); + return readFile(outPath, "utf8"); +} + +/** The (arity, kinds) arguments of each direct replacer call, and the + * number of calls through the boxed argument array. */ +function replacerCalls(llvm: string): { direct: string[]; boxed: number } { + const lines = llvm.split("\n").filter((line) => line.includes("call ")); + return { + direct: lines + .filter((line) => line.includes("@scr_regex_replace_fn(")) + .map((line) => /i32 (-?\d+), i32 (-?\d+), i1/.exec(line)!.slice(1, 3).join(":")), + boxed: lines.filter((line) => line.includes("@scr_regex_replace_callback(")).length, + }; +} + +describe("replace callback calls", () => { + test("unannotated parameters take the kinds the replace passes", async () => { + const llvm = await emit(` +const vars: Record = { a: "1" }; +const text = String(Date.now() > 0 ? "{a} {b}" : ""); +console.log(text.replace(/\\{(\\w+)\\}/g, (_m, key) => vars[key] ?? "")); +console.log(text.replace(/\\{(\\w)/g, (_m, c) => c.toUpperCase())); +console.log(text.replace(/(a)|(b)/g, (m, a, b) => \`\${m}\${a}\${b}\`)); +console.log(text.replace(/(a)(b)?/g, (m: string, a: string, b: string | undefined, at: number) => m + a + (b ?? "") + at)); +const half = (n: number): number => n / 2; +console.log(text.replace(/b/g, (m, at) => String(half(at)))); +console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b + c + at + all + extra)); +`); + expect(replacerCalls(llvm)).toEqual({ + // Typed parameters and result (0); a checked-dynamic result (bit 31); + // two captures a match can leave unset (bits 1 and 2); + // \`string | undefined\` at capture 2 (bit 8 + 2); an offset passed to a + // number parameter keeps its checked-dynamic box (bit 1). + direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2"], + // Seven parameters exceed the direct calls' limit. + boxed: 1, + }); + }); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json new file mode 100644 index 000000000..4de6ead6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8118-replacer-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json new file mode 100644 index 000000000..9a4c2b510 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8345-replace-callback-untyped-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json new file mode 100644 index 000000000..301203783 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8346-replace-callback-untyped-params.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index 158697a14..ab323c288 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -1529,49 +1529,101 @@ ScrStr *scr_regex_replace_callback(ScrStr *s, ScrRegex *re, ScrDyn *callback, bo return scr_jb_finish(&output); } -/* One call of a typed replacer (scr_regex_replace_fn's contract): string - * arguments are passed +1 (the closure ABI's owned parameters), the offset - * as a number, and the result is the closure's +1 string. */ -static ScrStr *scr_regex_call_replacer(ScrClosure *f, uint32_t arity, int ncap, - ScrStr *const *caps, double offset, ScrStr *subject) { - void *p[4] = { NULL, NULL, NULL, NULL }; - for (uint32_t i = 0; i < arity && i < 4; i++) { - if ((int)i < ncap) { - if (!caps[i]) scr_trap("scriptc: internal error: typed replacer capture is unset\n"); - p[i] = scr_str_retain(caps[i]); - } else if ((int)i == ncap + 1) { - p[i] = scr_str_retain(subject); - } - } +/* The closure signature scr_regex_replace_fn calls: `arity` pointer + * parameters, except a number at `number_at` (-1 when there is none). A + * string and a checked-dynamic value are both pointers, and so are both + * results. */ +#define SCR_REPLACER_CALL(...) return ((void *(*)(ScrClosure *, __VA_ARGS__))fn) +static inline __attribute__((always_inline)) void *scr_regex_call_replacer( + ScrClosure *f, uint32_t arity, int number_at, void *const *p, double offset) { void *fn = f->fn; - typedef ScrStr *R; - switch (arity) { - case 0: return ((R (*)(ScrClosure *))fn)(f); - case 1: return ((R (*)(ScrClosure *, void *))fn)(f, p[0]); - case 2: - if (ncap == 1) return ((R (*)(ScrClosure *, void *, double))fn)(f, p[0], offset); - return ((R (*)(ScrClosure *, void *, void *))fn)(f, p[0], p[1]); - case 3: - if (ncap == 1) return ((R (*)(ScrClosure *, void *, double, void *))fn)(f, p[0], offset, p[2]); - if (ncap == 2) return ((R (*)(ScrClosure *, void *, void *, double))fn)(f, p[0], p[1], offset); - return ((R (*)(ScrClosure *, void *, void *, void *))fn)(f, p[0], p[1], p[2]); - case 4: - if (ncap == 2) - return ((R (*)(ScrClosure *, void *, void *, double, void *))fn)(f, p[0], p[1], offset, p[3]); - if (ncap == 3) - return ((R (*)(ScrClosure *, void *, void *, void *, double))fn)(f, p[0], p[1], p[2], offset); - if (ncap >= 4) - return ((R (*)(ScrClosure *, void *, void *, void *, void *))fn)(f, p[0], p[1], p[2], p[3]); - break; + typedef void *P; + typedef double D; + switch (arity * 8 + (uint32_t)(number_at < 0 ? 7 : number_at)) { + case 0 * 8 + 7: return ((void *(*)(ScrClosure *))fn)(f); + case 1 * 8 + 7: SCR_REPLACER_CALL(P)(f, p[0]); + case 2 * 8 + 7: SCR_REPLACER_CALL(P, P)(f, p[0], p[1]); + case 2 * 8 + 1: SCR_REPLACER_CALL(P, D)(f, p[0], offset); + case 3 * 8 + 7: SCR_REPLACER_CALL(P, P, P)(f, p[0], p[1], p[2]); + case 3 * 8 + 1: SCR_REPLACER_CALL(P, D, P)(f, p[0], offset, p[2]); + case 3 * 8 + 2: SCR_REPLACER_CALL(P, P, D)(f, p[0], p[1], offset); + case 4 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P)(f, p[0], p[1], p[2], p[3]); + case 4 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P)(f, p[0], offset, p[2], p[3]); + case 4 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P)(f, p[0], p[1], offset, p[3]); + case 4 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D)(f, p[0], p[1], p[2], offset); + case 5 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P, P)(f, p[0], p[1], p[2], p[3], p[4]); + case 5 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P, P)(f, p[0], offset, p[2], p[3], p[4]); + case 5 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P, P)(f, p[0], p[1], offset, p[3], p[4]); + case 5 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D, P)(f, p[0], p[1], p[2], offset, p[4]); + case 5 * 8 + 4: SCR_REPLACER_CALL(P, P, P, P, D)(f, p[0], p[1], p[2], p[3], offset); + case 6 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P, P, P)(f, p[0], p[1], p[2], p[3], p[4], p[5]); + case 6 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P, P, P)(f, p[0], offset, p[2], p[3], p[4], p[5]); + case 6 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P, P, P)(f, p[0], p[1], offset, p[3], p[4], p[5]); + case 6 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D, P, P)(f, p[0], p[1], p[2], offset, p[4], p[5]); + case 6 * 8 + 4: SCR_REPLACER_CALL(P, P, P, P, D, P)(f, p[0], p[1], p[2], p[3], offset, p[5]); + case 6 * 8 + 5: SCR_REPLACER_CALL(P, P, P, P, P, D)(f, p[0], p[1], p[2], p[3], p[4], offset); default: break; } scr_trap("scriptc: internal error: typed replacer signature\n"); } +#undef SCR_REPLACER_CALL + +/* Typed replacer argument k: a capture (NULL for an unset optional one) + * or the subject; the offset is passed separately. The compiler admits a + * typed parameter only at those positions. Inlined with each constant k. */ +static inline __attribute__((always_inline)) void scr_regex_typed_arg( + void **p, uint32_t k, uint32_t kinds, int capture_count, ScrStr *const *caps, ScrStr *subject) { + if ((int)k < capture_count) { + if (!caps[k] && !(kinds & (SCR_REPLACER_OPTIONAL << k))) + scr_trap("scriptc: internal error: typed replacer capture is unset\n"); + p[k] = caps[k] ? scr_str_retain(caps[k]) : NULL; + } else if ((int)k == capture_count + 1) { + p[k] = scr_str_retain(subject); + } +} + +/* One match's replacer arguments when some parameter is checked-dynamic + * (scr_regex_replace_fn's `kinds`): typed ones +1, the rest new boxes. + * `boxed_subject` is the subject's box; `groups` the match's groups + * object, or NULL when no parameter reads it. */ +__attribute__((noinline)) static void scr_regex_replacer_args( + void **p, uint32_t arity, uint32_t kinds, int capture_count, ScrStr *const *caps, double offset, + ScrStr *subject, ScrDyn *boxed_subject, ScrDyn *groups) { + for (uint32_t k = 0; k < arity; k++) { + int at = (int)k; + if (kinds & (1u << k)) { + if (at < capture_count) + p[k] = caps[k] ? scr_dyn_new_str(caps[k]) : scr_dyn_retain(scr_dyn_undefined()); + else if (at == capture_count) + p[k] = scr_dyn_new_num(offset); + else if (at == capture_count + 1) + p[k] = scr_dyn_retain(boxed_subject); + else if (at == capture_count + 2 && groups) + p[k] = scr_dyn_retain(groups); + else + p[k] = scr_dyn_retain(scr_dyn_undefined()); + } else if (at < capture_count) { + if (!caps[k] && !(kinds & (SCR_REPLACER_OPTIONAL << k))) + scr_trap("scriptc: internal error: typed replacer capture is unset\n"); + p[k] = caps[k] ? scr_str_retain(caps[k]) : NULL; + } else if (at == capture_count + 1) { + p[k] = scr_str_retain(subject); + } else if (at != capture_count) { + scr_trap("scriptc: internal error: typed replacer signature\n"); + } + } +} /* scr_regex_replace_callback's algorithm for a typed replacer: the matches - * are collected first, keeping only the captures the replacer reads, then - * the replacer runs once per match in order. */ -ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, bool all) { + * are collected first, keeping only the captures and groups objects the + * replacer reads, then the replacer runs once per match in order. Typed + * parameters receive their values +1 (the closure ABI's owned + * parameters) and an unset optional capture NULL; checked-dynamic ones + * receive new boxes. Inlined into one copy for kinds 0, where the boxed + * handling folds away, and one for the other kinds. */ +static inline __attribute__((always_inline)) ScrStr * +scr_regex_replace_fn_impl(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, uint32_t kinds, + bool all) { uint8_t *bc = scr_regex_bc(re); int flags = lre_get_flags(bc); bool global = (flags & LRE_FLAG_GLOBAL) != 0; @@ -1580,15 +1632,28 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } + if (arity > SCR_REPLACER_MAX_ARITY) scr_trap("scriptc: internal error: typed replacer arity\n"); bool sticky = (flags & LRE_FLAG_STICKY) != 0; bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; int capture_count = lre_get_capture_count(bc); size_t keep = arity < (uint32_t)capture_count ? arity : (size_t)capture_count; + /* Node passes the groups object after the subject when the pattern has + * named groups; only a checked-dynamic parameter can read it, and no + * typed parameter follows the subject. */ + bool past_subject = (uint32_t)capture_count + 2 < arity; + if (past_subject && !(kinds & SCR_REPLACER_BOXED_PARAMS)) + scr_trap("scriptc: internal error: typed replacer signature\n"); + const char *groupnames = past_subject ? lre_get_groupnames(bc) : NULL; + bool groups_read = groupnames != NULL; + if (groups_read && !(kinds & (1u << (capture_count + 2)))) + scr_trap("scriptc: internal error: typed replacer groups parameter\n"); + int number_at = capture_count < (int)arity && !(kinds & (1u << capture_count)) ? capture_count : -1; ScrRegexInput input; scr_regex_input_init(&input, re, bc, s); int len = input.len; struct { int start, end; } *matches = NULL; ScrStr **caps = NULL; + ScrDyn **groups = NULL; size_t count = 0, capacity = 0; if (global) re->last_index = 0; int position = sticky ? scr_regex_start(re, len) : 0; @@ -1610,6 +1675,11 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar if (!grown) scr_regex_oom(); caps = grown; } + if (groups_read) { + grown = realloc(groups, next * sizeof(*groups)); + if (!grown) scr_regex_oom(); + groups = grown; + } capacity = next; } for (size_t group = 0; group < keep; group++) { @@ -1618,6 +1688,18 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar cs && ce ? scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)) : NULL; } + if (groups_read) { + ScrDyn *object = scr_dyn_new_obj_null_proto(); + const char *name = groupnames; + for (int group = 1; group < capture_count; group++) { + size_t length = strlen(name); + if (length && (capture[group * 2] || !scr_dyn_obj_get(object, name, length))) + scr_dyn_obj_set(object, name, length, + scr_regex_capture_value(&input, capture[group * 2], capture[group * 2 + 1])); + name += length + LRE_GROUP_NAME_TRAILER_LEN; + } + groups[count] = object; + } matches[count].start = start; matches[count].end = end; count++; @@ -1627,18 +1709,43 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar ScrJsonBuf output; scr_jb_init(&output); int next = 0; + /* One box of the subject serves every match. */ + ScrDyn *subject = NULL; + if (kinds & SCR_REPLACER_BOXED_PARAMS) subject = scr_dyn_new_str(s); scr_dyn_this_push_dyn(NULL); for (size_t i = 0; i < count; i++) { - ScrStr *replacement = scr_regex_call_replacer(f, arity, capture_count, caps + i * keep, - (double)matches[i].start, s); - if (scr_exc_pending()) break; + void *p[SCR_REPLACER_MAX_ARITY] = { NULL }; + double offset = (double)matches[i].start; + ScrStr *const *match_caps = caps + i * keep; + if (kinds & SCR_REPLACER_BOXED_PARAMS) { + scr_regex_replacer_args(p, arity, kinds, capture_count, match_caps, offset, s, subject, + groups_read ? groups[i] : NULL); + } else { + for (uint32_t k = 0; k < SCR_REPLACER_MAX_ARITY; k++) + if (k < arity) scr_regex_typed_arg(p, k, kinds, capture_count, match_caps, s); + } + void *result = scr_regex_call_replacer(f, arity, number_at, p, offset); + ScrStr *replacement = (ScrStr *)result; + if (kinds & SCR_REPLACER_BOXED_RESULT) { + replacement = scr_exc_pending() ? NULL : scr_dyn_string_coerce_js((ScrDyn *)result); + scr_dyn_release((ScrDyn *)result); + } + if (scr_exc_pending()) { + scr_str_release(replacement); + break; + } scr_regex_put_span(&output, &input, next, matches[i].start); scr_jb_put_str(&output, replacement); scr_str_release(replacement); next = matches[i].end; } scr_dyn_this_pop(); + if (subject) scr_dyn_release(subject); for (size_t i = 0; i < count * keep; i++) scr_str_release(caps[i]); + if (groups_read) { + for (size_t i = 0; i < count; i++) scr_dyn_release(groups[i]); + free(groups); + } free(caps); free(matches); if (scr_exc_pending()) { @@ -1651,6 +1758,24 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar return scr_jb_finish(&output); } +__attribute__((noinline)) static ScrStr *scr_regex_replace_typed(ScrStr *s, ScrRegex *re, + ScrClosure *f, uint32_t arity, + bool all) { + return scr_regex_replace_fn_impl(s, re, f, arity, 0, all); +} + +__attribute__((noinline)) static ScrStr *scr_regex_replace_kinds(ScrStr *s, ScrRegex *re, + ScrClosure *f, uint32_t arity, + uint32_t kinds, bool all) { + return scr_regex_replace_fn_impl(s, re, f, arity, kinds, all); +} + +ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, + uint32_t kinds, bool all) { + if (kinds == 0) return scr_regex_replace_typed(s, re, f, arity, all); + return scr_regex_replace_kinds(s, re, f, arity, kinds, all); +} + /* The pinned Node ANSI matcher derives from ansi-regex, copyright (c) * Sindre Sorhus , used under the MIT license: * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index 01c545bc5..cb61e49f3 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -80,16 +80,26 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */ ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */ /* replace: first match without /g, every match with it. */ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep); -/* replace/replaceAll with a typed replacer closure of `arity` (at most 4) - * parameters returning a string. Parameter i receives what Node passes at - * position i: the match and captures (strings) below the capture count n, - * the match offset (a number) at n, the subject (a string) at n + 1. The - * compiler admits a closure only when every position it declares is one of - * those with the declared kind, and every capture it reads is set in every - * match (scr_regex_replace_callback handles the rest). Borrows all. */ +/* replace/replaceAll with a replacer closure of `arity` (at most + * SCR_REPLACER_MAX_ARITY) parameters. Parameter i receives what Node passes + * at position i: the match and captures below the capture count n, the + * match offset at n, the subject at n + 1, the groups object at n + 2 when + * the pattern has named groups, and undefined after that. Bit i of + * `kinds` makes parameter i a checked-dynamic value, and bit + * SCR_REPLACER_OPTIONAL << i a capture held as a string or NULL for + * undefined; otherwise it is a string, or a number at n. + * SCR_REPLACER_BOXED_RESULT makes the result a checked-dynamic value, + * converted with ToString. The compiler admits a closure only when every + * typed position it declares is one of those with the declared kind, and + * every capture a string parameter reads is set in every match + * (scr_regex_replace_callback handles the rest). Borrows all. */ +#define SCR_REPLACER_MAX_ARITY 6 +#define SCR_REPLACER_BOXED_PARAMS 0xffu +#define SCR_REPLACER_OPTIONAL (1u << 8) +#define SCR_REPLACER_BOXED_RESULT (1u << 31) struct ScrClosure; ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, struct ScrClosure *f, uint32_t arity, - bool all); + uint32_t kinds, bool all); /* replaceAll: throws Node's TypeError when /g is missing (may-throw). */ ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep); /* split: captured substrings are interleaved with the split pieces; diff --git a/tests/corpus/8118-replacer-params.ts b/tests/corpus/8118-replacer-params.ts new file mode 100644 index 000000000..14579ec84 --- /dev/null +++ b/tests/corpus/8118-replacer-params.ts @@ -0,0 +1,43 @@ +// Inline replace callbacks whose parameters after the match are left +// untyped: captures every match sets, captures that can be unset (in an +// alternation, an optional or zero-repeat group, a lookahead), the offset, +// the subject and the groups object, reassigned parameters, named groups, +// patterns with classes, escapes and lookbehind, function expressions and +// replaceAll. +const lines = ["id7,tag12;tag3", "x9 y10", "", "naïve 😀 42", "a-b-c"]; +for (const line of lines) { + console.log(line.replace(/tag(\d+)/g, (_m, d) => String(Number(d) + 1))); + console.log(line.replace(/(\w)(\d+)/g, (m, letter, digits, offset, subject) => + `[${letter}:${digits.length}@${offset}/${subject.length}]`)); + console.log(line.replace(/(a)|(\d)/g, (m, a, digit) => `<${typeof a},${typeof digit}>`)); + console.log(line.replace(/(x)?(\d)/g, (m, x, d) => `${x === undefined ? "-" : x}${d}`)); + console.log(line.replace(/(?:(t)a)*g/g, (m, t, offset) => `{${t}|${offset}}`)); + console.log(line.replace(/(?=(\d))\d/g, (m, ahead, offset) => `${ahead}${offset}`)); + console.log(line.replace(/(?\d)(\d)?/g, (m, n, rest, offset, subject, groups) => + `${n}${rest ?? "_"}${offset}${typeof subject}${JSON.stringify(groups)}`)); + console.log(line.replace(/([\(\)\[\]]|[a-c])(-)/g, (m, c, dash) => c.toUpperCase() + dash.length)); + console.log(line.replace(/(?<=-)(\w)/g, (m, c, offset) => c + offset.toFixed(1))); + console.log(line.replaceAll(/(\d)/g, function (m, d, offset) { + return `${d * 2}:${offset + 1}`; + })); + console.log(line.replace(/(\d)/g, (m, d) => { + d = d.length > 0 ? 5 : 6; + return String(d + 1); + })); + console.log(line.replace(/(\d)\1?/g, (m, d, offset, subject) => `${d}${offset}${subject === line}`)); + console.log(line.replace(/(.)/u, (m, c, offset, subject, extra) => `${c.codePointAt(0)}${offset}${extra}`)); + console.log(line.replace(/(\d){0,2}x/g, (m, d) => `${d}`)); + console.log(line.replace(/(\w)(\w)/g, (m, a, b, offset) => + [a.toUpperCase(), b.toLowerCase(), a.trim(), a.charCodeAt(0), b.codePointAt(0), a.slice(0, 1), + b.substring(0), a.repeat(2), b.padStart(3, "."), a.padEnd(2, "!"), b.includes("1"), + a.startsWith("t"), b.endsWith("g"), a.indexOf("a"), a.length, parseInt(b, 36), + parseFloat(b), Number(b), String(offset), offset - 1, offset * 2, offset % 3, offset < 4, + a === b, a !== "x", a + offset, offset + 1, `${offset}`].join(","))); + console.log(line.replace(/(\d+)/g, (m, d) => (d))); + console.log(line.replace(/(\d+)/g, function (m, d) { return d + d; })); +} +let count = 0; +console.log("tag1 tag2".replace(/tag(\d)/g, (m, d) => { + count += Number(d); + return d.repeat(2); +}), count); diff --git a/tests/corpus/8345-replace-callback-untyped-params.ts b/tests/corpus/8345-replace-callback-untyped-params.ts new file mode 100644 index 000000000..31d3a038b --- /dev/null +++ b/tests/corpus/8345-replace-callback-untyped-params.ts @@ -0,0 +1,85 @@ +// String replace/replaceAll with inline callbacks whose parameters have no +// annotations, so the lib types every parameter after the match `any`. +// Each such parameter holds what Node passes at its position: the captures, +// the offset, the subject, the groups object and then undefined. Captures a +// match can leave unset hold undefined, and a result that is not a string +// converts with ToString. +const vars: Record = { name: "world", place: "earth" }; +const template = "Hello {name}, welcome to {place}! {missing} {name}."; +console.log(template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log(template.replace(/\{(\w+)\}/, (_m, key) => key.toUpperCase())); +console.log("some_snake_case_name".replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log("2024-01-15 and 1999-07-04".replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, mo, d) => `${d}/${mo}/${y}`)); +console.log("a1b22".replace(/\d+/g, (m, offset, subject) => `[${m}@${offset}/${subject.length}]`)); +console.log("x1y2".replace(/(\d)/g, (m, digit, offset, subject) => `${m}${digit}${offset}${subject}`)); +console.log("k=v".replace(/(\w)=(\w)/, function (m, k, v, at, all) { + return `${v}=${k}:${at}:${all}:${m}`; +})); + +console.log("k=v".replace(/(\w)=(\w)/, function (this: void, m, k, v, at) { + return `${typeof this}:${v}=${k}:${at}:${m}`; +})); + +// A parameter the body assigns keeps `any`. +console.log("a1".replace(/(\d)/, (_m, d) => { + d = Number(d) + 1; + return String(d * 2); +})); + +// Captures that a match can leave unset. +console.log("ab".replace(/(a)|(b)/g, (m, a, b) => `${m}:${a}:${b};`)); +console.log("b xb".replace(/(x)?b/g, (m, x, at) => `${m}:${x}:${at}`)); +console.log("ab".replace(/((a)|b)/g, (m, outer, inner) => `${outer}:${inner};`)); +console.log("ab".replace(/(?:(a)|b)/g, (m, a, at) => `${m}:${a}:${at};`)); +console.log("aab".replace(/(a){0,2}b/, (m, a, at) => `${m}:${a}:${at}`)); +console.log("ab".replace(/(?=(a))a|b/g, (m, look, at) => `${m}:${look}:${at};`)); +console.log("ab".replace(/(?!(x))a/g, (m, x, at) => `${m}:${x}:${at};`)); +console.log("ab".replace(/(a)*b/, (m, a, at) => `${m}:${a}:${at}`)); +console.log("a(b)".replace(/\((b)\)/, (m, b, at) => `${m}:${b}:${at}`)); +console.log("a|b".replace(/[|(](b)?/, (m, b, at) => `${m}:${b}:${at}`)); + +// Annotated `string | undefined` captures hold undefined when unset. +console.log("2024-01-15 1999-07".replace(/(\d{4})-(\d{2})(-(\d{2}))?/g, (_m, y: string, mo: string, _x: string | undefined, d: string | undefined) => `${d ?? "??"}/${mo}/${y}`)); +console.log("ab".replace(/(a)|(b)/g, (m: string, a: string | undefined, b: string | undefined, at: number) => `${m}:${a ?? "-"}:${b ?? "-"}:${at};`)); +console.log("abc".replace(/(a)(b)(c)/, (m: string, a: string, b: string, c: string, at: number, all: string) => `${c}${b}${a}${m}${at}${all}`)); +console.log("abcd".replace(/(a)(b)(c)(d)/, (m, a, b, c, d, at, all) => `${d}${c}${b}${a}${m}${at}${all}`)); + +// Named groups pass the groups object after the subject; later parameters +// are undefined. +console.log("2024-01".replace(/(?\d{4})-(?\d{2})/, (m, y, mo, at, all, groups) => `${groups.m}.${groups.y}:${y}${mo}:${at}:${all}`)); +console.log("ab".replace(/(?a)|(?b)/g, (m, a, b, at, all, groups) => `${m}:${JSON.stringify(groups)};`)); +console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); +console.log("ab".replace(/(a)(b)/, (m, a, b, at, all, extra, more) => `${m}${a}${b}${at}${all}${extra}${more}`)); + +// Results that are not strings convert with ToString. +const counts: Record = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; +console.log("a b c d e".replace(/\w/g, (m) => counts[m])); +console.log("abc".replace(/(\w)/g, (m, c) => counts[c])); + +// String search patterns pass (match, offset, subject). +console.log("one two one".replaceAll("one", (m, at, subject) => `${m.length}@${at}/${subject.length}`)); +console.log("one two one".replace("two", (m, at, subject, extra) => `${m}${at}${subject.length}${extra}`)); + +// A throw stops the replacement. +try { + "a1b2c3".replace(/(\d)/g, (_m, d) => { + if (d === "2") throw new Error("stop at " + d); + return "#"; + }); +} catch (error) { + console.log((error as Error).message); +} +try { + "a1".replace(/(\d)/g, (_m, d) => counts.e.missing.field + d); +} catch (error) { + console.log(error instanceof TypeError); +} +try { + "ab".replace(/(a)|b/g, (m, a) => a.toUpperCase()); +} catch (error) { + console.log(error instanceof TypeError, (error as Error).message); +} + +let total = 0; +for (let i = 0; i < 2000; i++) total += template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? String(i)).length; +console.log(total); diff --git a/tests/corpus/8346-replace-callback-untyped-params.mjs b/tests/corpus/8346-replace-callback-untyped-params.mjs new file mode 100644 index 000000000..2552113f3 --- /dev/null +++ b/tests/corpus/8346-replace-callback-untyped-params.mjs @@ -0,0 +1,32 @@ +// JavaScript string replace/replaceAll with inline callbacks. Each +// parameter holds what Node passes at its position: the captures, the +// offset, the subject, the groups object and then undefined. Captures a +// match can leave unset hold undefined, and a result that is not a string +// converts with ToString. +const vars = { name: "world", place: "earth" }; +const template = "Hello {name}, welcome to {place}! {missing} {name}."; +console.log(template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log("some_snake_case_name".replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log("2024-01-15 and 1999-07-04".replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, mo, d) => `${d}/${mo}/${y}`)); +console.log("a1b22".replace(/\d+/g, (m, offset, subject) => `[${m}@${offset}/${subject.length}]`)); +console.log("k=v".replace(/(\w)=(\w)/, function (m, k, v, at, all) { + return `${this === undefined}:${v}=${k}:${at}:${all}:${m}`; +})); +console.log("ab".replace(/(a)|(b)/g, (m, a, b) => `${m}:${a}:${b};`)); +console.log("b xb".replace(/(x)?b/g, (m, x, at) => `${m}:${x}:${at}`)); +console.log("2024-01".replace(/(?\d{4})-(?\d{2})/, (m, y, mo, at, all, groups) => `${groups.m}.${groups.y}:${y}${mo}:${at}:${all}`)); +console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); +const counts = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; +console.log("a b c d e".replace(/\w/g, (m) => counts[m])); +console.log("1-2".replace(/(\d)/g, (_m, d) => Number(d) * 10)); +console.log("one two one".replaceAll("one", (m, at, subject) => `${m.length}@${at}/${subject.length}`)); +let seen = ""; +console.log("xyz".replace(/(y)/, (m, y, at, subject) => { + seen = typeof m + typeof y + typeof at + typeof subject; + return y + at; +}), seen); +try { + "ab".replace(/(a)|b/g, (m, a) => a.toUpperCase()); +} catch (error) { + console.log(error instanceof TypeError, error.message); +} diff --git a/tests/harness/corpus-costs/8118-replacer-params.ts.json b/tests/harness/corpus-costs/8118-replacer-params.ts.json new file mode 100644 index 000000000..9b0bcec50 --- /dev/null +++ b/tests/harness/corpus-costs/8118-replacer-params.ts.json @@ -0,0 +1 @@ +{ "plain": 3670, "san": 4410 } diff --git a/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json b/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json new file mode 100644 index 000000000..5d5bbcefe --- /dev/null +++ b/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json @@ -0,0 +1 @@ +{ "plain": 3630, "san": 4420 } diff --git a/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json b/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json new file mode 100644 index 000000000..cdddf6d76 --- /dev/null +++ b/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json @@ -0,0 +1 @@ +{ "plain": 2880, "san": 5080 } From ceb29078dc763bb0ccad928bb563ee90963d1b8c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:31:51 -0500 Subject: [PATCH 218/395] Skip comments when renaming a range's debug ids --- .../compiler/src/backend/llvm/emit-journal.ts | 47 ++++++++++++++---- .../compiler/test/chunked-emission.test.ts | 49 +++++++++++++++++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-journal.ts b/packages/compiler/src/backend/llvm/emit-journal.ts index d6fed03a6..b2b3fc4cc 100644 --- a/packages/compiler/src/backend/llvm/emit-journal.ts +++ b/packages/compiler/src/backend/llvm/emit-journal.ts @@ -175,22 +175,49 @@ export function renameChunk( }; } -/** Renames metadata references `!N` outside string literals. Debug builds - * attach no alias tags, so every such reference in a function's text is a - * debug metadata id. */ +/** Renames metadata references `!N` outside string literals and comments. + * Debug builds attach no alias tags, so every such reference in a + * function's text is a debug metadata id. A string literal or a comment + * (`;` outside a string literal) ends at the end of its line; a quote + * inside a string literal is escaped (`\22`). */ function renameDebugIds(text: string, ids: ReadonlyMap): string { const parts: string[] = []; let copied = 0; - // The next quote at or after the scan, and whether the scan is inside a - // string literal. - let quote = text.indexOf('"'); + // The line of the reference being looked at, and whether the reference + // is inside a string literal or a comment there. The next quote and + // semicolon positions only move forward, so each is found once. + let lineEnd = -1; let quoted = false; + let comment = false; + let quote = text.indexOf('"'); + let semicolon = text.indexOf(";"); for (let at = text.indexOf("!"); at >= 0; at = text.indexOf("!", at + 1)) { - while (quote >= 0 && quote < at) { - quoted = !quoted; - quote = text.indexOf('"', quote + 1); + if (at > lineEnd) { + lineEnd = text.indexOf("\n", at); + if (lineEnd < 0) lineEnd = text.length; + quoted = false; + comment = false; + // Quotes and semicolons of earlier lines end with them. + if ((quote >= 0 && quote < at) || (semicolon >= 0 && semicolon < at)) { + const lineStart = text.lastIndexOf("\n", at) + 1; + while (quote >= 0 && quote < lineStart) quote = text.indexOf('"', quote + 1); + while (semicolon >= 0 && semicolon < lineStart) + semicolon = text.indexOf(";", semicolon + 1); + } } - if (quoted) continue; + while (!comment) { + const nextQuote = quote >= 0 && quote < at ? quote : -1; + const nextSemicolon = semicolon >= 0 && semicolon < at ? semicolon : -1; + if (nextQuote < 0 && nextSemicolon < 0) break; + if (nextQuote >= 0 && (nextSemicolon < 0 || nextQuote < nextSemicolon)) { + quoted = !quoted; + quote = text.indexOf('"', quote + 1); + } else { + if (!quoted) comment = true; + semicolon = text.indexOf(";", semicolon + 1); + } + } + if (quoted || comment) continue; if (at > 0 && isNameChar(text.charCodeAt(at - 1))) continue; let end = at + 1; while (end < text.length && isDigit(text.charCodeAt(end))) end++; diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 567a082ef..47e82cf9e 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -177,3 +177,52 @@ test("debug builds emitted in ranges merge into the text one emitter gives", asy await rm(dir, { recursive: true, force: true }); } }, 300_000); + +// A method name holding a double quote is written into a comment after the +// function's `!dbg` attachment; the rename of the range's debug ids reads +// comments and string literals as ending at the end of their line. +test("debug ids after a comment that holds a quote are renamed", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-quote-")); + try { + const entry = join(dir, "main.ts"); + const source = [ + "class Greeter {", + " ['say \"hi'](n: number): number {", + " return n + 1;", + " }", + "}", + "const greeter = new Greeter();", + "function twice(x: number): number {", + " return x * 2;", + "}", + "function report(label: string, value: number): void {", + " console.log(label, value);", + "}", + 'report("method", greeter[\'say "hi\'](1));', + 'report("twice", twice(3));', + "", + ].join("\n"); + await writeFile(entry, source); + const outPath = join(dir, "main.ir.json"); + const result = await compile(entry, { + outDir: dir, + outPath, + outputKind: "ir", + optimization: "dev", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ir = await readFile(outPath, "utf8"); + const options: LlvmTargetOptions = { debugSources: new Map([[entry, source]]) }; + const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); + expect(serial).toMatch(/!dbg ![0-9]+ \{ ;[^\n]*"/); + for (const functionChunks of [2, 3]) { + const chunked = text( + emitLlvmModuleSource(deserializeModule(ir), { ...options, functionChunks }), + ); + expect(chunked === serial, `${functionChunks} chunks`).toBe(true); + } + } finally { + await rm(dir, { recursive: true, force: true }); + } +}, 60_000); From 24ef53ac237dd58c79aa590cff61c1775b275a86 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:32:18 -0500 Subject: [PATCH 219/395] Have tsc-ts read its library files from disk when it can Every file tsc-ts reads goes through scriptc's file system callbacks, including the ~80 files of tsc-ts's own default library directory: one round trip from the checker to the compiler and back for each. Those files belong to the installed tsc-ts (the build caches key on its executable's identity), and no build serves them differently. When tsc-ts takes --default-libraries-from-disk, the native compiler passes it to the checker it starts and to the shared checker server, so tsc-ts reads those files itself; every other path keeps the callbacks. Released tsc-ts versions reject the flag, so checker-features.ts asks each tsc-ts executable once (tsc-ts --api --default-libraries-from-disk with an empty input exits with 0 only when it knows the flag) and keeps the answer in the native cache, keyed by the executable's identity. With a tsc-ts that does not know the flag, builds run as before. Edit builds of small programs through the shared checker server are about 18 ms faster, and 6-9 ms faster without it. --- .../compiler/native/ts7-process-child.test.c | 10 ++-- packages/compiler/native/ts7-process.c | 27 ++++++---- packages/compiler/native/ts7-process.ffi.json | 4 +- packages/compiler/native/ts7-process.h | 5 +- packages/compiler/native/ts7-process.test.c | 24 +++++---- .../src/frontend/ts7/checker-features.test.ts | 45 ++++++++++++++++ .../src/frontend/ts7/checker-features.ts | 53 +++++++++++++++++++ .../src/frontend/ts7/checker-server.ts | 7 ++- .../compiler/src/frontend/ts7/native-api.ts | 12 +++-- .../src/frontend/ts7/native-process.ts | 27 ++++++++-- packages/compiler/src/native/cli.ts | 6 +++ 11 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 packages/compiler/src/frontend/ts7/checker-features.test.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-features.ts diff --git a/packages/compiler/native/ts7-process-child.test.c b/packages/compiler/native/ts7-process-child.test.c index 386a0a7c3..4d1cbc081 100644 --- a/packages/compiler/native/ts7-process-child.test.c +++ b/packages/compiler/native/ts7-process-child.test.c @@ -64,8 +64,11 @@ static int child_main(int argc, char **argv) { #endif int listening = argc >= 9 && strcmp(argv[5], "--listen") == 0 && strcmp(argv[7], "--idle-exit") == 0; int fixed = listening ? 9 : 5; - if (argc < fixed || argc > fixed + 1 || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || - strncmp(argv[4], "--callbacks=", 12) != 0 || (argc == fixed + 1 && strcmp(argv[fixed], "--timing") != 0)) + int at = fixed, timing = 0, libraries = 0; + if (at < argc && strcmp(argv[at], "--timing") == 0) { timing = 1; at++; } + if (at < argc && strcmp(argv[at], "--default-libraries-from-disk") == 0) { libraries = 1; at++; } + if (argc < fixed || at != argc || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || + strncmp(argv[4], "--callbacks=", 12) != 0) return 71; const char *mode = argv[3]; #ifndef _WIN32 @@ -76,7 +79,8 @@ static int child_main(int argc, char **argv) { fputc('\n', stdout); fputs(argv[4], stdout); fputc('\n', stdout); - fputs(argc == 6 ? "timing\n" : "plain\n", stdout); + fputs(timing ? "timing\n" : "plain\n", stdout); + if (libraries) fputs("libraries from disk\n", stdout); return ferror(stdout) ? 72 : 0; } if (strcmp(mode, "exit") == 0) return 0; diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index 3fd38a423..8261c341b 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -161,7 +161,7 @@ static wchar_t *command_line(wchar_t **arguments, size_t count) { } static int start_process(Ts7Process *p, char **args, size_t argc) { - wchar_t *wide[6] = {0}; + wchar_t *wide[7] = {0}; wchar_t *line = NULL; HANDLE child_input = NULL, child_output = NULL, child_error = NULL; LPPROC_THREAD_ATTRIBUTE_LIST attributes = NULL; @@ -330,7 +330,7 @@ static void stop_process(Ts7Process *p) { uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing) { + uint8_t timing, uint8_t libraries_from_disk) { if (next_id == 0) { error_text("server handle IDs are exhausted"); return 0; } char *exe = copy_argument(executable, executable_size, ""); if (exe == NULL) return 0; @@ -346,8 +346,12 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, } exit_registered = 1; } - char *args[] = {exe, "--api", "--cwd", directory, callback_flag, timing ? "--timing" : NULL, NULL}; - int started = start_process(p, args, timing ? 6 : 5); + char *args[8] = {exe, "--api", "--cwd", directory, callback_flag}; + size_t argc = 5; + if (timing) args[argc++] = "--timing"; + if (libraries_from_disk) args[argc++] = "--default-libraries-from-disk"; + args[argc] = NULL; + int started = start_process(p, args, argc); free(exe); free(directory); free(callback_flag); if (!started) { free(p); return 0; } p->id = next_id++; @@ -361,9 +365,10 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory) { + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory) { (void)name; (void)name_size; (void)executable; (void)executable_size; (void)cwd; (void)cwd_size; - (void)callbacks; (void)callbacks_size; (void)timing; (void)idle_exit; (void)memory; + (void)callbacks; (void)callbacks_size; (void)timing; (void)libraries_from_disk; (void)idle_exit; (void)memory; error_text("a shared checker server needs Unix sockets"); return 0; } @@ -455,7 +460,8 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory) { + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory) { if (next_id == 0) { error_text("server handle IDs are exhausted"); return 0; } char *server = copy_argument(name, name_size, ""); if (server == NULL) return 0; @@ -491,8 +497,11 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, (void)snprintf(limit, sizeof(limit), "TSC_TS_LISTEN_MEMORY=%lu", (unsigned long)memory); pid_t pid = -1; if (callback_flag != NULL) { - char *args[] = {exe, "--api", "--cwd", dir, callback_flag, "--listen", socket_path, "--idle-exit", idle, - timing ? "--timing" : NULL, NULL}; + char *args[12] = {exe, "--api", "--cwd", dir, callback_flag, "--listen", socket_path, "--idle-exit", idle}; + size_t argc = 9; + if (timing) args[argc++] = "--timing"; + if (libraries_from_disk) args[argc++] = "--default-libraries-from-disk"; + args[argc] = NULL; pid = start_server(args, limit); } free(exe); free(dir); free(callback_flag); diff --git a/packages/compiler/native/ts7-process.ffi.json b/packages/compiler/native/ts7-process.ffi.json index c503e3cae..963c81f15 100644 --- a/packages/compiler/native/ts7-process.ffi.json +++ b/packages/compiler/native/ts7-process.ffi.json @@ -1,8 +1,8 @@ { "ffi_format": 6, "functions": [ - { "name": "ts7NativeOpen", "symbol": "scriptc_ts7_open", "params": ["string", "string", "string", "bool"], "returns": "u32" }, - { "name": "ts7NativeAttach", "symbol": "scriptc_ts7_attach", "params": ["string", "string", "string", "string", "bool", "u32", "u32"], "returns": "u32" }, + { "name": "ts7NativeOpen", "symbol": "scriptc_ts7_open", "params": ["string", "string", "string", "bool", "bool"], "returns": "u32" }, + { "name": "ts7NativeAttach", "symbol": "scriptc_ts7_attach", "params": ["string", "string", "string", "string", "bool", "bool", "u32", "u32"], "returns": "u32" }, { "name": "ts7NativeRead", "symbol": "scriptc_ts7_read", "params": ["u32", "mutable-bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativeWrite", "symbol": "scriptc_ts7_write", "params": ["u32", "bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativePid", "symbol": "scriptc_ts7_pid", "params": ["u32"], "returns": "u32" }, diff --git a/packages/compiler/native/ts7-process.h b/packages/compiler/native/ts7-process.h index 4199e8795..d51f939a9 100644 --- a/packages/compiler/native/ts7-process.h +++ b/packages/compiler/native/ts7-process.h @@ -12,7 +12,7 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing); + uint8_t timing, uint8_t libraries_from_disk); /* Connects to the shared checker server `name` (`--api --listen`) in this * user's private server directory, starting it in its own process group * with `--idle-exit idle_exit` and a limit of `memory` megabytes @@ -25,7 +25,8 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory); + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory); int32_t scriptc_ts7_read(uint32_t handle, uint8_t *bytes, size_t size, uint32_t offset, uint32_t length); int32_t scriptc_ts7_write(uint32_t handle, const uint8_t *bytes, size_t size, diff --git a/packages/compiler/native/ts7-process.test.c b/packages/compiler/native/ts7-process.test.c index dc0c67ced..b6d760abd 100644 --- a/packages/compiler/native/ts7-process.test.c +++ b/packages/compiler/native/ts7-process.test.c @@ -21,12 +21,14 @@ static const char *executable; static const char *callbacks = "readFile,fileExists"; -static uint32_t start(const char *mode, int timing) { +static uint32_t start_with(const char *mode, int timing, int libraries) { return scriptc_ts7_open((const uint8_t *)executable, strlen(executable), (const uint8_t *)mode, strlen(mode), - (const uint8_t *)callbacks, strlen(callbacks), (uint8_t)timing); + (const uint8_t *)callbacks, strlen(callbacks), (uint8_t)timing, (uint8_t)libraries); } +static uint32_t start(const char *mode, int timing) { return start_with(mode, timing, 0); } + static void fail_message(void) { uint8_t message[1024]; int32_t size = scriptc_ts7_error(message, sizeof(message)); @@ -83,11 +85,13 @@ static void argument_cases(void) { "", "a b", "double\"quote", "end\\", "many\\\\\"quotes\\\\", "a;&$()`'b", "h\xc3\xa9llo \xf0\x9f\x8c\x8d", }; for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) { - for (int timing = 0; timing <= 1; timing++) { + for (int flags = 0; flags < 4; flags++) { + int timing = flags & 1, libraries = flags >> 1; char mode[256], expected[512]; snprintf(mode, sizeof(mode), "argv:%s", values[i]); - snprintf(expected, sizeof(expected), "%s\n--callbacks=%s\n%s\n", values[i], callbacks, timing ? "timing" : "plain"); - uint32_t id = start(mode, timing); + snprintf(expected, sizeof(expected), "%s\n--callbacks=%s\n%s\n%s", values[i], callbacks, + timing ? "timing" : "plain", libraries ? "libraries from disk\n" : ""); + uint32_t id = start_with(mode, timing, libraries); assert(id != 0); uint8_t actual[512]; read_exact(id, actual, strlen(expected)); @@ -147,10 +151,10 @@ static void failure_cases(void) { executable = "/scriptc/does-not-exist/tsgo"; for (int i = 0; i < 30; i++) { assert(start("echo", 0) == 0); fail_message(); } executable = saved; - assert(scriptc_ts7_open(NULL, 0, bytes, 1, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)"abc\0def", 7, bytes, 1, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), NULL, 0, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), bytes, 1, NULL, 0, 0) == 0); + assert(scriptc_ts7_open(NULL, 0, bytes, 1, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)"abc\0def", 7, bytes, 1, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), NULL, 0, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), bytes, 1, NULL, 0, 0, 0) == 0); for (int i = 0; i < 30; i++) { uint32_t id = start("exit", 0); assert(id != 0); @@ -177,7 +181,7 @@ static void failure_cases(void) { static uint32_t attach(const char *name, const char *mode, uint32_t memory) { return scriptc_ts7_attach((const uint8_t *)name, strlen(name), (const uint8_t *)executable, strlen(executable), (const uint8_t *)mode, strlen(mode), (const uint8_t *)callbacks, strlen(callbacks), - 0, 1, memory); + 0, 0, 1, memory); } static void expect_line(uint32_t id, const char *expected) { diff --git a/packages/compiler/src/frontend/ts7/checker-features.test.ts b/packages/compiler/src/frontend/ts7/checker-features.test.ts new file mode 100644 index 000000000..fcd3576cb --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-features.test.ts @@ -0,0 +1,45 @@ +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, afterEach, expect, test } from "vitest"; +import { checkerReadsLibrariesFromDisk, setCheckerFeatureStore } from "./checker-features.js"; + +const dir = mkdtempSync(join(tmpdir(), "scriptc-checker-features-")); +afterAll(() => rmSync(dir, { recursive: true, force: true })); +afterEach(() => setCheckerFeatureStore(null)); + +/** A stand-in checker that records its arguments and exits with `status`. */ +function checker(name: string, status: number): { path: string; log: string } { + const path = join(dir, name); + const log = path + ".log"; + writeFileSync(path, `#!/bin/sh\necho "$@" >> '${log}'\nexit ${status}\n`); + chmodSync(path, 0o755); + return { path, log }; +} + +test.skipIf(process.platform === "win32")( + "the checker is asked once whether it reads its library files from the disk, and the answer is stored", + async () => { + const { readFileSync, existsSync } = await import("node:fs"); + const entries = new Map(); + setCheckerFeatureStore({ + read: (key) => entries.get(key) ?? null, + write: (key, text) => void entries.set(key, text), + }); + const current = checker("current", 0); + const released = checker("released", 2); + expect(checkerReadsLibrariesFromDisk(current.path, {})).toBe(true); + expect(checkerReadsLibrariesFromDisk(released.path, {})).toBe(false); + expect(readFileSync(current.log, "utf8")).toBe("--api --default-libraries-from-disk\n"); + expect([...entries.values()].sort()).toEqual(["0", "1"]); + // Later asks use the answers: the checkers do not run again. + expect(checkerReadsLibrariesFromDisk(current.path, {})).toBe(true); + expect(checkerReadsLibrariesFromDisk(released.path, {})).toBe(false); + expect(readFileSync(current.log, "utf8").split("\n").length).toBe(2); + expect(readFileSync(released.log, "utf8").split("\n").length).toBe(2); + // tsgo is never asked. + const tsgo = checker("tsgo", 0); + expect(checkerReadsLibrariesFromDisk(tsgo.path, { SCRIPTC_CHECKER: "tsgo" })).toBe(false); + expect(existsSync(tsgo.log)).toBe(false); + }, +); diff --git a/packages/compiler/src/frontend/ts7/checker-features.ts b/packages/compiler/src/frontend/ts7/checker-features.ts new file mode 100644 index 000000000..b80a54994 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-features.ts @@ -0,0 +1,53 @@ +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { checkerBackend } from "./checker-backend.js"; +import { checkerExecutableIdentity } from "./lib-diagnostics.js"; + +/** Text entries kept between builds (the native compiler's cache). */ +export interface CheckerFeatureStore { + read(key: string): string | null; + write(key: string, text: string): void; +} + +let featureStore: CheckerFeatureStore | null = null; +const answers = new Map(); + +/** Keeps the answers of checkerReadsLibrariesFromDisk between builds. */ +export function setCheckerFeatureStore(store: CheckerFeatureStore | null): void { + featureStore = store; +} + +/** + * Whether the tsc-ts at `executable` takes `--api --default-libraries-from-disk`, which has it read the files + * of its default library directory from the disk instead of through the file system callbacks. tsc-ts releases + * before it reject the flag, so it is asked once per executable identity: the stored answer, or else + * `tsc-ts --api --default-libraries-from-disk` with an empty input, which exits with 0 when the flag is known + * and with a usage error when it is not. tsgo is never asked. + */ +export function checkerReadsLibrariesFromDisk( + executable: string, + env: NodeJS.ProcessEnv = process.env, +): boolean { + if (checkerBackend(env) !== "tsc-ts") return false; + const key = createHash("sha256") + .update(JSON.stringify(["default-libraries-from-disk", checkerExecutableIdentity(executable)])) + .digest("hex"); + const known = answers.get(key); + if (known !== undefined) return known; + const stored = featureStore?.read(key) ?? null; + let answer: boolean; + if (stored === "1" || stored === "0") answer = stored === "1"; + else { + const probe = spawnSync(executable, ["--api", "--default-libraries-from-disk"], { + encoding: "utf8", + stdio: "pipe", + timeout: 10_000, + }); + answer = probe.error === undefined && probe.status === 0; + // A probe that could not run says nothing about the flag: ask again next time. + if (probe.error === undefined && probe.status !== null) + featureStore?.write(key, answer ? "1" : "0"); + } + answers.set(key, answer); + return answer; +} diff --git a/packages/compiler/src/frontend/ts7/checker-server.ts b/packages/compiler/src/frontend/ts7/checker-server.ts index 6d80e2599..94c80f8db 100644 --- a/packages/compiler/src/frontend/ts7/checker-server.ts +++ b/packages/compiler/src/frontend/ts7/checker-server.ts @@ -19,14 +19,16 @@ export const CHECKER_SERVER_MEMORY = 2048; * answers, and the server keeps the files it parsed for the next build. * Returns the server's name, a digest of everything a server's answers * depend on besides the files it reads through the build: the tsc-ts - * executable, the working directory, the file system callbacks, timing, and - * the `TSC_TS_*` environment. Returns null when the shared server is not used. + * executable, the working directory, the file system callbacks, timing, + * whether it reads its library files from the disk, and the `TSC_TS_*` + * environment. Returns null when the shared server is not used. */ export function checkerServerName( executable: string, cwd: string, timing: boolean, env: NodeJS.ProcessEnv = process.env, + librariesFromDisk = false, ): string | null { if (env["SCRIPTC_CHECKER_SERVER"] !== "1") return null; if (process.platform !== "linux" && process.platform !== "darwin") return null; @@ -42,6 +44,7 @@ export function checkerServerName( cwd, TS7_FILE_SYSTEM_CALLBACKS, timing, + librariesFromDisk, tscTsEnv, ]), ) diff --git a/packages/compiler/src/frontend/ts7/native-api.ts b/packages/compiler/src/frontend/ts7/native-api.ts index f8e10efc5..fa0690e69 100644 --- a/packages/compiler/src/frontend/ts7/native-api.ts +++ b/packages/compiler/src/frontend/ts7/native-api.ts @@ -1,4 +1,5 @@ import { Ts7Api } from "./api.js"; +import { checkerReadsLibrariesFromDisk } from "./checker-features.js"; import { checkerServerName } from "./checker-server.js"; import { attachNativeTs7Process, spawnNativeTs7Wire } from "./native-process.js"; import { Ts7RpcClient } from "./rpc-client.js"; @@ -15,12 +16,17 @@ export function createNativeTs7Api(options: { shared?: boolean; }): Ts7Api { const timing = options.collectTiming ?? false; + // The checker reads its own library files when it can: they are part of the + // installed tsc-ts, so no build serves them differently. + const libraries = checkerReadsLibrariesFromDisk(options.executable); const server = - options.shared === false ? null : checkerServerName(options.executable, options.cwd, timing); + options.shared === false + ? null + : checkerServerName(options.executable, options.cwd, timing, process.env, libraries); const shared = server === null ? null - : attachNativeTs7Process(server, options.executable, options.cwd, timing); + : attachNativeTs7Process(server, options.executable, options.cwd, timing, libraries); if (shared !== null) { const rpc = new Ts7RpcClient(new Ts7Wire(shared)); try { @@ -32,7 +38,7 @@ export function createNativeTs7Api(options: { } } return new Ts7Api( - new Ts7RpcClient(spawnNativeTs7Wire(options.executable, options.cwd, timing)), + new Ts7RpcClient(spawnNativeTs7Wire(options.executable, options.cwd, timing, libraries)), options.fs, timing, ); diff --git a/packages/compiler/src/frontend/ts7/native-process.ts b/packages/compiler/src/frontend/ts7/native-process.ts index 0582ae673..d59543f32 100644 --- a/packages/compiler/src/frontend/ts7/native-process.ts +++ b/packages/compiler/src/frontend/ts7/native-process.ts @@ -10,6 +10,7 @@ declare function ts7NativeOpen( cwd: string, callbacks: string, timing: boolean, + librariesFromDisk: boolean, ): number; declare function ts7NativeAttach( name: string, @@ -17,6 +18,7 @@ declare function ts7NativeAttach( cwd: string, callbacks: string, timing: boolean, + librariesFromDisk: boolean, idleExit: number, memory: number, ): number; @@ -63,12 +65,26 @@ export class NativeTs7Process implements Ts7WireIo { private handle: number; readonly pid: number; - /** With `handle`, a connection to a shared checker server (attachNativeTs7Process). */ - constructor(executable: string, cwd: string, collectTiming = false, handle = 0) { + /** With `handle`, a connection to a shared checker server (attachNativeTs7Process). With + * `librariesFromDisk`, the server reads its default library files from the disk + * (checkerReadsLibrariesFromDisk). */ + constructor( + executable: string, + cwd: string, + collectTiming = false, + handle = 0, + librariesFromDisk = false, + ) { this.handle = handle !== 0 ? handle - : ts7NativeOpen(executable, cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming); + : ts7NativeOpen( + executable, + cwd, + TS7_FILE_SYSTEM_CALLBACKS, + collectTiming, + librariesFromDisk, + ); if (this.handle === 0) throw nativeError("startup"); this.pid = ts7NativePid(this.handle); } @@ -125,6 +141,7 @@ export function attachNativeTs7Process( executable: string, cwd: string, collectTiming = false, + librariesFromDisk = false, ): NativeTs7Process | null { const handle = ts7NativeAttach( name, @@ -132,6 +149,7 @@ export function attachNativeTs7Process( cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming, + librariesFromDisk, CHECKER_SERVER_IDLE_EXIT, CHECKER_SERVER_MEMORY, ); @@ -142,6 +160,7 @@ export function spawnNativeTs7Wire( executable: string, cwd: string, collectTiming = false, + librariesFromDisk = false, ): Ts7Wire { - return new Ts7Wire(new NativeTs7Process(executable, cwd, collectTiming)); + return new Ts7Wire(new NativeTs7Process(executable, cwd, collectTiming, 0, librariesFromDisk)); } diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b729b2557..b3de05846 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -21,6 +21,7 @@ import { checkerExecutableIdentity, setLibDiagnosticsStore, } from "../frontend/ts7/lib-diagnostics.js"; +import { setCheckerFeatureStore } from "../frontend/ts7/checker-features.js"; import { loadNativeToolchain } from "./toolchain.js"; import { buildSanitizedRuntime } from "./sanitizer.js"; import { runCompilerTask } from "./task.js"; @@ -67,6 +68,11 @@ async function main(): Promise { }, checkerExecutableIdentity(toolchain.ts7Executable), ); + setCheckerFeatureStore({ + read: (key) => + store.read("checker-features", key, NATIVE_STRING_BYTES)?.toString("utf8") ?? null, + write: (key, text) => store.write("checker-features", key, text), + }); } compiler = new NativeCompiler(toolchain, cache); return compiler; From d8f287486087df2931f33db768b615c42fe8535f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:42:25 -0500 Subject: [PATCH 220/395] Keep replace callback parameters checked-dynamic where parseInt or parseFloat expects another type parseFloat and the text of parseInt take a string, and the radix of parseInt takes a number. An unannotated offset passed as their text, or a capture passed as the radix, is no longer bound to its value's type; it stays checked-dynamic, so the call compiles as it did before the binding. --- .../lowering/containers/replacer-params.ts | 26 +++++++++++++------ .../compiler/test/replacer-params.test.ts | 7 ++++- .../8345-replace-callback-untyped-params.ts | 5 ++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts index e84ca1461..3a3eff1cf 100644 --- a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts @@ -115,7 +115,10 @@ const STRING_MEMBERS: ReadonlySet = new Set([ /** Whether every read of `symbol` in `body` is one that means the same * for an `any` value and for a `kind` value, and lowers for both: a - * template span, a Number/String/parseInt/parseFloat argument, a listed + * template span, the argument of Number or String, the text argument of + * parseInt or parseFloat for a string and the radix of parseInt for a + * number (each a parameter of that type, which an `any` value converts + * to), a listed * string member, `+`, strict equality, arithmetic and comparisons of * numbers, a returned value, and a string key of an element access on a * string-indexed record or an `any` value. Anything else keeps the @@ -144,13 +147,20 @@ function readsFit( return shape?.indexValue !== undefined && !shape.tuple; } if (ts.isArrowFunction(parent)) return parent.body === node; - if (ts.isCallExpression(parent)) - return ( - parent.expression !== node && - ["Number", "String", "parseInt", "parseFloat"].some((global) => - lowerer.isStdlibGlobal(parent.expression, global), - ) - ); + if (ts.isCallExpression(parent)) { + if (parent.expression === node) return false; + const position = parent.arguments.nodes.indexOf(node as ts.Expression); + if ( + lowerer.isStdlibGlobal(parent.expression, "Number") || + lowerer.isStdlibGlobal(parent.expression, "String") + ) + return position === 0; + if (lowerer.isStdlibGlobal(parent.expression, "parseFloat")) + return position === 0 && kind === "string"; + if (lowerer.isStdlibGlobal(parent.expression, "parseInt")) + return position === 0 ? kind === "string" : position === 1 && kind === "f64"; + return false; + } if (ts.isPropertyAccessExpression(parent)) { const member = parent.name.text; if (kind !== "string" || parent.expression !== node || !STRING_MEMBERS.has(member)) diff --git a/packages/compiler/test/replacer-params.test.ts b/packages/compiler/test/replacer-params.test.ts index bb5549a86..a5d22bb82 100644 --- a/packages/compiler/test/replacer-params.test.ts +++ b/packages/compiler/test/replacer-params.test.ts @@ -69,6 +69,8 @@ console.log(text.replace(/(a)|(b)/g, (m, a, b) => \`\${m}\${a}\${b}\`)); console.log(text.replace(/(a)(b)?/g, (m: string, a: string, b: string | undefined, at: number) => m + a + (b ?? "") + at)); const half = (n: number): number => n / 2; console.log(text.replace(/b/g, (m, at) => String(half(at)))); +console.log(text.replace(/b/g, (m, at) => String(parseFloat(at) + parseInt(at)))); +console.log(text.replace(/(\\d)/g, (m, d) => String(parseInt("11", d)))); console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b + c + at + all + extra)); `); expect(replacerCalls(llvm)).toEqual({ @@ -76,7 +78,10 @@ console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b // two captures a match can leave unset (bits 1 and 2); // \`string | undefined\` at capture 2 (bit 8 + 2); an offset passed to a // number parameter keeps its checked-dynamic box (bit 1). - direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2"], + // parseFloat and parseInt take text, and parseInt's radix is a + // number: the offset passed as text and the capture passed as the + // radix stay checked-dynamic (bit 1), so both calls compile. + direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2", "2:2", "2:2"], // Seven parameters exceed the direct calls' limit. boxed: 1, }); diff --git a/tests/corpus/8345-replace-callback-untyped-params.ts b/tests/corpus/8345-replace-callback-untyped-params.ts index 31d3a038b..18ddb3389 100644 --- a/tests/corpus/8345-replace-callback-untyped-params.ts +++ b/tests/corpus/8345-replace-callback-untyped-params.ts @@ -51,6 +51,11 @@ console.log("ab".replace(/(?a)|(?b)/g, (m, a, b, at, all, groups) => `${m} console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); console.log("ab".replace(/(a)(b)/, (m, a, b, at, all, extra, more) => `${m}${a}${b}${at}${all}${extra}${more}`)); +// An offset passed to parseFloat converts with ToString, and captures +// passed to parseInt and parseFloat are their text. +console.log("a1b22".replace(/\d+/g, (m, at) => `<${parseFloat(at) + parseInt(m, at + 10)}>`)); +console.log("1.5 2e1".replace(/(\S+)/g, (_m, d) => String(parseFloat(d) + parseInt(d) + Number(d)))); + // Results that are not strings convert with ToString. const counts: Record = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; console.log("a b c d e".replace(/\w/g, (m) => counts[m])); From c2e6c962e84988d9e178b82eb80b9841f4cdbec9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:49:46 -0500 Subject: [PATCH 221/395] Build the sanitized three.js nodes fixture in its own CI job On the san 1/20 shard the sanitized nodes build shares the runner with the corpus's other two workers and the side queue. It took 944 s and 860 s on #2194's runs and passed 1200 s on main's 7fa88083, failing the test timeout. The Linux san corpus now skips that one case (SCRIPTC_THREE_SANITIZED_NODES_JOB), and a three_nodes_san job runs it alone with the corpus's LeakSanitizer settings. The required test check waits for the new job. Every other three.js case, the plain nodes build and the macOS suites are unchanged. --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++++ scripts/ci-test-plan.mjs | 10 ++++++++ tests/harness/ci-test-plan.test.ts | 37 ++++++++++++++++++++++++++++++ tests/harness/three.test.ts | 9 ++++++++ 4 files changed, 89 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e949339fc..2b690b100 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -346,6 +346,37 @@ jobs: - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" - run: pnpm test tests/harness/effect.test.ts tests/harness/effect4.test.ts + # The sanitized three.js nodes build alone: beside the san corpus's other + # workers it took 860 to more than 1200 seconds of its shard + # (SCRIPTC_THREE_SANITIZED_NODES_JOB in scripts/ci-test-plan.mjs). It keeps + # the corpus's LeakSanitizer settings. + three_nodes_san: + needs: [validation_scope, lanes, native_linux] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} + name: test (Linux, san, three.js nodes) + runs-on: ubuntu-24.04 + timeout-minutes: 40 + env: + SCRIPTC_SAN: "1" + ASAN_OPTIONS: detect_leaks=1 + LSAN_OPTIONS: suppressions=${{ github.workspace }}/tests/harness/lsan.supp:print_suppressions=0 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + - run: pnpm install --frozen-lockfile + - uses: actions/download-artifact@v4 + with: + name: native-linux-x64-gnu + path: ${{ runner.temp }} + - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" + - run: pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'" + # Build each instrumented seed once. Downstream contracts retain the same # compiler and source artifacts without repeating seed construction. bootstrap_seed: @@ -950,6 +981,7 @@ jobs: tests_macos, native_linux, effects, + three_nodes_san, bootstrap_seed, bootstrap, linux_host_clang, @@ -982,6 +1014,7 @@ jobs: lane tests_macos "${{ needs.tests_macos.result }}" "${{ needs.lanes.outputs.macos }}" lane native_linux "${{ needs.native_linux.result }}" "${{ needs.lanes.outputs.linux }}" lane effects "${{ needs.effects.result }}" "${{ needs.lanes.outputs.effects }}" + lane three_nodes_san "${{ needs.three_nodes_san.result }}" "${{ needs.lanes.outputs.linux }}" lane bootstrap_seed "${{ needs.bootstrap_seed.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}" diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 184f04d1c..88a77e32e 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -134,6 +134,15 @@ export const leakSuppressions = fileURLToPath( new URL("../tests/harness/lsan.supp", import.meta.url), ); +// The sanitized three.js nodes build runs in its own Linux job +// (three_nodes_san in .github/workflows/ci.yml). Beside the corpus's other +// workers it took 860 to more than 1200 seconds, past its test timeout. +export const sanitizedNodesJobEnv = { SCRIPTC_THREE_SANITIZED_NODES_JOB: "1" }; + +function corpusThreeEnv(flavor, platform) { + return flavor === "san" && platform === "linux" ? sanitizedNodesJobEnv : {}; +} + function corpusLeakEnv(flavor, platform) { if (flavor !== "san" || platform !== "linux") return {}; return { @@ -246,6 +255,7 @@ export function ciTestPlan({ env: { ...env, ...corpusLeakEnv(flavor, platform), + ...corpusThreeEnv(flavor, platform), SCRIPTC_TEST_WORKERS: String(workers - sideWorkers), SCRIPTC_LLVM_TEST_MODE: "dev", }, diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 29268c841..e67e5582b 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -12,6 +12,7 @@ import { isolatedFiles, leakSuppressions, memoryHeavyFiles, + sanitizedNodesJobEnv, sideOwners, testCosts, } from "../../scripts/ci-test-plan.mjs"; @@ -90,6 +91,42 @@ test("LeakSanitizer runs over the sanitized corpus on Linux only", () => { for (const task of plan("san", "linux").side) expect(task.env.ASAN_OPTIONS).toBeUndefined(); }); +test("the sanitized three.js nodes build leaves the Linux corpus for its own job", () => { + const plan = (flavor: "plain" | "san", platform: string) => + ciTestPlan({ shard: "1/20", flavor, workers: 4, platform }); + const [name, value] = Object.entries(sanitizedNodesJobEnv)[0]!; + expect(plan("san", "linux").corpus.env[name]).toBe(value); + for (const env of [plan("plain", "linux").corpus.env, plan("san", "darwin").corpus.env]) + expect(env[name]).toBeUndefined(); + const three = readFileSync(new URL("./three.test.ts", import.meta.url), "utf8"); + expect(three).toContain(`process.env["${name}"] === "${value}"`); + const { jobs } = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), + ) as { + jobs: Record< + string, + { needs: string[]; env: Record; steps: { run?: string }[] } + >; + }; + const job = jobs["three_nodes_san"]!; + expect(job.needs).toContain("native_linux"); + expect(jobs["test"]!.needs).toContain("three_nodes_san"); + expect(job.env[name]).toBeUndefined(); + expect(job.env["SCRIPTC_SAN"]).toBe("1"); + // The same LeakSanitizer settings as the corpus it left. + const corpus = plan("san", "linux").corpus.env; + expect(job.env["ASAN_OPTIONS"]).toBe(corpus.ASAN_OPTIONS); + expect(job.env["LSAN_OPTIONS"]).toBe( + corpus.LSAN_OPTIONS!.replace( + leakSuppressions, + "${{ github.workspace }}/tests/harness/lsan.supp", + ), + ); + expect(job.steps.at(-1)!.run).toBe( + `pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'"`, + ); +}); + test("the side queue balances measured files across the twenty Linux shards", () => { for (const flavor of ["plain", "san"] as const) { const costs = testCosts[flavor] as Record; diff --git a/tests/harness/three.test.ts b/tests/harness/three.test.ts index 27fb86d1f..e55242149 100644 --- a/tests/harness/three.test.ts +++ b/tests/harness/three.test.ts @@ -68,6 +68,15 @@ test.for(cases)( async ({ fixture, target }, context) => { const entry = join(import.meta.dirname, `../fixtures/three/${fixture}.mjs`); if (target === "wasm32-wasi" && !hasZig) context.skip(); + // CI's Linux sanitized lane builds this one in its own job + // (scripts/ci-test-plan.mjs). + if ( + fixture === "nodes" && + target === "native" && + process.env["SCRIPTC_SAN"] === "1" && + process.env["SCRIPTC_THREE_SANITIZED_NODES_JOB"] === "1" + ) + context.skip(); const dir = await mkdtemp("/tmp/scriptc-three-"); const previousTarget = process.env["SCRIPTC_TARGET"]; const previousCc = process.env["SCRIPTC_CC"]; From c80945ec21ddbd208f7191b2547ad217d6b76987 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:50:19 -0500 Subject: [PATCH 222/395] Leave timing and cache placement variables out of the frontend cache key The native frontend cache key included every SCRIPTC_* variable, so turning SCRIPTC_TIMING on or off, or changing SCRIPTC_CACHE_DIR, SCRIPTC_CACHE_MAX_MB, SCRIPTC_EMIT_WORKERS or SCRIPTC_EMIT_WORKER_FUNCTIONS, reran the frontend on an unchanged program. The frontend's output does not depend on these variables, so the key now leaves them out. Every other SCRIPTC_* variable still keys the entry. --- packages/compiler/src/native/prepare.test.ts | 27 +++++++++++++++++++- packages/compiler/src/native/prepare.ts | 26 ++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/native/prepare.test.ts b/packages/compiler/src/native/prepare.test.ts index 5d0f9faae..07698820e 100644 --- a/packages/compiler/src/native/prepare.test.ts +++ b/packages/compiler/src/native/prepare.test.ts @@ -1,7 +1,7 @@ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, expect, test } from "vitest"; +import { afterEach, expect, test, vi } from "vitest"; import { LINUX_ARM64_GNU_TARGET, nativeCodegenTarget, @@ -23,6 +23,7 @@ function text(input: ReturnType["prepare"]>) { const scratch: string[] = []; afterEach(() => { + vi.unstubAllEnvs(); for (const path of scratch.splice(0)) rmSync(path, { recursive: true, force: true }); }); @@ -197,6 +198,30 @@ test("structurally invalid cached features cause a rebuild even with a valid dig expect(f.loads()).toBe(2); }); +test("timing, cache placement and emission worker variables keep the frontend output", () => { + for (const name of [ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", + "SCRIPTC_TEST_FRONTEND_KEY", + ]) + vi.stubEnv(name, undefined); + const f = fixture(); + expect(f.prepare().ok).toBe(true); + vi.stubEnv("SCRIPTC_CACHE_DIR", f.cache.root); + vi.stubEnv("SCRIPTC_CACHE_MAX_MB", "8192"); + vi.stubEnv("SCRIPTC_EMIT_WORKERS", "1"); + vi.stubEnv("SCRIPTC_EMIT_WORKER_FUNCTIONS", "100"); + vi.stubEnv("SCRIPTC_TIMING", "0"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(1); + vi.stubEnv("SCRIPTC_TEST_FRONTEND_KEY", "1"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(2); +}); + test("serialized IR travels as bounded parts through the frontend cache", () => { const f = fixture(); const first = f.prepare({ emitIr: true }); diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index a10d16585..11b8f9dd1 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -211,6 +211,24 @@ function implementationIdentity(toolchain: NativeToolchain): string { ); } +/** SCRIPTC_* variables that report on a build, place its cache or set + * backend emission parallelism. The frontend's output does not depend on + * them, so they are left out of its cache key. */ +const FRONTEND_INDEPENDENT_VARIABLES = new Set([ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", +]); + +/** The SCRIPTC_* variables a frontend cache entry depends on, sorted by name. */ +function frontendEnvironment(): [string, string | undefined][] { + return Object.entries(process.env) + .filter(([name]) => name.startsWith("SCRIPTC_") && !FRONTEND_INDEPENDENT_VARIABLES.has(name)) + .sort(([a], [b]) => a.localeCompare(b)); +} + function validFeatures(value: unknown): value is NativeLinkFeatures { if (value === null || typeof value !== "object" || Array.isArray(value)) return false; const features = value as Record; @@ -363,9 +381,7 @@ export function prepareNativeExecutable( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions); @@ -493,9 +509,7 @@ export function prepareNativeLibrary( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions); From 5247d2dfdd6bc2d0274151aec0d3059a5191962b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:28:48 -0500 Subject: [PATCH 223/395] List bench/regex-replacers among the dynamic-tier workloads Its replace callbacks receive checked-dynamic values where the lib types their parameters any: captures a match can leave unset, and results the checker types any. --- scripts/zero-cost-compare.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index 7ae33d64a..df407bbb7 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -398,7 +398,10 @@ export function dynamicTierSymbols(names) { * later too, decodes JSON.parse trees. bench/throw-chains, also added * later, tests caught values with `instanceof`, as bench/validate-errors * does. bench/dynamic-methods and - * bench/dynamic-properties are untyped JavaScript. Every other bench workload is fully typed and must link none. The gate and + * bench/dynamic-properties are untyped JavaScript. bench/regex-replacers, + * also added later, passes checked-dynamic values to replace callbacks + * whose parameters the lib types `any`: captures a match can leave unset + * and results the checker types `any`. Every other bench workload is fully typed and must link none. The gate and * tests/harness/bench-dynamic-tier.test.ts enforce that, and the test also * fails when a listed workload stops linking them, so remove a workload here * when a leak fix makes its binary clean. */ @@ -421,6 +424,7 @@ export const DYNAMIC_WORKLOADS = new Set([ "bench/line-reader", "bench/json-unions", "bench/throw-chains", + "bench/regex-replacers", ]); /** Names of the functions (text and weak symbols) in `nm` output. Mach-O From 63a99cc4d94be47f7282a6df4926e8041ac1ced1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:37:50 -0500 Subject: [PATCH 224/395] Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it A versioned loop over a non-ASCII string passes the range of indexes it reads: a counted loop's start and limit, or a while loop's counter and bound. The runtime converts the string at once when that range has at least one unit per 16 bytes of it, and otherwise counts the loop as one indexed read, so a loop over a line of a long source text no longer keeps a UTF-16 copy of all of it. The publish call is weighted as unlikely, since loops re-entered over one string skip it. --- .../src/backend/llvm/bytes-loop-versions.ts | 48 +++++++-- .../compiler/src/backend/llvm/string-reads.ts | 8 +- .../test/string-unit-loop-emission.test.ts | 101 ++++++++++++++++-- packages/runtime/src/scr_string.c | 22 ++-- packages/runtime/src/scr_string_methods.h | 6 +- packages/runtime/test/test_string.c | 28 ++++- 6 files changed, 182 insertions(+), 31 deletions(-) diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index de704ad42..ab941e658 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -1146,7 +1146,7 @@ export function emitBytesLoopVersionGuard( }; const lengths = receivers.map((receiver, index) => { if (index === unitsIndex) { - const read = emitUnitsLength(host, receiver); + const read = emitUnitsLength(host, receiver, () => emitUnitsSpan(host, version, index)); units.read = read; return read.length; } @@ -1359,15 +1359,49 @@ export function emitBytesLoopVersionGuard( return { units: taken.pointer, length }; } +/** The range of indexes a version's loop reads from its units receiver, as + * two doubles: a counted loop's start and limit, or a while loop's one + * counter and the receiver's bound. Both NaN when the version gives no such + * range. The runtime converts the receiver at once only when the range is + * long enough to pay for it (scr_str_scan_units_for). */ +function emitUnitsSpan( + host: LlvmEmitterContext, + version: BytesLoopVersion, + receiver: number, +): { from: string; to: string } { + // Locals and literals only: they read no string (a `length` of the + // receiver would measure it). Anything else leaves the range unbounded. + const number = (expr: IrExpr): string | null => { + if (expr.kind !== "varRef" && expr.kind !== "numLit") return null; + const value = host.emitExpr(expr); + return value.type.kind === "f64" ? value.name : null; + }; + const unbounded = { from: "0x7FF8000000000000", to: "0x7FF8000000000000" }; + const counted = version.counted; + let from: IrExpr, to: IrExpr; + if (counted) [from, to] = [counted.start, counted.limit]; + else { + const bounded = version.bounded.filter((check) => check.receiver === receiver); + if (version.counters.length !== 1 || bounded.length !== 1 || version.paired !== null) + return unbounded; + [from, to] = [version.counters[0]!.ref, bounded[0]!.bound]; + } + const start = number(from), + limit = number(to); + return start !== null && limit !== null ? { from: start, to: limit } : unbounded; +} + /** The length of a version's units receiver `s` in UTF-16 units: its byte * length when it is ASCII; otherwise, once it is the scan receiver, the * published unit count (0 when the runtime does not hold its units). When * `s` is not already the scan receiver, the runtime converts it to units - * and publishes it (scr_str_scan_units). Also the units pointer, whether - * the loop is to read it, and whether the published receiver is `s`. */ + * and publishes it if the loop's `span` makes that pay + * (scr_str_scan_units_for). Also the units pointer, whether the loop is to + * read it, and whether the published receiver is `s`. */ function emitUnitsLength( host: LlvmEmitterContext, s: string, + span: () => { from: string; to: string }, ): { length: string; pointer: string; taken: string; published: string } { const B = host.B; const sz = host.sizeType; @@ -1381,14 +1415,15 @@ function emitUnitsLength( B.startBlock(publish); // When `s` is already the scan receiver (an earlier loop over it published // it), its units stay published until the runtime clears the scan: skip - // the call. + // the call. Loops re-entered over one string take that path; the weights + // keep the call and its reach out of the loop's code. const held = B.tmp(), holds = B.tmp(); B.line(`${held} = load ptr, ptr @scr_str_scan`); B.line(`${holds} = icmp eq ptr ${held}, ${s}`); const call = B.newLabel("bytes.version.publish.units"), load = B.newLabel("bytes.version.publish.load"); - B.condBr(holds, load, call); + B.condBr(holds, load, call, `, !prof !{!"branch_weights", i32 1000, i32 1}`); B.startBlock(call); host.declare(STRING_SCAN_UNITS_DECL); const receiver = B.tmp(), @@ -1398,7 +1433,8 @@ function emitUnitsLength( countSlot = B.tmp(), count = B.tmp(), published = B.tmp(); - B.line(`call void @scr_str_scan_units(ptr ${s})`); + const { from: first, to: bound } = span(); + B.line(`call void @scr_str_scan_units_for(ptr ${s}, double ${first}, double ${bound})`); B.br(load); B.startBlock(load); B.line(`${receiver} = load ptr, ptr @scr_str_scan`); diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 9fd2fa78c..5b7a26c6c 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -25,9 +25,11 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js"; export const STRING_UTF16_LEN_DECL = "declare double @scr_str_utf16_len(ptr)"; export const STRING_CHAR_CODE_AT_DECL = "declare double @scr_str_char_code_at(ptr, double)"; /** Converts a string to UTF-16 units and publishes it as the scan receiver - * (bytes-loop-versions.ts). Like the fallbacks below, it can evict an - * index entry, so it carries no memory restriction. */ -export const STRING_SCAN_UNITS_DECL = "declare void @scr_str_scan_units(ptr)"; + * when the loop about to read it can read enough of it (the other two + * operands are the range of indexes it reads; bytes-loop-versions.ts). + * Like the fallbacks below, it can evict an index entry, so it carries no + * memory restriction. */ +export const STRING_SCAN_UNITS_DECL = "declare void @scr_str_scan_units_for(ptr, double, double)"; /** The runtime fallbacks are cold: both common cases read inline. They * carry no * memory restriction: a fallback can evict an index entry, which resets diff --git a/packages/compiler/test/string-unit-loop-emission.test.ts b/packages/compiler/test/string-unit-loop-emission.test.ts index fa11af007..f522159dc 100644 --- a/packages/compiler/test/string-unit-loop-emission.test.ts +++ b/packages/compiler/test/string-unit-loop-emission.test.ts @@ -51,8 +51,11 @@ console.log(total, spaces("plain ascii text"), spaces("")); const spaces = body("spaces"); // A string that is not ASCII is converted to units and published as the - // scan receiver once, before the loop, and its copy loads units. - expect(spaces).toMatch(/call void @scr_str_scan_units\(ptr %\S+\)/); + // scan receiver once, before the loop, and its copy loads units. The + // loop runs to the length, so it gives the runtime no range (NaN). + expect(spaces).toMatch( + /call void @scr_str_scan_units_for\(ptr %\S+, double 0x7FF8000000000000, double 0x7FF8000000000000\)/, + ); // The copies follow the checked one: bytes, then units. const bytesAt = spaces.search(/^bytes\.version\.fast\d+:/m); const unitsAt = spaces.search(/^bytes\.version\.fast\.units\d+:/m); @@ -127,19 +130,27 @@ console.log(words(text), words(text.replace(/a/g, "ä")), words("plain ascii")); // the runtime to publish `s` only when they differ. const guards = [ ...words.matchAll( - /^bytes\.version\.publish\d+:[^\n]*\n\s+(%\S+) = load ptr, ptr @scr_str_scan\n\s+(%\S+) = icmp eq ptr \1, (%\S+)\n\s+br i1 \2, label %(bytes\.version\.publish\.load\d+), label %(bytes\.version\.publish\.units\d+)\n/gm, + /^bytes\.version\.publish\d+:[^\n]*\n\s+(%\S+) = load ptr, ptr @scr_str_scan\n\s+(%\S+) = icmp eq ptr \1, (%\S+)\n\s+br i1 \2, label %(bytes\.version\.publish\.load\d+), label %(bytes\.version\.publish\.units\d+)[^\n]*\n/gm, ), ]; expect(guards.length).toBeGreaterThanOrEqual(2); for (const [, , , s, load, convert] of guards) { - const block = words.slice(words.search(new RegExp(`^${convert}:`, "m"))); - expect(block).toMatch( - new RegExp( - `^${convert}:[^\\n]*\\n\\s+call void @scr_str_scan_units\\(ptr ${s}\\)\\n\\s+br label %${load}\\n`, - ), + // The block reads the loop's range and makes no other call. + const block = words + .slice(words.search(new RegExp(`^${convert}:`, "m"))) + .match(new RegExp(`^${convert}:[^\\n]*\\n([\\s\\S]*?)\\n\\s+br label %${load}\\n`))!; + expect(block).not.toBeNull(); + const lines = block[1]!.split("\n").map((line) => line.trim()); + expect(lines.at(-1)).toMatch( + new RegExp(`^call void @scr_str_scan_units_for\\(ptr ${s}, double \\S+, double \\S+\\)$`), + ); + expect(lines.slice(0, -1).filter((line) => /\bcall\b(?! double @llvm\.)/.test(line))).toEqual( + [], ); } - const calls = words.match(/call void @scr_str_scan_units\(ptr %\S+\)/g)!; + const calls = words.match( + /call void @scr_str_scan_units_for\(ptr %\S+, double \S+, double \S+\)/g, + )!; expect(calls.length).toBe(guards.length); const exe = join(dir, "main"); @@ -150,3 +161,75 @@ console.log(words(text), words(text.replace(/a/g, "ä")), words("plain ascii")); await rm(dir, { recursive: true, force: true }); } }); + +test("loops over part of a string give the runtime the range of indexes they read", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unit-reach-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + ` +function trim(text: string, start: number, end: number): string { + while (start < end && text.charCodeAt(start) === 32) start++; + return text.substring(start, end); +} +function hasBreak(text: string, pos: number, end: number): boolean { + for (let i = pos; i < end; i++) if (text.charCodeAt(i) === 10) return true; + return false; +} +let text = ""; +for (let k = 0; k < 2000; k++) text += k % 7 === 0 ? " café\\n" : " naïve façade"; +let kept = 0, + breaks = 0; +for (let at = 0; at + 12 < text.length; at += 12) { + kept += trim(text, at, at + 12).length; + if (hasBreak(text, at, at + 12)) breaks++; +} +console.log(kept, breaks); +`, + ); + 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}(?:\\.i32)?\\([^\\n]*\\) #0 \\{[\\s\\S]*?\\n\\}`, + ), + )![0]; + // The publish block passes the loop's range with the receiver: the + // counter and the bound (a while loop), or the start and the limit (a + // counted loop), here the function's parameters. + for (const name of ["trim", "hasBreak"]) { + const block = body(name).match( + /^bytes\.version\.publish\.units\d+:[^\n]*\n([\s\S]*?)\n\s+br label %bytes\.version\.publish\.load\d+\n/m, + )!; + expect(block, name).not.toBeNull(); + const [, lines] = block; + const call = lines!.match( + /call void @scr_str_scan_units_for\(ptr %\S+, double (%\S+), double (%\S+)\)/, + )!; + expect(call, name).not.toBeNull(); + expect(call[1]).not.toBe(call[2]); + } + + const exe = join(dir, "main"); + const built = await compile(entry, { outputKind: "exe", outDir: dir, outPath: exe }); + if (!built.ok) throw new Error(JSON.stringify(built.diagnostics)); + let text = ""; + for (let k = 0; k < 2000; k++) text += k % 7 === 0 ? " café\n" : " naïve façade"; + let kept = 0, + breaks = 0; + for (let at = 0; at + 12 < text.length; at += 12) { + let start = at; + while (start < at + 12 && text.charCodeAt(start) === 32) start++; + kept += at + 12 - start; + if (text.slice(at, at + 12).includes("\n")) breaks++; + } + expect(execFileSync(exe, { encoding: "utf8" })).toBe(`${kept} ${breaks}\n`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index dfd0d23f8..1e7802fbb 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1889,14 +1889,24 @@ static bool scr_sidx_units(const ScrStr *s, ScrSidx *e) { return true; } -/* A loop about to read every unit of s (bytes-loop-versions.ts) converts it - * at once instead of after the threshold's reads, and publishes it as the - * scan's receiver. ASCII receivers and receivers past SCR_SIDX_UNITS_MAX - * publish nothing; the caller compares scr_str_scan.s with s. */ -void scr_str_scan_units(ScrStr *s) { +/* A loop about to read s (bytes-loop-versions.ts) between the indexes + * `from` and `to` (NaN: no bound) converts it at once instead of after the + * threshold's reads, and publishes it as the scan's receiver, when that + * range has at least one unit per 16 bytes of s (the threshold's ratio). + * A shorter loop, such as one over a line of a source text, counts as one + * read, so a short loop does not keep all of a long string's units. ASCII + * receivers and receivers past SCR_SIDX_UNITS_MAX publish nothing; the + * caller compares scr_str_scan.s with s. */ +void scr_str_scan_units_for(ScrStr *s, double from, double to) { if (s->ascii || s->len > SCR_SIDX_UNITS_MAX) return; ScrSidx *e = scr_sidx(s); - if (!e->units) e->units = scr_str_units(s, &e->nunits); + if (!e->units) { + if (fabs(to - from) * 16 < (double)s->len) { + (void)scr_sidx_units(s, e); + return; + } + e->units = scr_str_units(s, &e->nunits); + } if (scr_str_scan.s != s) { scr_str_scan.s = s; scr_str_scan.units = e->units; diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 820b1c9e6..01ccdd8ae 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -43,9 +43,11 @@ extern SCR_TL ScrStrScan scr_str_scan; double scr_str_char_code_at(ScrStr *s, double i); /* Converts a non-ASCII s up to the units cap to UTF-16 at once and - * publishes it in scr_str_scan, for a loop about to read all of it. Leaves + * publishes it in scr_str_scan, for a loop about to read it between the + * indexes `from` and `to` (NaN: no bound), when that range has at least + * one unit per 16 bytes. A shorter loop counts as one indexed read. Leaves * scr_str_scan unchanged for any other s. */ -void scr_str_scan_units(ScrStr *s); +void scr_str_scan_units_for(ScrStr *s, double from, double to); /* indexOf(needle, fromIndex): UTF-16 index of the first occurrence at or * after fromIndex (clamped to [0, length]), or -1. Empty needle returns the diff --git a/packages/runtime/test/test_string.c b/packages/runtime/test/test_string.c index 3e14a5958..f41c2de50 100644 --- a/packages/runtime/test/test_string.c +++ b/packages/runtime/test/test_string.c @@ -523,27 +523,45 @@ static void short_string_asserts(void) { } /* A loop's publish converts a non-ASCII receiver before any read, while a - * lone read still walks bytes. ASCII receivers publish nothing. */ + * lone read still walks bytes. ASCII receivers publish nothing. A loop + * that can read fewer than one unit per 16 bytes counts as one read. */ static void scan_units_asserts(void) { ScrStr *s = scr_str_new("a\xC3\xA9\xF0\x9F\x98\x80" "b", 8); /* a é 😀 b */ static const double codes[] = {97, 233, 0xD83D, 0xDE00, 98}; if (scr_str_char_code_at(s, 0) != 97 || scr_str_scan.s == s) failed++; - scr_str_scan_units(s); + scr_str_scan_units_for(s, NAN, NAN); if (scr_str_scan.s != s || scr_str_scan.n != 5) failed++; for (size_t i = 0; i < 5; i++) if (scr_str_scan.n == 5 && (scr_str_scan.units[i] != codes[i] || scr_str_char_code_at(s, (double)i) != codes[i])) failed++; if (!isnan(scr_str_char_code_at(s, 5))) failed++; - scr_str_scan_units(s); /* republishing keeps the same units */ + scr_str_scan_units_for(s, NAN, NAN); /* republishing keeps the same units */ const uint16_t *units = scr_str_scan.units; - scr_str_scan_units(s); + scr_str_scan_units_for(s, 0, 1); if (scr_str_scan.s != s || scr_str_scan.units != units) failed++; ScrStr *ascii = scr_str_new("plain", 5); - scr_str_scan_units(ascii); + scr_str_scan_units_for(ascii, NAN, NAN); if (scr_str_scan.s != s) failed++; scr_str_release(ascii); scr_str_release(s); if (scr_str_scan.s != NULL) failed++; + /* 200 units in 400 bytes: a loop over 25 indexes converts at once. */ + char text[400]; + for (size_t i = 0; i < sizeof text; i += 2) memcpy(text + i, "\xC3\xA9", 2); + ScrStr *wide = scr_str_new(text, sizeof text); + scr_str_scan_units_for(wide, 175, 200); + if (scr_str_scan.s != wide || scr_str_scan.n != 200 || scr_str_scan.units[199] != 233) failed++; + scr_str_release(wide); + /* A loop over 24 does not; each such loop counts as one read, so the + * 32nd converts (32 reads, one per 12.5 bytes). */ + ScrStr *line = scr_str_new(text, sizeof text); + scr_str_scan_units_for(line, 24, 0); + if (scr_str_scan.s == line) failed++; + for (int i = 1; i < 31; i++) scr_str_scan_units_for(line, i, i + 1); + if (scr_str_scan.s == line) failed++; + scr_str_scan_units_for(line, 31, 32); + if (scr_str_scan.s != line || scr_str_scan.n != 200) failed++; + scr_str_release(line); total++; } #ifdef SCR_SIDX_TEST From 390b404251c2894582740f1373937eb9d7d659bc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:46:24 -0500 Subject: [PATCH 225/395] Keep a union loop binding in the debug-variables fixture The fixture's loop iterated `[6, 7] as (number | undefined)[]` so that its binding kept the union layout the LLDB check reads through `item->tag` and `item->slot.arm0`. Since (T | undefined)[] stores T elements, an array that never holds undefined gives a `number` binding, and the check failed with "item is not a pointer". The loop now iterates `[6, 7, undefined]`, whose binding needs the union, and the check also reads the undefined arm's tag. --- packages/compiler/test/debug-variables.test.ts | 3 +++ tests/corpus/3068-debug-variables/main.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/debug-variables.test.ts b/packages/compiler/test/debug-variables.test.ts index ea92cc1d3..946f0f5d1 100644 --- a/packages/compiler/test/debug-variables.test.ts +++ b/packages/compiler/test/debug-variables.test.ts @@ -63,6 +63,8 @@ test.runIf(process.platform === "darwin")( "continue", "frame variable item->tag item->slot.arm0", "continue", + "frame variable item->tag", + "continue", "frame variable hoisted", "continue", "frame variable moduleValue", @@ -91,6 +93,7 @@ test.runIf(process.platform === "darwin")( "item->tag = f64", "item->slot.arm0 = 6", "item->slot.arm0 = 7", + "item->tag = undefined", "hoisted = 13", ]) { expect(debug.stdout).toContain(value); diff --git a/tests/corpus/3068-debug-variables/main.ts b/tests/corpus/3068-debug-variables/main.ts index d9399b49d..2e7cef4d6 100644 --- a/tests/corpus/3068-debug-variables/main.ts +++ b/tests/corpus/3068-debug-variables/main.ts @@ -21,7 +21,7 @@ function inspect(input: number, enabled: boolean, fallback = 9): void { for (let i = 0; i < bytes.length; i++) { console.log("loop", i, bytes[i]); // debug: loop } - for (const item of [6, 7] as (number | undefined)[]) { + for (const item of [6, 7, undefined]) { console.log("item"); // debug: item console.log(item); } From 2823840134976580d129093f6f95cbf1a534f50f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:49:06 -0500 Subject: [PATCH 226/395] Miss non-object receivers early in the property probes and inline the entry check Since the probes began caching absent properties (#2186), an untyped property read that reaches the load or get probe costs more than before: - Receivers that are not ordinary objects, such as class instances, have no shape and always miss, but they set up the probe's frame first. The exported probes now return the miss for them before calling the probe body, which stays out of line. - Each matching entry was answered through an out-of-line call (scr_js_entry_answer). It is inlined into the probe again. dynamic-properties executed 3.1% more instructions than before #2186; it now executes 0.4% fewer. --- packages/runtime/src/scr_js_object.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index e97651557..e3d0b3a54 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -308,13 +308,10 @@ static bool scr_js_absent_hit(const ScrDyn *receiver, const ScrJsIcEntry *entry) walked->v.obj.shape == entry->holder_shape && walked->prototype == NULL; } -/* A load site's probe. With `absent`, a read of a key that the receiver and - * its chain of plain shaped objects (ending in the default prototype) lack - * answers undefined and caches that answer. Method reads leave absent - * methods to the slow path, which names the missing function. */ /* The answer of a matching entry for `receiver`, or NULL when the entry no * longer holds. */ -static ScrDyn *scr_js_entry_answer(ScrDyn *receiver, const ScrJsIcEntry *entry) { +static inline __attribute__((always_inline)) ScrDyn *scr_js_entry_answer(ScrDyn *receiver, + const ScrJsIcEntry *entry) { if (entry->target == &scr_js_absent) return scr_js_absent_hit(receiver, entry) ? scr_dyn_retain(scr_dyn_undefined()) : NULL; return scr_js_load_hit(receiver, entry); @@ -345,7 +342,8 @@ static void scr_js_load_fill(ScrJsIc *ic, uint32_t stale, ScrJsSharedLoad *share * its chain of plain shaped objects (ending in the default prototype) lack * answers undefined and caches that answer. Method reads leave absent * methods to the slow path, which names the missing function. */ -static ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic, bool absent) { +static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, + ScrJsIc *ic, bool absent) { const ScrJsShape *shape = scr_js_shape_of(receiver); if (!shape) return NULL; uint32_t stale = UINT32_MAX; @@ -397,11 +395,15 @@ static ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, ScrJsIc *i return scr_dyn_retain(found->value); } +/* Receivers other than ordinary objects have no shape: they miss before + * the probe sets up its frame. */ ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return NULL; return scr_js_probe_load(receiver, key, ic, false); } ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return NULL; return scr_js_probe_load(receiver, key, ic, true); } From b28af56ba87803d8ec352d709a9649803df3c9d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:53:02 -0500 Subject: [PATCH 227/395] Skip the weak-key table when freeing objects that were never keys Once a program creates a weak collection, every object freed afterwards called into the weak-key table to remove entries keyed by it, although almost none are keys: zod's validation freed 16.6 million objects against 23 keys. A per-thread bit filter indexed by the object's address now marks every address that entered the table (cleared when the table empties), and the free paths test it inline, so an object whose bit is clear makes no call. --- .../corpus/weak-keys-freed-reused.mjs.json | 6 ++++ packages/runtime/src/scr_json.c | 19 +++++++++-- tests/corpus/weak-keys-freed-reused.mjs | 32 +++++++++++++++++++ .../weak-keys-freed-reused.mjs.json | 1 + 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json create mode 100644 tests/corpus/weak-keys-freed-reused.mjs create mode 100644 tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json new file mode 100644 index 000000000..ab4ca8bda --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/weak-keys-freed-reused.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 43b107ae3..cd9eeecde 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1165,8 +1165,20 @@ static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d); * are never the native records the kept-source table is keyed by. */ static SCR_TL bool scr_weak_observing; static void scr_weak_disposed(void *key); +/* One bit per key address, set when a key enters the weak-key table: a + * disposed object whose bit is clear was never a key, so its disposal makes + * no call. Bits are cleared when the table empties. */ +#define SCR_WEAK_KEY_FILTER_BITS ((size_t)1 << 16) +static SCR_TL uint64_t scr_weak_key_filter[SCR_WEAK_KEY_FILTER_BITS / 64]; +static inline size_t scr_weak_key_filter_bit(const void *key) { + return ((uintptr_t)key >> 4) & (SCR_WEAK_KEY_FILTER_BITS - 1); +} +static inline bool scr_weak_maybe_key(const void *key) { + size_t bit = scr_weak_key_filter_bit(key); + return scr_weak_observing && (scr_weak_key_filter[bit >> 6] & (UINT64_C(1) << (bit & 63))); +} static inline void scr_dyn_weak_dispose(ScrDyn *d) { - if (scr_weak_observing) scr_weak_disposed(d); + if (scr_weak_maybe_key(d)) scr_weak_disposed(d); } /* A disposed node's child: most getters, setters, prototypes and symbol @@ -3479,7 +3491,7 @@ static void scr_record_source_disposed(void *rec) { /* The single native dispose hook serves weak collections and the table * above; each observer runs only while it has entries to forget. */ static void scr_dispose_observers(void *object) { - if (scr_weak_observing) scr_weak_disposed(object); + if (scr_weak_maybe_key(object)) scr_weak_disposed(object); if (scr_record_source_count) scr_record_source_disposed(object); } @@ -17413,6 +17425,8 @@ static ScrWeakKeySlot *scr_weak_key_slot_put(void *key) { scr_weak_keys[j].key = key; scr_weak_keys[j].head = NULL; scr_weak_keys_used++; + size_t bit = scr_weak_key_filter_bit(key); + scr_weak_key_filter[bit >> 6] |= UINT64_C(1) << (bit & 63); } return &scr_weak_keys[j]; } @@ -17422,6 +17436,7 @@ static void scr_weak_key_slot_delete(ScrWeakKeySlot *slot) { if (--scr_weak_keys_used == 0) { free(scr_weak_keys); scr_weak_keys = NULL; + memset(scr_weak_key_filter, 0, sizeof scr_weak_key_filter); return; } for (size_t k = i;;) { diff --git a/tests/corpus/weak-keys-freed-reused.mjs b/tests/corpus/weak-keys-freed-reused.mjs new file mode 100644 index 000000000..b24823526 --- /dev/null +++ b/tests/corpus/weak-keys-freed-reused.mjs @@ -0,0 +1,32 @@ +// Weak collection keys that become unreachable leave the collection, and +// new objects allocated afterwards (which can reuse the freed storage) are +// not found in it: plain objects, arrays, class instances and functions, +// alongside many objects that were never keys. +class Point { + constructor(x) { + this.x = x; + } +} +const map = new WeakMap(); +const set = new WeakSet(); +const kept = []; +function fill(round) { + for (let i = 0; i < 200; i++) { + const key = i % 4 === 0 ? { i } : i % 4 === 1 ? [i] : i % 4 === 2 ? new Point(i) : () => i; + map.set(key, round * 1000 + i); + set.add(key); + if (i % 50 === 0) kept.push(key); + } +} +let stale = 0; +let found = 0; +for (let round = 0; round < 20; round++) { + fill(round); + // Objects that were never keys, freed and reallocated in between. + for (let i = 0; i < 500; i++) { + const other = i % 2 ? { other: i } : new Point(-i); + if (map.has(other) || set.has(other)) stale++; + } +} +for (const key of kept) if (map.has(key) && set.has(key)) found++; +console.log(stale, found, kept.length, map.get(kept[0]), map.get(kept[kept.length - 1])); diff --git a/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json b/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json new file mode 100644 index 000000000..738211b5e --- /dev/null +++ b/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json @@ -0,0 +1 @@ +{ "plain": 5167, "san": 10508 } From b1a68147966691e22246422c80ef03d6e0d533cf Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:06:24 -0500 Subject: [PATCH 228/395] Prove typed-array loop bounds in worker programs when a view has shared storage A typed-array loop version in a worker program tested its bounds and its views' ownership together: every view had to have no shared storage. A loop over views of a SharedArrayBuffer always failed that guard and ran the checked copy, testing every element's index. The guard now tests the bounds first and the ownership after. When the bounds hold and every view is unshared, the loop runs the owned copy as before. When a view is shared, it runs a new third copy whose accesses skip their bounds checks and keep the shared-or-local path, without alias scopes. Versions with a UTF-16 units copy, and programs without workers, are unchanged. Corpus 8350 runs such loops over shared, unshared and mixed views, to counts within and past the views and over subarrays, with a worker writing through shared views. The emission test checks the split guard and the shared copy's accesses. --- .../backend/llvm/bytes-loop-versions.test.ts | 10 ++- .../src/backend/llvm/bytes-loop-versions.ts | 38 +++++++- .../src/backend/llvm/emit-statements.ts | 20 ++++- .../8350-worker-shared-loop-bounds.ts.json | 6 ++ ...orker-shared-loop-version-emission.test.ts | 74 ++++++++++++++++ .../corpus/8350-worker-shared-loop-bounds.ts | 86 +++++++++++++++++++ 6 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json create mode 100644 packages/compiler/test/worker-shared-loop-version-emission.test.ts create mode 100644 tests/corpus/8350-worker-shared-loop-bounds.ts 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 096712c97..0fa8fea34 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -317,13 +317,19 @@ test("worker programs test each receiver's shared storage once in the guard", () const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; const guard = body.slice(0, body.search(/^bytes\.version\.(fast|slow)\d+:$/m)); expect(guard.match(/icmp eq ptr %\S+, null/g)).toHaveLength(2); - const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; + 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. + // The checked copy keeps the shared-memory path for each access, and so + // does the copy for shared views whose bounds hold. 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"); }); test("index temps of a version's counter are integers in the fast copy", () => { diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index de704ad42..472934f72 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -20,6 +20,7 @@ import { } from "./counted-loops.js"; import { emitBytesData, emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; import { f64Lit } from "./common.js"; +import { InternalCompilerError } from "../../errors.js"; import { emitStringAsciiFlag, emitStringByteLength, @@ -85,8 +86,15 @@ export interface BytesLoopVersion { /** Root receiver bindings, read once before the loop; `string` marks a * string whose `charCodeAt` and `charAt` reads the version covers. */ receivers: { ref: VarRef; global: boolean; string: boolean }[]; - /** Receivers the guard requires to have no shared storage. */ + /** Receivers the guard requires to have no shared storage for the fast + * copy. */ ownedReceivers: number[]; + /** Worker programs: when the bounds hold but a receiver in + * `ownedReceivers` has shared storage, the loop runs a second fast copy + * instead of the checked one. It skips the bounds checks of `bounds` and + * keeps shared-safe accesses (emitBytesLoopVersionGuard's `sharedFast`). Set + * when the version proves bounds as well as ownership. */ + sharedCopy: boolean; /** The string reads among `bounds`. */ stringReads: ReadonlySet; /** The version's one string receiver, when the loop may also run over @@ -752,6 +760,7 @@ export function matchBytesLoopVersion( owned: ownedNodes, receivers: order.map((index) => receivers[index]!), ownedReceivers: ownedKept.map(([receiver]) => renumber.get(receiver)!), + sharedCopy: ownedKept.length > 0 && bounds.size > 0, stringReads, unitsReceiver, unitLengths: unitLengths ?? new Set(), @@ -1102,6 +1111,10 @@ function findWhileCounterAccesses( * storage, and to `slow` otherwise (including a counted loop whose * integer form is not established, or a module receiver not yet set). * + * With `sharedFast` (a version with `sharedCopy`), the ownership tests are + * separate: when everything else holds but a view has shared storage, the + * guard branches to `sharedFast` instead of `slow`. + * * With `unitsFast`, the version's units receiver may instead be a string * that is not ASCII: the guard has the runtime publish it as the scan * receiver (one read), tests the same accesses against its UTF-16 length, @@ -1115,6 +1128,7 @@ export function emitBytesLoopVersionGuard( fast: string, slow: string, unitsFast?: string, + sharedFast?: string, ): { units: string | null; length: string | null } | null { const B = host.B; const next = (cond: string): void => { @@ -1165,10 +1179,13 @@ export function emitBytesLoopVersionGuard( if (string && index !== unitsIndex) conditions.push(emitStringAsciiFlag(host, receivers[index]!)); }); + // Without `sharedFast`, shared storage sends the loop to the checked copy + // with the other failed tests; with it, only to the shared fast copy. + const ownership: string[] = []; for (const index of version.ownedReceivers) { const owned = B.tmp(); B.line(`${owned} = icmp eq ptr ${emitBytesSharedStorage(host, receivers[index]!)}, null`); - conditions.push(owned); + (sharedFast !== undefined ? ownership : conditions).push(owned); } for (const check of version.fixed) { const ok = B.tmp(); @@ -1351,7 +1368,22 @@ export function emitBytesLoopVersionGuard( const length = version.unitsReceiver !== null ? lengths[version.unitsReceiver]! : null; const taken = units.read; const target = taken ? B.newLabel("bytes.version.units") : fast; - if (valid === "true") B.br(target); + if (sharedFast !== undefined && taken) + throw new InternalCompilerError("llvm emitter bug: a shared fast copy with a units copy"); + if (sharedFast !== undefined && ownership.length > 0) { + // The bounds hold: unshared views take the owned copy. + const split = B.newLabel("bytes.version.owned"); + let owned = ownership[0]!; + for (const cond of ownership.slice(1)) { + const all = B.tmp(); + B.line(`${all} = and i1 ${owned}, ${cond}`); + owned = all; + } + if (valid === "true") B.br(split); + else B.condBr(valid, split, slow); + B.startBlock(split); + B.condBr(owned, target, sharedFast); + } else if (valid === "true") B.br(target); else B.condBr(valid, target, slow); if (!taken) return { units: null, length }; B.startBlock(target); diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index cbdeb0558..dc4e5090c 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2139,7 +2139,9 @@ function declareAliasScopes(emitter: LlEmitter, scopes: readonly string[]): void * fast copy is only emitted when every resume point exists. */ /** A typed-array loop version: the guard, then the checked copy, which * runs whenever the guard fails, and the copy whose accesses the guard - * proves in bounds (or thread-owned). */ + * proves in bounds (or thread-owned). In worker programs a version that + * proves both also has a copy for views with shared storage, in bounds but + * not owned. */ function emitVersionedLoop( emitter: LlEmitter, s: IrStmt & { kind: "for" | "while" }, @@ -2151,7 +2153,11 @@ function emitVersionedLoop( done = B.newLabel("bytes.version.done"); const unitsFast = version.unitsReceiver !== null ? B.newLabel("bytes.version.fast.units") : undefined; - const guard = emitBytesLoopVersionGuard(emitter, version, fast, slow, unitsFast); + const sharedFast = + version.sharedCopy && unitsFast === undefined + ? B.newLabel("bytes.version.fast.shared") + : undefined; + const guard = emitBytesLoopVersionGuard(emitter, version, fast, slow, unitsFast, sharedFast); const units = guard?.units ?? null; const originalBounds = emitter.bytesBounds, originalOwned = emitter.bytesOwned, @@ -2190,6 +2196,16 @@ function emitVersionedLoop( ]); emitter.emitStmt(s); B.br(done); + if (sharedFast !== undefined) { + // The bounds hold and a view has shared storage: the accesses skip + // their bounds checks and stay shared-safe, without alias scopes + // (which worker programs give only thread-owned views). + B.startBlock(sharedFast); + emitter.bytesOwned = originalOwned; + emitter.bytesAliasScopes = originalScopes; + emitter.emitStmt(s); + B.br(done); + } if (unitsFast !== undefined && units !== null) { // The string reads take the receiver's UTF-16 units. B.startBlock(unitsFast); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json new file mode 100644 index 000000000..db4376635 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8350-worker-shared-loop-bounds.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 new file mode 100644 index 000000000..7eebcc2e4 --- /dev/null +++ b/packages/compiler/test/worker-shared-loop-version-emission.test.ts @@ -0,0 +1,74 @@ +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"; + +async function emitted(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-worker-shared-loop-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +const kernel = ` +function sumTo(view: Float64Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += view[i]!; + return total; +} +`; + +function body(ll: string, name: string): string { + return new RegExp(`^define internal [^\\n]*@sc_bf_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec( + ll, + )![0]; +} + +/** The instructions of the blocks whose labels start with `prefix`. */ +function blocks(fn: string, prefix: string): string { + return fn + .split(/\n(?=[\w.$-]+:)/) + .filter((block) => block.startsWith(prefix)) + .join("\n"); +} + +test("a worker program's loop over views that may be shared keeps a bounds-proven copy", async () => { + const ll = await emitted(` +import { Worker, isMainThread } from "node:worker_threads"; +${kernel} +if (isMainThread) { + const view = new Float64Array(new SharedArrayBuffer(64)); + console.log(sumTo(view, 8)); + new Worker(new URL(import.meta.url)); +} +`); + const fn = body(ll, "sumTo"); + // 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. + 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/); +}); + +test("a program without workers keeps two copies of the loop", async () => { + const ll = await emitted(`${kernel} +console.log(sumTo(new Float64Array(8), 8)); +`); + const fn = body(ll, "sumTo"); + expect(fn).toMatch(/^bytes\.version\.fast\d*:/m); + expect(fn).not.toMatch(/bytes\.version\.fast\.shared/); + expect(fn).not.toMatch(/bytes\.version\.owned/); +}); diff --git a/tests/corpus/8350-worker-shared-loop-bounds.ts b/tests/corpus/8350-worker-shared-loop-bounds.ts new file mode 100644 index 000000000..e5fd1ea9e --- /dev/null +++ b/tests/corpus/8350-worker-shared-loop-bounds.ts @@ -0,0 +1,86 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// Typed-array loops in a worker program that run to a count passed in, not +// to a view's length, over views of a SharedArrayBuffer, of a thread's own +// buffer, and both in one loop. Counts within the views, past their ends +// (reads give undefined, writes are dropped) and over subarrays at an +// offset. A worker's writes through shared views are visible to the parent. + +interface Job { + shared: SharedArrayBuffer; + n: number; +} + +function rowSums(u: Float64Array, v: Float64Array, n: number, rows: number): void { + for (let i = 0; i < rows; i++) { + let t = 0; + for (let j = 0; j < n; j++) t += u[j]! / (i + j + 1); + v[i] = t; + } +} + +function copyScaled(src: Int32Array, dst: Int32Array, n: number, k: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + dst[i] = (src[i]! * k) | 0; + sum += dst[i]!; + } + return sum; +} + +function sumTo(view: Float64Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += view[i]!; + return total; +} + +function fixed(view: Float64Array, count: number): string { + const parts: string[] = []; + for (let i = 0; i < count; i++) parts.push(view[i]!.toFixed(6)); + return parts.join(" "); +} + +function run(label: string, u: Float64Array, v: Float64Array, ints: Int32Array, out: Int32Array, n: number): string { + rowSums(u, v, n, 4); + const scaled = copyScaled(ints, out, n, 3); + return `${label}: rows ${fixed(v, 4)}; scaled ${scaled}; tail ${sumTo(u, n + 3)}`; +} + +function views(buffer: ArrayBufferLike, n: number): { u: Float64Array; v: Float64Array; ints: Int32Array; out: Int32Array } { + const u = new Float64Array(buffer, 0, n); + const v = new Float64Array(buffer, 8 * n, n); + const ints = new Int32Array(buffer, 16 * n, n); + const out = new Int32Array(buffer, 20 * n, n); + for (let i = 0; i < n; i++) { + u[i] = 1 + i * 0.25; + ints[i] = i * 7 - 20; + } + return { u, v, ints, out }; +} + +if (isMainThread) { + const n = 12; + const shared = new SharedArrayBuffer(24 * n); + const own = views(new ArrayBuffer(24 * n), n); + console.log(run("own", own.u, own.v, own.ints, own.out, n)); + const sh = views(shared, n); + console.log(run("shared", sh.u, sh.v, sh.ints, sh.out, n)); + console.log(run("shared, count past the end", sh.u, sh.v, sh.ints, sh.out, n + 5)); + const mixed = new Float64Array(n); + rowSums(sh.u, mixed, n, 4); + console.log("mixed", fixed(mixed, 4)); + console.log("subarray", sumTo(sh.u.subarray(3), n - 3), sumTo(sh.u.subarray(3), n)); + const worker = new Worker(new URL(import.meta.url), { workerData: { shared, n } satisfies Job }); + worker.on("message", (line: string) => { + console.log(line); + console.log("parent sees", sh.out.subarray(0, 5).join(","), sh.v[0]!.toFixed(6)); + }); +} else { + const { shared, n } = workerData as Job; + const u = new Float64Array(shared, 0, n); + const v = new Float64Array(shared, 8 * n, n); + const ints = new Int32Array(shared, 16 * n, n); + const out = new Int32Array(shared, 20 * n, n); + for (let i = 0; i < n; i++) ints[i] = i * 5 + 1; + parentPort!.postMessage(run("worker", u, v, ints, out, n)); +} From c6679d43fbc63840755e9e2e5c0e3a3063537284 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:18:46 -0500 Subject: [PATCH 229/395] Add no empty chunk to a byte-mode Readable's buffer push() of an empty Buffer outside object mode put a zero-length entry into the read buffer, and the chunks pushed after it stalled behind it: a 'data' listener saw nothing more and a readline interface never closed. Node's readableAddChunk adds no empty chunk outside object mode; it clears the reading flag and may read more. The runtime now does the same, as it already did for an encoded push that decodes to nothing. --- .../corpus/7516-readable-empty-push.ts.json | 6 ++ packages/runtime/src/scr_stream.c | 8 ++- tests/corpus/7516-readable-empty-push.ts | 69 +++++++++++++++++++ .../7516-readable-empty-push.ts.json | 1 + 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json create mode 100644 tests/corpus/7516-readable-empty-push.ts create mode 100644 tests/harness/corpus-costs/7516-readable-empty-push.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json new file mode 100644 index 000000000..9388c4f9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7516-readable-empty-push.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 406d91599..201d6e34b 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -954,8 +954,14 @@ static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) { } entry = dec; /* owned */ } else { - entry = scr_bytes_retain(chunk); entry_len = chunk->len; + if (entry_len == 0) { + /* Node's readableAddChunk adds no empty chunk outside object mode: + * it clears reading and may read more */ + if (!front) scr_stream_maybe_read_more(s); + return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0); + } + entry = scr_bytes_retain(chunk); } if (entry_len > 0 && st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync && !front && diff --git a/tests/corpus/7516-readable-empty-push.ts b/tests/corpus/7516-readable-empty-push.ts new file mode 100644 index 000000000..27d88e3ed --- /dev/null +++ b/tests/corpus/7516-readable-empty-push.ts @@ -0,0 +1,69 @@ +// readable.push() of an empty Buffer outside object mode adds nothing, as +// in Node: the chunks pushed after it still reach 'data' listeners, +// readline and read(), and push(null) still ends the stream. In object +// mode an empty Buffer is a value like any other. +import { createInterface } from "node:readline"; +import { Readable } from "node:stream"; + +function lines(chunks: string[]): Promise { + return new Promise((resolve) => { + const src = new Readable({ read() {} }); + const rl = createInterface({ input: src, crlfDelay: Infinity }); + rl.on("line", (line: string) => console.log("line", JSON.stringify(line))); + rl.on("close", () => { + console.log("readline closed"); + resolve(); + }); + for (const chunk of chunks) src.push(Buffer.from(chunk, "utf8")); + src.push(null); + }); +} + +function data(): Promise { + return new Promise((resolve) => { + const src = new Readable({ read() {} }); + src.on("data", (chunk: Buffer) => console.log("data", JSON.stringify(String(chunk)))); + src.on("end", () => { + console.log("data end"); + resolve(); + }); + console.log("push a", src.push(Buffer.from("a"))); + console.log("push empty", src.push(Buffer.alloc(0))); + console.log("push b", src.push(Buffer.from("b"))); + src.push(null); + }); +} + +function paused(): void { + const src = new Readable({ read() {} }); + src.push(Buffer.from("x")); + src.push(Buffer.alloc(0)); + src.push(Buffer.from("y")); + console.log("readableLength", src.readableLength); + console.log("read", JSON.stringify(String(src.read()))); +} + +function objectMode(): Promise { + return new Promise((resolve) => { + const src = new Readable({ objectMode: true, read() {} }); + src.on("data", (chunk: Buffer) => console.log("object chunk length", chunk.length)); + src.on("end", () => { + console.log("object end"); + resolve(); + }); + src.push(Buffer.alloc(0)); + src.push(Buffer.from("z")); + src.push(null); + }); +} + +async function main(): Promise { + await lines(["a\nb", "", "c\n"]); + await lines(["", "", "only\n"]); + await data(); + paused(); + await objectMode(); + console.log("done"); +} + +main(); diff --git a/tests/harness/corpus-costs/7516-readable-empty-push.ts.json b/tests/harness/corpus-costs/7516-readable-empty-push.ts.json new file mode 100644 index 000000000..1cfe56769 --- /dev/null +++ b/tests/harness/corpus-costs/7516-readable-empty-push.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 3200 } From 9b2b97033aae21cf659623d605442f8f988eaa85 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:24:12 -0500 Subject: [PATCH 230/395] Add object literal members through the store caches in untyped code Untyped functions built each member of an object literal with scr_dyn_key_set: a lookup of the key in the new object, a walk of its empty prototype chain, a second lookup before the append, and a hash of the key to share its bytes. The first cached read of the object then computed its shape from its keys, hashing each key again. A member whose key is a constant string other than __proto__ now owns a store cache site and calls scr_js_store_probe, which appends the member through the cached shape transition, takes the key bytes from the shape, and leaves the object shaped. Members the probe does not take (Error field names, members past the 64th, a site's first object of a shape) keep scr_dyn_key_set, as do computed keys and literals in module initializers. --- .../compiler/src/backend/llvm/expr-dynamic.ts | 8 ++- packages/compiler/src/backend/llvm/js-ic.ts | 23 +++++++ packages/compiler/src/ir/js-ic.test.ts | 17 +++++ packages/compiler/src/ir/js-ic.ts | 17 ++++- packages/compiler/test/js-ic-emission.test.ts | 31 +++++++++ .../corpus/object-literal-members.mjs.json | 6 ++ tests/corpus/object-literal-members.mjs | 67 +++++++++++++++++++ .../object-literal-members.mjs.json | 1 + tests/harness/library-mode.test.ts | 4 +- tests/library-mode/js-ic-sessions/work.mjs | 9 ++- 10 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json create mode 100644 tests/corpus/object-literal-members.mjs create mode 100644 tests/harness/corpus-costs/object-literal-members.mjs.json diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 849715ab0..55bbb828b 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -31,7 +31,8 @@ import { elemAccess, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; import { emitUnionWiden } from "./expr-records.js"; -import { emitJsIcMethod, jsIcMethods } from "./js-ic.js"; +import { emitJsIcLiteralMember, emitJsIcMethod, jsIcMethods } from "./js-ic.js"; +import { jsIcLiteralKey } from "../../ir/js-ic.js"; import { DYN_BUFFER_METHODS } from "../../ir/runtime-features.js"; export function emitDynamicExpr( @@ -316,9 +317,14 @@ export function emitDynamicExpr( const obj = B.tmp(); B.line(`${obj} = call ptr @scr_dyn_new_obj()`); const out = host.own({ name: obj, type: e.type }); + const cached = jsIcMethods(host); for (const f of e.fields ?? []) { const k = emitBorrowedInput(host, f.key); const v = emitBorrowedInput(host, f.value); + if (cached && jsIcLiteralKey(f.key) !== null) { + emitJsIcLiteralMember(host, obj, k.name, v.name); + continue; + } B.line( `call void @${f.key.type.kind === "dyn" ? "scr_dyn_key_set_computed" : "scr_dyn_key_set"}(ptr ${obj}, ptr ${k.name}, ptr ${v.name})`, ); diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index b69d3199d..11b894a60 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -14,6 +14,8 @@ * keyed dense array element at an integral number key: inline; * otherwise scr_js_keyed_probe (string keys of ordinary objects) * method dynInvoke's method read: scr_js_load_probe + * literal an object literal's constant-key member: scr_js_store_probe, + * else scr_dyn_key_set * * A probe that cannot answer from plain data leaves everything untouched, * and the site calls its helper (or the generic runtime entry), which @@ -838,6 +840,27 @@ function emitKeyed(host: Host, e: ExprOf<"call">, callee: IrFunction): LlValue { return host.own({ name: out, type: e.type }); } +/** One member of an object literal (ir/js-ic.ts jsIcLiteralKey) added to + * the fresh object `obj` through the site's store cache, else with + * scr_dyn_key_set. The key and value are borrowed. */ +export function emitJsIcLiteralMember(host: Host, obj: string, key: string, value: string): void { + const B = host.B; + const ic = host.jsIcSite(); + host.declare(`declare zeroext i1 @scr_js_store_probe(ptr, ptr, ptr, ptr)`); + const done = B.tmp(); + B.line( + `${done} = call zeroext i1 @scr_js_store_probe(ptr ${obj}, ptr ${key}, ptr ${value}, ptr ${ic})`, + ); + const slow = B.newLabel("icl.slow"); + const join = B.newLabel("icl.j"); + B.condBr(done, join, slow); + B.startBlock(slow); + B.line(`call void @scr_dyn_key_set(ptr ${obj}, ptr ${key}, ptr ${value})`); + host.emitPendingCheck(); + B.br(join); + B.startBlock(join); +} + /** dynInvoke's method read on an ordinary object through the site's * cache, else scr_dyn_prepare_method. Returns the owned method (or NULL, * which scr_dyn_invoke_prepared_id answers with the full dispatch). */ diff --git a/packages/compiler/src/ir/js-ic.test.ts b/packages/compiler/src/ir/js-ic.test.ts index f5391b83e..35de18085 100644 --- a/packages/compiler/src/ir/js-ic.test.ts +++ b/packages/compiler/src/ir/js-ic.test.ts @@ -311,6 +311,23 @@ test("a function holds a site when it calls a classified helper or dispatches a expect(hasJsIcSite({ ...caller, name: "%init.0" }, sites)).toBe(true); }); +test("a function holds a site when an object literal has a constant-key member", () => { + const sites = sitesOf(); + const literal = (key: IrExpr): IrFunction => + helper( + "f", + ["o"], + [ret({ kind: "dynObjLit", fields: [{ key, value: ref("o") }], type: DYN, loc })], + ); + expect(hasJsIcSite(literal(str("x")), sites)).toBe(true); + // `__proto__` sets the prototype, and a computed key has no constant + // name: both keep the generic member store. + expect(hasJsIcSite(literal(str("__proto__")), sites)).toBe(false); + expect(hasJsIcSite(literal(ref("k")), sites)).toBe(false); + // Module initializers build their literals once. + expect(hasJsIcSite({ ...literal(str("x")), name: "%init.0" }, sites)).toBe(false); +}); + test("bag helpers that exit early for values no class branch matches are still the receiver", () => { /** `if (!(v)) return `: the class tables' early exit. */ const exit = (test: "classCapsule" | "funcKind", value: IrExpr, negated = true): IrStmt => ({ diff --git a/packages/compiler/src/ir/js-ic.ts b/packages/compiler/src/ir/js-ic.ts index 48019636a..5a483c585 100644 --- a/packages/compiler/src/ir/js-ic.ts +++ b/packages/compiler/src/ir/js-ic.ts @@ -296,20 +296,33 @@ function unwrapFromDyn(e: IrExpr | null | undefined): IrExpr | null { return e; } -/** Whether a function holds a cache site: a call to a classified helper or - * a method dispatch. */ +/** Whether a function holds a cache site: a call to a classified helper, + * a method dispatch, or an object literal member with a constant key. */ export function hasJsIcSite(fn: IrFunction, sites: JsIcSites): boolean { const methods = jsIcMethodSites(fn.name); return !everyStmtList(fn.body, { expr: (e) => !( (methods && e.kind === "dynInvoke") || + (methods && + e.kind === "dynObjLit" && + (e.fields ?? []).some((f) => jsIcLiteralKey(f.key) !== null)) || (e.kind === "call" && sites.site(e.callee) !== null) ), stmt: () => true, }); } +/** The key of an object literal member that its site adds through the + * store cache: a constant string other than `__proto__`, or null. The + * literal starts as an empty object with the default prototype, so each + * such member appends one data property, and the site caches the shape + * transition and shares the key bytes the shape holds. Module + * initializers have no literal sites (jsIcMethodSites). */ +export function jsIcLiteralKey(key: IrExpr): string | null { + return key.kind === "strLit" && key.value !== "__proto__" ? key.value : null; +} + /** Whether method dispatch in a function goes through caches. Module * initializers (`%init.N`) run once, and the methods they call at top level * are overwhelmingly registrations on native handles (`worker.on(...)`, diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 15e8e92bc..79342e99d 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -64,6 +64,37 @@ console.log(read(o), at(dyn([5, 6]), 1), call(o), o.n); } }); +test("object literal members with constant keys add through store caches", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-js-ic-literal-")); + try { + const entry = join(dir, "main.js"); + const outPath = join(dir, "main.ir.json"); + await writeFile( + entry, + ` +function make(v, k) { return { a: v, b: 2, [k]: 3 }; } +var top = { t: 1 }; +console.log(JSON.stringify(make(1, "c")), top.t); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const mod = deserializeModule(await readFile(outPath, "utf8")); + expect(moduleUsesDynTier(mod)).toBe(true); + const llvm = emitLlvmModule(mod); + const make = body(llvm, "make"); + // Each constant-key member tries its site's store cache first and + // falls back to the generic member store; the computed key keeps the + // generic store only. + expect(make.match(/@scr_js_store_probe\(/g)).toHaveLength(2); + expect(make.match(/@scr_dyn_key_set\(/g)).toHaveLength(2); + expect(make).toContain("@scr_dyn_key_set_computed("); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + test("property sites keep their caches in programs that declare classes and subclasses", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-js-ic-class-")); try { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json new file mode 100644 index 000000000..f1f9017ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/object-literal-members.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/object-literal-members.mjs b/tests/corpus/object-literal-members.mjs new file mode 100644 index 000000000..4bd211435 --- /dev/null +++ b/tests/corpus/object-literal-members.mjs @@ -0,0 +1,67 @@ +// Object literals built in untyped functions, many times through the same +// sites: key order (integer-like keys first), members named +// like Object.prototype members and Error fields, __proto__ members, wide +// literals, literals that differ only in a later member, and objects +// changed after they are built. +function point(x, y) { + return { x, y, label: "p" + x }; +} +function ordered(i) { + return { b: i, 2: "two", a: i + 1, 1: "one", "01": "zero-one" }; +} +function named(i) { + return { constructor: i, toString: "s" + i, message: "m" + i, name: "n" + i, code: i, stack: "st" }; +} +function withProto(proto, i) { + if (proto === null) return { __proto__: null, own: i }; + const o = { own: i }; + Object.setPrototypeOf(o, proto); + return o; +} +function wide(i) { + return { + k0: i, k1: i, k2: i, k3: i, k4: i, k5: i, k6: i, k7: i, k8: i, k9: i, + k10: i, k11: i, k12: i, k13: i, k14: i, k15: i, k16: i, k17: i, k18: i, k19: i, + k20: i, k21: i, k22: i, k23: i, k24: i, k25: i, k26: i, k27: i, k28: i, k29: i, + k30: i, k31: i, k32: i, k33: i, k34: i, k35: i, k36: i, k37: i, k38: i, k39: i, + k40: i, k41: i, k42: i, k43: i, k44: i, k45: i, k46: i, k47: i, k48: i, k49: i, + k50: i, k51: i, k52: i, k53: i, k54: i, k55: i, k56: i, k57: i, k58: i, k59: i, + k60: i, k61: i, k62: i, k63: i, k64: i, k65: i, k66: i, k67: i, k68: i, k69: i, + }; +} +function variant(i) { + const head = { kind: "v", n: i }; + return i % 2 ? { ...head, odd: true } : { ...head, even: true, n: -i }; +} + +let sum = 0; +const points = []; +for (let i = 0; i < 2000; i++) { + const p = point(i, i * 2); + sum += p.x + p.y; + if (i % 500 === 0) points.push(p); +} +points[0].z = 9; +delete points[1].label; +console.log(sum, JSON.stringify(points), Object.keys(points[2]).join()); + +const o = [ordered(1), ordered(2)]; +console.log(Object.keys(o[1]).join(), JSON.stringify(o[0]), o[1]["01"], o[1][1]); + +const n = [named(1), named(2)]; +console.log(JSON.stringify(n[1]), typeof n[0].toString, n[1].constructor, n[1] instanceof Error); + +const proto = { greet() { return "hi " + this.own; } }; +const w = [withProto(proto, 1), withProto(proto, 2), withProto(null, 3)]; +console.log(w[1].greet(), Object.getPrototypeOf(w[0]) === proto, Object.getPrototypeOf(w[2]), Object.keys(w[1]).join()); + +let wideSum = 0; +for (let i = 0; i < 50; i++) { + const big = wide(i); + wideSum += big.k0 + big.k69; +} +console.log(wideSum, Object.keys(wide(1)).length, Object.keys(wide(2))[69]); + +const v = []; +for (let i = 0; i < 6; i++) v.push(variant(i)); +console.log(JSON.stringify(v)); diff --git a/tests/harness/corpus-costs/object-literal-members.mjs.json b/tests/harness/corpus-costs/object-literal-members.mjs.json new file mode 100644 index 000000000..882d60088 --- /dev/null +++ b/tests/harness/corpus-costs/object-literal-members.mjs.json @@ -0,0 +1 @@ +{ "plain": 3565, "san": 6266 } diff --git a/tests/harness/library-mode.test.ts b/tests/harness/library-mode.test.ts index 4c1b96b37..4a3d4c390 100644 --- a/tests/harness/library-mode.test.ts +++ b/tests/harness/library-mode.test.ts @@ -386,7 +386,7 @@ globals: undefined fenced const run = runProbe(probe); expect(run.signal).toBeNull(); expect(run.status).toBe(0); - expect(run.stdout).toBe("run: 20000\n".repeat(3)); + expect(run.stdout).toBe("run: 40000\n".repeat(3)); }); /* ── K5/K6/K7: the trap channel ──────────────────────────────────────── */ @@ -614,7 +614,7 @@ survived, sink_calls=1 const run = runProbe(probe); expect(run.signal).toBeNull(); expect(run.status).toBe(0); - expect(run.stdout).toBe("run: 20000\n".repeat(3)); + expect(run.stdout).toBe("run: 40000\n".repeat(3)); }); platformTest("K10: K5/K7 under ASan", async () => { diff --git a/tests/library-mode/js-ic-sessions/work.mjs b/tests/library-mode/js-ic-sessions/work.mjs index 5c1afcd18..e6c4a2fc3 100644 --- a/tests/library-mode/js-ic-sessions/work.mjs +++ b/tests/library-mode/js-ic-sessions/work.mjs @@ -1,6 +1,6 @@ // Dynamic-tier property caches across library sessions: objects built empty -// and filled through untyped stores, read back through untyped loads, and a -// class instance at the same kind of sites. Each session must start with +// and filled through untyped stores or built by object literals, read back +// through untyped loads, and a class instance at the same kind of sites. Each session must start with // empty caches, since the shapes their entries named are freed when the // previous session ends. class Box { @@ -13,6 +13,9 @@ function fill(o, n) { o.b = n + 1; return o; } +function pair(n) { + return { a: n, b: n + 1 }; +} function sum(o) { return o.a + o.b; } @@ -21,6 +24,6 @@ function unbox(b) { } export function runAll() { let total = 0; - for (let i = 0; i < 100; i++) total += sum(fill({}, i)); + for (let i = 0; i < 100; i++) total += sum(fill({}, i)) + sum(pair(i)); return total + unbox(new Box(total)); } From 0488bef1a9ad7959de22df9ab78108e1c499f657 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:00:40 -0500 Subject: [PATCH 231/395] Describe RSA-PSS AUTO verification against restricted keys as Node does RSA_PSS_SALTLEN_AUTO recovers the salt length from the signature and does not enforce the key's minimum salt length, as in Node (OpenSSL). The runtime's exemption now says so, and corpus 8178's header, which said the minimum was still enforced, describes what it checks. --- packages/runtime/src/scr_pki.c | 3 +++ tests/corpus/8178-rsa-pss-auto-min-salt.cjs | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index 9877ac6c9..e41dd79c3 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -3255,6 +3255,9 @@ static bool sp_pss_key_rules(SpSigKey *key, mbedtls_md_type_t md, size_t hash_le return false; } key->mgf1 = k->pss_mgf1; + /* RSA_PSS_SALTLEN_AUTO (-2) is exempt from the key's minimum, as in + * OpenSSL: verification recovers the salt length from the signature and + * accepts any length (corpus 8178). */ if (!key->salt_given) { key->salt = k->pss_salt; } else if (key->salt != -2 && (key->salt == -1 ? (int)hash_len : key->salt) < k->pss_salt) { diff --git a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs index 97b9d1c05..78e288591 100644 --- a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs +++ b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs @@ -1,6 +1,8 @@ // Verifying with RSA_PSS_SALTLEN_AUTO against an RSA-PSS key whose -// parameters set a minimum salt length still enforces that minimum, as in -// Node: a valid signature with a shorter salt does not verify. The keys use +// parameters set a minimum salt length recovers the salt length from the +// signature and does not enforce that minimum, as in Node: a valid +// signature with a shorter salt verifies. The default salt length (the +// key's minimum) accepts only that length. The keys use // an MGF1 digest equal to the message digest and a different one. The // stricter public key is the generated one with its minimum salt length // raised from 16 to 32 in its DER. From 21e4c3683febd387866bfd20d8cee2ea2ade4420 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:13:52 -0500 Subject: [PATCH 232/395] Keep the budget in typed-array length loops that assign their array A loop to a typed array's length reads the length again on every iteration, so a body that assigns the array's binding changes the limit for the next iteration. The strip-mined form took the length once per block of 64 iterations, and after the body assigned a shorter array it ran on to the block's end, reading past the new length. The loop to a typed array's length is now strip-mined only when the array is a local of the function that the body never assigns (stableReceiver on the match); others keep the budget and test the length on every iteration. New corpus program: native-worker-length-loop-reassign.ts. --- .../src/backend/llvm/emit-statements.ts | 5 +- packages/compiler/src/ir/integer-loops.ts | 18 +++++- .../test/counted-loop-emission.test.ts | 15 ++++- ...native-worker-length-loop-reassign.ts.json | 6 ++ .../native-worker-length-loop-reassign.ts | 58 +++++++++++++++++++ ...native-worker-length-loop-reassign.ts.json | 1 + 6 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json create mode 100644 tests/corpus/native-worker-length-loop-reassign.ts create mode 100644 tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 2efe2cf3a..d7fccb2d4 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1216,14 +1216,15 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { // length, polls between blocks of WORKER_POLL_BLOCK iterations instead // of counting a budget in its body: the inner loop over a block then // has no exit besides its own bound, which LLVM can vectorize. A typed - // array's length is fixed, so each block can take it as its limit. + // array's length is fixed, so each block can take it as its limit when + // the loop does not assign the array's binding. const stripMined = capturedLets.length === 0 && integerSlot !== null && ((countedLoop !== null && countedLimit !== null && Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || - (integerLoop !== null && integerArrayLoop === null)) && + (integerLoop !== null && integerArrayLoop === null && integerLoop.stableReceiver)) && emitter.workerLoopPolls(s) && loopAccessesTypedArrays(s); const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index 7cc49b7a0..ea866a134 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -12,6 +12,9 @@ import { preservesArrayLength } from "./array-length-stability.js"; export interface IntegerBytesForLoop { localId: string; limitReceiver: IrExpr; + /** The receiver is a local of the function that the body never assigns + * (a typed array's length is then the same on every iteration). */ + stableReceiver: boolean; } export interface IntegerCountedForLoop { @@ -682,7 +685,12 @@ export function matchIntegerBytesForLoop( if (!isUnitIncrement(stmt.update, init.localId)) return null; if (writesLocal(stmt.body, init.localId)) return null; - return { localId: init.localId, limitReceiver: cond.right.receiver }; + return { + localId: init.localId, + limitReceiver: cond.right.receiver, + stableReceiver: + receiverLocal !== undefined && !writesLocal(stmt.body, cond.right.receiver.localId), + }; } /** Array lengths are uint32, so zero-based loops and nested `j = i + 1` @@ -729,5 +737,11 @@ export function matchIntegerArrayForLoop( writesLocal(stmt.body, local.id) ) return null; - return { localId: local.id, limitReceiver: cond.right.receiver }; + return { + localId: local.id, + limitReceiver: cond.right.receiver, + stableReceiver: + locals.has(cond.right.receiver.localId) && + !writesLocal(stmt.body, cond.right.receiver.localId), + }; } diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 206a3c57e..f617c6ab4 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -58,6 +58,14 @@ test("worker programs poll counted loops over typed arrays between blocks of ite "export function codes(a: Uint8Array): void {", " for (let i = 0; i < a.length; ++i) a[i] = a[i] === 97 ? 0 : 3;", "}", + "export function shrink(a: Uint8Array): number {", + " let t = 0;", + " for (let i = 0; i < a.length; ++i) {", + " t += a[i]!;", + " if (i === 2) a = a.subarray(0, 3);", + " }", + " return t;", + "}", "export function mix(n: number): number {", " let t = 0;", " for (let i = 0; i < n; i++) t = (t * 31 + i) % 1000003;", @@ -68,7 +76,7 @@ test("worker programs poll counted loops over typed arrays between blocks of ite "scale(a, b, 8);", "const c = new Uint8Array(8).fill(97);", "codes(c);", - "console.log(b[7], mix(8), c[0]);", + "console.log(b[7], mix(8), c[0], shrink(new Uint8Array(8).fill(1)));", workers ? "if (!isMainThread) new Worker(new URL(import.meta.url));" : "", ].join("\n"); const emit = async (workers: boolean): Promise => { @@ -113,6 +121,11 @@ test("worker programs poll counted loops over typed arrays between blocks of ite expect(codes).toMatch(/@llvm\.uadd\.sat\.i64\(i64 %\w+, i64 64\)/); expect(codes).toMatch(/@llvm\.umin\.i64\(i64 %\w+, i64 %\w+\)/); expect(codes).toMatch(/^loop\.poll\d+:$/m); + // A loop that assigns its array reads the new length on the next + // iteration: it keeps the budget. + const shrink = bodies(workerModule, "shrink"); + expect(shrink).toContain("worker cancellation budget"); + expect(shrink).not.toMatch(/^loop\.poll\d+:$/m); const mix = bodies(workerModule, "mix"); expect(mix).toContain("worker cancellation budget"); expect(mix).not.toContain("loop.poll"); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json new file mode 100644 index 000000000..f5230bace --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-length-loop-reassign.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-length-loop-reassign.ts b/tests/corpus/native-worker-length-loop-reassign.ts new file mode 100644 index 000000000..7ec152b16 --- /dev/null +++ b/tests/corpus/native-worker-length-loop-reassign.ts @@ -0,0 +1,58 @@ +// Loops to a typed array's length in a program that starts workers, where +// the body assigns the loop's array: the condition reads the new array's +// length on the next iteration, shorter or longer. +import { Worker, isMainThread, parentPort } from "node:worker_threads"; + +function shrink(values: Float64Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < values.length; i++) { + total += values[i]!; + steps++; + if (i === 100) values = values.subarray(0, 50); + } + return `shrink ${steps} ${total}`; +} + +function grow(values: Uint8Array, longer: Uint8Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < values.length; i++) { + total += values[i]!; + steps++; + if (i === 10) values = longer; + } + return `grow ${steps} ${total}`; +} + +function swap(a: Int32Array, b: Int32Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < a.length; i++) { + total += a[i]!; + steps++; + if (i % 70 === 69) { + const t = a; + a = b; + b = t; + } + } + return `swap ${steps} ${total}`; +} + +function run(): string[] { + const values = new Float64Array(300).map((_, i) => i * 0.5); + const short = new Uint8Array(20).fill(3); + const long = new Uint8Array(200).map((_, i) => i & 7); + const a = new Int32Array(100).map((_, i) => i); + const b = new Int32Array(160).map((_, i) => -i); + return [shrink(values), grow(short, long), swap(a, b)]; +} + +if (isMainThread) { + for (const line of run()) console.log(line); + const worker = new Worker(new URL(import.meta.url)); + worker.on("message", (lines: string[]) => { + for (const line of lines) console.log("worker", line); + }); +} else parentPort!.postMessage(run()); diff --git a/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json b/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json new file mode 100644 index 000000000..5ee17db16 --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json @@ -0,0 +1 @@ +{ "plain": 3158, "san": 20404 } From 64e350edb6fca488a26982b9abc0a3729ccbb5fd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:25:45 -0500 Subject: [PATCH 233/395] Drop a socket's 'drain' listeners when it closes The close step drops a socket's listener lists so that a listener which captures its socket does not keep it alive; the 'drain' list was not among them. A once('drain') listener still pending when the peer disconnected, or an on('drain') listener, held its socket and the socket's buffers after 'close'. The close step now drops the 'drain' listeners with the others. --- ...19-net-socket-drain-listener-close.ts.json | 6 ++ packages/runtime/src/scr_net.c | 1 + .../7519-net-socket-drain-listener-close.ts | 62 +++++++++++++++++++ ...19-net-socket-drain-listener-close.ts.json | 1 + 4 files changed, 70 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json create mode 100644 tests/corpus/7519-net-socket-drain-listener-close.ts create mode 100644 tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json new file mode 100644 index 000000000..a3c10da99 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7519-net-socket-drain-listener-close.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 85c7b7db7..a940fb7f1 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -5648,6 +5648,7 @@ static bool scr_net_sock_settle(ScrNetSocket *sock) { scr_net_ls_drop(&sock->conn_ls); scr_net_ls_drop(&sock->timeout_ls); scr_net_ls_drop(&sock->readable_ls); + scr_net_ls_drop(&sock->drain_ls); scr_net_ls_drop(&sock->finish_ls); scr_net_ls_drop(&sock->wcb_ls); scr_net_ls_drop(&sock->ready_ls); diff --git a/tests/corpus/7519-net-socket-drain-listener-close.ts b/tests/corpus/7519-net-socket-drain-listener-close.ts new file mode 100644 index 000000000..e1b51e7b4 --- /dev/null +++ b/tests/corpus/7519-net-socket-drain-listener-close.ts @@ -0,0 +1,62 @@ +// 'drain' listeners that capture their socket do not keep it after it +// closes: a once('drain') listener still pending when the peer disconnects +// before the buffer drains, and an on('drain') listener on a socket that +// drained and then closed. The sanitized lane's exit audit checks that +// both sockets are freed. +import { connect, createServer, Socket } from "node:net"; + +function disconnectBeforeDrain(): Promise { + return new Promise((resolve) => { + const server = createServer((socket: Socket) => { + socket.on("error", () => {}); + socket.once("drain", () => console.log("unexpected drain", socket.writableLength)); + console.log("server write", socket.write(Buffer.alloc(16 * 1024 * 1024, 97))); + socket.on("close", () => { + console.log("server socket closed, need drain", socket.writableNeedDrain); + server.close(() => resolve()); + }); + }); + server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1", () => { + client.pause(); + setTimeout(() => client.destroy(), 30); + }); + }); + }); +} + +function drainThenClose(): Promise { + return new Promise((resolve) => { + const server = createServer((socket: Socket) => { + socket.pause(); + setTimeout(() => socket.resume(), 30); + }); + server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1", () => { + let drains = 0; + client.on("drain", () => { + drains++; + console.log("drain", drains, client.writableLength); + client.end(); + }); + console.log("client write", client.write(Buffer.alloc(16 * 1024 * 1024, 98))); + }); + client.on("close", () => { + console.log("client closed"); + server.close(() => resolve()); + }); + }); + }); +} + +async function main(): Promise { + await disconnectBeforeDrain(); + await drainThenClose(); + console.log("done"); +} + +main(); diff --git a/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json b/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json new file mode 100644 index 000000000..78ee9421a --- /dev/null +++ b/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json @@ -0,0 +1 @@ +{ "plain": 2300, "san": 3100 } From 16a7ae69c534225a1ed914476f01a8a32348e334 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:32:47 -0500 Subject: [PATCH 234/395] Describe socket write state as Node's state.length in the signature table The comments on net.sockWrite and net.sockWritableLength still described the unsent bytes; the runtime counts each write not yet completely sent in full. --- packages/compiler/src/ir/builtin-signatures.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index f5c2ea339..576386851 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1547,9 +1547,9 @@ function buildLibFnSignatures() { * true } and a callback taking an address string or list, then dials * the answers in order. MAY THROW (host validation, a lookup throw). */ "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - /** socket.write: Node's Writable answer — false when the bytes the write - * left unsent reach the 65536-byte high-water mark ('drain' follows), - * and on an ended or closed socket. */ + /** socket.write: Node's Writable answer — false when the writes not yet + * completely sent, each counted in full, reach the 65536-byte + * high-water mark ('drain' follows), and on an ended or closed socket. */ "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: BOOL }, "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: BOOL }, "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, @@ -2148,7 +2148,8 @@ function buildLibFnSignatures() { /** socket.address(): { address, family, port } or {} as a dyn. */ "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, - /** socket.writableLength: the bytes written and not yet sent. */ + /** socket.writableLength: Node's state.length, the length of every + * write not yet completely sent. */ "net.sockWritableLength": { argTypes: [NETSOCKET_T], result: F64 }, /** socket.writableNeedDrain: a write answered false and 'drain' has not * fired, with the write half open. */ From c1c4daed82d7c52ab1016d40c317bf37fecab52b Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 22:36:05 -0700 Subject: [PATCH 235/395] Throw Node's TypeError when an asserted callee holds no function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `f!()`, `t[k]!()` and `o.cb!()` on a value that holds undefined threw scriptc's checked-extraction message ("undefined is not representable in the target union …"). The non-null assertion is erased in JavaScript, so Node throws the call's own " is not a function". The checked extraction behind an asserted callee now carries that message, with V8's spelling of the callee: parentheses and non-null assertions dropped and call results printed as `f(...)`. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/coercions/state.ts | 5 +++- .../src/frontend/lowering/coercions/unions.ts | 27 +++++++++++++++---- .../lowering/expressions/narrowing.ts | 9 ++++++- .../src/frontend/lowering/lower-exprs.ts | 22 +++++++++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 4 +-- .../frontend/lowering/union-narrow.test.ts | 19 +++++++++++++ .../src/frontend/lowering/union-narrow.ts | 8 ++++-- .../8240-asserted-callee-errors.ts.json | 6 +++++ tests/corpus/8240-asserted-callee-errors.ts | 25 +++++++++++++++++ .../8240-asserted-callee-errors.ts.json | 1 + 10 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json create mode 100644 tests/corpus/8240-asserted-callee-errors.ts create mode 100644 tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index ad1d7c6ea..db9bb53ad 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -12,7 +12,10 @@ export class CoercionState { // Provenance for checked extraction; consumers must not infer it from names. readonly checkedNarrows = new Set(); // The extracted arm of each checked (non-defaulting) extraction helper. - readonly checkedNarrowArms = new Map(); + readonly checkedNarrowArms = new Map< + string, + { unionId: string; tag: number; target: IrType; message?: string } + >(); readonly retags = new Map(); readonly narrows = new Map(); } diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 434e687a5..53c87e429 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -394,18 +394,21 @@ export function unionRetagHelper( * the catchable TypeError — divergence 38's lying-assertion stance (an * unchecked unionNarrow would misread the payload where JS lets the * impossible value flow on). Null when the target isn't a non-unit arm - * of the union — those uses keep their erasure/fences. */ + * of the union — those uses keep their erasure/fences. `message` replaces + * the failure message (the TypeError of the operation consuming the + * value, such as a call's "f is not a function"). */ export function narrowedArmHelper( lowerer: Lowerer, fromId: string, target: IrType, loc: SrcLoc, + message?: string, ): string | null { const from = lowerer.unions.get(fromId); if (!from || isUnitType(target)) return null; const tag = lowerer.armTag(fromId, target); if (tag < 0) return null; - const key = `${fromId}:${tag}`; + const key = message === undefined ? `${fromId}:${tag}` : `${fromId}:${tag}:${message}`; const existing = lowerer.coercions.narrows.get(key); if (existing) { lowerer.coercions.checkedNarrows.add(existing); @@ -414,9 +417,22 @@ export function narrowedArmHelper( const name = keyedHelperName(lowerer, "%union.narrow", key); lowerer.coercions.narrows.set(key, name); lowerer.coercions.checkedNarrows.add(name); - const fn = buildUnionNarrow(name, from, target, loc, (type) => lowerer.fmt(type)); + const fn = buildUnionNarrow( + name, + from, + target, + loc, + (type) => lowerer.fmt(type), + null, + message ?? null, + ); if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); - lowerer.coercions.checkedNarrowArms.set(name, { unionId: fromId, tag, target: from.arms[tag]! }); + lowerer.coercions.checkedNarrowArms.set(name, { + unionId: fromId, + tag, + target: from.arms[tag]!, + ...(message === undefined ? {} : { message }), + }); lowerer.liftedFns.push(fn); return name; } @@ -458,7 +474,8 @@ export function checkedNarrowCall( let failure: IrExpr | null = null; for (let tag = from.arms.length - 1; tag >= 0; tag--) { if (tag === arm.tag) continue; - const message = unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); + const message = + arm.message ?? unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); const thrown = nodeThrowExpr(1, "", message, type, loc); failure = failure === null diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 01cab9901..9f0559672 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -875,8 +875,15 @@ function checkedStaleArm( /** V8's spelling of a callee in "is not a function": a string-literal * element key reads as a property name (`o["a"]` is `o.a`), other keys - * keep their brackets. */ + * keep their brackets, parentheses and non-null assertions disappear, and + * a call result reads `f(...)`. */ export function v8CalleeText(node: ts.Node): string { + // `!` is type-only syntax: the running JavaScript has no trace of it. + if (ts.isNonNullExpression(node) || ts.isParenthesizedExpression(node)) + return v8CalleeText(node.expression); + // A call result prints with its arguments elided. + if (ts.isCallExpression(node) && !node.questionDotToken) + return `${v8CalleeText(node.expression)}(...)`; if (ts.isElementAccessExpression(node)) { const key = node.argumentExpression; const object = v8CalleeText(node.expression); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 20d05f3b7..006975085 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -89,7 +89,7 @@ import { classSymbolKeyOf } from "./symbol-fields.js"; import { lowerTypeofExpr } from "./expressions/typeof.js"; import { lowerIdentifierExpr } from "./expressions/identifiers.js"; import { lowerFieldCompoundValue, lowerIncDec } from "./expressions/updates.js"; -import { lowerTernary } from "./expressions/narrowing.js"; +import { lowerTernary, v8CalleeText } from "./expressions/narrowing.js"; import { classFieldTarget, lowerPropertyAccessExpr, @@ -433,7 +433,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const operand = assertionOperand(lowerer, expr, inner, target ?? null, loc); if (operand) return operand; if (target && target.kind !== "union" && !typeEquals(target, inner.type)) { - const helper = lowerer.narrowedArmHelper(inner.type.unionId, target, loc); + const helper = lowerer.narrowedArmHelper( + inner.type.unionId, + target, + loc, + assertedCalleeMessage(expr, target), + ); if (helper) { return lowerer.checkedNarrowCall(helper, inner, target, loc); } @@ -1160,6 +1165,19 @@ const NUMERIC_COMPOUNDS = new Set([ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ]); +/** The TypeError Node raises when an asserted callee (`t[k]!(…)`, `o.f!()`) + * holds no function: `!` is erased, so the call itself throws " is + * not a function" in V8's spelling of the callee. */ +function assertedCalleeMessage(expr: ts.NonNullExpression, target: IrType): string | undefined { + if (target.kind !== "func") return undefined; + let child: ts.Expression = expr; + while (ts.isParenthesizedExpression(child.parent)) child = child.parent; + const call = child.parent; + if (!ts.isCallExpression(call) || call.expression !== child || call.questionDotToken) + return undefined; + return `${v8CalleeText(expr.expression)} is not a function`; +} + /** Whether the use of `t[i]!` tells undefined from NaN: a converting use * that takes the value itself (see convertingUseOf), or a destination that * holds a missing element (see assertedElementKeepsUndefined) other than a diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e49fde6bb..56e39ff0c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -10525,8 +10525,8 @@ export class Lowerer { return unionRetagHelper(this, fromId, toId, loc, trappable); } - narrowedArmHelper(fromId: string, target: IrType, loc: SrcLoc): string | null { - return narrowedArmHelper(this, fromId, target, loc); + narrowedArmHelper(fromId: string, target: IrType, loc: SrcLoc, message?: string): string | null { + return narrowedArmHelper(this, fromId, target, loc, message); } checkedNarrowCall(helper: string, value: IrExpr, type: IrType, loc: SrcLoc): IrExpr { diff --git a/packages/compiler/src/frontend/lowering/union-narrow.test.ts b/packages/compiler/src/frontend/lowering/union-narrow.test.ts index ed60fac8d..5c2a81b5e 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.test.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.test.ts @@ -97,3 +97,22 @@ test("a deferred read requires a real undefined tag", () => { const value: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; expect(buildUnionNarrow("bad", from, BOOL, loc, () => "", value)).toBeNull(); }); + +test("a failure message replaces every other tag's TypeError", () => { + const fn = buildUnionNarrow( + "call", + union, + STRING, + loc, + (type) => type.kind, + null, + "f is not a function", + )!; + const thrown = branches(fn).flatMap((branch) => { + const statement = branch.then[0]!; + return statement.kind === "throw" ? [statement.value] : []; + }); + expect(thrown).toHaveLength(4); + for (const value of thrown) + expect(value).toMatchObject({ kind: "libCall", args: [{ value: "f is not a function" }] }); +}); diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 3bb09cb26..a167631fc 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -20,7 +20,10 @@ export function unionNarrowFailureMessage( * * Deferred scalar fields can supply the established undefined default: * false for booleans or NaN for numbers. No other tag receives that default, - * and reference-bearing targets always use checked extraction. */ + * and reference-bearing targets always use checked extraction. + * + * `failureMessage` replaces the TypeError message of every other tag (the + * error Node raises for the operation consuming the value). */ export function buildUnionNarrow( name: string, from: IrUnionDef, @@ -28,6 +31,7 @@ export function buildUnionNarrow( loc: SrcLoc, formatType: (type: IrType) => string, undefinedDefault: IrExpr | null = null, + failureMessage: string | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -66,7 +70,7 @@ export function buildUnionNarrow( args: [ { kind: "strLit", - value: unionNarrowFailureMessage(arm, formatType), + value: failureMessage ?? unionNarrowFailureMessage(arm, formatType), type: STRING, loc, }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json new file mode 100644 index 000000000..365bf7076 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8240-asserted-callee-errors.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8240-asserted-callee-errors.ts b/tests/corpus/8240-asserted-callee-errors.ts new file mode 100644 index 000000000..91537ed70 --- /dev/null +++ b/tests/corpus/8240-asserted-callee-errors.ts @@ -0,0 +1,25 @@ +// A non-null assertion is erased in JavaScript, so calling an asserted callee +// that holds undefined throws the call's own TypeError, with V8's spelling of +// the callee: element keys, literal keys as names, parentheses and asserted +// links dropped, call results as f(...). +type F = (x: number) => number; +const table: Record = { inc: (x) => x + 1 }; +const obj: { cb?: F; inner?: { cb?: F } } = { inner: {} }; +const arr: (F | undefined)[] = [undefined]; +let maybe: F | undefined; +function get(): F | undefined { return undefined; } +const key = process.argv.length > 99 ? "inc" : "missing"; +const cases: [string, () => number][] = [ + ["computed", () => table[key]!(1)], + ["literal key", () => table["missing"]!(1)], + ["dotted", () => obj.cb!(1)], + ["nested", () => obj.inner!.cb!(1)], + ["element", () => arr[0]!(1)], + ["local", () => maybe!(1)], + ["parens", () => (table[key])!(1)], + ["call result", () => get()!(1)], + ["parens outside", () => (table[key]!)(1)], +]; +for (const [name, run] of cases) { + try { console.log(name, run()); } catch (e) { console.log(name, (e as Error).name + ": " + (e as Error).message); } +} diff --git a/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json b/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } From ffc7af36c760b0660e7ba6c2fcb3bda1cd66e14d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:59:43 -0500 Subject: [PATCH 236/395] Read a typed array's length without an invariant header load `length` and `byteLength` loaded the typed array's header with `!invariant.load` (#2016). LLVM may move such a load down to its use, past the release that frees the view, so `const slots = this.slots; this.slots = next; return slots.length` returned a garbage number in release builds. These reads are now ordinary loads that keep the header's alias tag. Header loads that only bound or address an element access stay invariant: their values reach only that access, which runs while the view is alive, so typed-array loops still hoist the length and data pointer. Corpus typed-array-length-after-release covers a replaced field, a deleted map entry and a rebound global. --- .../compiler/src/backend/llvm/bytes-alias.ts | 18 ++++-- .../compiler/src/backend/llvm/expr-bytes.ts | 17 ++++-- .../test/bytes-header-load-emission.test.ts | 60 +++++++++++++++++++ .../typed-array-length-after-release.ts.json | 6 ++ .../typed-array-length-after-release.ts | 56 +++++++++++++++++ .../typed-array-length-after-release.ts.json | 1 + 6 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 packages/compiler/test/bytes-header-load-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json create mode 100644 tests/corpus/typed-array-length-after-release.ts create mode 100644 tests/harness/corpus-costs/typed-array-length-after-release.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-alias.ts b/packages/compiler/src/backend/llvm/bytes-alias.ts index b7a672754..2dff9f70b 100644 --- a/packages/compiler/src/backend/llvm/bytes-alias.ts +++ b/packages/compiler/src/backend/llvm/bytes-alias.ts @@ -9,15 +9,25 @@ import type { LlvmEmitterContext } from "./expr-context.js"; * receiver slots loop-invariant. Untagged accesses and calls still alias * every category. * - * Header loads are also `!invariant.load`: no store or call between the - * construction of a view and its release changes them. Without it, a data - * pointer load ahead of a bounds branch is sunk into the in-bounds block, - * and LICM keeps a conditionally executed load inside the loop. */ + * Header loads that only bound or address an access to the view are also + * `!invariant.load`: no store or call between the construction of a view + * and its release changes them, and their values reach only that access, + * which runs while the view is alive. Without it, a data pointer load + * ahead of a bounds branch is sunk into the in-bounds block, and LICM keeps + * a conditionally executed load inside the loop. */ export function bytesHeaderAlias(host: LlvmEmitterContext): string { const tag = host.memoryAliasAttachment("bytes:header"); return tag === "" ? "" : `${tag}, !invariant.load !{}`; } +/** A header load whose value the program reads (`length`, `byteLength`) + * keeps the alias tag only. The value can outlive the view, and LLVM may + * move an invariant load to its use, past the release that frees the + * view. */ +export function bytesHeaderValueAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:header"); +} + /** Element storage's tag, and the alias scope of the access being emitted * in a loop version's fast copy (bytesAccessScope). */ export function bytesElementAlias(host: LlvmEmitterContext): string { diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index a50464cd6..6f0b0380d 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -8,7 +8,7 @@ import { alwaysExactInteger, exactInteger } from "./integer-values.js"; import { byteNumberAccess } from "../../ir/byte-numbers.js"; import { emitByteNumber } from "./byte-numbers.js"; import { integerOrNaNIndex, integerOrNaNRange } from "./wrapping-sums.js"; -import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; +import { bytesElementAlias, bytesHeaderAlias, bytesHeaderValueAlias } from "./bytes-alias.js"; const BYTES_NUM_KIND: Record = { u8: { kind: 0, le: false }, @@ -160,12 +160,19 @@ export function emitBytesIndex( return idx; } -/** The element count of a typed array, in the target's size type. */ -export function emitBytesHeaderLength(host: LlvmEmitterContext, receiver: string): string { +/** 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. */ +export function emitBytesHeaderLength( + host: LlvmEmitterContext, + receiver: string, + value = false, +): string { const p = host.B.tmp(); const len = host.B.tmp(); + const alias = value ? bytesHeaderValueAlias(host) : bytesHeaderAlias(host); host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${bytesHeaderAlias(host)}`); + host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${alias}`); return len; } @@ -184,7 +191,7 @@ export function emitBytesLength( bytes: boolean, ): LlValue { const B = host.B; - const len = emitBytesHeaderLength(host, receiver); + const len = emitBytesHeaderLength(host, receiver, true); const size = BYTES_ELEMENT_SIZE[elem]; const count = bytes && size !== 1 ? B.tmp() : len; if (count !== len) B.line(`${count} = shl ${host.sizeType} ${len}, ${Math.log2(size)}`); diff --git a/packages/compiler/test/bytes-header-load-emission.test.ts b/packages/compiler/test/bytes-header-load-emission.test.ts new file mode 100644 index 000000000..bc7cb9803 --- /dev/null +++ b/packages/compiler/test/bytes-header-load-emission.test.ts @@ -0,0 +1,60 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule, validateModule } from "../src/index.js"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; +import { type IrModule } from "../src/ir/ir.js"; + +async function lower(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-bytes-header-")); + 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 module; + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function body(llvm: string, symbol: string): string { + const found = new RegExp(`^define internal [^\\n]*@${symbol}(?:\\.i32)?\\([^]*?^}`, "m").exec( + llvm, + ); + expect(found, symbol).not.toBeNull(); + return found![0]; +} + +const INVARIANT_HEADER = /load (?:i64|i32|ptr), ptr [^\n]*, !tbaa !\d+, !invariant\.load !\{\}$/m; + +test("a typed array's length read as a value is an ordinary load; access bounds stay invariant", async () => { + const module = await lower(` +class Table { + slots: Int32Array = new Int32Array(4); + swap(): number { + const slots = this.slots; + this.slots = new Int32Array(1); + return slots.length + slots.byteLength; + } +} +function sum(values: Float64Array, scale: number): number { + let total = 0; + for (let i = 0; i < values.length; i++) total += values[i]! * scale; + return total; +} +const table = new Table(); +console.log(table.swap(), sum(new Float64Array([1, 2]), 2)); +`); + const llvm = emitLlvmModule(module); + // The length outlives the view: the release must not be crossed. + const swap = body(llvm, "sc_bf__x25_Table_swap"); + expect(swap).toMatch(/load i64, ptr [^\n]*, !tbaa !\d+$/m); + expect(swap).not.toContain("!invariant.load"); + expect(body(llvm, "sc_bf_sum")).toMatch(INVARIANT_HEADER); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json new file mode 100644 index 000000000..ca6fcb551 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/typed-array-length-after-release.ts" + ], + "diags": [] +} diff --git a/tests/corpus/typed-array-length-after-release.ts b/tests/corpus/typed-array-length-after-release.ts new file mode 100644 index 000000000..81fad4c77 --- /dev/null +++ b/tests/corpus/typed-array-length-after-release.ts @@ -0,0 +1,56 @@ +// A typed array's length and byteLength read just before the view's last +// reference goes away: the values stay those of the old view after a field +// replaces it, a map entry is deleted, or a local is rebound. +function grow(slots: Int32Array): Int32Array { + const next = new Int32Array(slots.length * 2); + next.set(slots); + return next; +} + +class Table { + slots: Int32Array = new Int32Array(4); + count = 0; + add(value: number): number { + const slots = this.slots; + slots[this.count % slots.length] = value; + this.count++; + if (this.count === slots.length) this.slots = grow(slots); + return this.slots.length; + } + swap(): number { + const slots = this.slots; + this.slots = new Int32Array(1); + return slots.length; + } + swapBytes(): number { + const slots = this.slots; + const bytes = slots.byteLength; + this.slots = new Int32Array(2); + return bytes + slots.length; + } +} + +const table = new Table(); +let size = 0; +for (let i = 0; i < 9; i++) size = table.add(i); +console.log(size, table.swap(), table.slots.length, table.add(5)); +console.log(table.swapBytes(), table.slots.length); + +const views = new Map(); +views.set("a", new Float64Array(6)); +function take(key: string): number { + const view = views.get(key); + if (view === undefined) return -1; + const length = view.length; + views.delete(key); + return length + view.byteLength; +} +console.log(take("a"), take("a"), views.size); + +let current = new Uint8Array(3); +function rebind(): number { + const old = current; + current = new Uint8Array(old.length + 1); + return old.length * 10 + current.length; +} +console.log(rebind(), rebind(), current.length); diff --git a/tests/harness/corpus-costs/typed-array-length-after-release.ts.json b/tests/harness/corpus-costs/typed-array-length-after-release.ts.json new file mode 100644 index 000000000..b062243b7 --- /dev/null +++ b/tests/harness/corpus-costs/typed-array-length-after-release.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 2500 } From 541b05ddea63bf8ddde6b4828769ba4e46886717 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 09:22:06 -0700 Subject: [PATCH 237/395] Map generic alias casts inside generic bodies with the instance's arguments A cast to a generic type alias spelled over the enclosing function's own type parameters (`return x as Catalog`) stays symbolic in the checker inside the body, so a mapped type with key remapping, a template key filter or a conditional member refused with SC2001 in every instance. The checker interns alias instantiations, so the body's `Catalog` is the same type the signature declares. Binding unification now records, per call-keyed instance, each declared generic alias reference together with the alias the call's resolved signature instantiates. When the symbolic form does not map, mapType maps that concrete instantiation instead. A body forwarding its own alias to another generic resolves through the enclosing instance. Types that already mapped are unaffected. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 28 +++++ .../compiler/src/frontend/lowering/lowerer.ts | 10 ++ packages/compiler/src/frontend/type-mapper.ts | 15 +++ .../generic-alias-cast-instances.ts.json | 6 + tests/corpus/generic-alias-cast-instances.ts | 103 ++++++++++++++++++ .../generic-alias-cast-instances.ts.json | 1 + 6 files changed, 163 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json create mode 100644 tests/corpus/generic-alias-cast-instances.ts create mode 100644 tests/harness/corpus-costs/generic-alias-cast-instances.ts.json diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 0f9e41723..7d7124027 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -921,6 +921,27 @@ function bindDefaultTypeParams( }); } +/** Generic alias references of a call-keyed instance's declared signature + * (`Catalog`), each paired with the same alias as the call's resolved + * signature instantiates it (`Catalog<{ g1: … }, "acme">`). The checker + * keeps the alias symbolic inside the generic body and offers no way to + * instantiate it with the bound arguments, but alias instantiations are + * interned: the body's `x as Catalog` is the very type the signature + * declares, so the pair recorded during unification is that re-instantiation. + * Keyed by the instance's ts-level bindings map, which every scope that + * lowers the instance installs (typeParamTsBindings). */ +const boundAliasInstances = new WeakMap, Map>(); + +/** The concrete instantiation of a generic alias reference inside the + * instance whose ts-level bindings are `tsBindings` (boundAliasInstances), + * or null when its signature never instantiated that reference. */ +export function boundAliasInstance( + tsBindings: Map, + type: ts.Type, +): ts.Type | null { + return boundAliasInstances.get(tsBindings)?.get(type) ?? null; +} + /** The structural half of binding inference: unify the DECLARED signature * (whose types mention the type parameters) against a TARGET signature * with the substitution applied — a call's resolved signature, or the @@ -1040,6 +1061,13 @@ function unifySignatureBindings( dAliasArgs.length && iAliasArgs.length === dAliasArgs.length ) { + if (tsBindings && declared !== inst) { + let pairs = boundAliasInstances.get(tsBindings); + if (!pairs) boundAliasInstances.set(tsBindings, (pairs = new Map())); + // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. + if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); + } dAliasArgs.forEach((da, i) => { const ia = iAliasArgs[i]; if (ia) unify(da, ia, depth + 1); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..52f3567fb 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -456,6 +456,7 @@ import { inferTypeParamBindings, lowerGenericInstance, genericLocalClassBinding, + boundAliasInstance, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; @@ -3792,6 +3793,7 @@ export class Lowerer { classNamer: (decl) => this.classNamer(decl), resolveTypeParam: this.typeParamResolver, resolveTypeParamTs: this.typeParamTsResolver, + resolveBoundAliasTs: this.boundAliasTsResolver, isErasing: () => this.erasingDepth > 0, genericClassInstance: (decl, ref, mapArgument) => this.genericClassInstanceType(decl, ref, mapArgument), @@ -9550,6 +9552,14 @@ export class Lowerer { return (sym && this.typeParamTsBindings.get(sym)) ?? null; }; + /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares + * (boundAliasInstance). Inert outside generic function instantiation. */ + readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { + if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; + return boundAliasInstance(this.typeParamTsBindings, t); + }; + irTypeOf(node: ts.Node): IrType { const t = this.typeOf(node); // JS inference can leave mutable fields, accessor parameters and diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..5fa361db2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1266,6 +1266,11 @@ export interface TypeMapperCtx { * only where literal identity matters — indexed accesses (`T[K]`) inside * generic bodies whose K is bound to a literal key. */ resolveTypeParamTs?: TypeParamTsResolver; + /** A generic alias reference inside a generic body (`Catalog`) + * re-instantiated with the instance's bound type arguments: the concrete + * checker type its signature instantiates, or null. Consulted only when + * the symbolic form does not map. */ + resolveBoundAliasTs?: TypeParamTsResolver; /** 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 @@ -1830,6 +1835,16 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (!topLevel || !ts.isUnreadableConstituentsPanic(e)) throw e; mapped = null; } + // A generic alias the body spells over its own type parameters (`x as + // Catalog`) stays symbolic in the checker; when that form does not + // map, the instance's concrete instantiation of the same alias does. + if (mapped === null && ctx.resolveBoundAliasTs) { + const concrete = ctx.resolveBoundAliasTs(type); + if (concrete !== null && concrete !== type) { + mapped = mapType(concrete, ctx); + if (mapped !== null) contextResolutions++; + } + } if ( mapped !== null && memoKey !== null && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json new file mode 100644 index 000000000..a229ee862 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-alias-cast-instances.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-alias-cast-instances.ts b/tests/corpus/generic-alias-cast-instances.ts new file mode 100644 index 000000000..11a098214 --- /dev/null +++ b/tests/corpus/generic-alias-cast-instances.ts @@ -0,0 +1,103 @@ +// Casts to a generic type alias spelled over the enclosing function's own +// type parameters (`x as Catalog`). The checker keeps such an alias +// symbolic inside the generic body; each instance maps it with its own bound +// type arguments. + +type Groups = Record>; + +type EntryKey = { [A in keyof G]: keyof G[A] }[keyof G] & string; +type KeyOfKind = Extract, `${K}:${string}`>; +type EntryId = K extends `${string}:${infer I}` ? I : never; + +type Catalog = { + [Key in KeyOfKind as EntryId]: { id: EntryId; label: string; owner: P }; +}; + +function flatten(groups: Groups, kind: string): Record { + return Object.fromEntries( + Object.values(groups) + .flatMap((entries) => Object.entries(entries)) + .filter(([key]) => key.startsWith(kind + ":")) + .map(([, entry]) => [(entry as { id: string }).id, entry]), + ); +} + +function toolCatalog( + _owner: P, + groups: G, +): Catalog { + return flatten(groups, "tool") as Catalog; +} + +function partCatalog( + _owner: P, + groups: G, +): Catalog { + return flatten(groups, "part") as Catalog; +} + +const workshop = { + bench: { + "tool:saw": { id: "saw", label: "Hand saw", owner: "acme" }, + "part:bolt": { id: "bolt", label: "Hex bolt", owner: "acme" }, + }, + shelf: { + "tool:drill": { id: "drill", label: "Cordless drill", owner: "acme" }, + }, +}; + +const tools = toolCatalog("acme", workshop); +console.log(tools.saw.label, tools.drill.id, tools.drill.owner); +console.log(Object.keys(tools).join(","), JSON.stringify(tools.saw)); + +const parts = partCatalog("acme", workshop); +console.log(parts.bolt.label, Object.keys(parts).length); + +// A second instantiation with different groups gets its own layout. +const garden = { + shed: { + "tool:rake": { id: "rake", label: "Leaf rake", owner: "green" }, + "tool:hoe": { id: "hoe", label: "Garden hoe", owner: "green" }, + }, +}; +const gardenTools = toolCatalog("green", garden); +console.log(gardenTools.rake.label, gardenTools.hoe.label, Object.keys(gardenTools).join(",")); + +// No entry of the kind: the alias resolves to the empty record. +const gardenParts = partCatalog("green", garden); +console.log(Object.keys(gardenParts).length, JSON.stringify(gardenParts)); + +// A homomorphic mapped alias over the bound type, cast inside the body. +type Boxed = { [K in keyof T]: { value: T[K] } }; + +function boxAll>(values: T): Boxed { + const out: Record = {}; + for (const key of Object.keys(values)) out[key] = { value: values[key] }; + return out as Boxed; +} + +const boxed = boxAll({ width: 3, height: 4 }); +console.log(boxed.width.value * boxed.height.value); + +// The alias nested in an array type of the signature. +function boxEach>(rows: T[]): Boxed[] { + return rows.map((row) => { + const out: Record = {}; + for (const key of Object.keys(row)) out[key] = { value: row[key] }; + return out as Boxed; + }); +} + +const rows = boxEach([ + { x: 1, y: 2 }, + { x: 5, y: 8 }, +]); +console.log(rows.map((r) => r.x.value + r.y.value).join(" ")); + +// A generic body forwarding its parameters to another generic cast. +function ownedTools(groups: G): Catalog { + return toolCatalog("shared", groups); +} + +const shared = ownedTools({ rack: { "tool:clamp": { id: "clamp", label: "C-clamp", owner: "shared" } } }); +console.log(shared.clamp.label, shared.clamp.owner); diff --git a/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json b/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } From 7a175c374f1ea2a241a6958858abd44b1b84e5ef Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 11:26:13 -0500 Subject: [PATCH 238/395] Prepare v0.4.5 release --- CHANGELOG.md | 38 ++++++++++++++++++- packages/cli-darwin-arm64/package.json | 2 +- packages/cli-darwin-x64/package.json | 2 +- packages/cli-linux-arm64-gnu/package.json | 2 +- packages/cli-linux-arm64-musl/package.json | 2 +- packages/cli-linux-x64-gnu/package.json | 2 +- packages/cli-linux-x64-musl/package.json | 2 +- packages/cli-win32-x64-msvc/package.json | 2 +- packages/cli/package.json | 2 +- packages/compiler/package.json | 2 +- packages/compiler/surface-manifest.json | 2 +- packages/experimental/package.json | 2 +- packages/llvm-darwin-arm64/package.json | 2 +- packages/llvm-darwin-x64/package.json | 2 +- packages/llvm-linux-arm64-gnu/package.json | 2 +- packages/llvm-linux-arm64-musl/package.json | 2 +- packages/llvm-linux-x64-gnu/package.json | 2 +- packages/llvm-linux-x64-musl/package.json | 2 +- packages/llvm-win32-x64-msvc/package.json | 2 +- packages/runtime-android-arm64/package.json | 2 +- packages/runtime-darwin-arm64/package.json | 2 +- packages/runtime-darwin-x64/package.json | 2 +- packages/runtime-ios-arm64/package.json | 2 +- .../runtime-ios-simulator-arm64/package.json | 2 +- packages/runtime-linux-arm64-gnu/package.json | 2 +- .../runtime-linux-arm64-musl/package.json | 2 +- packages/runtime-linux-x64-gnu/package.json | 2 +- packages/runtime-linux-x64-musl/package.json | 2 +- packages/runtime-wasm32-wasi/package.json | 2 +- packages/runtime-win32-x64-msvc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/threads/package.json | 2 +- packages/types/package.json | 2 +- 33 files changed, 68 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 612df47b5..e8ab725f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,42 @@ All notable changes to scriptc will be documented in this file. +## 0.4.5 + +### Highlights + +- **Fixes:** + - Release builds read a typed array's length without a header load that could read a freed view. + - `net.Socket` `write()` returns Node's backpressure boolean and fires `'drain'`. + - Super calls in JavaScript method values call the declared base method. + - Calling a non-function through an asserted callee throws Node's TypeError. +- **Faster programs:** + - Exceptions propagate by unwinding from directly called functions on Linux, instead of status checks after every call. + - Typed-array loops prove their bounds and keep their views disjoint in more cases, including views over shared memory in worker programs; integer loops stay in integers across nested counted loops, pure helper calls, powers of two and byte reads. + - Lone array callbacks run as loops with their bodies inlined, and row sums run two rows per pass. + - Number-key maps look keys up on their integer view and write updates through the entry the read found; small number-key maps use a direct index. + - Strings: base64 decodes eight characters at a time; Buffers fill, swap byte order and decode `utf16le` a word at a time; readline splits input in one scan; string scans over non-ASCII strings use their UTF-16 units. + - Untyped code: property caches also cache absent properties, object literal members go through the store caches, calls on plain arrays skip the method ladder, and dynamic values dispose without key-cache walks. + - JSON parses into discriminated unions, and dynamic values convert into nested unions, in one pass. + - BigInt division settles quotients from the top limbs, and limb shifts are vectorized. +- **Faster builds:** + - Function bodies are emitted on up to eight workers, including in dev builds, and repeated work in LLVM module emission is cut. + - Linux executables link with lld when the driver finds it, and unchanged executables are reused without selecting the runtime pack. + - Dev builds reuse their frontend output after edits that keep token positions, and unchanged frontend inputs are checked by file identity before hashing. + - Lowering fetches only the syntax trees it reads, and the large compiler tables are built on first use. + - Release builds of the native compiler use a profile collected on the release commit. + - Type checking can use a tsc-ts server shared by later builds (opt-in). +- **Node.js parity:** + - Crypto: RSA key generation lengths, RSA-PSS keys, encrypted private keys, `X509Certificate` fields and methods, and TLS certificates and `tls.Server` contexts. + - zlib: Brotli compression and decompression. + - Dates are mutable objects with Node's setters and conversions; URL hosts and paths parse as the URL Standard does, including non-ASCII hosts with UTS #46. + - `Math`, `JSON` and `Reflect` are real objects in JavaScript; objects can inherit from JavaScript class prototypes; typed arrays held as untyped values support named properties and `Symbol.species`. + - Error messages: `JSON.parse` syntax errors use V8's messages and positions; `Object.getPrototypeOf` of an Error answers `Error.prototype`. + - `util.inspect` of dynamic classes, functions and `%o` arguments, and `Symbol.toStringTag` fields and getters, match Node. +- **npm packages:** more npm and CommonJS dependencies compile statically, including namespace-style packages with guarded declarations and `var` exports, and untyped Maps, Sets and arrays are typed from their uses. + + + ## 0.4.4 ### Highlights @@ -29,8 +65,6 @@ All notable changes to scriptc will be documented in this file. - Split CommonJS `require` bindings, `__`-prefixed exports, string-key destructuring of `any` values, and record key order for values JavaScript built. - **npm packages:** zod 4 and `@hono/zod-validator` values are projected at the declaration boundary. - - ## 0.4.3 ### Highlights diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index bac01cc44..1a8ee5d2c 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for darwin-arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index 6c2fb3123..0cb24b908 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for darwin-x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-gnu/package.json b/packages/cli-linux-arm64-gnu/package.json index 289d8d6bd..3a1c7488d 100644 --- a/packages/cli-linux-arm64-gnu/package.json +++ b/packages/cli-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-arm64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-musl/package.json b/packages/cli-linux-arm64-musl/package.json index 3a601ca33..e671e926b 100644 --- a/packages/cli-linux-arm64-musl/package.json +++ b/packages/cli-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-arm64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-gnu/package.json b/packages/cli-linux-x64-gnu/package.json index 8f6b1aa5e..2b56308fd 100644 --- a/packages/cli-linux-x64-gnu/package.json +++ b/packages/cli-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-x64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-musl/package.json b/packages/cli-linux-x64-musl/package.json index ff0d1d686..4ca4c2837 100644 --- a/packages/cli-linux-x64-musl/package.json +++ b/packages/cli-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-x64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-win32-x64-msvc/package.json b/packages/cli-win32-x64-msvc/package.json index e94ba31df..d734c0807 100644 --- a/packages/cli-win32-x64-msvc/package.json +++ b/packages/cli-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for win32-x64-msvc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli/package.json b/packages/cli/package.json index 26cf10f07..59f9db90a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "scriptc", - "version": "0.4.4", + "version": "0.4.5", "description": "Compile ordinary TypeScript and JavaScript to small, fast native executables and WebAssembly modules that run without Node.js", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 570c0a1a5..03af418a5 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/compiler", - "version": "0.4.4", + "version": "0.4.5", "description": "The scriptc compiler — TypeScript/JavaScript frontend, typed IR, LLVM backend", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 58b3da7e2..5f5d8f7e0 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "compilerVersion": "0.4.4", + "compilerVersion": "0.4.5", "coverage": [ "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", diff --git a/packages/experimental/package.json b/packages/experimental/package.json index 583a2bc58..4636be674 100644 --- a/packages/experimental/package.json +++ b/packages/experimental/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/experimental", - "version": "0.4.4", + "version": "0.4.5", "description": "Experimental scriptc APIs, with Node.js implementations: lifetime regions for ProgramLifetime objects, intern tables shared between threads, and per-thread slots on shared objects", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-arm64/package.json b/packages/llvm-darwin-arm64/package.json index 76d5fdb72..d1ac54f3e 100644 --- a/packages/llvm-darwin-arm64/package.json +++ b/packages/llvm-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on macOS arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-x64/package.json b/packages/llvm-darwin-x64/package.json index fbb755f4a..b96a90cae 100644 --- a/packages/llvm-darwin-x64/package.json +++ b/packages/llvm-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on macOS x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-gnu/package.json b/packages/llvm-linux-arm64-gnu/package.json index 81fdc7d5b..49796f522 100644 --- a/packages/llvm-linux-arm64-gnu/package.json +++ b/packages/llvm-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Linux arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-musl/package.json b/packages/llvm-linux-arm64-musl/package.json index 8b3b2f185..3001949c6 100644 --- a/packages/llvm-linux-arm64-musl/package.json +++ b/packages/llvm-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Statically linked LLVM code-generation helper for scriptc on Linux arm64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-gnu/package.json b/packages/llvm-linux-x64-gnu/package.json index 79b7eba3f..11b375ba4 100644 --- a/packages/llvm-linux-x64-gnu/package.json +++ b/packages/llvm-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Linux x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-musl/package.json b/packages/llvm-linux-x64-musl/package.json index 5bffa5bef..ffe1ca7ae 100644 --- a/packages/llvm-linux-x64-musl/package.json +++ b/packages/llvm-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Statically linked LLVM code-generation helper for scriptc on Linux x64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-win32-x64-msvc/package.json b/packages/llvm-win32-x64-msvc/package.json index 14c747a4a..77e6fe687 100644 --- a/packages/llvm-win32-x64-msvc/package.json +++ b/packages/llvm-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Windows x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-android-arm64/package.json b/packages/runtime-android-arm64/package.json index 1dfa1b177..7aea1da1e 100644 --- a/packages/runtime-android-arm64/package.json +++ b/packages/runtime-android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-android-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for Android arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-arm64/package.json b/packages/runtime-darwin-arm64/package.json index 32463357f..ae068a00a 100644 --- a/packages/runtime-darwin-arm64/package.json +++ b/packages/runtime-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for macOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-x64/package.json b/packages/runtime-darwin-x64/package.json index c0577b04c..7dbdaf6b9 100644 --- a/packages/runtime-darwin-x64/package.json +++ b/packages/runtime-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for macOS x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-arm64/package.json b/packages/runtime-ios-arm64/package.json index 9c2134280..8a868a606 100644 --- a/packages/runtime-ios-arm64/package.json +++ b/packages/runtime-ios-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for iOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-simulator-arm64/package.json b/packages/runtime-ios-simulator-arm64/package.json index 6b7563a6a..011d41b39 100644 --- a/packages/runtime-ios-simulator-arm64/package.json +++ b/packages/runtime-ios-simulator-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-simulator-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for iOS simulator arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-gnu/package.json b/packages/runtime-linux-arm64-gnu/package.json index f0ae56929..92a3192e7 100644 --- a/packages/runtime-linux-arm64-gnu/package.json +++ b/packages/runtime-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux arm64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-musl/package.json b/packages/runtime-linux-arm64-musl/package.json index 79fa8e7dd..5e751d829 100644 --- a/packages/runtime-linux-arm64-musl/package.json +++ b/packages/runtime-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux arm64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-gnu/package.json b/packages/runtime-linux-x64-gnu/package.json index 45af8d2b7..63c045385 100644 --- a/packages/runtime-linux-x64-gnu/package.json +++ b/packages/runtime-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux x64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-musl/package.json b/packages/runtime-linux-x64-musl/package.json index fe9d11bcd..a8602b395 100644 --- a/packages/runtime-linux-x64-musl/package.json +++ b/packages/runtime-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux x64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-wasm32-wasi/package.json b/packages/runtime-wasm32-wasi/package.json index ac2f4a5e9..799863cf0 100644 --- a/packages/runtime-wasm32-wasi/package.json +++ b/packages/runtime-wasm32-wasi/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-wasm32-wasi", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for WASI Preview 1", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-win32-x64-msvc/package.json b/packages/runtime-win32-x64-msvc/package.json index 9a5731da2..421f1f71f 100644 --- a/packages/runtime-win32-x64-msvc/package.json +++ b/packages/runtime-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Windows x64 MSVC", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 1d58b00e5..90140ab15 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime", - "version": "0.4.4", + "version": "0.4.5", "description": "scriptc native runtime sources and vendored dependencies", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/threads/package.json b/packages/threads/package.json index c29967cc6..6ebcf5b8b 100644 --- a/packages/threads/package.json +++ b/packages/threads/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/threads", - "version": "0.4.4", + "version": "0.4.5", "description": "Zero-copy publication of immutable object graphs to worker threads (scriptc), with a deep-freezing Node.js implementation", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/types/package.json b/packages/types/package.json index db88b4b2f..b2aa805be 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/types", - "version": "0.4.4", + "version": "0.4.5", "description": "Contract types for scriptc: type-only annotations (Int32, NoThrow, Immutable) that scriptc checks and optimizes, with no runtime semantics", "license": "Apache-2.0", "homepage": "https://scriptc.dev", From 25a630a8ca359a4e0dfd4883e5f045000d89d562 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 09:37:46 -0700 Subject: [PATCH 239/395] Map intersections of equally represented constituents as that type An intersection that restates an optional member as required with a separately written element type (`Opts & { files: Array<{ path: string }> }` over `files?: Array<{ path: string }>`) gives the member the type `A[] & B[]`. The checker keeps both element literals, and no intersection rule mapped an intersection of arrays, so the record was refused (SC2009). When every constituent maps to one IR type that is not the dynamic value, the intersection now maps to it. The rule runs only after every other intersection rule declined, so intersections that already mapped keep their mapping. pi's NormalizedBuildSystemPromptOptions is such an intersection; it sits on the chain from main()'s options to the extension API and refused main(). Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 26 ++++++ .../intersection-restated-members.ts.json | 6 ++ tests/corpus/intersection-restated-members.ts | 82 +++++++++++++++++++ .../intersection-restated-members.ts.json | 1 + 4 files changed, 115 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json create mode 100644 tests/corpus/intersection-restated-members.ts create mode 100644 tests/harness/corpus-costs/intersection-restated-members.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..edb3cf144 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -4948,6 +4948,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const record = mapRecordType(widened, ctx); if (record) return record; } + // An intersection whose constituents all map to ONE representation is + // that representation. An intersection that restates an optional member + // as required (`Opts & { files: Array<{ path: string }> }` over + // `files?: Array<{ path: string }>`) gives the member the type `A[] & B[]` + // with two separately written but structurally equal element literals; + // the checker keeps both, while their arrays (tuples, maps, ...) intern + // to one IR type. Reached only after every other intersection rule + // declined, so intersections that already mapped keep their mapping. A + // dynamic result stays unmapped: two opaque types are not one type. + if (widened.isIntersectionType()) { + const same = mapEqualConstituentsIntersection(widened, ctx); + if (same) return same; + } // Unions. `boolean` is internally `true | false`, and narrowing produces // literal unions like `"a" | "b"` — parts that map to the SAME IR type // collapse (deduplicated by typeKey), so a single surviving arm is just @@ -6813,6 +6826,19 @@ export function withUnitArm( * assigned keys, which is also Node's answer for Object.assign(fn, obj) * (a function's own length/name are non-enumerable). Null whenever the * shape doesn't fit — the caller falls through to the plain-func mapping. */ +/** The IR type every constituent of an intersection maps to, or null when a + * constituent is unmappable or dynamic, or two constituents differ. */ +function mapEqualConstituentsIntersection(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { + let same: IrType | null = null; + for (const part of ts.constituentTypes(widened)) { + const mapped = mapType(part, ctx); + if (mapped === null || mapped.kind === "dyn") return null; + if (same === null) same = mapped; + else if (!typeEquals(same, mapped)) return null; + } + return same; +} + function mapHybridCallableIntersection(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { const { checker, shapes } = ctx; if (!widened.isIntersectionType()) return null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json new file mode 100644 index 000000000..4560cb4ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/intersection-restated-members.ts" + ], + "diags": [] +} diff --git a/tests/corpus/intersection-restated-members.ts b/tests/corpus/intersection-restated-members.ts new file mode 100644 index 000000000..249b9ddfb --- /dev/null +++ b/tests/corpus/intersection-restated-members.ts @@ -0,0 +1,82 @@ +// An intersection that restates a member with a separately written but +// structurally equal type maps as that one type. Restating an optional member +// as required makes it required; the checker keeps both element literals +// (`A[] & B[]`), and their arrays, tuples, and maps share one representation. + +interface Options { + root: string; + files?: Array<{ path: string; size: number }>; +} +type Normalized = Options & { files: Array<{ path: string; size: number }> }; + +function normalize(options: Options): Normalized { + return { ...options, files: options.files ?? [] }; +} + +function totalSize(n: Normalized): number { + let total = 0; + for (const f of n.files) total += f.size; + return total; +} + +const plain = normalize({ root: "/a" }); +console.log(plain.root, plain.files.length, totalSize(plain)); +const full = normalize({ + root: "/b", + files: [ + { path: "x.txt", size: 3 }, + { path: "y.txt", size: 4 }, + ], +}); +full.files.push({ path: "z.txt", size: 5 }); +console.log(full.files.map((f) => f.path).join(","), totalSize(full)); +console.log(JSON.stringify(full)); + +let maybe: Normalized | undefined; +if (process.argv.length < 99) maybe = { root: "/", files: [{ path: "a", size: 1 }] }; +console.log(maybe?.root, maybe?.files[0]?.path); + +// Nested records whose inner arrays are restated. +interface Settings { + name: string; + meta?: { tags: Array<{ key: string; value: string }> }; +} +type Resolved = Settings & { meta: { tags: Array<{ key: string; value: string }> } }; +const resolved: Resolved = { name: "s", meta: { tags: [{ key: "k", value: "v" }] } }; +resolved.meta.tags.push({ key: "k2", value: "v2" }); +for (const t of resolved.meta.tags) console.log(`${t.key}=${t.value}`); + +// Tuples, nested arrays, and maps. +interface Layout { + pair?: [{ x: number }, string]; + grid?: Array>; + index?: Map; +} +type Filled = Layout & { + pair: [{ x: number }, string]; + grid: Array>; + index: Map; +}; +const filled: Filled = { + pair: [{ x: 7 }, "seven"], + grid: [[{ cell: 1 }, { cell: 2 }], [{ cell: 3 }]], + index: new Map([["home", { hits: 2 }]]), +}; +filled.index.set("about", { hits: 1 }); +console.log(filled.pair[0].x, filled.pair[1]); +console.log(filled.grid.map((row) => row.map((c) => c.cell).join("+")).join(" | ")); +for (const [k, v] of filled.index) console.log(k, v.hits); + +// Both sides required, and both sides optional (the member stays optional). +type Twice = { items: Array<{ n: number }> } & { items: Array<{ n: number }> }; +const twice: Twice = { items: [{ n: 1 }, { n: 2 }] }; +console.log(twice.items.reduce((s, i) => s + i.n, 0)); +type BothOptional = { extra?: Array<{ n: number }> } & { extra?: Array<{ n: number }> }; +const none: BothOptional = {}; +const some: BothOptional = { extra: [{ n: 9 }] }; +console.log(none.extra === undefined, some.extra?.[0]?.n); + +// A restated member read through a function value. +type Handler = (event: Normalized) => string; +const describe: Handler = (event) => `${event.root}:${event.files.length}`; +console.log(describe(full), describe(plain)); diff --git a/tests/harness/corpus-costs/intersection-restated-members.ts.json b/tests/harness/corpus-costs/intersection-restated-members.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/intersection-restated-members.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 30e6ca9160d33cb24933fc392e86c36358dfc681 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:07:51 -0700 Subject: [PATCH 240/395] Convert promise and function values into a union's arm of their kind A promise or function value flowing into a union whose only arm of that kind has a wider payload or result (`Promise` into `Promise | undefined`, `(s) => Promise` into an optional `(s) => Promise` member) was refused with SC2003: an arm value wrapped into a union only when the union held an identical arm. It now takes the arm-level conversion (the checked payload widening, the function adapter) and wraps the result. `p === u` against such a union compares both sides as untyped values, so a promise keeps its identity although converting it into the union's arm builds a new promise. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/expressions/binary.ts | 25 +++++++ .../compiler/src/frontend/lowering/lowerer.ts | 25 +++++++ ...nion-promise-function-arm-widening.ts.json | 6 ++ .../union-promise-function-arm-widening.ts | 68 +++++++++++++++++++ ...nion-promise-function-arm-widening.ts.json | 1 + 5 files changed, 125 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json create mode 100644 tests/corpus/union-promise-function-arm-widening.ts create mode 100644 tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index c172fc662..01b48554e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -2388,6 +2388,31 @@ export function lowerStrictEquality( loc, }; } + // A plain promise against a union whose promise arm has a different + // payload (`chains.get(id) === tail` with a `Map>`): + // wrapping would convert the promise into that arm, and the converted + // promise is a new object. Promises are objects to ===, so compare both + // sides as untyped values, where a converted promise answers with its + // origin's identity (the plain promise-vs-promise rule below). + const plainPromise = + left.type.kind === "promise" ? left : right.type.kind === "promise" ? right : null; + if ( + !bothUnion && + plainPromise !== null && + lowerer.armTag(ut.unionId, plainPromise.type) < 0 && + unionArms.some((arm) => arm.kind === "promise") && + lowerer.dynConvertible(left.type) && + lowerer.dynConvertible(right.type) + ) { + return { + kind: "dynScalarEq", + left: lowerer.coerceToExpected(left, DYN), + right: lowerer.coerceToExpected(right, DYN), + ...(negated ? { negated: true as const } : {}), + type: BOOL, + loc, + }; + } if ((sameUnion || !bothUnion) && lowerer.eqComparableUnion(ut.unionId)) { aliasRefusal(ut); return { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..059fab488 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -10397,6 +10397,31 @@ export class Lowerer { } } } + // A promise or function value against a union whose ONLY arm of + // that kind differs from it (`Promise` into `Promise | + // undefined`, `() => Promise` into an optional + // `() => Promise` member): convert into + // that arm with the arm-level conversion (the checked payload + // widening, the function adapter), then wrap. Several arms of the + // kind stay ambiguous and fall through to the union fence. + if (expr.type.kind === "promise" || expr.type.kind === "func") { + const arms = this.unions.get(expected.unionId)?.arms ?? []; + const candidates = arms.filter((arm) => arm.kind === expr.type.kind); + const arm = candidates.length === 1 ? candidates[0]! : null; + if (arm) { + const converted = this.coerceToExpected(expr, arm); + if (converted !== expr && typeEquals(converted.type, arm)) { + return { + kind: "unionWrap", + unionId: expected.unionId, + tag: arms.indexOf(arm), + value: converted, + type: expected, + loc: expr.loc, + }; + } + } + } // A width-coercible value against a union: coerce into the SINGLE // width-liftable arm, then wrap like any arm value (widthLiftPlan's // liftWrap — several candidate arms are ambiguous and decline). diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json new file mode 100644 index 000000000..91c268992 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-promise-function-arm-widening.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-promise-function-arm-widening.ts b/tests/corpus/union-promise-function-arm-widening.ts new file mode 100644 index 000000000..751af6f41 --- /dev/null +++ b/tests/corpus/union-promise-function-arm-widening.ts @@ -0,0 +1,68 @@ +// A promise or function value flowing into a union whose only arm of that +// kind has a wider payload or result: the value converts into that arm. +// Promises keep their identity under === and !== against the union. + +const chains = new Map>(); + +async function serialize(key: string, task: () => Promise): Promise { + const previous = chains.get(key) ?? Promise.resolve(); + const queued = (async () => { + await previous.catch(() => {}); + return task(); + })(); + const tail = queued.catch(() => {}); + chains.set(key, tail); + void tail.then(() => { + if (chains.get(key) === tail) chains.delete(key); + console.log(`settled ${key}: ${chains.get(key) !== tail}`); + }); + return queued; +} + +interface Store { + pending?: Promise; + lookup?: (name: string) => Promise; + scale?: (n: number) => number | string; +} + +async function findName(name: string): Promise { + return name.length > 3 ? name.toUpperCase() : null; +} + +function double(n: number): number { + return n * 2; +} + +async function failing(): Promise { + throw new Error("task failed"); +} + +async function main(): Promise { + const a = await serialize("alpha", async () => 1); + const b = await serialize("alpha", async () => "two"); + const c = await serialize("beta", async (): Promise => true); + console.log(a, b, c, chains.size); + try { + await serialize("alpha", failing); + } catch (e) { + console.log("caught", (e as Error).message); + } + for (let i = 0; i < 4; i++) await Promise.resolve(); + console.log("remaining", chains.size); + + const store: Store = { lookup: findName, scale: double }; + const work: Promise = Promise.resolve(41); + store.pending = work; + console.log(store.pending === work, store.pending !== work); + const other: Promise = Promise.resolve(41); + console.log(store.pending === other); + console.log(await store.pending); + + const lookup = store.lookup; + if (lookup) console.log(await lookup("abcd"), await lookup("ab")); + console.log(store.scale?.(21)); + const empty: Store = {}; + console.log(empty.pending === work, empty.lookup === undefined, empty.scale?.(1)); +} + +main(); diff --git a/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json b/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json new file mode 100644 index 000000000..5c3da345d --- /dev/null +++ b/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json @@ -0,0 +1 @@ +{ "plain": 14002, "san": 16102 } From 9cdbc83aee7e85428c44bff096663e16b94fd6b4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:07:59 -0700 Subject: [PATCH 241/395] Prefer the union arm that keeps every field of a converted record A record flowing into a union where several record arms could hold it by a width conversion was refused with SC2003 as ambiguous, even when one arm keeps every field of the record and the others hold it only by dropping fields (a model record whose option union is narrower than the catalog's, beside a classifier arm with a subset of its fields). That arm is the value's own layout, widened: it now wins when it is the only one. Plans that already resolved (a single candidate, the absent-field preference, discriminant and presence routes) are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../lowering/coercions/structural-plans.ts | 19 +++++++ .../corpus/union-record-lossless-arm.ts.json | 6 ++ tests/corpus/union-record-lossless-arm.ts | 55 +++++++++++++++++++ .../union-record-lossless-arm.ts.json | 1 + 4 files changed, 81 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json create mode 100644 tests/corpus/union-record-lossless-arm.ts create mode 100644 tests/harness/corpus-costs/union-record-lossless-arm.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 f42663a13..bff419d48 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts @@ -128,6 +128,25 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width const route = presenceRoute(lowerer, src.shapeId, selected[0]!, selected[1]!); if (route) return route; } + // 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. + 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; + } if (selected.length !== 1) return null; return { how: "liftWrap", tag: selected[0]!.tag, arm: selected[0]!.arm }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json new file mode 100644 index 000000000..143cda7d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-record-lossless-arm.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-record-lossless-arm.ts b/tests/corpus/union-record-lossless-arm.ts new file mode 100644 index 000000000..b90d5486e --- /dev/null +++ b/tests/corpus/union-record-lossless-arm.ts @@ -0,0 +1,55 @@ +// A record flowing into a union where several record arms can hold it by a +// width conversion: the arm that keeps every one of its fields is the +// value's own layout, widened, and wins over arms that drop fields. + +interface FastOpts { + fast?: boolean; + strict?: boolean; +} +interface DepthOpts { + depth?: number; +} +interface StrictOpts { + strict?: boolean; +} + +interface Wide { + id: string; + window: number; + kind?: string; + limit: number; + opts?: FastOpts | DepthOpts | StrictOpts; +} + +interface Judge { + id: string; + window: number; + kind: string; + opts?: StrictOpts; +} + +interface Narrow { + id: string; + window: number; + kind?: string; + limit: number; + opts?: FastOpts | DepthOpts; +} + +function show(entry: Wide | Judge): string { + const limit = "limit" in entry ? ` limit=${entry.limit}` : ""; + return `${entry.kind ?? "wide"} ${entry.id} window=${entry.window}${limit} ${JSON.stringify(entry.opts ?? null)}`; +} + +const narrow: Narrow = { id: "n1", window: 4, limit: 2, opts: { depth: 3 } }; +console.log(show(narrow)); + +const entries: (Wide | Judge)[] = [narrow, { id: "j1", window: 1, kind: "judge", opts: { strict: true } }]; +const plain: Narrow = { id: "n2", window: 8, kind: "custom", limit: 5 }; +entries.push(plain); +for (const entry of entries) console.log(show(entry)); + +let latest: Wide | Judge | undefined; +for (const item of [narrow, plain]) latest = item; +console.log(latest === undefined ? "none" : show(latest)); + diff --git a/tests/harness/corpus-costs/union-record-lossless-arm.ts.json b/tests/harness/corpus-costs/union-record-lossless-arm.ts.json new file mode 100644 index 000000000..011a8fbbf --- /dev/null +++ b/tests/harness/corpus-costs/union-record-lossless-arm.ts.json @@ -0,0 +1 @@ +{ "plain": 11809, "san": 13580 } From c46f70e451ba33c75183764b7797eafea6b8c6c3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:27:52 -0700 Subject: [PATCH 242/395] Fix wrong npm declarations locally with type patches A project can now patch a package's declarations in package.json: "scriptc": { "typePatches": { "@": { "declarations": { "": "" } } } }. A leading "|" widens the declared type, any other type replaces it. Selectors name declarations by path (Type.member, fn(), fn(param), list()[], Env[string], export=(), default, file.js:local()) and apply to the package's .d.ts, its JSDoc and its @types twin alike. Patches edit file text where every consumer reads it: the program host's source reads (both checker worlds) and the npm-static passes that read package files themselves. Only type nodes and JSDoc type tags change, and no edit adds or removes a line. Installed copies are selected by version; an @types twin by the runtime package's installed version. Patches that do not apply as written warn (SC8030-SC8033) and the warnings repeat on executable cache hits. Without the setting nothing changes. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 33 + packages/compiler/src/cli/command.ts | 23 +- .../compiler/src/diagnostics/diagnostic.ts | 23 +- packages/compiler/src/diagnostics/envelope.ts | 8 +- packages/compiler/src/diagnostics/render.ts | 17 +- .../frontend/npm-static-type-exports-host.ts | 7 +- packages/compiler/src/frontend/npm-static.ts | 9 +- packages/compiler/src/frontend/pipeline.ts | 12 +- packages/compiler/src/frontend/program.ts | 6 +- .../compiler/src/frontend/ts7/program-host.ts | 6 +- .../src/frontend/type-patches/layer.ts | 360 +++++++ .../src/frontend/type-patches/locate.test.ts | 201 ++++ .../src/frontend/type-patches/locate.ts | 936 ++++++++++++++++++ .../frontend/type-patches/selector.test.ts | 59 ++ .../src/frontend/type-patches/selector.ts | 133 +++ .../src/frontend/type-patches/semver.test.ts | 48 + .../src/frontend/type-patches/semver.ts | 229 +++++ packages/compiler/src/index.ts | 26 +- packages/compiler/src/project-config.test.ts | 77 ++ packages/compiler/src/project-config.ts | 164 ++- .../npm/cases/type-patch-dts/main.ts.json | 6 + .../cases/type-patch-types-twin/main.ts.json | 6 + packages/compiler/test/type-patches.test.ts | 124 +++ tests/fixtures/commander-calc/package.json | 10 + .../fixtures/npm/cases/type-patch-dts/main.ts | 15 + .../node_modules/profilekit/index.d.ts | 10 + .../node_modules/profilekit/index.js | 14 + .../node_modules/profilekit/package.json | 1 + .../npm/cases/type-patch-dts/package.json | 15 + .../npm/cases/type-patch-types-twin/main.ts | 11 + .../node_modules/@types/tallyjs/index.d.ts | 7 + .../node_modules/@types/tallyjs/package.json | 1 + .../node_modules/tallyjs/index.js | 11 + .../node_modules/tallyjs/package.json | 1 + .../cases/type-patch-types-twin/package.json | 15 + tests/fixtures/type-patches/jsdoc.ts | 11 + tests/fixtures/type-patches/ms.ts | 9 + .../type-patches/node_modules/ms/index.js | 162 +++ .../type-patches/node_modules/ms/license.md | 21 + .../type-patches/node_modules/ms/package.json | 38 + .../node_modules/termctx/index.js | 43 + .../node_modules/termctx/package.json | 1 + tests/fixtures/type-patches/package.json | 26 + tests/fixtures/type-patches/stale/main.ts | 4 + .../fixtures/type-patches/stale/package.json | 17 + tests/harness/type-patches.test.ts | 143 +++ 46 files changed, 3076 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/layer.ts create mode 100644 packages/compiler/src/frontend/type-patches/locate.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/locate.ts create mode 100644 packages/compiler/src/frontend/type-patches/selector.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/selector.ts create mode 100644 packages/compiler/src/frontend/type-patches/semver.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/semver.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json create mode 100644 packages/compiler/test/type-patches.test.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-dts/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/package.json create mode 100644 tests/fixtures/type-patches/jsdoc.ts create mode 100644 tests/fixtures/type-patches/ms.ts create mode 100644 tests/fixtures/type-patches/node_modules/ms/index.js create mode 100644 tests/fixtures/type-patches/node_modules/ms/license.md create mode 100644 tests/fixtures/type-patches/node_modules/ms/package.json create mode 100644 tests/fixtures/type-patches/node_modules/termctx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/termctx/package.json create mode 100644 tests/fixtures/type-patches/package.json create mode 100644 tests/fixtures/type-patches/stale/main.ts create mode 100644 tests/fixtures/type-patches/stale/package.json create mode 100644 tests/harness/type-patches.test.ts diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index a9c5ae138..ec1eb0601 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -141,6 +141,39 @@ CommonJS packages compile with Node.js's module semantics. Each `require()` eval A JavaScript package can contain operations scriptc can't compile, often in functions your program never calls. These don't fail the build: each one compiles to code that throws a runtime error if it ever runs. Coverage reports list them as deferred sites, so review the list and test the parts of the package your program uses. Sites in a module's top-level code are listed under `runs at startup`: that code runs when the module loads, at startup for a module the program imports. +## Fix a package's types + +scriptc trusts declared types: a package's `.d.ts` files, its `@types/` package, and the JSDoc types in its JavaScript. A value that contradicts its declared type throws a `TypeError` instead of flowing on, so a package whose declarations are wrong can stop a program that runs under Node.js. A common case is a member declared `string` or `number` that is sometimes `undefined`. + +Fix such a declaration in your project with a type patch in `package.json`: + +```json:package.json +{ + "scriptc": { + "typePatches": { + "commander@^15.0.0": { + "declarations": { + "HelpContext.helpWidth": "| undefined" + } + } + } + } +} +``` + +Each key names a package and a semver range of the installed versions the patch describes. `declarations` maps a declaration to a type: + +
    +
    `"| undefined"`
    +
    A type that starts with `|` widens the declared type, here to `number | undefined`.
    +
    `"string | null"`
    +
    Any other type replaces the declared type.
    +
    + +A declaration is named by its path: `Options.name` for a member of a type, `parse()` for a function's return type, `parse(text)` for a parameter, `list()[]` for an array's elements, and `Env[string]` for an index signature. `export=()` names the return type of the function the module exports, `default` its default export, and a file prefix such as `index.js:parse()` names a declaration inside one of the package's files. One patch applies to the package's own declarations, its JSDoc, and its `@types/` package. The patched type then holds everywhere, so your code has to handle the `undefined` that a widened declaration allows. An optional `"note"` string can link the upstream issue. + +Patches change only declarations, never the package's code, and values are still checked against the patched types. A build warns when a patch doesn't apply as written: when no range covers the installed version, when a declaration path matches nothing, when the declaration already has the type, or when a path is ambiguous. Report wrong declarations to the package too, so the patch can be removed later. + ## Provenance sources (experimental) `--provenance-sources` compiles a package from the source at the commit its npm provenance attestation names, instead of from its published JavaScript. This lets packages written in TypeScript compile from their original TypeScript. Packages without a usable attestation compile from their installed files as usual. diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 608b97b67..8b2e4c480 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -19,7 +19,12 @@ import { } from "../node-islands/policy.js"; import { TIER_MODES, type TierMode } from "../frontend/tier-analysis.js"; import { buildEnvelope } from "../diagnostics/envelope.js"; -import { renderConfigWarnings, renderWarnings } from "../diagnostics/render.js"; +import { + renderConfigWarnings, + renderTypePatchWarnings, + renderWarnings, +} from "../diagnostics/render.js"; +import { isTypePatchCode } from "../diagnostics/diagnostic.js"; import { isPerformanceCode, renderPerformanceWarnings } from "../diagnostics/dynamic-tier.js"; import { isNativeOptimization } from "../backend/optimization.js"; import { @@ -637,20 +642,30 @@ async function main(args: string[], host: CliHost): Promise { // keeps stderr for the program's own output. const color = process.stderr.isTTY ?? false; const config = warnings.filter((d) => d.code === "SC8004"); - const divergences = warnings.filter((d) => !isPerformanceCode(d.code) && d.code !== "SC8004"); + const patches = warnings.filter((d) => isTypePatchCode(d.code)); + const divergences = warnings.filter( + (d) => !isPerformanceCode(d.code) && d.code !== "SC8004" && !isTypePatchCode(d.code), + ); const performance = warnings.filter((d) => isPerformanceCode(d.code)); if (config.length > 0) process.stderr.write( renderConfigWarnings(config, result.sourceTexts ?? new Map(), { color }) + "\n", ); - if (divergences.length > 0) { + if (patches.length > 0) { if (config.length > 0) process.stderr.write("\n"); + process.stderr.write( + renderTypePatchWarnings(patches, result.sourceTexts ?? new Map(), { color }) + "\n", + ); + } + if (divergences.length > 0) { + if (config.length > 0 || patches.length > 0) process.stderr.write("\n"); process.stderr.write( renderWarnings(divergences, result.sourceTexts ?? new Map(), { color }) + "\n", ); } if (performance.length > 0) { - if (divergences.length > 0 || config.length > 0) process.stderr.write("\n"); + if (divergences.length > 0 || config.length > 0 || patches.length > 0) + process.stderr.write("\n"); process.stderr.write(renderPerformanceWarnings(performance) + "\n"); } } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 75827376e..8010cbb17 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -71,7 +71,9 @@ * package required to be native (SC8003) or every island * refused (SC8002) while outside the native subset, and the * R2 object-model constructs in user JavaScript (SC8101–SC8105, - * behind --strict-user-js) + * behind --strict-user-js); type patches from + * `scriptc.typePatches` that did not apply as written + * (SC8030–SC8033, warnings) * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; @@ -264,6 +266,25 @@ export function nodeIslandsOffModuleDiag( }; } +/** SC8030–SC8033 — a type patch (`scriptc.typePatches`) that did not + * apply as written: no range covers the installed copy (SC8030), its + * selector matches nothing (SC8031), the declaration already has the type + * (SC8032), or the selector is ambiguous or not patchable (SC8033). + * Warnings: the build goes on with the declarations it has. */ +export function typePatchDiag( + code: "SC8030" | "SC8031" | "SC8032" | "SC8033", + message: string, + hint: string, + loc: SrcLoc, +): ScrDiagnostic { + return { code, message, loc, hint }; +} + +/** True for the type patch warnings (SC8030–SC8033). */ +export function isTypePatchCode(code: string): boolean { + return /^SC803[0-3]$/.test(code); +} + /** SC8003 — a package marked "native" is outside the native subset. */ export function nativePackageDiag(pkg: string, why: string, loc: SrcLoc): ScrDiagnostic { return { diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index ad2e31507..bdb545ff3 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -15,7 +15,7 @@ * version; renaming or removing a field, or changing a field's meaning, * bumps it. */ import { readFileSync } from "node:fs"; -import type { ScrDiagnostic } from "./diagnostic.js"; +import { isTypePatchCode, type ScrDiagnostic } from "./diagnostic.js"; import { isPerformanceCode } from "./dynamic-tier.js"; import type { WasmModuleReport } from "../wasm/discover.js"; @@ -217,6 +217,7 @@ export function categoryOf(diag: ScrDiagnostic): DiagnosticCategory { if (code === "SC0002" || code === "SC0004") return "environment"; if (/^SC[35]\d{3}$/.test(code)) return "environment"; if (code === "SC8004" || code === "SC8005" || code === "SC8012") return "environment"; + if (isTypePatchCode(code)) return "environment"; if (/^SC9\d{3}$/.test(code)) return "internal"; return "unsupported"; } @@ -335,7 +336,10 @@ export function locateDiagnostic( code: diag.code, category: categoryOf(diag), severity: - isDivergenceCode(diag.code) || isPerformanceCode(diag.code) || diag.code === "SC8004" + isDivergenceCode(diag.code) || + isPerformanceCode(diag.code) || + diag.code === "SC8004" || + isTypePatchCode(diag.code) ? "warning" : "error", scope, diff --git a/packages/compiler/src/diagnostics/render.ts b/packages/compiler/src/diagnostics/render.ts index 16fe3f0bf..a370eb3b1 100644 --- a/packages/compiler/src/diagnostics/render.ts +++ b/packages/compiler/src/diagnostics/render.ts @@ -4,7 +4,7 @@ * longer than FRAME_MAX_WIDTH (minified sources) shows a window of that * width around the diagnostic's column, with "…" where text is cut. */ -import type { ScrDiagnostic } from "./diagnostic.js"; +import { isTypePatchCode, type ScrDiagnostic } from "./diagnostic.js"; export interface RenderOptions { color?: boolean; @@ -157,7 +157,20 @@ export function renderDiagnostics( * settings in the project's scriptc configuration (SC8004), which builds do * not use. */ export function isWarningCode(code: string): boolean { - return /^SC6\d{3}$/.test(code) || code === "SC8004"; + return /^SC6\d{3}$/.test(code) || code === "SC8004" || isTypePatchCode(code); +} + +/** A build's type patch warnings (SC8030–SC8033). */ +export function renderTypePatchWarnings( + diags: readonly ScrDiagnostic[], + sourceTextByFile: Map, + opts: RenderOptions = {}, +): string { + const n = diags.length; + return ( + renderDiagnostics([...diags], sourceTextByFile, opts) + + `\n\n${n} warning${n === 1 ? "" : "s"}: ${n === 1 ? "this type patch" : "these type patches"} did not apply as written; the build uses the package's own declarations there.` + ); } /** A build's SC8004 warnings: the unknown scriptc settings it ignored. */ diff --git a/packages/compiler/src/frontend/npm-static-type-exports-host.ts b/packages/compiler/src/frontend/npm-static-type-exports-host.ts index 8a61ed62b..f8aaa20f9 100644 --- a/packages/compiler/src/frontend/npm-static-type-exports-host.ts +++ b/packages/compiler/src/frontend/npm-static-type-exports-host.ts @@ -2,7 +2,8 @@ * package's declaration entry and runtime entry, copies the declaration * files the entry reaches, and builds the runtime entry's typedefs. */ import { dirname, relative, resolve as resolvePath } from "node:path"; -import { trackedFileExists, trackedReadFile } from "./input-tracker.js"; +import { trackedFileExists } from "./input-tracker.js"; +import { packageSourceText } from "./type-patches/layer.js"; import { resolveBareModule } from "./resolve.js"; import { isJsSourceFileName } from "./tsc-codes.js"; import { NPM_STATIC_TYPES_SUFFIX } from "./npm-static.js"; @@ -78,7 +79,7 @@ export function npmStaticTypeExportPlan( const parse = (file: string): ts.SourceFile | null => { let sf = parsed.get(file); if (sf === undefined) { - const text = trackedReadFile(file); + const text = packageSourceText(file); sf = text === null ? null : services.parse(file, text, "ts"); parsed.set(file, sf); } @@ -212,7 +213,7 @@ export function npmStaticTypeExportPlan( continue; const runtime = resolveBareModule(entryPath, pkg, "js-only"); if (runtime === null || !isJsSourceFileName(runtime.typesFile)) continue; - const runtimeText = trackedReadFile(runtime.typesFile); + const runtimeText = packageSourceText(runtime.typesFile); if (runtimeText === null) continue; const { types, values } = typesOf(declared.typesFile, new Set()); const declaredByRuntime = runtimeTypeNames(services, runtime.typesFile, runtimeText); diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index f51c6bbe2..ed442be79 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -23,6 +23,7 @@ import { trackedRealpath, } from "./input-tracker.js"; import { npmBuiltinAliasOf } from "./builtin-modules.js"; +import { packageSourceText } from "./type-patches/layer.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -82,7 +83,7 @@ export function setNpmStaticTypeExports( function withTypeExportTypedefs(path: string, text: string | null): string | null { const typedefs = typeExportTypedefs.get(path.split("\\").join("/")); if (typedefs === undefined) return text; - const source = text ?? trackedReadFile(path); + const source = text ?? packageSourceText(path); return source === null ? text : `${source}\n${typedefs}\n`; } @@ -467,7 +468,11 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow if (hit !== undefined) return hit ?? undefined; let rewritten: string | null = null; try { - let original = trackedReadFile(path); + // A project type patch (type-patches/layer.ts) edits the text + // as the package wrote it, before any rewrite reads its JSDoc + // (unrewritten files fall through to the host, which serves the + // patched text too). + let original = packageSourceText(path); // A bundle's CommonJS chunks move to their own modules first, so // every later pass (and this shadow, for each chunk) sees plain // CommonJS module text. diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index b5771dedd..585aca867 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -47,6 +47,7 @@ import { THREADS_MODULE } from "./threads-import.js"; import { erasedTypeOnlyImport } from "./type-only-imports.js"; import { EXPERIMENTAL_MODULE } from "./experimental-import.js"; import { readProjectConfig } from "../project-config.js"; +import { typePatchDiagnostics } from "./type-patches/layer.js"; import { packageDirOf } from "../node-islands/origin.js"; import { scanFiles, @@ -735,10 +736,14 @@ function loadFrontend( const projectConfig = readProjectConfig(entryPath); const configDiagnostics = projectConfig.diagnostics; const report = options.nodeIslands === true; + // Type patches that did not apply as written (SC8030–SC8033) warn; the + // build goes on with the packages' own declarations there. + const patchDiagnostics = typePatchDiagnostics()?.diagnostics ?? []; + const buildWarnings = [...(report ? [] : configDiagnostics), ...patchDiagnostics]; return { preflight: report && configDiagnostics.length > 0 ? [...configDiagnostics, ...preflight] : preflight, - ...(!report && configDiagnostics.length > 0 ? { warnings: configDiagnostics } : {}), + ...(buildWarnings.length > 0 ? { warnings: buildWarnings } : {}), entryText: () => finalLoad.entry.text, entryExports: () => entryFunctionExports(finalLoad.entry), // The contract scans the PROGRAM's source files, not the runtime @@ -773,9 +778,10 @@ function loadFrontend( ...finalLoad.program.getImplementationSourceFiles().filter((sf) => !sf.isDeclarationFile), ].map((sf) => [sf.fileName, sf.text]), ); - // SC8004 diagnostics point into the project's package.json. + // SC8004 and type patch diagnostics point into the project's + // package.json. if ( - configDiagnostics.length > 0 && + (configDiagnostics.length > 0 || patchDiagnostics.length > 0) && projectConfig.manifest !== null && projectConfig.manifestText !== null ) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 00afcd696..b93e3f217 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -140,6 +140,7 @@ import { } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; +import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { @@ -477,7 +478,7 @@ function visitNpmStaticDeclarationClosure( if (normalized !== packageRoot && !normalized.startsWith(packageRoot + "/")) return; if (seen.has(path)) return; seen.add(path); - const source = trackedReadFile(path); + const source = packageSourceText(path); if (source === null) return; onSource(path, source); for (const specifier of npmStaticDeclarationReexports(services.parse(path, source, "ts"))) { @@ -1068,6 +1069,9 @@ export function loadProgram( // JavaScript, even when named. A registry entry's `sourceVersions` // (chalk 6) compile from their own source. configurePackageShims(entryPath); + // The project's type patches (type-patches/layer.ts), read by every host + // this load creates; nothing at all without `scriptc.typePatches`. + configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); const npmStaticPackages = [...new Set(opts?.npmStatic ?? [])].filter( (name) => !npmStaticOmitsPackage(entryPath, name), ); diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 84cbe2474..9e8f51bb3 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -46,6 +46,7 @@ import { trackedRealpath, } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; +import { typePatchedText } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; @@ -160,7 +161,7 @@ export class Ts7Host { const virtualFiles = this.virtualFiles; const shadow = options?.fsShadow ?? null; // A virtual file, a shadow's replacement, a package shim's declaration - // entry or the file on disk; null when shadowed out of existence or + // entry, a type-patched package file or the file on disk; null when shadowed out of existence or // missing. this.readSource = (fileName) => { const virtual = virtualFiles.get(tsgoPath(fileName)); @@ -175,6 +176,9 @@ export class Ts7Host { // program the host creates. const shim = packageShimDeclarationText(fileName); if (shim !== undefined) return shim; + // A package file a project type patch edits (type-patches/layer.ts). + const patched = typePatchedText(fileName); + if (patched !== undefined) return stripSourceBom(patched); const source = trackedReadFile(fileName); return source === null ? null : stripSourceBom(source); }; diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts new file mode 100644 index 000000000..48ef6ecf0 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -0,0 +1,360 @@ +/* Type patches: project-owned fixes for wrong npm declarations. + * + * `scriptc.typePatches` in the entry's package.json maps + * `"@"` to declaration patches (project-config.ts): + * + * "commander@^15.0.0": { "declarations": { "HelpContext.helpWidth": "| undefined" } } + * + * The patches are applied to file TEXT, in one place every consumer reads + * through: the program host's source reads (ts7/program-host.ts), which + * serve both checker worlds (the lowering world that types package + * JavaScript from its JSDoc, and the declaration world that types user + * code at the declaration boundary), and the npm-static passes that read + * package files themselves (`packageSourceText`). Downstream, the patched + * declaration simply IS the declaration: scriptc enforces it exactly as it + * enforced the original, so a patch can never make a check weaker than the + * declaration it replaces. + * + * A patch applies to an installed copy whose version satisfies its range; + * an `@types/` twin is matched by the runtime package's name and + * installed version. Without patches nothing here runs: `typePatchedText` + * answers undefined from one null test, so every program reads the same + * bytes as before (the zero-cost rule). + * + * See scriptc-perf/results/design-type-patches/DESIGN.md. */ + +import { dirname, relative } from "node:path"; +import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import { typePatchDiag } from "../../diagnostics/diagnostic.js"; +import { readProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import { trackedReadFile } from "../input-tracker.js"; +import type { SourceFile } from "../ts7/syntax.js"; +import { applyPatchEdits, patchFile, type PatchMatch, type PatchRequest } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { satisfies } from "./semver.js"; + +export type TypePatchParse = (path: string, text: string, kind: "ts" | "js") => SourceFile; + +interface Request extends PatchRequest { + entry: TypePatchEntry; + /** Offset of the selector in the manifest. */ + offset: number; +} + +interface CopyInfo { + /** The runtime package's name (for an `@types` twin, the package it + * types). */ + pkg: string; + version: string | null; + root: string; + /** Requests whose entry's range the version satisfies. */ + requests: Request[]; +} + +interface MatchRecord extends PatchMatch { + file: string; + pkg: string; + version: string | null; +} + +interface State { + manifest: string; + manifestText: string; + parse: TypePatchParse; + byPackage: Map; + copies: Map; + files: Map; + /** Package copies whose version no patch range covers. */ + unmatchedCopies: Map; + /** Requests applicable to a copy this load read files of. */ + touched: Set; + matches: MatchRecord[]; + problems: Map; + /** Patched spans per served file (patched-text coordinates). */ + spans: Map; +} + +let state: State | null = null; + +/** Registers the project's type patches for one program load (or clears + * them). Called once per load, like configurePackageShims. */ +export function configureTypePatches(entryPath: string, parse: TypePatchParse): void { + const config = readProjectConfig(entryPath); + if (config.typePatches.length === 0 || config.manifest === null) { + state = null; + return; + } + const byPackage = new Map(); + let id = 0; + for (const entry of config.typePatches) { + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector); + const op = parseTypeText(decl.type); + if ("error" in selector || "error" in op) continue; // reported as SC8004 + const list = byPackage.get(entry.pkg) ?? []; + list.push({ + id: id++, + selector: selector as Selector, + op: op as TypePatchOp, + entry, + offset: decl.offset, + }); + byPackage.set(entry.pkg, list); + } + } + state = { + manifest: config.manifest, + manifestText: config.manifestText ?? "", + parse, + byPackage, + copies: new Map(), + files: new Map(), + unmatchedCopies: new Map(), + touched: new Set(), + matches: [], + problems: new Map(), + spans: new Map(), + }; +} + +/** True while the current load has type patches. */ +export function typePatchesActive(): boolean { + return state !== null; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +/** `@types/foo__bar` types `@foo/bar`. */ +function unmangleTypesName(name: string): string { + const bare = name.slice("@types/".length); + const i = bare.indexOf("__"); + return i > 0 ? `@${bare.slice(0, i)}/${bare.slice(i + 2)}` : bare; +} + +function versionAt(dir: string): string | null { + const text = trackedReadFile(`${dir}/package.json`); + if (text === null) return null; + try { + const parsed = JSON.parse(text) as { version?: unknown }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +/** The installed package copy a file belongs to, with the requests that + * apply to it; null for files outside node_modules or of unpatched + * packages. */ +function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null { + const marker = "/node_modules/"; + const at = path.lastIndexOf(marker); + if (at < 0) return null; + const after = path.slice(at + marker.length); + const parts = after.split("/"); + const nameParts = parts[0]!.startsWith("@") ? 2 : 1; + if (parts.length <= nameParts) return null; + const name = parts.slice(0, nameParts).join("/"); + const root = path.slice(0, at + marker.length) + name; + const rel = parts.slice(nameParts).join("/"); + let copy = s.copies.get(root); + if (copy === undefined) { + copy = null; + const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; + const requests = s.byPackage.get(pkg); + if (requests !== undefined) { + // A twin describes the runtime package installed beside it. + const version = name.startsWith("@types/") + ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) + : versionAt(root); + const applicable = requests.filter( + (r) => version !== null && satisfies(version, r.entry.range), + ); + // Later entries win on the same selector. + const bySelector = new Map(); + for (const r of applicable) bySelector.set(r.selector.text, r); + const chosen = [...bySelector.values()]; + if (chosen.length === 0) { + const entries = [...new Set(requests.map((r) => r.entry))]; + s.unmatchedCopies.set(root, { pkg, version, entries }); + } + copy = chosen.length === 0 ? null : { pkg, version, root, requests: chosen }; + } + s.copies.set(root, copy); + } + if (copy === null) return null; + for (const r of copy.requests) s.touched.add(r); + return { copy, rel }; +} + +/** Names a request's root can appear under in a file's text. */ +function rootWords(sel: Selector): string[] { + if (sel.root === "export=") return ["exports", "export"]; + if (sel.root === "default") return ["default"]; + return [sel.root]; +} + +/** The patched text of a package file, or undefined when no patch applies + * to it (callers then read the file as usual). */ +export function typePatchedText(fileName: string): string | undefined { + const s = state; + if (s === null) return undefined; + const path = fileName.split("\\").join("/"); + if (!PATCHABLE.test(path)) return undefined; + const cached = s.files.get(path); + if (cached !== undefined) return cached ?? undefined; + let result: string | null = null; + const located = copyOf(s, path); + if (located !== null) { + const text = trackedReadFile(path); + const requests = located.copy.requests.filter( + (r) => + (r.selector.file === null || r.selector.file === located.rel) && + rootWords(r.selector).some((w) => text?.includes(w)), + ); + if (text !== null && requests.length > 0) { + const kind = /\.[cm]?ts$/.test(path) ? "ts" : "js"; + let out: ReturnType | null = null; + try { + out = patchFile(located.rel, text, s.parse(path, text, kind), requests); + } catch { + out = null; // unparseable: served unpatched, like other npm-static passes + } + if (out !== null) { + const byId = new Map(requests.map((r) => [r.id, r])); + for (const m of out.matches) { + s.matches.push({ + ...m, + file: path, + pkg: located.copy.pkg, + version: located.copy.version, + }); + } + for (const p of out.problems) { + const r = byId.get(p.id); + const prior = r === undefined ? undefined : s.problems.get(r); + if (r !== undefined && (prior === undefined || (p.always === true && !prior.always))) + s.problems.set(r, { + message: `${p.message} (${located.rel})`, + always: p.always === true, + }); + } + if (out.edits.length > 0) { + result = applyPatchEdits(text, out.edits); + s.spans.set(path, patchedSpans(out.edits)); + } + } + } + } + s.files.set(path, result); + return result ?? undefined; +} + +/** Edit spans in the patched text's coordinates. */ +function patchedSpans( + edits: readonly { start: number; end: number; text: string }[], +): { start: number; end: number }[] { + const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end); + const out: { start: number; end: number }[] = []; + let shift = 0; + for (const e of sorted) { + const start = e.start + shift; + out.push({ start, end: start + e.text.length }); + shift += e.text.length - (e.end - e.start); + } + return out; +} + +/** Text a package file is read as by passes that read package files + * themselves (npm-static): the patched text, or the file. */ +export function packageSourceText(path: string): string | null { + return typePatchedText(path) ?? trackedReadFile(path); +} + +/** The spans a type patch edited in a served file (patched-text offsets), + * for passes that rewrite package JSDoc and must keep patched comments + * (the shape overlay). Empty when nothing was patched. */ +export function typePatchedSpans(fileName: string): readonly { start: number; end: number }[] { + return state?.spans.get(fileName.split("\\").join("/")) ?? []; +} + +/** Every applied match of the current load (for `scriptc types patch + * --list` and tests). */ +export function typePatchMatches(): readonly MatchRecord[] { + return state?.matches ?? []; +} + +function loc(s: State, offset: number): { file: string; start: number; end: number } { + return { file: s.manifest, start: offset, end: offset }; +} + +/** The current load's type patch warnings: SC8030 (no range covers an + * installed copy), SC8031 (a selector matched nothing), SC8032 (redundant: + * the declaration already has the type), SC8033 (ambiguous or not + * patchable as written). Only packages the program loads are reported. */ +export function typePatchDiagnostics(): { + diagnostics: ScrDiagnostic[]; + manifest: string; + manifestText: string; +} | null { + const s = state; + if (s === null) return null; + const out: ScrDiagnostic[] = []; + for (const [root, info] of s.unmatchedCopies) { + const shown = info.version ?? "an unknown version"; + for (const entry of info.entries) { + out.push( + typePatchDiag( + "SC8030", + `type patch '${entry.key}' does not apply to ${info.pkg} ${shown} at ${relativeToManifest(s, root)}; its declarations are used unpatched`, + info.version === null + ? "the installed package has no version" + : `check whether the declarations of ${info.pkg} ${info.version} still need the patch, then widen the range or remove it`, + loc(s, entry.offset), + ), + ); + } + } + for (const r of s.touched) { + const mine = s.matches.filter((m) => m.id === r.id); + // A selector that matched somewhere (the .d.ts) is not a problem where + // it cannot apply (undocumented JavaScript), unless a declaration it + // names was left unpatched. + const problem = s.problems.get(r); + if (problem !== undefined && (mine.length === 0 || problem.always)) { + out.push( + typePatchDiag( + "SC8033", + `type patch '${r.entry.key}': ${problem.message}`, + "name one declaration, or patch the declared type it refers to", + loc(s, r.offset), + ), + ); + continue; + } + if (mine.length === 0) { + out.push( + typePatchDiag( + "SC8031", + `type patch '${r.entry.key}': '${r.selector.text}' matches no declaration in the files of ${r.entry.pkg} this program loads`, + "check the spelling against the package's declarations and JSDoc; the package may have renamed it", + loc(s, r.offset), + ), + ); + continue; + } + if (mine.every((m) => m.redundant)) + out.push( + typePatchDiag( + "SC8032", + `type patch '${r.entry.key}': '${r.selector.text}' is already declared as ${mine[0]!.before}`, + "the package's declarations already say this; remove the patch", + loc(s, r.offset), + ), + ); + } + return { diagnostics: out, manifest: s.manifest, manifestText: s.manifestText }; +} + +function relativeToManifest(s: State, path: string): string { + return relative(dirname(s.manifest), path).split("\\").join("/"); +} diff --git a/packages/compiler/src/frontend/type-patches/locate.test.ts b/packages/compiler/src/frontend/type-patches/locate.test.ts new file mode 100644 index 000000000..d43695d9a --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/locate.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, test } from "vitest"; +import { nodeFrontendServices } from "../services-node.js"; +import { applyPatchEdits, patchFile, type PatchRequest } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; + +function patch(file: string, text: string, patches: Record) { + const requests: PatchRequest[] = Object.entries(patches).map(([sel, type], id) => ({ + id, + selector: parseSelector(sel) as Selector, + op: parseTypeText(type) as TypePatchOp, + })); + const kind = /\.[cm]?ts$/.test(file) ? "ts" : "js"; + const sf = nodeFrontendServices().parse(`/pkg/${file}`, text, kind); + const result = patchFile(file, text, sf, requests); + const out = applyPatchEdits(text, result.edits); + expect(out.split("\n").length).toBe(text.split("\n").length); + return { ...result, text: out }; +} + +const COMMANDER_LIKE = `class Command { + /** + * @typedef HelpContext + * @type {object} + * @property {boolean} error + * @property {number} helpWidth + * @property {function} write - includes stripColor if needed + * + * @returns {HelpContext} + * @private + */ + _getOutputContext(contextOptions) { + let helpWidth; + return { error: false, write: () => {}, helpWidth }; + } +} +module.exports = { Command }; +`; + +const MS_LIKE = `/** + * @param {String|Number} val + * @param {Object} [options] + * @return {String|Number} + */ +module.exports = function (val, options) { + return parse(val); +}; + +/** + * @param {String} str + * @return {Number} + */ +function parse(str) { + if (str.length > 100) { + return; + } + return 1; +} +`; + +describe("JSDoc", () => { + test("a @typedef property widens", () => { + const r = patch("lib/command.js", COMMANDER_LIKE, { "HelpContext.helpWidth": "| undefined" }); + expect(r.text).toContain("@property {number | undefined} helpWidth"); + expect(r.text).toContain("@returns {HelpContext}"); + expect(r.matches).toMatchObject([ + { line: 6, before: "number", after: "number | undefined", redundant: false }, + ]); + }); + + test("returns of export= and of a file-local function widen", () => { + const r = patch("index.js", MS_LIKE, { + "export=()": "| undefined", + "index.js:parse()": "| undefined", + }); + expect(r.text).toContain("@return {String|Number | undefined}\n */\nmodule.exports"); + expect(r.text).toContain("@return {Number | undefined}\n */\nfunction parse"); + expect(r.problems).toEqual([]); + }); + + test("a file prefix restricts the root to that file", () => { + const r = patch("other.js", MS_LIKE, { "index.js:parse()": "| undefined" }); + expect(r.matches).toEqual([]); + expect(r.text).toBe(MS_LIKE); + }); + + test("parameters, optional parameters and redundancy", () => { + const r = patch("index.js", MS_LIKE, { + "export=(options)": "| undefined", + "parse(str)": "string | undefined", + }); + expect(r.matches[0]).toMatchObject({ redundant: true }); + expect(r.text).toContain("@param {string | undefined} str"); + }); + + test("a replacement on an undocumented declaration inserts a tag", () => { + const text = + "/** Adds. */\nfunction add(a, b) { return a + b; }\nfunction sub(a, b) { return a - b; }\n"; + const r = patch("index.js", text, { "add()": "number", "sub(a)": "number" }); + expect(r.text).toBe( + "/** Adds. @returns {number} */\nfunction add(a, b) { return a + b; }\n/** @param {number} a */ function sub(a, b) { return a - b; }\n", + ); + }); + + test("a widening needs a written type", () => { + const r = patch("index.js", "function f() { return 1; }\n", { "f()": "| undefined" }); + expect(r.problems[0]!.message).toContain("no written type to widen"); + }); + + test("members of exported objects, classes and constructor assignments", () => { + const text = `class Box { + constructor() { + /** @type {string} */ + this.label = ""; + } + /** @returns {number} */ + size() { return 1; } +} +module.exports = { + /** @returns {string} */ + name() { return ""; }, + Box, +}; +`; + const r = patch("index.js", text, { + "Box.label": "| undefined", + "Box.size()": "| null", + "export=.name()": "| undefined", + }); + expect(r.text).toContain("@type {string | undefined} */\n this.label"); + expect(r.text).toContain("@returns {number | null} */\n size()"); + expect(r.text).toContain("@returns {string | undefined} */\n name()"); + }); + + test("function types are parenthesized before widening", () => { + const r = patch("index.js", "/** @type {(a: string) => void} */\nvar cb = null;\n", { + cb: "| undefined", + }); + expect(r.text).toContain("@type {((a: string) => void) | undefined}"); + }); +}); + +describe("declaration files", () => { + const DTS = `export interface Options { + name: string; + retries?: number; + env: Env; +} +export interface Env { + [key: string]: string; +} +export declare function fetchUser(id: string): Promise<{ email: string; id: string }>; +export declare function load(): User; +interface User { name: string; tags: string[] } +declare function ms(value: string): number; +declare function ms(value: number): string; +declare namespace ms { const version: string; } +export = ms; +`; + + test("members, optional members, index signatures and navigation", () => { + const r = patch("index.d.ts", DTS, { + "Options.name": "| undefined", + "Options.retries": "| undefined", + "Env[string]": "| undefined", + "fetchUser().email": "string | null", + "load().tags[]": "| undefined", + "Options.env[string]": "| null", + }); + expect(r.text).toContain(" name: string | undefined;"); + expect(r.text).toContain("[key: string]: string | undefined | null;"); + expect(r.text).toContain("Promise<{ email: string | null; id: string }>"); + expect(r.text).toContain("tags: (string | undefined)[]"); + expect(r.matches.find((m) => m.id === 1)).toMatchObject({ redundant: true }); + }); + + test("export= widens every overload, and a replacement of several is refused", () => { + const r = patch("index.d.ts", DTS, { "export=()": "| undefined" }); + expect(r.text).toContain("declare function ms(value: string): number | undefined;"); + expect(r.text).toContain("declare function ms(value: number): string | undefined;"); + const refused = patch("index.d.ts", DTS, { "export=()": "number" }); + expect(refused.problems[0]!.message).toContain("matches 2 declarations"); + expect(refused.text).toBe(DTS); + }); + + test("namespace members and generic types", () => { + const r = patch("index.d.ts", DTS, { "ms.version": "| undefined" }); + expect(r.text).toContain("const version: string | undefined;"); + const generic = patch( + "index.d.ts", + "export declare function f(): Map;\n", + { "f().a": "| undefined" }, + ); + expect(generic.problems[0]!.message).toContain("cannot follow"); + }); + + test("multi-line types keep their line count when replaced", () => { + const text = "export interface A {\n b: {\n c: number;\n };\n}\n"; + const r = patch("index.d.ts", text, { "A.b": "string" }); + expect(r.text.startsWith("export interface A {\n b: string\n\n;")).toBe(true); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/locate.ts b/packages/compiler/src/frontend/type-patches/locate.ts new file mode 100644 index 000000000..fc85a1634 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/locate.ts @@ -0,0 +1,936 @@ +/* Finds the declarations a type patch selector names in one package file + * and computes the text edits that apply the patch. + * + * Declaration files (and TypeScript sources) are matched on their syntax + * tree: the edit replaces or widens a written type node. JavaScript is + * matched on its JSDoc: the edit replaces or widens the `{...}` of a + * `@typedef`'s `@property`, a `@returns`, a `@param` or a `@type` tag. + * Executable code is never edited, and no edit adds or removes a line + * break, so every later pass and diagnostic keeps its line. */ + +import * as ts from "../ts7/syntax.js"; +import { tokenStartOfNode } from "../ts7/node-functions.js"; +import { + normalizeType, + splitUnion, + type Selector, + type SelectorSegment, + type TypePatchOp, +} from "./selector.js"; + +export interface PatchRequest { + /** Caller's id, echoed in matches and problems. */ + id: number; + selector: Selector; + op: TypePatchOp; +} + +export interface PatchMatch { + id: number; + /** 1-based line of the patched type. */ + line: number; + before: string; + after: string; + /** The declaration already has the patched type: nothing was edited. */ + redundant: boolean; +} + +export interface PatchProblem { + id: number; + message: string; + /** Reported even when the selector matched elsewhere (an ambiguous + * replacement left a declaration unpatched). */ + always?: true; +} + +export interface PatchEdit { + start: number; + end: number; + text: string; +} + +export interface FilePatchResult { + edits: PatchEdit[]; + matches: PatchMatch[]; + problems: PatchProblem[]; +} + +/** A place a type is (or can be) written. */ +interface Slot { + /** The written type's span, or null when there is none. */ + span: { start: number; end: number } | null; + /** The declaration is optional (`x?:`, `[x]`), so it already admits + * undefined. */ + optional: boolean; + /** Where a replacement goes when nothing is written: the inserted text + * is `prefix + type + suffix`. */ + insert: { at: number; prefix: string; suffix: string } | null; + /** A JSDoc slot inside a comment (one-line edits only). */ + jsdoc: boolean; + /** The type is an array element (`T[]`): a union must be wrapped. */ + wrap?: boolean; +} + +type Step = { slots: Slot[] } | { problem: string }; + +/** Type texts that need parentheses before `| T` is appended. */ +function needsParens(text: string): boolean { + return /=>|\?|\bextends\b|^\s*new\b|^\s*function\b|^\s*!/.test(text); +} + +function lineOf(text: string, pos: number): number { + let line = 1; + for (let i = 0; i < pos && i < text.length; i++) if (text.charCodeAt(i) === 10) line++; + return line; +} + +function editsFor( + text: string, + req: PatchRequest, + slot: Slot, +): { edits: PatchEdit[]; match: PatchMatch } | { problem: string } { + const op = req.op; + if (slot.span === null) { + if (op.kind === "widen" || slot.insert === null) + return { + problem: `'${req.selector.text}' has no written type to widen; give the whole type instead`, + }; + const ins = slot.insert; + return { + edits: [{ start: ins.at, end: ins.at, text: ins.prefix + op.text + ins.suffix }], + match: { + id: req.id, + line: lineOf(text, ins.at), + before: "(none)", + after: op.text, + redundant: false, + }, + }; + } + const { start, end } = slot.span; + const original = text.slice(start, end); + const line = lineOf(text, start); + if (op.kind === "widen") { + const present = new Set(splitUnion(original).map(normalizeType)); + if (slot.optional) present.add("undefined"); + const add = op.arms.filter((a) => !present.has(normalizeType(a))); + if (add.length === 0) + return { + edits: [], + match: { id: req.id, line, before: original, after: original, redundant: true }, + }; + // `(() => void) | undefined`; an array element wraps the whole union: + // `(string | undefined)[]`. + const inner = needsParens(original); + const outer = slot.wrap === true; + const suffix = ` | ${add.join(" | ")}`; + const open = (outer ? "(" : "") + (inner ? "(" : ""); + const close = (inner ? ")" : "") + suffix + (outer ? ")" : ""); + const edits: PatchEdit[] = + open === "" + ? [{ start: end, end, text: close }] + : [ + { start, end: start, text: open }, + { start: end, end, text: close }, + ]; + const after = `${open}${original}${close}`; + return { edits, match: { id: req.id, line, before: original, after, redundant: false } }; + } + if (normalizeType(original) === normalizeType(op.text)) + return { + edits: [], + match: { id: req.id, line, before: original, after: original, redundant: true }, + }; + const breaks = original.split("\n").length - 1; + if (breaks > 0 && slot.jsdoc) + return { + problem: `'${req.selector.text}' names a JSDoc type that spans lines; replace it in one line upstream or widen it`, + }; + const replacement = + slot.wrap === true && (splitUnion(op.text).length > 1 || needsParens(op.text)) + ? `(${op.text})` + : op.text; + return { + edits: [{ start, end, text: replacement + "\n".repeat(breaks) }], + match: { id: req.id, line, before: original, after: replacement, redundant: false }, + }; +} + +/** Applies a file's requests. `sf` is the file's syntax tree, parsed as + * TypeScript for declaration files and TypeScript sources and as + * JavaScript otherwise. */ +export function patchFile( + relPath: string, + text: string, + sf: ts.SourceFile, + requests: readonly PatchRequest[], +): FilePatchResult { + const js = !/\.(d\.)?[mc]?ts$/.test(relPath); + const resolver = js ? new JsResolver(text, sf) : new TsResolver(text, sf); + const result: FilePatchResult = { edits: [], matches: [], problems: [] }; + for (const req of requests) { + if (req.selector.file !== null && req.selector.file !== relPath) continue; + const step = resolver.resolve(req.selector); + if ("problem" in step) { + result.problems.push({ id: req.id, message: step.problem }); + continue; + } + if (step.slots.length === 0) continue; + if (req.op.kind === "replace" && step.slots.length > 1) { + result.problems.push({ + id: req.id, + message: `'${req.selector.text}' matches ${step.slots.length} declarations in ${relPath}; a replacement must name one (widen them, or replace the declaration file)`, + always: true, + }); + continue; + } + for (const slot of step.slots) { + const out = editsFor(text, req, slot); + if ("problem" in out) { + result.problems.push({ id: req.id, message: out.problem }); + continue; + } + result.edits.push(...out.edits); + result.matches.push(out.match); + } + } + return result; +} + +/** Applies edits (sorted by position; at one position, in request order). + * An edit overlapping an earlier replacement is dropped. */ +export function applyPatchEdits(text: string, edits: readonly PatchEdit[]): string { + const sorted = edits + .map((e, i) => ({ e, i })) + .sort((a, b) => a.e.start - b.e.start || a.e.end - b.e.end || a.i - b.i) + .map((x) => x.e); + let out = ""; + let at = 0; + for (const e of sorted) { + if (e.start < at) continue; + out += text.slice(at, e.start) + e.text; + at = e.end; + } + return out + text.slice(at); +} + +function nameText(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isPrivateIdentifier(name)) + return name.text; + if (ts.isNumericLiteral(name)) return name.text; + return null; +} + +function hasDefaultModifier(node: ts.Node): boolean { + return (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.DefaultKeyword); +} + +/* ── declaration files ─────────────────────────────────────────────── */ + +type TsPos = { node: ts.Node; optional: boolean; element?: boolean }; + +class TsResolver { + /** Top-level declarations by name, ambient module bodies included. */ + private readonly roots = new Map(); + private exportEquals: string | null = null; + private exportDefault: ts.Node[] = []; + + constructor( + private readonly text: string, + sf: ts.SourceFile, + ) { + this.collect(sf.statements.nodes, true); + } + + private collect(statements: readonly ts.Statement[], top: boolean): void { + for (const s of statements) { + if (ts.isExportAssignment(s)) { + if (ts.isIdentifier(s.expression)) { + if (s.isExportEquals) this.exportEquals = s.expression.text; + else this.exportDefault.push(...this.rootsNamed(s.expression.text)); + } + continue; + } + if (ts.isModuleDeclaration(s) && ts.isStringLiteral(s.name)) { + // `declare module "pkg" { ... }`: its statements are the module's. + if (top && s.body !== undefined && ts.isModuleBlock(s.body)) + this.collect(s.body.statements.nodes, false); + continue; + } + if (ts.isVariableStatement(s)) { + for (const d of s.declarationList.declarations.nodes) this.add(nameText(d.name), d); + continue; + } + const named = + ts.isFunctionDeclaration(s) || + ts.isClassDeclaration(s) || + ts.isInterfaceDeclaration(s) || + ts.isTypeAliasDeclaration(s) || + ts.isModuleDeclaration(s) || + ts.isEnumDeclaration(s); + if (!named) continue; + this.add(nameText((s as ts.FunctionDeclaration).name), s); + if ((ts.isFunctionDeclaration(s) || ts.isClassDeclaration(s)) && hasDefaultModifier(s)) + this.exportDefault.push(s); + } + } + + private add(name: string | null, node: ts.Node): void { + if (name === null) return; + const list = this.roots.get(name); + if (list === undefined) this.roots.set(name, [node]); + else list.push(node); + } + + private rootsNamed(name: string): ts.Node[] { + return this.roots.get(name) ?? []; + } + + resolve(sel: Selector): Step { + let start: ts.Node[]; + if (sel.root === "export=") + start = this.exportEquals === null ? [] : this.rootsNamed(this.exportEquals); + else if (sel.root === "default") start = this.exportDefault; + else start = this.rootsNamed(sel.root); + let positions: TsPos[] = start.map((node) => ({ node, optional: false })); + // A position that cannot continue is a problem only when no other + // position of the same name matches (a function merged with a + // namespace, an export and its declaration). + let problem: string | null = null; + for (const seg of sel.segments) { + const next: TsPos[] = []; + for (const pos of positions) { + const step = this.step(pos, seg); + if (typeof step === "string") problem ??= step; + else next.push(...step); + } + positions = next; + if (positions.length === 0) + return problem === null ? { slots: [] } : { problem: `'${sel.text}' ${problem}` }; + } + const slots: Slot[] = []; + for (const pos of positions) { + const slot = this.slotOf(pos); + if (typeof slot === "string") problem ??= slot; + else slots.push(slot); + } + if (slots.length === 0 && problem !== null) return { problem: `'${sel.text}' ${problem}` }; + return { slots }; + } + + private typeSlot(type: ts.TypeNode | undefined, optional: boolean): Slot { + return { + span: type === undefined ? null : { start: tokenStartOfNode(type), end: type.end }, + optional, + insert: null, + jsdoc: false, + }; + } + + private slotOf(pos: TsPos): Slot | string { + const n = pos.node; + if (ts.isTypeNode(n)) return { ...this.typeSlot(n, pos.optional), wrap: pos.element === true }; + if (ts.isPropertySignature(n) || ts.isPropertyDeclaration(n)) + return this.typeSlot( + n.type, + pos.optional || n.postfixToken?.kind === ts.SyntaxKind.QuestionToken, + ); + if ( + ts.isVariableDeclaration(n) || + ts.isTypeAliasDeclaration(n) || + ts.isIndexSignatureDeclaration(n) + ) + return this.typeSlot(n.type, false); + if (ts.isParameter(n)) + return this.typeSlot(n.type, n.questionToken !== undefined || n.initializer !== undefined); + if (ts.isGetAccessor(n)) return this.typeSlot(n.type, false); + if (this.isFunctionLike(n)) + return "names a function; add '()' for its return type or '(name)' for a parameter"; + return "names a declaration without a type of its own; name one of its members"; + } + + private isFunctionLike(n: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(n) || + ts.isMethodSignatureDeclaration(n) || + ts.isMethodDeclaration(n) || + ts.isCallSignatureDeclaration(n) || + ts.isFunctionTypeNode(n) + ); + } + + /** The type a position stands for when a segment navigates into it. */ + private typeOf(n: ts.Node): ts.TypeNode | undefined { + if ( + ts.isPropertySignature(n) || + ts.isPropertyDeclaration(n) || + ts.isVariableDeclaration(n) || + ts.isTypeAliasDeclaration(n) || + ts.isParameter(n) + ) + return n.type; + return undefined; + } + + /** Object-like members a type node holds, following parentheses, unions, + * intersections, `Promise` and references to declarations in this + * file. Null when it passes through a type it cannot follow. */ + private containers(type: ts.TypeNode, depth = 0): ts.Node[] | null { + if (depth > 8) return null; + if (ts.isTypeLiteralNode(type)) return [type]; + if (ts.isParenthesizedTypeNode(type)) return this.containers(type.type, depth + 1); + if (ts.isUnionTypeNode(type) || ts.isIntersectionTypeNode(type)) { + const out: ts.Node[] = []; + for (const arm of type.types.nodes) { + if (arm.kind === ts.SyntaxKind.LiteralType || this.isKeywordType(arm)) continue; + const c = this.containers(arm, depth + 1); + if (c === null) return null; + out.push(...c); + } + return out; + } + if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) { + const name = type.typeName.text; + const args = type.typeArguments?.nodes ?? []; + if ((name === "Promise" || name === "PromiseLike") && args.length === 1) + return this.containers(args[0]!, depth + 1); + if (args.length > 0) return null; + const decls = this.rootsNamed(name); + if (decls.length === 0) return null; + const out: ts.Node[] = []; + for (const d of decls) { + if (ts.isTypeAliasDeclaration(d)) { + const c = this.containers(d.type, depth + 1); + if (c === null) return null; + out.push(...c); + } else out.push(d); + } + return out; + } + return null; + } + + private isKeywordType(n: ts.Node): boolean { + return ( + n.kind === ts.SyntaxKind.NullKeyword || + n.kind === ts.SyntaxKind.UndefinedKeyword || + n.kind === ts.SyntaxKind.StringKeyword || + n.kind === ts.SyntaxKind.NumberKeyword || + n.kind === ts.SyntaxKind.BooleanKeyword + ); + } + + private membersOf(n: ts.Node): readonly ts.Node[] | null { + if (ts.isInterfaceDeclaration(n) || ts.isTypeLiteralNode(n) || ts.isClassDeclaration(n)) + return n.members.nodes; + if (ts.isModuleDeclaration(n)) + return n.body !== undefined && ts.isModuleBlock(n.body) ? n.body.statements.nodes : []; + return null; + } + + /** Expands a position into the object-like nodes that hold members. */ + private holders(pos: TsPos): ts.Node[] | string { + const n = pos.node; + if (this.membersOf(n) !== null) return [n]; + if (ts.isTypeAliasDeclaration(n) || ts.isTypeNode(n) || this.typeOf(n) !== undefined) { + const type = ts.isTypeNode(n) ? n : this.typeOf(n)!; + const c = this.containers(type); + if (c === null) + return "passes through a type it cannot follow (a generic, mapped, conditional or imported type); name the declared type itself"; + return c; + } + if (ts.isFunctionDeclaration(n) || ts.isEnumDeclaration(n)) return []; + return "has a member segment after a declaration without members"; + } + + private signaturesOf(pos: TsPos): ts.Node[] | string { + const n = pos.node; + if (this.isFunctionLike(n)) return [n]; + if (ts.isInterfaceDeclaration(n) || ts.isTypeLiteralNode(n)) + return n.members.nodes.filter((m) => ts.isCallSignatureDeclaration(m)); + const type = ts.isTypeNode(n) ? n : this.typeOf(n); + if (type !== undefined) { + let t: ts.TypeNode = type; + while (ts.isParenthesizedTypeNode(t)) t = t.type; + if (ts.isFunctionTypeNode(t)) return [t]; + const c = this.containers(t); + if (c === null) + return "passes through a type it cannot follow; name the declared type itself"; + return c.flatMap((h) => + ts.isInterfaceDeclaration(h) || ts.isTypeLiteralNode(h) + ? h.members.nodes.filter((m) => ts.isCallSignatureDeclaration(m)) + : [], + ); + } + if (ts.isModuleDeclaration(n) || ts.isClassDeclaration(n)) return []; + return "has '()' after a declaration that is not a function"; + } + + private step(pos: TsPos, seg: SelectorSegment): TsPos[] | string { + switch (seg.kind) { + case "member": { + const holders = this.holders(pos); + if (typeof holders === "string") return holders; + const out: TsPos[] = []; + for (const h of holders) { + const members = this.membersOf(h) ?? []; + for (const m of members) { + if (ts.isVariableStatement(m)) { + for (const d of m.declarationList.declarations.nodes) + if (nameText(d.name) === seg.name) out.push({ node: d, optional: false }); + continue; + } + if (nameText((m as ts.PropertySignatureDeclaration).name) === seg.name) + out.push({ node: m, optional: false }); + } + } + return out; + } + case "return": + case "param": { + const sigs = this.signaturesOf(pos); + if (typeof sigs === "string") return sigs; + const out: TsPos[] = []; + for (const s of sigs) { + const sig = s as ts.FunctionDeclaration; + if (seg.kind === "return") { + if (sig.type !== undefined) out.push({ node: sig.type, optional: false }); + else return "has no written return type"; + } else { + for (const p of sig.parameters.nodes) + if (nameText(p.name) === seg.name) out.push({ node: p, optional: false }); + } + } + return out; + } + case "element": { + let t: ts.TypeNode | undefined = ts.isTypeNode(pos.node) ? pos.node : this.typeOf(pos.node); + while (t !== undefined && ts.isParenthesizedTypeNode(t)) t = t.type; + if (t !== undefined && ts.isArrayTypeNode(t)) + return [{ node: t.elementType, optional: false, element: true }]; + if ( + t !== undefined && + ts.isTypeReferenceNode(t) && + ts.isIdentifier(t.typeName) && + (t.typeName.text === "Array" || t.typeName.text === "ReadonlyArray") && + t.typeArguments?.nodes.length === 1 + ) + return [{ node: t.typeArguments.nodes[0]!, optional: false }]; + return "has '[]' after a type that is not an array"; + } + case "index": { + const holders = this.holders(pos); + if (typeof holders === "string") return holders; + const want = + seg.key === "string" ? ts.SyntaxKind.StringKeyword : ts.SyntaxKind.NumberKeyword; + const out: TsPos[] = []; + for (const h of holders) + for (const m of this.membersOf(h) ?? []) + if (ts.isIndexSignatureDeclaration(m) && m.parameters.nodes[0]?.type?.kind === want) + out.push({ node: m, optional: false }); + return out; + } + } + } +} + +/* ── JSDoc in JavaScript ───────────────────────────────────────────── */ + +interface JsDocTag { + tag: string; + /** Inner span of the `{...}` type, or null. */ + type: { start: number; end: number } | null; + /** Parameter or property name (brackets and defaults removed). */ + name: string | null; + optional: boolean; + /** Offset of the `@`. */ + at: number; +} + +interface JsDocComment { + start: number; + end: number; + tags: JsDocTag[]; +} + +function parseJsDoc(text: string, start: number, end: number): JsDocComment { + const tags: JsDocTag[] = []; + const re = /(^|[\s*])@([A-Za-z]+)/g; + re.lastIndex = start + 3; + const body = text.slice(0, end - 2); + for (let m = re.exec(body); m !== null; m = re.exec(body)) { + const at = m.index + m[1]!.length; + const tag = m[2]!; + let i = at + 1 + tag.length; + while (i < body.length && (body[i] === " " || body[i] === "\t")) i++; + let type: JsDocTag["type"] = null; + if (body[i] === "{") { + let depth = 0; + let j = i; + for (; j < body.length; j++) { + if (body[j] === "{") depth++; + else if (body[j] === "}") { + depth--; + if (depth === 0) break; + } + } + if (j >= body.length) continue; + type = { start: i + 1, end: j }; + // Trim the inner span to the type text. + while (type.start < type.end && /\s/.test(text[type.start]!)) type.start++; + while (type.end > type.start && /\s/.test(text[type.end - 1]!)) type.end--; + i = j + 1; + re.lastIndex = i; + } + let name: string | null = null; + let optional = false; + const rest = /^[ \t]*(\[)?[ \t]*([A-Za-z_$][\w$.]*)/.exec(body.slice(i, i + 200)); + if (rest !== null) { + name = rest[2]!; + optional = rest[1] !== undefined; + } + if (type !== null && text[type.end - 1] === "=") optional = true; + tags.push({ tag, type, name, optional, at }); + } + return { start, end, tags }; +} + +const RETURN_TAGS = new Set(["returns", "return"]); +const PROPERTY_TAGS = new Set(["property", "prop"]); + +/** A declaration in JavaScript a selector can stand on. */ +type JsPos = + | { kind: "decl"; node: ts.Node; owner: ts.Node; fn: ts.Node | null; members: ts.Node | null } + | { kind: "typedef"; comment: JsDocComment; tagIndex: number; prefix: string } + | { kind: "slot"; slot: Slot }; + +class JsResolver { + private readonly comments: JsDocComment[] = []; + private readonly roots = new Map(); + private readonly exportEquals: JsPos[] = []; + private readonly exportDefault: JsPos[] = []; + + constructor( + private readonly text: string, + sf: ts.SourceFile, + ) { + const re = /\/\*\*(?!\/)[\s\S]*?\*\//g; + for (let m = re.exec(text); m !== null; m = re.exec(text)) + this.comments.push(parseJsDoc(text, m.index, m.index + m[0].length)); + for (const c of this.comments) + c.tags.forEach((t, i) => { + if ((t.tag === "typedef" || t.tag === "callback") && t.name !== null) + this.add(t.name, { kind: "typedef", comment: c, tagIndex: i, prefix: "" }); + }); + for (const s of sf.statements.nodes) this.collect(s); + } + + private add(name: string, pos: JsPos): void { + const list = this.roots.get(name); + if (list === undefined) this.roots.set(name, [pos]); + else list.push(pos); + } + + /** The position of a declaration whose value is `value` (or none). */ + private declPos(node: ts.Node, owner: ts.Node, value: ts.Node | undefined): JsPos { + let fn: ts.Node | null = null; + let members: ts.Node | null = null; + if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) fn = node; + else if (ts.isClassDeclaration(node)) members = node; + if (value !== undefined) { + let v = value; + while (ts.isParenthesizedExpression(v)) v = v.expression; + if (ts.isFunctionExpression(v) || ts.isArrowFunction(v)) fn = v; + else if (ts.isClassExpression(v) || ts.isObjectLiteralExpression(v)) members = v; + } + return { kind: "decl", node, owner, fn, members }; + } + + private collect(s: ts.Statement): void { + if (ts.isFunctionDeclaration(s) || ts.isClassDeclaration(s)) { + const pos = this.declPos(s, s, undefined); + const name = nameText(s.name); + if (name !== null) this.add(name, pos); + if (hasDefaultModifier(s)) this.exportDefault.push(pos); + return; + } + if (ts.isVariableStatement(s)) { + const decls = s.declarationList.declarations.nodes; + for (const d of decls) { + const name = nameText(d.name); + if (name !== null) + this.add(name, this.declPos(d, decls.length === 1 ? s : d, d.initializer)); + } + return; + } + if (ts.isExportAssignment(s)) { + if (!s.isExportEquals) this.exportDefault.push(this.declPos(s, s, s.expression)); + return; + } + if (!ts.isExpressionStatement(s)) return; + const e = s.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; + const target = exportTarget(e.left); + if (target === null) return; + if (target === "export=") { + this.exportEquals.push(this.declPos(s, s, e.right)); + // `module.exports = name`: the named declaration is the export. + if (ts.isIdentifier(e.right)) { + const named = this.roots.get(e.right.text); + if (named !== undefined) this.exportEquals.push(...named); + } + } else if (ts.isIdentifier(e.right) && this.roots.has(e.right.text)) { + // `exports.name = name`: the named declaration is the export. + for (const pos of this.roots.get(e.right.text)!) + if (pos.kind === "decl") this.add(target, pos); + } else this.add(target, this.declPos(s, s, e.right)); + } + + /** The last JSDoc comment directly before a node's first token. */ + private docOf(owner: ts.Node): JsDocComment | null { + const start = tokenStartOfNode(owner); + for (let i = this.comments.length - 1; i >= 0; i--) { + const c = this.comments[i]!; + if (c.end > start) continue; + return /^\s*$/.test(this.text.slice(c.end, start)) ? c : null; + } + return null; + } + + /** Tags of a comment that belong to the declaration it documents, not + * to a `@typedef` or `@callback` block inside it. */ + private ownTags(c: JsDocComment): JsDocTag[] { + const out: JsDocTag[] = []; + let inBlock = false; + for (const t of c.tags) { + if (t.tag === "typedef" || t.tag === "callback") { + inBlock = true; + continue; + } + if (inBlock && (PROPERTY_TAGS.has(t.tag) || t.tag === "type")) continue; + if (inBlock && t.tag !== "param" && !RETURN_TAGS.has(t.tag)) inBlock = false; + if (inBlock) continue; + out.push(t); + } + return out; + } + + private tagSlot(tag: JsDocTag): Slot { + return { span: tag.type, optional: tag.optional, insert: null, jsdoc: true }; + } + + /** A slot for a tag of the declaration `owner` documents; when it has no + * such tag, a replacement inserts one. */ + private docSlot(owner: ts.Node, tagNames: ReadonlySet, name: string | null): Slot { + const doc = this.docOf(owner); + if (doc !== null) { + const tag = this.ownTags(doc).find( + (t) => tagNames.has(t.tag) && (name === null || t.name === name), + ); + if (tag !== undefined && tag.type !== null) return this.tagSlot(tag); + } + const tagName = [...tagNames][0]!; + const named = name === null ? "" : ` ${name}`; + if (doc !== null) { + const at = doc.end - 2; + const pad = /\s/.test(this.text[at - 1] ?? "") ? "" : " "; + return { + span: null, + optional: false, + insert: { at, prefix: `${pad}@${tagName} {`, suffix: `}${named} ` }, + jsdoc: true, + }; + } + return { + span: null, + optional: false, + insert: { at: tokenStartOfNode(owner), prefix: `/** @${tagName} {`, suffix: `}${named} */ ` }, + jsdoc: true, + }; + } + + resolve(sel: Selector): Step { + let positions: JsPos[]; + if (sel.root === "export=") positions = this.exportEquals; + else if (sel.root === "default") positions = this.exportDefault; + else positions = this.roots.get(sel.root) ?? []; + let problem: string | null = null; + for (const seg of sel.segments) { + const next: JsPos[] = []; + for (const pos of positions) { + const step = this.step(pos, seg); + if (typeof step === "string") problem ??= step; + else next.push(...step); + } + positions = next; + if (positions.length === 0) + return problem === null ? { slots: [] } : { problem: `'${sel.text}' ${problem}` }; + } + const slots: Slot[] = []; + for (const pos of positions) { + const slot = this.slotOf(pos); + if (typeof slot === "string") problem ??= slot; + else if (slot !== null) slots.push(slot); + } + if (slots.length === 0 && problem !== null) return { problem: `'${sel.text}' ${problem}` }; + return { slots }; + } + + private slotOf(pos: JsPos): Slot | string | null { + if (pos.kind === "slot") return pos.slot; + if (pos.kind === "typedef") { + const own = pos.comment.tags[pos.tagIndex]!; + if (pos.prefix === "") { + // A typedef's own type: only when it is not the `object` that + // its @property tags describe. + const typeTag = this.typedefTypeTag(pos); + if (typeTag === null || /^object$/i.test(this.text.slice(typeTag.start, typeTag.end))) + return "names a @typedef of an object; name one of its properties"; + return { span: typeTag, optional: false, insert: null, jsdoc: true }; + } + void own; + return null; + } + if (pos.fn !== null && pos.members === null) + return "names a function; add '()' for its return type or '(name)' for a parameter"; + if (pos.members !== null && !ts.isObjectLiteralExpression(pos.members)) + return "names a class; name one of its members"; + return this.docSlot(pos.owner, new Set(["type"]), null); + } + + private typedefTypeTag(pos: { + comment: JsDocComment; + tagIndex: number; + }): { start: number; end: number } | null { + const tags = pos.comment.tags; + const own = tags[pos.tagIndex]!; + if (own.type !== null) return own.type; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "type") return t.type; + if (!PROPERTY_TAGS.has(t.tag)) break; + } + return null; + } + + private step(pos: JsPos, seg: SelectorSegment): JsPos[] | string { + // A JSDoc type is not navigated (a nested @property path continues + // from its typedef position instead). + if (pos.kind === "slot") return []; + if (pos.kind === "typedef") { + const tags = pos.comment.tags; + const own = tags[pos.tagIndex]!; + if (own.tag === "callback") { + if (seg.kind !== "return" && seg.kind !== "param") + return "has a member segment after a @callback"; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "typedef" || t.tag === "callback") break; + if ( + seg.kind === "return" + ? RETURN_TAGS.has(t.tag) + : t.tag === "param" && t.name === seg.name + ) + return t.type === null ? [] : [{ kind: "slot", slot: this.tagSlot(t) }]; + } + return []; + } + if (seg.kind !== "member") return "can only name properties of a @typedef"; + const name = pos.prefix === "" ? seg.name : `${pos.prefix}.${seg.name}`; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "typedef" || t.tag === "callback") break; + if (!PROPERTY_TAGS.has(t.tag) && t.tag !== "type") break; + if (PROPERTY_TAGS.has(t.tag) && t.name === name && t.type !== null) { + // A nested property path continues from here. + return [ + { kind: "slot", slot: this.tagSlot(t) }, + { ...pos, prefix: name }, + ]; + } + } + return []; + } + switch (seg.kind) { + case "return": + if (pos.fn === null) return "has '()' after a declaration that is not a function"; + return [{ kind: "slot", slot: this.docSlot(this.docOwner(pos), RETURN_TAGS, null) }]; + case "param": { + if (pos.fn === null) return "has '(name)' after a declaration that is not a function"; + const fn = pos.fn as ts.FunctionExpression; + if (!fn.parameters.nodes.some((p) => nameText(p.name) === seg.name)) return []; + return [ + { kind: "slot", slot: this.docSlot(this.docOwner(pos), new Set(["param"]), seg.name) }, + ]; + } + case "member": { + if (pos.members === null) return []; + return this.membersNamed(pos.members, seg.name); + } + default: + return "navigates into a JSDoc type; name the declared type itself"; + } + } + + /** The node whose leading comment documents a declaration. */ + private docOwner(pos: { owner: ts.Node }): ts.Node { + return pos.owner; + } + + private membersNamed(holder: ts.Node, name: string): JsPos[] { + const out: JsPos[] = []; + if (ts.isObjectLiteralExpression(holder)) { + for (const p of holder.properties.nodes) { + if (nameText((p as ts.PropertyAssignment).name) !== name) continue; + if (ts.isPropertyAssignment(p)) out.push(this.declPos(p, p, p.initializer)); + else if (ts.isMethodDeclaration(p)) out.push(this.declPos(p, p, undefined)); + // `{ parse }`: the declaration of that name. + else if (ts.isShorthandPropertyAssignment(p)) out.push(...(this.roots.get(name) ?? [])); + } + return out; + } + const cls = holder as ts.ClassDeclaration; + for (const m of cls.members.nodes) { + if (ts.isConstructorDeclaration(m) && m.body !== undefined) { + // `this.name = value` in the constructor, documented by its comment. + for (const s of m.body.statements.nodes) { + if (!ts.isExpressionStatement(s) || !ts.isBinaryExpression(s.expression)) continue; + const e = s.expression; + if ( + e.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(e.left) && + e.left.expression.kind === ts.SyntaxKind.ThisKeyword && + nameText(e.left.name) === name + ) + out.push(this.declPos(s, s, e.right)); + } + continue; + } + if (nameText((m as ts.MethodDeclaration).name) !== name) continue; + if (ts.isMethodDeclaration(m)) out.push(this.declPos(m, m, undefined)); + else if (ts.isPropertyDeclaration(m)) out.push(this.declPos(m, m, m.initializer)); + else if (ts.isGetAccessor(m)) + out.push({ kind: "decl", node: m, owner: m, fn: m, members: null }); + } + return out; + } +} + +/** `module.exports` → "export="; `exports.x` / `module.exports.x` → "x". */ +function exportTarget(left: ts.Expression): string | null { + if (!ts.isPropertyAccessExpression(left)) return null; + const obj = left.expression; + const prop = nameText(left.name); + if (prop === null) return null; + if (ts.isIdentifier(obj) && obj.text === "module" && prop === "exports") return "export="; + if (ts.isIdentifier(obj) && obj.text === "exports") return prop; + if ( + ts.isPropertyAccessExpression(obj) && + ts.isIdentifier(obj.expression) && + obj.expression.text === "module" && + nameText(obj.name) === "exports" + ) + return prop; + return null; +} diff --git a/packages/compiler/src/frontend/type-patches/selector.test.ts b/packages/compiler/src/frontend/type-patches/selector.test.ts new file mode 100644 index 000000000..716c994a8 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/selector.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from "vitest"; +import { parseSelector, parseTypeText, splitUnion } from "./selector.js"; + +describe("parseSelector", () => { + test("members, returns, parameters, elements and index signatures", () => { + expect(parseSelector("HelpContext.helpWidth")).toEqual({ + file: null, + root: "HelpContext", + segments: [{ kind: "member", name: "helpWidth" }], + text: "HelpContext.helpWidth", + }); + expect(parseSelector("index.js:parse()")).toMatchObject({ + file: "index.js", + root: "parse", + segments: [{ kind: "return" }], + }); + expect(parseSelector("export=()")).toMatchObject({ + root: "export=", + segments: [{ kind: "return" }], + }); + expect(parseSelector("fetchUser(id)")).toMatchObject({ + segments: [{ kind: "param", name: "id" }], + }); + expect(parseSelector("list()[].name")).toMatchObject({ + segments: [{ kind: "return" }, { kind: "element" }, { kind: "member", name: "name" }], + }); + expect(parseSelector("Env[string]")).toMatchObject({ + segments: [{ kind: "index", key: "string" }], + }); + }); + + test("malformed selectors say why", () => { + expect(parseSelector("")).toEqual({ error: "is empty" }); + expect(parseSelector("../x.js:f()")).toEqual({ error: "names a file outside the package" }); + expect(parseSelector("1abc")).toHaveProperty("error"); + expect(parseSelector("a.")).toHaveProperty("error"); + expect(parseSelector("a(1)")).toHaveProperty("error"); + }); +}); + +describe("parseTypeText", () => { + test("a leading bar widens, anything else replaces", () => { + expect(parseTypeText("| undefined")).toEqual({ kind: "widen", arms: ["undefined"] }); + expect(parseTypeText("| undefined | null")).toEqual({ + kind: "widen", + arms: ["undefined", "null"], + }); + expect(parseTypeText("string | null")).toEqual({ kind: "replace", text: "string | null" }); + expect(parseTypeText("a\nb")).toEqual({ error: "must be one line" }); + }); + + test("splitUnion keeps nested unions and arrows together", () => { + expect(splitUnion("(a | b) | (() => c | d) | { x: e | f }")).toEqual([ + "(a | b)", + "(() => c | d)", + "{ x: e | f }", + ]); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/selector.ts b/packages/compiler/src/frontend/type-patches/selector.ts new file mode 100644 index 000000000..8fdd8b67c --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/selector.ts @@ -0,0 +1,133 @@ +/* Type patch selectors: names of declarations in a package, written the + * same way for JSDoc in its JavaScript and for its declaration files. + * + * selector := [ file ":" ] root segment* + * root := identifier | "export=" | "default" + * segment := "." name member (interface, type alias, @typedef, + * class, namespace, object type) + * | "()" return type + * | "(" name ")" type of the named parameter + * | "[]" element type of an array type + * | "[string]" | "[number]" value type of an index signature + * + * Examples: `HelpContext.helpWidth`, `index.js:parse()`, `export=()`, + * `fetchUser().email`, `Env[string]`. */ + +export type SelectorSegment = + | { kind: "member"; name: string } + | { kind: "return" } + | { kind: "param"; name: string } + | { kind: "element" } + | { kind: "index"; key: "string" | "number" }; + +export interface Selector { + /** Package-relative file the root must be declared in, or null. */ + file: string | null; + /** A declared name, `export=` or `default`. */ + root: string; + segments: readonly SelectorSegment[]; + /** The selector as written. */ + text: string; +} + +const IDENT = /^[A-Za-z_$][\w$]*/; + +/** The parsed selector, or a message saying what is wrong with it. */ +export function parseSelector(text: string): Selector | { error: string } { + let rest = text.trim(); + if (rest === "") return { error: "is empty" }; + let file: string | null = null; + const colon = rest.indexOf(":"); + if (colon >= 0) { + file = rest.slice(0, colon).replace(/^\.\//, ""); + rest = rest.slice(colon + 1); + if (file === "" || file.startsWith("/") || file.split("/").includes("..")) + return { error: "names a file outside the package" }; + } + let root: string; + if (rest.startsWith("export=")) { + root = "export="; + rest = rest.slice("export=".length); + } else { + const m = IDENT.exec(rest); + if (m === null) return { error: "must start with a declared name, 'export=' or 'default'" }; + root = m[0]; + rest = rest.slice(root.length); + } + const segments: SelectorSegment[] = []; + while (rest !== "") { + if (rest.startsWith(".")) { + const m = IDENT.exec(rest.slice(1)); + if (m === null) return { error: `has no member name after '.' at '${rest}'` }; + segments.push({ kind: "member", name: m[0] }); + rest = rest.slice(1 + m[0].length); + } else if (rest.startsWith("()")) { + segments.push({ kind: "return" }); + rest = rest.slice(2); + } else if (rest.startsWith("(")) { + const m = /^\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(rest); + if (m === null) return { error: `has a malformed parameter at '${rest}'` }; + segments.push({ kind: "param", name: m[1]! }); + rest = rest.slice(m[0].length); + } else if (rest.startsWith("[]")) { + segments.push({ kind: "element" }); + rest = rest.slice(2); + } else if (rest.startsWith("[string]") || rest.startsWith("[number]")) { + segments.push({ kind: "index", key: rest.startsWith("[string]") ? "string" : "number" }); + rest = rest.slice("[string]".length); + } else { + return { error: `has an unexpected '${rest}'` }; + } + } + return { file, root, segments, text: text.trim() }; +} + +/** A widening (`| undefined`) or a replacement type. */ +export type TypePatchOp = + | { kind: "widen"; arms: readonly string[] } + | { kind: "replace"; text: string }; + +/** The operation a patch's type text stands for, or a message. */ +export function parseTypeText(text: string): TypePatchOp | { error: string } { + if (/[\r\n]/.test(text)) return { error: "must be one line" }; + const t = text.trim(); + if (t === "") return { error: "is empty" }; + if (t.startsWith("|")) { + const arms = splitUnion(t.slice(1)); + if (arms.some((a) => a === "")) return { error: "has an empty union member" }; + return { kind: "widen", arms }; + } + return { kind: "replace", text: t }; +} + +/** The top-level union members of a type text, trimmed. */ +export function splitUnion(text: string): string[] { + const out: string[] = []; + let depth = 0; + let quote: string | null = null; + let start = 0; + for (let i = 0; i < text.length; i++) { + const ch = text[i]!; + if (quote !== null) { + if (ch === "\\") i++; + else if (ch === quote) quote = null; + continue; + } + if (ch === '"' || ch === "'" || ch === "`") quote = ch; + else if (ch === "(" || ch === "[" || ch === "{" || ch === "<") depth++; + else if (ch === ")" || ch === "]" || ch === "}" || ch === ">") { + // `=>` is not a closing bracket. + if (!(ch === ">" && text[i - 1] === "=")) depth--; + } else if (ch === "|" && depth === 0) { + out.push(text.slice(start, i).trim()); + start = i + 1; + } + } + out.push(text.slice(start).trim()); + return out; +} + +/** Whitespace-insensitive text of a type for comparisons. */ +export function normalizeType(text: string): string { + return text.replace(/\s+/g, ""); +} diff --git a/packages/compiler/src/frontend/type-patches/semver.test.ts b/packages/compiler/src/frontend/type-patches/semver.test.ts new file mode 100644 index 000000000..83663e3a2 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/semver.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "vitest"; +import { isValidRange, satisfies } from "./semver.js"; + +describe("satisfies", () => { + test.each([ + ["15.0.0", "^15.0.0", true], + ["15.4.2", "^15.0.0", true], + ["16.0.0", "^15.0.0", false], + ["0.2.5", "^0.2.3", true], + ["0.3.0", "^0.2.3", false], + ["0.0.3", "^0.0.3", true], + ["0.0.4", "^0.0.3", false], + ["1.2.9", "~1.2.3", true], + ["1.3.0", "~1.2.3", false], + ["1.9.0", "~1", true], + ["2.1.3", "2.1.x", true], + ["2.2.0", "2.1.x", false], + ["2.1.3", "2", true], + ["3.0.0", "2", false], + ["2.1.3", "*", true], + ["1.2.3", "1.2.3", true], + ["1.2.4", "=1.2.3", false], + ["1.5.0", ">=1.2.0 <2.0.0", true], + ["2.0.0", ">=1.2.0 <2.0.0", false], + ["2.0.0", ">= 1.2.0", true], + ["1.3.0", ">1.2", true], + ["1.2.9", ">1.2", false], + ["1.2.9", "<=1.2", true], + ["1.3.0", "<=1.2", false], + ["2.3.9", "1.2.3 - 2.3", true], + ["2.4.0", "1.2.3 - 2.3", false], + ["3.1.0", "^1.0.0 || ^3.0.0", true], + ["2.1.0", "^1.0.0 || ^3.0.0", false], + ["2.0.0-beta.1", "^1.0.0", false], + ["2.0.0-beta.1", "^2.0.0", false], + ["2.0.0-beta.2", "^2.0.0-beta.1", true], + ["2.0.0-alpha", "^2.0.0-beta.1", false], + ["2.1.0-beta.2", "^2.0.0-beta.1", false], + ])("%s satisfies %s: %s", (version, range, expected) => { + expect(satisfies(version, range)).toBe(expected); + }); + + test("malformed input never satisfies", () => { + expect(satisfies("not-a-version", "^1.0.0")).toBe(false); + expect(isValidRange("^banana")).toBe(false); + expect(isValidRange(">=1 <2 || 3.x")).toBe(true); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/semver.ts b/packages/compiler/src/frontend/type-patches/semver.ts new file mode 100644 index 000000000..86e9d1183 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/semver.ts @@ -0,0 +1,229 @@ +/* The npm semver range subset type patches key on (`commander@^15.0.0`): + * exact versions, x-ranges, `^`, `~`, comparators, hyphen ranges and `||`. + * A prerelease version satisfies a comparator set only when one of the + * set's comparators names a prerelease of the same major.minor.patch, as + * npm does. */ + +interface Version { + major: number; + minor: number; + patch: number; + pre: readonly (string | number)[]; +} + +interface Comparator { + op: "<" | "<=" | ">" | ">=" | "="; + version: Version; +} + +/** A partial version: missing or wildcard parts are null. */ +interface Partial { + major: number | null; + minor: number | null; + patch: number | null; + pre: readonly (string | number)[]; +} + +const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/; + +function preParts(text: string | undefined): (string | number)[] { + if (text === undefined || text === "") return []; + return text.split(".").map((p) => (/^\d+$/.test(p) ? Number(p) : p)); +} + +/** A full version, or null. */ +export function parseVersion(text: string): Version | null { + const m = VERSION.exec(text.trim()); + if (m === null) return null; + return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: preParts(m[4]) }; +} + +function parsePartial(text: string): Partial | null { + const m = + /^v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec( + text, + ); + if (m === null) return null; + const part = (s: string | undefined): number | null => + s === undefined || s === "x" || s === "X" || s === "*" ? null : Number(s); + const major = part(m[1]); + const minor = major === null ? null : part(m[2]); + const patch = minor === null ? null : part(m[3]); + return { major, minor, patch, pre: patch === null ? [] : preParts(m[4]) }; +} + +function v( + major: number, + minor: number, + patch: number, + pre: readonly (string | number)[] = [], +): Version { + return { major, minor, patch, pre }; +} + +/** `-0` marks the lowest prerelease, so `<2.0.0-0` excludes 2.0.0's + * prereleases as npm's desugaring does. */ +const LOWEST: readonly (string | number)[] = [0]; + +function comparePre(a: readonly (string | number)[], b: readonly (string | number)[]): number { + if (a.length === 0 && b.length === 0) return 0; + if (a.length === 0) return 1; + if (b.length === 0) return -1; + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const x = a[i]; + const y = b[i]; + if (x === undefined) return -1; + if (y === undefined) return 1; + if (x === y) continue; + if (typeof x === "number" && typeof y === "number") return x < y ? -1 : 1; + if (typeof x === "number") return -1; + if (typeof y === "number") return 1; + return x < y ? -1 : 1; + } + return 0; +} + +export function compareVersions(a: Version, b: Version): number { + if (a.major !== b.major) return a.major < b.major ? -1 : 1; + if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1; + if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1; + return comparePre(a.pre, b.pre); +} + +/** The comparators a partial with an operator stands for. */ +function desugar(op: string, p: Partial): Comparator[] | null { + const { major, minor, patch } = p; + if (op === "^") { + if (major === null) return []; + const lo = v(major, minor ?? 0, patch ?? 0, p.pre); + let hi: Version; + if (major > 0 || minor === null) hi = v(major + 1, 0, 0, LOWEST); + else if (minor > 0 || patch === null) hi = v(0, minor + 1, 0, LOWEST); + else hi = v(0, 0, patch + 1, LOWEST); + return [ + { op: ">=", version: lo }, + { op: "<", version: hi }, + ]; + } + if (op === "~") { + if (major === null) return []; + const lo = v(major, minor ?? 0, patch ?? 0, p.pre); + const hi = minor === null ? v(major + 1, 0, 0, LOWEST) : v(major, minor + 1, 0, LOWEST); + return [ + { op: ">=", version: lo }, + { op: "<", version: hi }, + ]; + } + if (op === "" || op === "=") { + if (major === null) return []; + if (minor === null) + return [ + { op: ">=", version: v(major, 0, 0) }, + { op: "<", version: v(major + 1, 0, 0, LOWEST) }, + ]; + if (patch === null) + return [ + { op: ">=", version: v(major, minor, 0) }, + { op: "<", version: v(major, minor + 1, 0, LOWEST) }, + ]; + return [{ op: "=", version: v(major, minor, patch, p.pre) }]; + } + if (major === null) + return op === "<" || op === ">" ? [{ op: "<", version: v(0, 0, 0, LOWEST) }] : []; + if (op === ">") { + if (minor === null) return [{ op: ">=", version: v(major + 1, 0, 0) }]; + if (patch === null) return [{ op: ">=", version: v(major, minor + 1, 0) }]; + return [{ op: ">", version: v(major, minor, patch, p.pre) }]; + } + if (op === ">=") return [{ op: ">=", version: v(major, minor ?? 0, patch ?? 0, p.pre) }]; + if (op === "<") { + if (minor === null) return [{ op: "<", version: v(major, 0, 0, LOWEST) }]; + if (patch === null) return [{ op: "<", version: v(major, minor, 0, LOWEST) }]; + return [{ op: "<", version: v(major, minor, patch, p.pre) }]; + } + if (op === "<=") { + if (minor === null) return [{ op: "<", version: v(major + 1, 0, 0, LOWEST) }]; + if (patch === null) return [{ op: "<", version: v(major, minor + 1, 0, LOWEST) }]; + return [{ op: "<=", version: v(major, minor, patch, p.pre) }]; + } + return null; +} + +/** One `||` alternative: a list of comparators, or null when malformed. */ +function parseSet(text: string): Comparator[] | null { + const t = text.trim(); + const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(t); + if (hyphen !== null) { + const lo = parsePartial(hyphen[1]!); + const hi = parsePartial(hyphen[2]!); + if (lo === null || hi === null) return null; + return [...(desugar(">=", lo) ?? []), ...(desugar("<=", hi) ?? [])]; + } + if (t === "") return []; + const out: Comparator[] = []; + // `>= 1.2.3` may carry a space between the operator and the version. + const tokens = t.replace(/(<=|>=|<|>|=|\^|~)\s+/g, "$1").split(/\s+/); + for (const token of tokens) { + const m = /^(<=|>=|<|>|=|\^|~>?|)(.*)$/.exec(token); + if (m === null) return null; + const op = m[1] === "~>" ? "~" : m[1]!; + const p = parsePartial(m[2]!); + if (p === null) return null; + const cs = desugar(op, p); + if (cs === null) return null; + out.push(...cs); + } + return out; +} + +/** The parsed range, or null when it is not a semver range. */ +export function parseRange(range: string): Comparator[][] | null { + const sets: Comparator[][] = []; + for (const alt of range.split("||")) { + const set = parseSet(alt); + if (set === null) return null; + sets.push(set); + } + return sets; +} + +export function isValidRange(range: string): boolean { + return parseRange(range) !== null; +} + +function test(c: Comparator, version: Version): boolean { + const cmp = compareVersions(version, c.version); + switch (c.op) { + case "<": + return cmp < 0; + case "<=": + return cmp <= 0; + case ">": + return cmp > 0; + case ">=": + return cmp >= 0; + default: + return cmp === 0; + } +} + +/** True when `version` satisfies `range` (false for malformed input). */ +export function satisfies(version: string, range: string): boolean { + const ver = parseVersion(version); + const sets = parseRange(range); + if (ver === null || sets === null) return false; + return sets.some((set) => { + if (!set.every((c) => test(c, ver))) return false; + if (ver.pre.length === 0) return true; + // A prerelease only matches a set that names a prerelease of the same + // version tuple (the LOWEST markers of upper bounds do not count). + return set.some( + (c) => + c.version.pre.length > 0 && + c.version.pre !== LOWEST && + c.version.major === ver.major && + c.version.minor === ver.minor && + c.version.patch === ver.patch, + ); + }); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 3e4871084..ffd56a1bf 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -52,7 +52,7 @@ import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; import { createHash } from "node:crypto"; -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; @@ -119,6 +119,7 @@ import { ffiNativeBuildDiag, iceDiag, nativeCodegenDiag, + isTypePatchCode, type ScrDiagnostic, } from "./diagnostics/diagnostic.js"; import { loadLibraryProfile, type LibraryProfile } from "./library/library-profile.js"; @@ -485,12 +486,31 @@ interface CompileWarningSink { sourceTexts: Map | null; } +/** The package.json texts type patch warnings point into, re-read for a + * cache hit's code frames (performance warnings need none). */ +function cachedWarningSources(warnings: readonly ScrDiagnostic[]): Map { + const texts = new Map(); + for (const w of warnings) { + if (!isTypePatchCode(w.code) || texts.has(w.loc.file)) continue; + try { + texts.set(w.loc.file, readFileSync(w.loc.file, "utf8")); + } catch { + // The frame is omitted; the message stands alone. + } + } + return texts; +} + /** The build's performance warnings (SC61xx), stored with its cache entry * so a cache hit repeats them. */ function performanceWarnings(sink: CompileWarningSink | undefined): { performance?: ScrDiagnostic[]; } { - const performance = (sink?.warnings ?? []).filter((d) => isPerformanceCode(d.code)); + // Type patch warnings (SC8030–SC8033) describe the same unchanged inputs, + // so a hit repeats them too. + const performance = (sink?.warnings ?? []).filter( + (d) => isPerformanceCode(d.code) || isTypePatchCode(d.code), + ); return performance.length === 0 ? {} : { performance }; } @@ -1602,7 +1622,7 @@ async function compileTracked( // names is just as untyped as when it was built. if (warningSink !== undefined && earlyHit.performance !== undefined) { warningSink.warnings = earlyHit.performance; - warningSink.sourceTexts = new Map(); + warningSink.sourceTexts = cachedWarningSources(earlyHit.performance); } const executableCacheOptions = earlyCacheOptions; // Route/proof metadata is independently evictable. A full-compiler diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 58ae914ca..04a5d2e0b 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -164,3 +164,80 @@ test("package setting keys named like Object.prototype members are unknown setti ]); expect(c.packages.has("debug")).toBe(false); }); + +test("typePatches: entries keyed by package and range", () => { + const c = config({ + typePatches: { + "commander@^15.0.0": { declarations: { "HelpContext.helpWidth": "| undefined" } }, + "@scope/lib@1.x || 2.x": { + declarations: { "Options.name": "string | null" }, + note: "https://example.invalid/issue/1", + }, + }, + }); + expect(c.diagnostics).toEqual([]); + expect( + c.typePatches.map(({ key, pkg, range, declarations }) => ({ + key, + pkg, + range, + declarations: declarations.map((d) => [d.selector, d.type]), + })), + ).toEqual([ + { + key: "commander@^15.0.0", + pkg: "commander", + range: "^15.0.0", + declarations: [["HelpContext.helpWidth", "| undefined"]], + }, + { + key: "@scope/lib@1.x || 2.x", + pkg: "@scope/lib", + range: "1.x || 2.x", + declarations: [["Options.name", "string | null"]], + }, + ]); + expect(c.typePatches[0]!.declarations[0]!.offset).toBeGreaterThan(0); +}); + +test("typePatches: malformed keys, ranges, selectors and types are SC8004", () => { + const c = config({ + typePatches: { + commander: { declarations: { "A.b": "| undefined" } }, + "ms@^banana": { declarations: {} }, + "@types/node@*": { declarations: {} }, + "typescript@5": { declarations: {} }, + "lib@^1.0.0": { + declarations: { + "1bad": "| undefined", + "A.b": 3, + "A.c": "string\nnumber", + "A.d": "| undefined", + }, + files: {}, + }, + "other@^1.0.0": [], + }, + }); + expect(c.diagnostics.every((d) => d.code === "SC8004")).toBe(true); + expect(c.diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.typePatches.commander' must name a package and a version range", + `package.json 'scriptc.typePatches.ms@^banana' has the version range "^banana", which is not a semver range`, + "package.json 'scriptc.typePatches.@types/node@*' patches @types/node, whose declarations ship with scriptc or TypeScript", + "package.json 'scriptc.typePatches.typescript@5' patches typescript, whose declarations ship with scriptc or TypeScript", + "package.json 'scriptc.typePatches.lib@^1.0.0.files' is not a known type patch setting", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.1bad' is not a selector: it must start with a declared name, 'export=' or 'default'", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.b' must map to a type string, not 3", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.c' has a type that must be one line", + "package.json 'scriptc.typePatches.other@^1.0.0' must be an object, not an array", + ]); + // The valid selector of an entry with an unknown key is dropped with it; + // entries with only bad selectors keep their valid ones. + expect(c.typePatches).toEqual([]); + const partial = config({ + typePatches: { + "lib@^1.0.0": { declarations: { "1bad": "| undefined", "A.d": "| undefined" } }, + }, + }); + expect(partial.typePatches[0]!.declarations.map((d) => d.selector)).toEqual(["A.d"]); +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index 997e997c1..c43a6bd2c 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -13,7 +13,12 @@ * "proper-lockfile": { "run": "node", "evaluate": "lazy" } * }, * "nodeIslands": { "auto": true, "modules": ["src/plugins/*.ts"] }, - * "wasm": ["build/module.wasm"] + * "wasm": ["build/module.wasm"], + * "typePatches": { // frontend/type-patches/layer.ts + * "commander@^15.0.0": { + * "declarations": { "HelpContext.helpWidth": "| undefined" } + * } + * } * } * * Node islands are report-only in this version: the policy decides which @@ -23,6 +28,8 @@ import { dirname, join, resolve } from "node:path"; import { projectConfigDiag, type ScrDiagnostic } from "./diagnostics/diagnostic.js"; import { trackedReadFile } from "./frontend/input-tracker.js"; +import { parseSelector, parseTypeText } from "./frontend/type-patches/selector.js"; +import { isValidRange } from "./frontend/type-patches/semver.js"; /** How a package runs: compiled natively, in a Node island, or decided by * the detector. */ @@ -57,6 +64,25 @@ export interface NodeIslandsSetting { speculativeStart: boolean; } +/** One declaration patch: a selector and the type text it maps to. */ +export interface TypePatchDeclaration { + selector: string; + type: string; + /** Offset of the selector key in the manifest, for diagnostics. */ + offset: number; +} + +/** One `scriptc.typePatches["@"]` entry. */ +export interface TypePatchEntry { + /** The key as written. */ + key: string; + pkg: string; + range: string; + declarations: TypePatchDeclaration[]; + /** Offset of the key in the manifest. */ + offset: number; +} + export interface ProjectConfig { /** The package.json the configuration came from, or null. */ manifest: string | null; @@ -66,6 +92,8 @@ export interface ProjectConfig { nodeIslands: NodeIslandsSetting; /** `scriptc.wasm`: module paths, resolved against the manifest. */ wasm: string[]; + /** `scriptc.typePatches`, in manifest order. */ + typePatches: TypePatchEntry[]; /** SC8004 for every unknown key or value. */ diagnostics: ScrDiagnostic[]; } @@ -117,6 +145,7 @@ export function emptyProjectConfig(): ProjectConfig { packages: new Map(), nodeIslands: defaultNodeIslands(), wasm: [], + typePatches: [], diagnostics: [], }; } @@ -299,6 +328,9 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi } } + const patches = root["typePatches"]; + if (patches !== undefined) readTypePatches(patches, config, problem, locate); + // `scriptc.wasm` keeps its lenient reading: string entries of an array. const wasm = root["wasm"]; if (Array.isArray(wasm)) { @@ -309,6 +341,136 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi return config; } +const TYPE_PATCH_KEYS = ["declarations", "note"]; +/** Packages whose declarations ship with scriptc or TypeScript: a wrong + * one is a bug to report, not a project-local patch. */ +const UNPATCHABLE = /^(?:@types\/node|typescript|@typescript\/.*)$/; +const PACKAGE_NAME = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i; + +/** Reads and validates `scriptc.typePatches`. */ +function readTypePatches( + patches: unknown, + config: ProjectConfig, + problem: (path: string[], detail: string, hint: string) => void, + locate: JsonLocator, +): void { + if (!isRecord(patches)) { + problem( + ["typePatches"], + `must be an object mapping "@" to patches, not ${shown(patches)}`, + `write "typePatches": { "@^1.0.0": { "declarations": { "Type.member": "| undefined" } } }`, + ); + return; + } + for (const key of Object.keys(patches)) { + const at = key.lastIndexOf("@"); + const pkg = at > 0 ? key.slice(0, at) : key; + const range = at > 0 ? key.slice(at + 1).trim() : ""; + if (at <= 0 || range === "" || !PACKAGE_NAME.test(pkg)) { + problem( + ["typePatches", key], + "must name a package and a version range", + `write "${at > 0 ? pkg : key}@^1.0.0": the range says which installed versions the patch describes`, + ); + continue; + } + if (!isValidRange(range)) { + problem( + ["typePatches", key], + `has the version range "${range}", which is not a semver range`, + `use a range such as "^${/^\d/.test(range) ? range.replace(/[^\d.]/g, "") : "1.0.0"}" or "1.2.x"`, + ); + continue; + } + if (UNPATCHABLE.test(pkg)) { + problem( + ["typePatches", key], + `patches ${pkg}, whose declarations ship with scriptc or TypeScript`, + "report a wrong declaration there instead of patching it", + ); + continue; + } + const value = patches[key]; + if (!isRecord(value)) { + problem( + ["typePatches", key], + `must be an object, not ${shown(value)}`, + `write { "declarations": { "Type.member": "| undefined" } }`, + ); + continue; + } + const entry: TypePatchEntry = { + key, + pkg, + range, + declarations: [], + offset: locate.offsetOf(["scriptc", "typePatches", key]), + }; + let valid = true; + for (const k of Object.keys(value)) { + if (!TYPE_PATCH_KEYS.includes(k)) { + problem( + ["typePatches", key, k], + "is not a known type patch setting", + `the settings are ${TYPE_PATCH_KEYS.join(", ")}`, + ); + valid = false; + } + } + const note = value["note"]; + if (note !== undefined && typeof note !== "string") { + problem( + ["typePatches", key, "note"], + `must be a string, not ${shown(note)}`, + "write a short note, such as a link to the upstream issue", + ); + valid = false; + } + const decls = value["declarations"]; + if (decls !== undefined && !isRecord(decls)) { + problem( + ["typePatches", key, "declarations"], + `must be an object mapping selectors to types, not ${shown(decls)}`, + `write "declarations": { "Type.member": "| undefined" }`, + ); + valid = false; + } else if (decls !== undefined) { + for (const selector of Object.keys(decls)) { + const path = ["typePatches", key, "declarations", selector]; + const parsed = parseSelector(selector); + if ("error" in parsed) { + problem( + path, + `is not a selector: it ${parsed.error}`, + `name a declaration such as "Type.member", "fn()", "fn(param)", "file.js:local()" or "export=()"`, + ); + continue; + } + const type = decls[selector]; + if (typeof type !== "string") { + problem( + path, + `must map to a type string, not ${shown(type)}`, + `write "| undefined" to widen, or the whole type to replace it`, + ); + continue; + } + const op = parseTypeText(type); + if ("error" in op) { + problem( + path, + `has a type that ${op.error}`, + `write "| undefined" to widen, or the whole type on one line`, + ); + continue; + } + entry.declarations.push({ selector, type, offset: locate.offsetOf(["scriptc", ...path]) }); + } + } + if (valid) config.typePatches.push(entry); + } +} + let cachedManifest: string | null = null; let cachedText: string | null = null; let cachedConfig: ProjectConfig | null = null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json new file mode 100644 index 000000000..c9a8e15fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-dts/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json new file mode 100644 index 000000000..faa48f074 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-types-twin/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts new file mode 100644 index 000000000..729a0da37 --- /dev/null +++ b/packages/compiler/test/type-patches.test.ts @@ -0,0 +1,124 @@ +/* The type patch layer (frontend/type-patches/layer.ts) against real + * packages: commander's HelpContext.helpWidth @typedef property and ms's + * @return tags, patched from the fixtures' package.json; installed-version + * selection; and the warnings for patches that do not apply as written. */ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { + configureTypePatches, + typePatchDiagnostics, + typePatchedSpans, + typePatchedText, + typePatchMatches, +} from "../src/frontend/type-patches/layer.js"; + +const fixtures = join(import.meta.dirname, "../../../tests/fixtures"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); + +afterEach(() => configureTypePatches(join(tmpdir(), "none", "main.ts"), parse)); + +test("commander's HelpContext.helpWidth typedef property reads as number | undefined", () => { + const root = join(fixtures, "commander-calc"); + configureTypePatches(join(root, "calc.ts"), parse); + const command = join(root, "node_modules/commander/lib/command.js"); + const text = typePatchedText(command); + expect(text).toBeDefined(); + expect(text).toContain(" * @property {number | undefined} helpWidth\n"); + expect(text).toContain(" * @returns {HelpContext}\n"); + expect(typePatchMatches()).toMatchObject([ + { + file: command, + line: 2481, + before: "number", + after: "number | undefined", + pkg: "commander", + version: "15.0.0", + }, + ]); + const [span] = typePatchedSpans(command); + expect(text!.slice(span!.start, span!.end)).toBe(" | undefined"); + // Its declaration file has an unrelated HelpContext without helpWidth. + expect(typePatchedText(join(root, "node_modules/commander/typings/index.d.ts"))).toBeUndefined(); + // Other files of the package are served as they are. + expect(typePatchedText(join(root, "node_modules/commander/lib/help.js"))).toBeUndefined(); + expect(typePatchDiagnostics()?.diagnostics).toEqual([]); +}); + +test("ms's parse() and export return tags widen", () => { + const root = join(fixtures, "type-patches"); + configureTypePatches(join(root, "ms.ts"), parse); + const file = join(root, "node_modules/ms/index.js"); + const text = typePatchedText(file)!; + const original = readFileSync(file, "utf8"); + expect(text).toBe( + original + .replace(" * @return {String|Number}\n", " * @return {String|Number | undefined}\n") + .replace( + " * @return {Number}\n * @api private\n */\n\nfunction parse", + " * @return {Number | undefined}\n * @api private\n */\n\nfunction parse", + ), + ); + expect(text).not.toBe(original); + expect(typePatchDiagnostics()?.diagnostics.map((d) => d.code)).toEqual([]); +}); + +test("a project without type patches serves every file unchanged", () => { + configureTypePatches(join(fixtures, "npm-static", "ms-cli.ts"), parse); + expect(typePatchedText(join(fixtures, "npm-static/node_modules/ms/index.js"))).toBeUndefined(); + expect(typePatchDiagnostics()).toBeNull(); +}); + +test("patches that do not apply as written warn", () => { + const root = join(fixtures, "type-patches"); + configureTypePatches(join(root, "stale", "main.ts"), parse); + expect(typePatchedText(join(root, "node_modules/termctx/index.js"))).toBeUndefined(); + typePatchedText(join(root, "node_modules/ms/index.js")); + const diags = typePatchDiagnostics()!.diagnostics; + expect(diags.map((d) => [d.code, d.message])).toEqual([ + [ + "SC8030", + "type patch 'termctx@^2.0.0' does not apply to termctx 1.2.0 at ../node_modules/termctx; its declarations are used unpatched", + ], + [ + "SC8031", + "type patch 'ms@^2.1.0': 'index.js:format()' matches no declaration in the files of ms this program loads", + ], + ["SC8032", "type patch 'ms@^2.1.0': 'index.js:fmtShort(ms)' is already declared as Number"], + [ + "SC8033", + "type patch 'ms@^2.1.0': 'index.js:plural()' has no written type to widen; give the whole type instead (index.js)", + ], + ]); + expect( + diags.every((d) => d.loc.file === join(root, "stale", "package.json") && d.loc.start > 0), + ).toBe(true); +}); + +test("an @types twin is matched by the runtime package's installed version", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const twin = join(fixtures, "npm/cases/type-patch-types-twin"); + configureTypePatches(join(twin, "main.ts"), parse); + const text = typePatchedText(join(twin, "node_modules/@types/tallyjs/index.d.ts"))!; + expect(text).toContain(" max: number | undefined;\n mean: number | null;\n"); + // A range the installed runtime (0.4.2) does not satisfy leaves it alone, + // even though the twin's own version (0.4.0) would. + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { "tallyjs@0.4.0": { declarations: { "Totals.max": "| undefined" } } }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(twin, "node_modules/@types/tallyjs/index.d.ts"))).toBeUndefined(); + expect(typePatchDiagnostics()!.diagnostics.map((d) => d.code)).toEqual(["SC8030"]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/tests/fixtures/commander-calc/package.json b/tests/fixtures/commander-calc/package.json index f0ea76992..59f181b32 100644 --- a/tests/fixtures/commander-calc/package.json +++ b/tests/fixtures/commander-calc/package.json @@ -4,5 +4,15 @@ "type": "module", "dependencies": { "commander": "15.0.0" + }, + "scriptc": { + "typePatches": { + "commander@^15.0.0": { + "note": "off a TTY, _getOutputContext() returns helpWidth undefined although the @typedef says number", + "declarations": { + "HelpContext.helpWidth": "| undefined" + } + } + } } } diff --git a/tests/fixtures/npm/cases/type-patch-dts/main.ts b/tests/fixtures/npm/cases/type-patch-dts/main.ts new file mode 100644 index 000000000..1e442054b --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/main.ts @@ -0,0 +1,15 @@ +// A package whose .d.ts says `email: string` for a value that is missing, +// patched to `string | undefined` (package.json scriptc.typePatches): the +// profiles cross the declaration boundary as native records, and user +// code handles the missing email the patched type now makes visible. +import { findProfile, loadProfiles } from "profilekit"; + +const profiles = loadProfiles(); +for (const p of profiles) console.log(p.id, p.name, p.email ?? "(no email)", "email" in p); +console.log(JSON.stringify(profiles)); +const emails = profiles.filter((p) => p.email !== undefined).map((p) => p.email!.toUpperCase()); +console.log(emails.join(",")); +const found = findProfile(3); +console.log(found === undefined ? "none" : found.name); +const missing = findProfile(9); +console.log(missing === undefined ? "none" : missing.name); diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts new file mode 100644 index 000000000..445d78ea2 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts @@ -0,0 +1,10 @@ +// The declarations say every profile has an email and every lookup finds +// one; the JavaScript leaves email out for guests and returns undefined +// for unknown ids. +export type Profile = { + id: number; + name: string; + email: string; +}; +export declare function loadProfiles(): Profile[]; +export declare function findProfile(id: number): Profile; diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js new file mode 100644 index 000000000..a20a082f6 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js @@ -0,0 +1,14 @@ +"use strict"; +function row(id, name, email) { + const profile = { id: id, name: name }; + if (email !== null) profile.email = email; + return profile; +} +function loadProfiles() { + return [row(1, "ada", "ada@example.com"), row(2, "guest", null), row(3, "lin", "lin@example.com")]; +} +function findProfile(id) { + return loadProfiles().find((p) => p.id === id); +} +exports.loadProfiles = loadProfiles; +exports.findProfile = findProfile; diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json new file mode 100644 index 000000000..9982f9eef --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json @@ -0,0 +1 @@ +{ "name": "profilekit", "version": "3.1.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-dts/package.json b/tests/fixtures/npm/cases/type-patch-dts/package.json new file mode 100644 index 000000000..822491420 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/package.json @@ -0,0 +1,15 @@ +{ + "name": "type-patch-dts-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "profilekit@^3.0.0": { + "declarations": { + "Profile.email": "| undefined", + "findProfile()": "| undefined" + } + } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/main.ts b/tests/fixtures/npm/cases/type-patch-types-twin/main.ts new file mode 100644 index 000000000..702b51972 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/main.ts @@ -0,0 +1,11 @@ +// A plain-JavaScript package typed by an @types twin that says `max: +// number` and `mean: number`; the project's type patches (keyed by the +// runtime package and its installed version) make them `number | +// undefined` and `number | null`. +import { totals } from "tallyjs"; + +for (const values of [[3, 9, 4], []]) { + const t = totals(values); + console.log(t.count, t.sum, t.max ?? "no max", t.mean === null ? "no mean" : t.mean.toFixed(2)); + console.log(JSON.stringify(t), Object.keys(t).join("|")); +} diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts new file mode 100644 index 000000000..4a7b81dcc --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts @@ -0,0 +1,7 @@ +export type Totals = { + count: number; + sum: number; + max: number; + mean: number; +}; +export declare function totals(values: number[]): Totals; diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json new file mode 100644 index 000000000..fed3cc37a --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json @@ -0,0 +1 @@ +{ "name": "@types/tallyjs", "version": "0.4.0", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js new file mode 100644 index 000000000..88d5ffc8f --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js @@ -0,0 +1,11 @@ +"use strict"; +// Plain JavaScript; @types/tallyjs describes it, except that an empty +// list has no max (undefined) and no mean (null). +function totals(values) { + var sum = 0; + for (var i = 0; i < values.length; i++) sum += values[i]; + var max = values.length === 0 ? undefined : Math.max.apply(null, values); + var mean = values.length === 0 ? null : sum / values.length; + return { count: values.length, sum: sum, max: max, mean: mean }; +} +exports.totals = totals; diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json new file mode 100644 index 000000000..ad80ad430 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json @@ -0,0 +1 @@ +{ "name": "tallyjs", "version": "0.4.2", "main": "index.js" } diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/package.json new file mode 100644 index 000000000..53ec04ede --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/package.json @@ -0,0 +1,15 @@ +{ + "name": "type-patch-types-twin-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "tallyjs@~0.4.0": { + "declarations": { + "Totals.max": "| undefined", + "Totals.mean": "number | null" + } + } + } + } +} diff --git a/tests/fixtures/type-patches/jsdoc.ts b/tests/fixtures/type-patches/jsdoc.ts new file mode 100644 index 000000000..52cd13638 --- /dev/null +++ b/tests/fixtures/type-patches/jsdoc.ts @@ -0,0 +1,11 @@ +// A JSDoc @typedef property and a @returns that say number for a value +// that is undefined off a TTY, fixed by the project's type patches. +import { Terminal } from "termctx"; + +for (const tty of [false, true]) { + const term = new Terminal(tty); + const context = term.outputContext(false); + console.log(context.error, context.width, term.columns()); + console.log(term.describe(context)); + console.log(term.describe(term.outputContext(true))); +} diff --git a/tests/fixtures/type-patches/ms.ts b/tests/fixtures/type-patches/ms.ts new file mode 100644 index 000000000..a70e221d6 --- /dev/null +++ b/tests/fixtures/type-patches/ms.ts @@ -0,0 +1,9 @@ +// ms's JSDoc says parse() returns a Number and ms() a String or Number; +// for text it cannot parse both return undefined. +import ms from "ms"; + +for (const text of ["2 hours", "not a duration", "1".repeat(101), "-1.5d"]) { + const value = ms(text); + console.log(value === undefined ? "invalid" : value); +} +console.log(ms(90_000), ms(90_000, { long: true })); diff --git a/tests/fixtures/type-patches/node_modules/ms/index.js b/tests/fixtures/type-patches/node_modules/ms/index.js new file mode 100644 index 000000000..ea734fb73 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/index.js @@ -0,0 +1,162 @@ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function (val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} diff --git a/tests/fixtures/type-patches/node_modules/ms/license.md b/tests/fixtures/type-patches/node_modules/ms/license.md new file mode 100644 index 000000000..fa5d39b62 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/license.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/fixtures/type-patches/node_modules/ms/package.json b/tests/fixtures/type-patches/node_modules/ms/package.json new file mode 100644 index 000000000..b49dca98d --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/package.json @@ -0,0 +1,38 @@ +{ + "name": "ms", + "version": "2.1.3", + "description": "Tiny millisecond conversion utility", + "repository": "vercel/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "the formatter --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "4.18.2", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1", + "the formatter": "2.0.5" + } +} diff --git a/tests/fixtures/type-patches/node_modules/termctx/index.js b/tests/fixtures/type-patches/node_modules/termctx/index.js new file mode 100644 index 000000000..c8b2b9d2d --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/termctx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// A terminal helper whose JSDoc says widths are numbers; off a TTY they +// are undefined (the shape of commander's HelpContext.helpWidth). + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/termctx/package.json b/tests/fixtures/type-patches/node_modules/termctx/package.json new file mode 100644 index 000000000..1791f57af --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/termctx/package.json @@ -0,0 +1 @@ +{ "name": "termctx", "version": "1.2.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/package.json b/tests/fixtures/type-patches/package.json new file mode 100644 index 000000000..f1b9f630e --- /dev/null +++ b/tests/fixtures/type-patches/package.json @@ -0,0 +1,26 @@ +{ + "name": "type-patches-fixture", + "private": true, + "type": "module", + "dependencies": { + "ms": "2.1.3", + "termctx": "1.2.0" + }, + "scriptc": { + "typePatches": { + "termctx@^1.0.0": { + "declarations": { + "OutputContext.width": "| undefined", + "Terminal.columns()": "| undefined" + } + }, + "ms@^2.1.0": { + "note": "parse() returns undefined for text it cannot parse", + "declarations": { + "index.js:parse()": "| undefined", + "export=()": "| undefined" + } + } + } + } +} diff --git a/tests/fixtures/type-patches/stale/main.ts b/tests/fixtures/type-patches/stale/main.ts new file mode 100644 index 000000000..05ae25057 --- /dev/null +++ b/tests/fixtures/type-patches/stale/main.ts @@ -0,0 +1,4 @@ +import ms from "ms"; +import { Terminal } from "termctx"; + +console.log(ms(1000), new Terminal(true).columns()); diff --git a/tests/fixtures/type-patches/stale/package.json b/tests/fixtures/type-patches/stale/package.json new file mode 100644 index 000000000..53669c8b5 --- /dev/null +++ b/tests/fixtures/type-patches/stale/package.json @@ -0,0 +1,17 @@ +{ + "name": "type-patches-stale", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "termctx@^2.0.0": { "declarations": { "OutputContext.width": "| undefined" } }, + "ms@^2.1.0": { + "declarations": { + "index.js:format()": "| undefined", + "index.js:fmtShort(ms)": "| Number", + "index.js:plural()": "| undefined" + } + } + } + } +} diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts new file mode 100644 index 000000000..ecd8ee923 --- /dev/null +++ b/tests/harness/type-patches.test.ts @@ -0,0 +1,143 @@ +/* Type patches (package.json scriptc.typePatches): project-owned fixes for + * npm declarations that do not describe what the package does. A JSDoc + * @typedef property and @returns tag that say number for an undefined + * value (the shape of commander's HelpContext.helpWidth), and ms's @return + * tags, compile natively and byte-match Node once patched; without the + * patches the same declarations throw where Node runs. Patches that do not + * apply as written warn (SC8030-SC8033). The `.d.ts` and `@types` cases + * run in npm.test.ts (type-patch-dts, type-patch-types-twin), and + * commander-calc carries the HelpContext.helpWidth patch. */ +import { execFile } from "node:child_process"; +import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { describe, expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const fixture = join(repoRoot, "tests/fixtures/type-patches"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/type-patches"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +interface Run { + stdout: string; + stderr: string; + exitCode: number; +} + +async function run(cmd: string, args: string[]): Promise { + try { + const pending = execFileAsync(cmd, args, { encoding: "utf8" }); + pending.child.stdin?.end(); + const { stdout, stderr } = await pending; + return { stdout, stderr, exitCode: 0 }; + } catch (err) { + const e = err as { code?: unknown; stdout?: string; stderr?: string }; + if (typeof e.code !== "number") throw err; + return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", exitCode: e.code }; + } +} + +async function build(entry: string, name: string, npmStatic: string[]) { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, name), outDir, sanitize, npmStatic }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return result; +} + +/** A copy of a fixture directory whose package.json has no scriptc key. */ +function unpatched(dir: string): string { + const copy = mkdtempSync(join(tmpdir(), "scriptc-unpatched-")); + cpSync(dir, copy, { recursive: true }); + const manifest = join(copy, "package.json"); + const parsed = JSON.parse(readFileSync(manifest, "utf8")) as Record; + delete parsed["scriptc"]; + writeFileSync(manifest, JSON.stringify(parsed, null, 2)); + return copy; +} + +describe("type patches", () => { + test.for([ + ["jsdoc.ts", ["termctx"]], + ["ms.ts", ["ms"]], + ] as const)( + "%s compiles with its patches and byte-matches Node", + { timeout: 600_000 }, + async ([file, pkgs]) => { + const entry = join(fixture, file); + const result = await build(entry, file.replace(".ts", ""), [...pkgs]); + expect(result.warnings ?? []).toEqual([]); + const [node, native] = await Promise.all([ + run(process.execPath, [entry]), + run(result.binaryPath, []), + ]); + expect(node.exitCode).toBe(0); + expect(native).toEqual(node); + }, + ); + + test( + "without the patches the lying JSDoc throws where Node runs", + { timeout: 600_000 }, + async () => { + const copy = unpatched(fixture); + try { + const result = await build(join(copy, "jsdoc.ts"), "jsdoc-unpatched", ["termctx"]); + const native = await run(result.binaryPath, []); + expect(native.exitCode).not.toBe(0); + expect(native.stderr).toContain("TypeError"); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "without the patch the lying .d.ts throws at the declaration boundary", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-dts")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "dts-unpatched"), + outDir, + sanitize, + }); + // Unpatched, `p.email ?? ...` and `found === undefined` test values + // the declarations say cannot be missing; the build may refuse them + // or the boundary cast throws when the guest profile crosses. + if (result.ok) { + const native = await run(result.binaryPath, []); + expect(native.exitCode).not.toBe(0); + expect(native.stderr).toContain("TypeError: expected string at $, got undefined"); + } + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "patches that do not apply as written warn and the build goes on", + { timeout: 600_000 }, + async () => { + const entry = join(fixture, "stale/main.ts"); + const result = await build(entry, "stale", ["ms", "termctx"]); + expect((result.warnings ?? []).map((w) => w.code)).toEqual([ + "SC8030", + "SC8031", + "SC8032", + "SC8033", + ]); + const [node, native] = await Promise.all([ + run(process.execPath, [entry]), + run(result.binaryPath, []), + ]); + expect(native).toEqual(node); + }, + ); +}); From b0c01dc34174fb0f4257f686b9466ba1dcdbbcba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 12:48:07 -0500 Subject: [PATCH 243/395] Adapt the nursery trigger to where cyclic garbage dies A program's nursery trigger starts at 256 candidates and doubles, up to 4096, after each scheduled mature or full pass whose dead set held more mature and old objects than an eighth of what the nursery passes freed since the previous one; it halves after one that held less than a thirty-second of that. SCR_CYCLE_THRESHOLD and scr_cyc_set_nursery_threshold pin the trigger as before. The scheduled-age rule keeps counting collection points in the pinned or default trigger. --- packages/runtime/src/scr_cycle.c | 73 +++++++++++++++++++++++++++--- packages/runtime/src/scr_rc.h | 5 +- packages/runtime/test/test_cycle.c | 58 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index d08f54a21..0d9df7106 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -603,7 +603,8 @@ static SCR_TL unsigned scr_cyc_backlog_shift[SCR_CYC_NGENS]; static SCR_TL bool scr_cyc_backlog_judged[SCR_CYC_NGENS]; #endif -/* Mature objects in the last full pass's dead set (scr_cyc_adapt_growth). */ +/* Mature objects in the last pass's dead set (scr_cyc_adapt_growth, + * scr_cyc_adapt_nursery). */ #ifndef SCR_WORKERS static SCR_TL size_t scr_cyc_mature_dead = 0; #endif @@ -639,23 +640,74 @@ static SCR_TL size_t scr_cyc_death_due_live = 0; /* The nursery trigger a program sets for itself * (scr_cyc_set_nursery_threshold), 0 for the default; SCR_CYCLE_THRESHOLD - * takes precedence over it. */ + * takes precedence over it. Either pins the trigger. */ static long scr_cyc_nursery_threshold_set = 0; static SCR_TL size_t scr_cyc_nursery_threshold_cached = 0; +#define SCR_CYC_NURSERY_ADAPTIVE SIZE_MAX -static size_t scr_cyc_nursery_threshold(void) { +/* An unpinned trigger follows where cyclic garbage dies. Garbage that + * outlives the nursery window survives its nursery pass and is promoted, + * and then only a mature or full pass finds it, after walking that level's + * live candidates. So after each scheduled mature or full pass whose dead + * set held more mature and old objects than 1/SCR_CYC_NURSERY_RATIO of what + * the nursery passes since the previous one freed, the window doubles, up + * to SCR_CYC_NURSERY_MAX_SHIFT doublings (4096 candidates); after one that + * held less than a quarter of that share, it halves, down to + * SCR_CYC_NURSERY_CANDIDATES. + * `nursery_freed`: what the nursery passes freed since the last scheduled + * mature or full pass. */ +#define SCR_CYC_NURSERY_RATIO 8 +#define SCR_CYC_NURSERY_MAX_SHIFT 4 +static SCR_TL unsigned char scr_cyc_nursery_shift = 0; +static SCR_TL size_t scr_cyc_nursery_freed = 0; + +static size_t scr_cyc_nursery_pinned(void) { if (scr_cyc_nursery_threshold_cached == 0) { const char *env = scr_getenv("SCR_CYCLE_THRESHOLD"); long v = env ? strtol(env, NULL, 10) : 0; if (v <= 0) v = scr_cyc_nursery_threshold_set; - scr_cyc_nursery_threshold_cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_CANDIDATES; + scr_cyc_nursery_threshold_cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_ADAPTIVE; } return scr_cyc_nursery_threshold_cached; } +/* The pinned trigger, or the default one. The scheduled-age rule counts + * collection points in it, so the adaptation does not lengthen how long an + * older backlog waits at them. */ +static size_t scr_cyc_nursery_base(void) { + size_t pinned = scr_cyc_nursery_pinned(); + return pinned != SCR_CYC_NURSERY_ADAPTIVE ? pinned : SCR_CYC_NURSERY_CANDIDATES; +} + +static size_t scr_cyc_nursery_threshold(void) { + size_t pinned = scr_cyc_nursery_pinned(); + if (pinned != SCR_CYC_NURSERY_ADAPTIVE) return pinned; + return (size_t)SCR_CYC_NURSERY_CANDIDATES << scr_cyc_nursery_shift; +} + +/* Adapts an unpinned trigger to the mature and old objects a scheduled + * mature or full pass reclaimed; returns whether the trigger changed. */ +static SCR_CYC_SCHEDULING bool scr_cyc_adapt_nursery(size_t older_freed) { + size_t nursery_freed = scr_cyc_nursery_freed; + scr_cyc_nursery_freed = 0; + if (scr_cyc_nursery_pinned() != SCR_CYC_NURSERY_ADAPTIVE) return false; + if (older_freed * SCR_CYC_NURSERY_RATIO > nursery_freed) { + if (scr_cyc_nursery_shift == SCR_CYC_NURSERY_MAX_SHIFT) return false; + scr_cyc_nursery_shift++; + return true; + } + if (older_freed * SCR_CYC_NURSERY_RATIO * 4 < nursery_freed && scr_cyc_nursery_shift > 0) { + scr_cyc_nursery_shift--; + return true; + } + return false; +} + void scr_cyc_set_nursery_threshold(long threshold) { scr_cyc_nursery_threshold_set = threshold; scr_cyc_nursery_threshold_cached = 0; + scr_cyc_nursery_shift = 0; + scr_cyc_nursery_freed = 0; } /* The number of buffered candidates (across every generation) at which the @@ -881,7 +933,14 @@ static SCR_CYC_SCHEDULING void scr_cyc_scheduled_pass(unsigned gen, bool by_grow size_t freed = scr_cyc_pass(gen); scr_cyc_keep_base = false; size_t old_reclaimed = scr_cyc_old_freed - old_freed_before + scr_cyc_mature_dead; - if (gen == SCR_CYC_NURSERY) return; + if (gen == SCR_CYC_NURSERY) { + scr_cyc_nursery_freed += freed; + return; + } + if (scr_cyc_adapt_nursery(old_reclaimed)) { + scr_cyc_trigger = scr_cyc_buffered() + scr_cyc_nursery_threshold(); + scr_cyc_window_update(); + } if (scr_cands.n != 0) { scr_cyc_backlog_judged[gen] = true; if (freed * SCR_CYC_GROWTH_DIV >= scr_cands.n) @@ -940,7 +999,7 @@ void scr_cyc_collect_scheduled(void) { if (!scr_cyc_older_backlog()) { scr_cyc_scheduled_mature_age = 0; } else if (++scr_cyc_scheduled_mature_age - >= scr_cyc_nursery_threshold()) { + >= scr_cyc_nursery_base()) { scr_cyc_scheduled_pass(SCR_CYC_OLD, false, false); return; } @@ -1981,7 +2040,7 @@ static SCR_CYC_COMPACT size_t scr_cyc_settle(unsigned gen_limit, bool doomed) { ScrCycHdr *h = scr_cyc_hdr(obj); const ScrCycType *type = scr_cyc_type(h); if (gen_limit < SCR_CYC_OLD) scr_cyc_freed(type, gen_limit); - else scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; + scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; #ifdef SCR_YIN if (scr_cyc_gen(h) == SCR_CYC_YOUNG) scr_yin_leave(h, obj); #endif diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index de9f21f44..171e420c2 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -703,8 +703,9 @@ void scr_cyc_set_growth_cap(long cap); /* Sets the nursery trigger SCR_CYCLE_THRESHOLD sets (scr_cycle.c): the * number of new candidates at which the release path runs a pass, for a * program that knows its own collection profile. The environment variable - * takes precedence; 0 restores the default. The trigger re-arms with it at - * the end of the next pass. Call it before the program allocates much. */ + * takes precedence; 0 restores the default, which starts at 256 and adapts + * to the program. The trigger re-arms with it at the end of the next pass. + * Call it before the program allocates much. */ void scr_cyc_set_nursery_threshold(long threshold); void scr_cyc_context_cleanup(void); diff --git a/packages/runtime/test/test_cycle.c b/packages/runtime/test/test_cycle.c index 0ab827e47..dc48abd26 100644 --- a/packages/runtime/test/test_cycle.c +++ b/packages/runtime/test/test_cycle.c @@ -575,6 +575,60 @@ static void check_program_nursery_threshold(void) { scr_collect_cycles(); } +/* How many dead rings the release path takes to run its next pass, from + * the end of the previous one: the nursery trigger in effect. */ +static size_t nursery_window(void) { + size_t before = freed, n = 0; + while (freed == before && n < 65536) { + release_live(make_ring(1)); + n++; + } + check(freed - before == n, "a nursery pass left dead rings behind"); + return n; +} + +/* A scheduled full pass that reclaims nothing: its only candidate is an + * older live leaf. */ +static void scheduled_pass_finding_nothing(Node *leaf) { + size_t before = freed; + read_live(leaf); + for (size_t i = 0; i < configured_nursery_threshold(); i++) + scr_cyc_collect_scheduled(); + check(freed == before, "a scheduled pass over a live leaf freed something"); +} + +/* Unpinned, the nursery trigger starts at 256 candidates. Each scheduled + * full pass that reclaims promoted rings doubles it, up to 4096; each one + * that reclaims nothing after the nursery passes freed rings halves it, + * down to 256. */ +static void check_adaptive_nursery_threshold(void) { + if (getenv("SCR_CYCLE_THRESHOLD") != NULL) return; /* it pins the trigger */ + scr_cyc_set_nursery_threshold(0); + scr_collect_cycles(); + check(nursery_window() == 256, "the unpinned nursery trigger did not start at 256"); + static const size_t widened[] = { 512, 1024, 2048, 4096, 4096 }; + for (size_t i = 0; i < sizeof(widened) / sizeof(widened[0]); i++) { + collect_dead_mature_rings(); + check(nursery_window() == widened[i], + "a full pass reclaiming promoted rings did not double the nursery trigger"); + } + + static const size_t narrowed[] = { 2048, 1024, 512, 256, 256 }; + Node *leaf = make_leaf(); + read_live(leaf); + scr_collect_cycles(); /* promote */ + for (size_t i = 0; i < sizeof(narrowed) / sizeof(narrowed[0]); i++) { + scheduled_pass_finding_nothing(leaf); + check(nursery_window() == narrowed[i], + "a full pass reclaiming nothing did not halve the nursery trigger"); + } + leaf->rc--; + scr_cyc_on_dead(leaf); + node_free(leaf); + scr_cyc_set_nursery_threshold(0); + scr_collect_cycles(); +} + /* The phase sweep (scr_collect_cycles_phase) is for a caller that knows a * phase's data has just died: it sweeps, and the growth schedule starts * over. After passes that find nothing have widened the old level's @@ -1506,6 +1560,9 @@ static void check_acyclic_objects(void) { } int main(void) { + /* The scheduling checks count candidates against a fixed nursery trigger; + * check_adaptive_nursery_threshold covers the unpinned one. */ + scr_cyc_set_nursery_threshold((long)configured_nursery_threshold()); check_inline_buffering(); check_old_garbage_reclaimed_while_growing(); check_older_rings_into_old(); @@ -1541,6 +1598,7 @@ int main(void) { check_acyclic_objects(); check_program_growth_cap(); check_program_nursery_threshold(); + check_adaptive_nursery_threshold(); printf("cycle collection checks passed: threshold=%zu growth_cap=%zu\n", configured_nursery_threshold(), configured_growth_cap()); return 0; From e24ed129334c694ca15d0c04b36291fef7fa9b22 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 12:48:21 -0500 Subject: [PATCH 244/395] Decode string storage within its length in Buffer encodings String storage can hold bytes that are not well-formed UTF-8: an HTTP header value keeps the raw bytes a client sent. Encoding such a string read past its end. - scr_bytes_next_cp decoded any high byte as the lead of a sequence and read up to three bytes after it without a bound. It now takes the storage length, decodes only complete, well-formed sequences, and returns any other byte as U+0000..U+00FF, advancing by one. That is each such byte's value in Node, which decodes header values as latin1. This covers latin1, ascii, binary and utf16le encoding, hex and base64 of non-ASCII text (which narrow through latin1), Buffer#write and TextEncoder#encodeInto. - base64 decoding read one byte past its input when the eight-character groups ended exactly at the end. The runtime's bytes unit test (built with ASan) encodes ill-formed storage at every offset within a word and random high bytes, and compares latin1, ascii and utf16le with a scalar reference. A corpus program sends such header bytes to a server and encodes them as Node does. --- .../corpus/8010-http-header-raw-bytes.ts.json | 6 + packages/runtime/src/scr_bytes.c | 54 ++++---- packages/runtime/test/test_bytes.c | 130 ++++++++++++++++++ tests/corpus/8010-http-header-raw-bytes.ts | 72 ++++++++++ .../8010-http-header-raw-bytes.ts.json | 1 + 5 files changed, 235 insertions(+), 28 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json create mode 100644 tests/corpus/8010-http-header-raw-bytes.ts create mode 100644 tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json new file mode 100644 index 000000000..78240d310 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8010-http-header-raw-bytes.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index df64b2ab2..5fb8a58d4 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2240,29 +2240,26 @@ ScrStr *scr_text_decode_stream(ScrDyn *state, const ScrBytes *input, return result; } -/* Decode the next code point from an ScrStr's storage — ALWAYS valid - * UTF-8 (the string runtime never stores ill-formed sequences), so no - * validation re-runs here. */ -static uint32_t scr_bytes_next_cp(const uint8_t *in, size_t *ip) { - uint8_t b = in[*ip]; - if (b < 0x80) { - *ip += 1; - return b; - } - if ((b & 0xe0) == 0xc0) { - uint32_t cp = ((uint32_t)(b & 0x1f) << 6) | (in[*ip + 1] & 0x3f); - *ip += 2; - return cp; - } - if ((b & 0xf0) == 0xe0) { - uint32_t cp = ((uint32_t)(b & 0xf) << 12) | ((uint32_t)(in[*ip + 1] & 0x3f) << 6) | - (in[*ip + 2] & 0x3f); - *ip += 3; - return cp; - } - uint32_t cp = ((uint32_t)(b & 0x7) << 18) | ((uint32_t)(in[*ip + 1] & 0x3f) << 12) | - ((uint32_t)(in[*ip + 2] & 0x3f) << 6) | (in[*ip + 3] & 0x3f); - *ip += 4; +/* Decode the next code point from the n bytes of an ScrStr's storage. + * Storage is WTF-8, except where raw outside bytes become a string + * unchecked (HTTP header values). A byte that does not start a complete, + * well-formed sequence stands for U+0000..U+00FF, as each byte of such a + * header does in Node, and advances by one; nothing is read past n. */ +static uint32_t scr_bytes_next_cp(const uint8_t *in, size_t n, size_t *ip) { + size_t i = *ip; + uint8_t b = in[i]; + *ip = i + 1; + if (b < 0x80) return b; + size_t len = b >= 0xc2 && b <= 0xdf ? 2 : b >= 0xe0 && b <= 0xef ? 3 : b >= 0xf0 && b <= 0xf4 ? 4 : 0; + if (len == 0 || n - i < len) return b; + uint8_t lo = b == 0xe0 ? 0xa0 : b == 0xf0 ? 0x90 : 0x80; + uint8_t hi = b == 0xf4 ? 0x8f : 0xbf; + if (in[i + 1] < lo || in[i + 1] > hi) return b; + for (size_t k = 2; k < len; k++) + if ((in[i + k] & 0xc0) != 0x80) return b; + uint32_t cp = b & (0x7f >> len); + for (size_t k = 1; k < len; k++) cp = (cp << 6) | (in[i + k] & 0x3f); + *ip = i + len; return cp; } @@ -2287,7 +2284,7 @@ ScrDyn *scr_text_encode_into(const ScrDyn *source, const ScrDyn *destination) { read += ascii; if (written == limit) break; size_t end = written; - uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, &end); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, text->len, &end); if (end > dest->len) break; read += cp > 0xffff ? 2 : 1; written = end; @@ -2440,7 +2437,7 @@ ScrBytes *scr_buffer_transcode(const ScrDyn *source, const ScrDyn *from, const S } ScrStr *text = scr_str_from_utf8_lossy(input->data, len); for (size_t i = 0; i < text->len;) { - uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, &i); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, text->len, &i); written = scr_transcode_put(result->data, written, cp, to_encoding); } scr_str_release(text); @@ -2774,6 +2771,7 @@ static ScrBytes *scr_bytes_decode_b64(ScrBytesSink *sink, const uint8_t *in, siz o += 6; i += 8; } + if (i == n) break; /* the groups ended the input */ } /* Common complete groups need neither per-character branches nor state. */ if (have == 0 && n - i >= 4) { @@ -2824,7 +2822,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const size_t o = s->ascii ? n : scr_bytes_ascii_prefix(in, n), i = o; memcpy(b->data, in, o); while (i < n) { - uint32_t cp = scr_bytes_next_cp(in, &i); + uint32_t cp = scr_bytes_next_cp(in, n, &i); if (cp > 0xffff) { cp -= 0x10000; b->data[o++] = (uint8_t)(0xd800 + (cp >> 10)); @@ -2841,7 +2839,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const ScrBytes *b = scr_bytes_conversion_storage_in(sink, n * 2); size_t o = 0, i = 0; while (i < n) { - uint32_t cp = scr_bytes_next_cp(in, &i); + uint32_t cp = scr_bytes_next_cp(in, n, &i); if (cp > 0xffff) { cp -= 0x10000; uint32_t hi = 0xd800 + (cp >> 10), lo = 0xdc00 + (cp & 0x3ff); @@ -3745,7 +3743,7 @@ double scr_bytes_write_str(ScrBytes *b, const ScrStr *s, const ScrStr *enc, continue; } } - uint32_t cp = scr_bytes_next_cp((const uint8_t *)s->data, &i); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)s->data, s->len, &i); uint32_t first = cp, second = 0; if (cp > 0xffff) { cp -= 0x10000; diff --git a/packages/runtime/test/test_bytes.c b/packages/runtime/test/test_bytes.c index 8c6602948..1aea7a2d0 100644 --- a/packages/runtime/test/test_bytes.c +++ b/packages/runtime/test/test_bytes.c @@ -216,6 +216,135 @@ static void test_encodings(void) { scr_str_release(b64); } +/* The length of the well-formed WTF-8 sequence at in[i], or 0. */ +static size_t ref_seq_len(const uint8_t *in, size_t n, size_t i) { + uint8_t b = in[i]; + if (b < 0x80) return 1; + size_t len; + uint8_t lo = 0x80, hi = 0xbf; + if (b >= 0xc2 && b <= 0xdf) len = 2; + else if (b >= 0xe0 && b <= 0xef) len = 3; + else if (b >= 0xf0 && b <= 0xf4) len = 4; + else return 0; + if (b == 0xe0) lo = 0xa0; + if (b == 0xf0) lo = 0x90; + if (b == 0xf4) hi = 0x8f; + if (n - i < len || in[i + 1] < lo || in[i + 1] > hi) return 0; + for (size_t k = 2; k < len; k++) + if ((in[i + k] & 0xc0) != 0x80) return 0; + return len; +} + +/* The UTF-16 units of string storage, one at a time: a well-formed + * sequence gives its code point's units and any other byte stands for + * U+0000..U+00FF (a raw HTTP header byte). */ +static size_t ref_units(const uint8_t *in, size_t n, uint16_t *out) { + size_t k = 0; + for (size_t i = 0; i < n;) { + size_t len = ref_seq_len(in, n, i); + if (len == 0) { + out[k++] = in[i++]; + continue; + } + uint32_t cp = len == 1 ? in[i] : (uint32_t)(in[i] & (0x7f >> len)); + for (size_t j = 1; j < len; j++) cp = (cp << 6) | (in[i + j] & 0x3f); + i += len; + if (cp > 0xffff) { + out[k++] = (uint16_t)(0xd800 + ((cp - 0x10000) >> 10)); + out[k++] = (uint16_t)(0xdc00 + ((cp - 0x10000) & 0x3ff)); + } else { + out[k++] = (uint16_t)cp; + } + } + return k; +} + +/* Encodes storage that is not well-formed (raw header bytes become string + * storage unchecked) and compares latin1, ascii and utf16le with the + * reference; hex and base64 decode the latin1 bytes. ASan checks that no + * path reads or writes outside either buffer. */ +/* encs: latin1, ascii, utf16le, hex, base64. */ +static void check_raw_encodings(ScrStr *const *encs, const uint8_t *in, size_t n, const char *what) { + ScrStr *latin1 = encs[0], *ascii = encs[1], *utf16le = encs[2], *hex = encs[3], *b64 = encs[4]; + ScrStr *s = scr_str_new((const char *)in, n); + uint16_t units[64]; + size_t k = ref_units(in, n, units); + ScrStr *narrow[] = {latin1, ascii}; + for (size_t e = 0; e < 2; e++) { + ScrBytes *b = scr_bytes_from_str(s, narrow[e]); + bool ok = b->len == k; + for (size_t j = 0; ok && j < k; j++) ok = b->data[j] == (uint8_t)units[j]; + check(ok, what); + scr_bytes_release(b); + } + ScrBytes *wide = scr_bytes_from_str(s, utf16le); + bool ok = wide->len == 2 * k; + for (size_t j = 0; ok && j < k; j++) + ok = wide->data[2 * j] == (uint8_t)units[j] && wide->data[2 * j + 1] == units[j] >> 8; + check(ok, what); + scr_bytes_release(wide); + scr_bytes_release(scr_bytes_from_str(s, hex)); + scr_bytes_release(scr_bytes_from_str(s, b64)); + scr_str_release(s); +} + +static void test_raw_storage_encodings(void) { + /* Each pattern at every offset in an eight-byte word, alone and followed + * by ASCII, two-byte characters or more of the pattern. */ + static const struct { const char *bytes; size_t n; } patterns[] = { + {"\x80", 1}, {"\x80\x81\xbf\x90", 4}, {"\xc3", 1}, {"\xc3\xa9", 2}, + {"\xc3\xa9\xc3", 3}, {"\xc0\x80\xc1\xbf", 4}, {"\xe9\x41\xe9", 3}, + {"\xf0\x9f\x41", 3}, {"\xe2\x82", 2}, {"\xe2\x82\xac", 3}, + {"\xf0\x9f\x98\x80", 4}, {"\xed\xa0\x80", 3}, {"\xff\xfe\xf5", 3}, + {"\xdf\xbf\x80\x80", 4}, {"\xc2\x80\xc2", 3}, {"x\xe9y\x80\x80z\xdf", 7}, + }; + static const struct { const char *bytes; size_t n; } tails[] = { + {"", 0}, {"bcdefghij", 9}, {"\xc3\xa9\xc3\xa9\xc3\xa9\xc3\xa9", 8}, {"\x80\xc3\xa9", 3}, + }; + ScrStr *encs[] = {S("latin1"), S("ascii"), S("utf16le"), S("hex"), S("base64")}; + uint8_t buf[64]; + char what[96]; + for (size_t p = 0; p < sizeof patterns / sizeof patterns[0]; p++) { + for (size_t offset = 0; offset < 17; offset++) { + for (size_t t = 0; t < sizeof tails / sizeof tails[0]; t++) { + for (size_t lead = 0; lead < 2; lead++) { + /* lead: the offset is two-byte characters instead of ASCII. */ + size_t n = 0; + for (size_t j = 0; j < offset; j++) { + if (lead && j + 1 < offset) { + buf[n++] = 0xc3; + buf[n++] = 0xa9; + j++; + } else { + buf[n++] = 'a'; + } + } + memcpy(buf + n, patterns[p].bytes, patterns[p].n); + n += patterns[p].n; + memcpy(buf + n, tails[t].bytes, tails[t].n); + n += tails[t].n; + snprintf(what, sizeof what, "raw storage encodings: pattern %zu offset %zu tail %zu lead %zu", + p, offset, t, lead); + check_raw_encodings(encs, buf, n, what); + } + } + } + } + /* Random bytes, mostly high, of every length up to 40. */ + uint32_t seed = 12345; + for (int round = 0; round < 4000; round++) { + size_t n = (size_t)round % 41; + for (size_t j = 0; j < n; j++) { + seed = seed * 1103515245u + 12345u; + uint8_t r = (uint8_t)(seed >> 16); + buf[j] = (seed >> 27) & 1 ? (uint8_t)(0x80 | r) : (uint8_t)(r & 0x7f); + } + snprintf(what, sizeof what, "raw storage encodings: random round %d", round); + check_raw_encodings(encs, buf, n, what); + } + for (size_t e = 0; e < sizeof encs / sizeof encs[0]; e++) scr_str_release(encs[e]); +} + static void test_concat_and_u32be(void) { ScrArr *list = scr_arr_new(SCR_ELEM_BYTES, 0); ScrBytes *a = scr_bytes_new(SCR_BYTES_U8, 2); @@ -445,6 +574,7 @@ int main(int argc, char **argv) { scr_bytes_test_bulk_storage(); scr_bytes_test_buffer_pool(); test_encodings(); + test_raw_storage_encodings(); test_concat_and_u32be(); test_from_arr_and_random(); test_zlib_roundtrip(); diff --git a/tests/corpus/8010-http-header-raw-bytes.ts b/tests/corpus/8010-http-header-raw-bytes.ts new file mode 100644 index 000000000..d8408f335 --- /dev/null +++ b/tests/corpus/8010-http-header-raw-bytes.ts @@ -0,0 +1,72 @@ +// Header values hold the raw bytes a client sent, one character per byte +// (Node decodes them as latin1). Bytes that are not well-formed UTF-8 (lone +// continuation bytes, runs of them, truncated leads, overlong leads and +// bytes no sequence uses) at every offset within an eight-byte word encode +// to the same bytes as in Node with latin1, ascii, binary, hex, base64 and +// utf16le. Strings built in the program take the same encodings with two-, +// three- and four-byte characters at every offset. +import { createServer } from "node:http"; +import { connect } from "node:net"; + +const patterns: number[][] = [ + [0x80], + [0x80, 0x81, 0xbf, 0x90], + [0xc3], + [0xc0, 0x80, 0xc1, 0xbf], + [0xe9, 0x41, 0xe9], + [0xf0, 0x9f, 0x41], + [0xe2, 0x82], + [0xff, 0xfe, 0xf5], + [0x78, 0xe9, 0x79, 0x80, 0x80, 0x7a, 0xdf], +]; +const encodings = ["latin1", "ascii", "binary", "hex", "base64", "utf16le"] as const; + +function hex(bytes: Buffer): string { + return bytes.toString("hex"); +} + +function describe(label: string, value: string): void { + console.log(label, encodings.map((encoding) => `${encoding}=${hex(Buffer.from(value, encoding))}`).join(" ")); +} + +const names: string[] = []; +const head: Buffer[] = [Buffer.from("GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n")]; +for (let p = 0; p < patterns.length; p++) { + for (let offset = 0; offset < 10; offset++) { + for (const tail of ["", "bcdefghij"]) { + const name = `x-${p}-${offset}-${tail.length}`; + names.push(name); + head.push(Buffer.from(`${name}: ${"a".repeat(offset)}`)); + head.push(Buffer.from(patterns[p]!)); + head.push(Buffer.from(`${tail}\r\n`)); + } + } +} +head.push(Buffer.from("\r\n")); + +const server = createServer((req, res) => { + for (const name of names) { + const value = req.headers[name]; + if (typeof value === "string") describe(name, value); + else console.log(name, "missing"); + } + res.end("ok"); +}); + +server.listen(0, "127.0.0.1", () => { + const address = server.address(); + const port = typeof address === "object" && address !== null ? address.port : 0; + const socket = connect({ port, host: "127.0.0.1" }, () => { + socket.write(Buffer.concat(head)); + }); + socket.on("data", () => {}); + socket.on("close", () => { + server.close(); + for (const text of ["é", "éü", "߀", "😀", "é😀ü", "߿\u0080"]) { + for (let offset = 0; offset < 17; offset++) { + const value = "a".repeat(offset) + text + "b".repeat(offset % 9) + text; + describe(`text ${offset} ${JSON.stringify(text)}`, value); + } + } + }); +}); diff --git a/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json b/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json new file mode 100644 index 000000000..532e3bd74 --- /dev/null +++ b/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json @@ -0,0 +1 @@ +{ "plain": 3468, "san": 7463 } From a88fdacbe2ab53692a6e8ed9c76f6acba0847a7a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:00:18 -0500 Subject: [PATCH 245/395] Iterate strings over their bytes for-of, spread, Array.from, yield* and [Symbol.iterator]() over a string walked a UTF-16 index. Each step mapped the index to a byte offset, built the character, and measured its UTF-16 length to advance. They now walk a byte cursor over the WTF-8 data: - The cpAtByte intrinsic reads the character that starts at the cursor. ASCII bytes take the one-character table inline; other characters call scr_str_cp_at_byte. byteLength advances the cursor by the character's byte count. A lone surrogate is its own three-byte character, so it stays a one-unit character, as in Node. - Spread and Array.from of a string call scr_str_code_points, which counts the characters, allocates the array once and fills it. - The one-character strings of U+0080..U+07FF are made immortal on first use and shared, as the ASCII ones are static. The cpAt intrinsic, which no lowering emits any more, is removed. --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/string-iteration.json | 6 + .../runtime/workloads/string-iteration.ts | 59 +++++++++ .../src/backend/llvm/expr-containers.ts | 36 ++++-- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/backend/llvm/string-lifetimes.test.ts | 2 +- .../src/backend/llvm/string-lifetimes.ts | 3 +- .../compiler/src/backend/llvm/string-reads.ts | 27 ++++ .../lowering/containers/array-construction.ts | 115 +----------------- .../lowering/containers/array-pipelines.ts | 3 +- .../lowering/containers/collection-methods.ts | 2 +- .../lowering/expressions/array-literals.ts | 2 +- .../frontend/lowering/generator-delegation.ts | 5 +- .../frontend/lowering/iterator-adapters.ts | 6 +- .../src/frontend/lowering/lower-stmts.ts | 32 ++--- .../compiler/src/ir/builtin-signatures.ts | 1 + packages/compiler/src/ir/integer-ranges.ts | 2 + .../compiler/src/ir/intrinsic-signatures.ts | 3 +- packages/compiler/src/ir/ir.ts | 11 +- .../test/string-iteration-emission.test.ts | 47 +++++++ .../8347-string-iteration-code-points.ts.json | 6 + packages/runtime/src/scr_string.c | 72 ++++++++++- packages/runtime/src/scr_string_methods.h | 9 ++ tests/corpus/1561-forof-strings.ts | 4 +- .../8347-string-iteration-code-points.ts | 96 +++++++++++++++ .../8347-string-iteration-code-points.ts.json | 1 + 26 files changed, 399 insertions(+), 153 deletions(-) create mode 100644 benchmarks/runtime/workloads/string-iteration.json create mode 100644 benchmarks/runtime/workloads/string-iteration.ts create mode 100644 packages/compiler/test/string-iteration-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json create mode 100644 tests/corpus/8347-string-iteration-code-points.ts create mode 100644 tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index af3d3d239..0157503fd 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -32,6 +32,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `regex-logs` | cpu | regex `exec` with groups, `test`, global `replace` | | `regex-replacers` | cpu | regex `replace`/`replaceAll` with inline callbacks whose parameters have no annotations: dictionary placeholders, snake_case to camelCase, date reordering, optional captures, capitalization, and a string search that reads the offset | | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | +| `string-iteration` | cpu | code point iteration over text that mixes ASCII, Latin-1 letters, CJK and emoji: a for-of word tokenizer, spread reversal, and `Array.from` over words | | `template-render` | cpu | template literals, escaping, `map`/`join` | | `throw-chains` | cpu | exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and through a callback, and a backtracking parser that throws on a failed alternative | | `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread | diff --git a/benchmarks/runtime/workloads/string-iteration.json b/benchmarks/runtime/workloads/string-iteration.json new file mode 100644 index 000000000..21456bd23 --- /dev/null +++ b/benchmarks/runtime/workloads/string-iteration.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/string-iteration.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/string-iteration.ts b/benchmarks/runtime/workloads/string-iteration.ts new file mode 100644 index 000000000..2abebd635 --- /dev/null +++ b/benchmarks/runtime/workloads/string-iteration.ts @@ -0,0 +1,59 @@ +// Code point iteration over mixed text: a word tokenizer over for-of, a +// character histogram, reversal through spread, and Array.from over +// words. The text mixes ASCII, Latin-1 letters, CJK and emoji. +let seed = 11; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +const pieces = [ + "the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog", + "café", "naïve", "über", "señor", "straße", "déjà", "vu", + "日本", "語", "テキスト", "中文", "한국어", + "😀", "🚀", "👍", "🎉", +]; +const punctuation = [" ", " ", " ", ", ", ". ", "! ", "\n"]; +const scale = Number(process.argv[2] ?? "1"); +const lines: string[] = []; +for (let i = 0; i < Math.floor(4000 * scale); i++) { + let line = ""; + const words = 6 + Math.floor(random() * 10); + for (let w = 0; w < words; w++) { + line += pieces[Math.floor(random() * pieces.length)]!; + line += punctuation[Math.floor(random() * punctuation.length)]!; + } + lines.push(line); +} + +function isWordChar(c: string): boolean { + const code = c.codePointAt(0)!; + return (code >= 0x61 && code <= 0x7a) || (code >= 0x41 && code <= 0x5a) || code >= 0xc0; +} + +let tokens = 0; +let tokenUnits = 0; +let reversedLength = 0; +let fromLength = 0; +const histogram = new Map(); +for (let round = 0; round < 30; round++) { + for (const line of lines) { + let word = ""; + for (const c of line) { + if (isWordChar(c)) { + word += c; + continue; + } + if (word !== "") { + tokens++; + tokenUnits += word.length; + if (round === 0) histogram.set(word, (histogram.get(word) ?? 0) + 1); + fromLength += Array.from(word).length; + word = ""; + } + } + reversedLength += [...line].reverse().join("").length; + } +} +const top = [...histogram].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).slice(0, 5); +console.log(top.map(([word, count]) => `${word}:${count}`).join(" ")); +console.log("tokens", tokens, "units", tokenUnits, "reversed", reversedLength, "from", fromLength); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 307481737..4360ae924 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -49,11 +49,13 @@ import { emitFusedMapSet, rememberMapEntry } from "./map-entry-fusion.js"; import { emitScratchStringSlice, emitStringSliceRead } from "./string-slices.js"; import { STRING_CHAR_AT_DECL, + STRING_CP_AT_BYTE_DECL, STRING_CHAR_AT_INT_MARKER, STRING_CHAR_CODE_AT_DECL, STRING_CHAR_CODE_AT_INT_MARKER, emitAsciiChar, emitAsciiCharCode, + emitStringByteLength, emitUnitCharCode, STRING_UTF16_LEN_DECL, } from "./string-reads.js"; @@ -564,16 +566,30 @@ export function emitStrIntrinsic( return call("scr_str_is_well_formed", "zeroext i1 (ptr)", `ptr ${r.name}`, "i1", false); case "toWellFormed": return call("scr_str_to_well_formed", "ptr (ptr)", `ptr ${r.name}`, "ptr", true); - case "cpAt": - // The code point AT an index as a one-code-point string (+1) — - // the string-for-of desugar's read. - return call( - "scr_str_cp_at", - "ptr (ptr, double)", - `ptr ${r.name}, double ${args[0]!.name}`, - "ptr", - true, - ); + case "byteLength": { + const t = B.tmp(); + // A byte count fits in a signed size, whose conversion is one instruction. + B.line(`${t} = sitofp ${host.sizeType} ${emitStringByteLength(host, r.name)} to double`); + return { name: t, type: e.type }; + } + case "cpAtByte": { + // sc_str_cp_at_byte (string-reads.ts) answers ASCII bytes from the + // charAt table inline. The cursor is a character start, an exact + // integer below the byte count. + host.declare(STRING_CHAR_AT_DECL); + host.declare(STRING_CP_AT_BYTE_DECL); + const index = e.args[0] ? exactInteger(host, args[0]!, e.args[0]) : null; + let wide: string; + if (index) { + wide = widenInteger(host, index); + } else { + wide = B.tmp(); + B.line(`${wide} = fptosi double ${args[0]!.name} to i64`); + } + const t = B.tmp(); + B.line(`${t} = call ptr @sc_str_cp_at_byte(ptr ${r.name}, i64 ${wide})`); + return host.own({ name: t, type: e.type }); + } default: { const _exhaustive: never = method; void _exhaustive; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2548b75d5..7688746b8 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -104,6 +104,7 @@ export const LIB_FN_SYMS: Record = { "str.localeCase": "scr_str_locale_case", "str.replaceLiteral": "scr_str_replace_literal", "str.replaceChars": "scr_str_replace_chars", + "str.codePoints": "scr_str_code_points", "num.fromString": "scr_string_to_number", "math.round": "scr_math_round", "math.sin": "sin", diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts index 07ad31153..72fc57752 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts @@ -341,7 +341,7 @@ const methodCases: { { method: "normalize", args: [ref("argument")], result: STRING, target: "normalize" }, { method: "isWellFormed", args: [], result: BOOL, target: "is_well_formed" }, { method: "toWellFormed", args: [], result: STRING, target: "to_well_formed" }, - { method: "cpAt", args: [num(1)], result: STRING, target: "cp_at" }, + { method: "cpAtByte", args: [num(1)], result: STRING, target: "cp_at_byte", inline: true }, ]; test.each(methodCases)( diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.ts b/packages/compiler/src/backend/llvm/string-lifetimes.ts index 6c3079cc1..5a4504813 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.ts @@ -30,7 +30,8 @@ export function borrowsStringInputs(method: IrStrIntrinsicMethod): boolean { case "normalize": case "isWellFormed": case "toWellFormed": - case "cpAt": + case "byteLength": + case "cpAtByte": return true; default: return false; diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 5b7a26c6c..1f77de2c2 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -43,6 +43,10 @@ export const STRING_CHAR_CODE_AT_INT_MARKER = "; uses @sc_str_char_code_at_int"; export const STRING_CHAR_AT_DECL = "declare ptr @scr_str_char_at(ptr, double)"; /** Requests the integer-index charAt helper, as the marker above does. */ export const STRING_CHAR_AT_INT_MARKER = "; uses @sc_str_char_at_int"; +/** The code-point character starting at a byte offset (the string + * iterator's step). Requested together with STRING_CHAR_AT_DECL, which + * defines the ASCII table. */ +export const STRING_CP_AT_BYTE_DECL = "declare ptr @scr_str_cp_at_byte(ptr, i64)"; /** The proven-ASCII flag of string `s`, as an i1. */ export function emitStringAsciiFlag(host: LlvmEmitterContext, s: string): string { @@ -351,6 +355,29 @@ export const STRING_READ_HELPERS: readonly StringReadHelper[] = [ ]; }, }, + { + // The string iterator's step at byte `%b`, a character start below the + // byte count: an ASCII byte is the table entry for it; any other lead + // byte takes the runtime, which copies the character's bytes. + decl: STRING_CP_AT_BYTE_DECL, + define: (sz, attrs) => [ + `define internal ptr @sc_str_cp_at_byte(ptr %s, i64 %b) ${attrs} {`, + `entry:`, + ` %data = getelementptr inbounds %ScrStr, ptr %s, i32 1`, + ` %p = getelementptr inbounds i8, ptr %data, i64 %b`, + ` %byte = load i8, ptr %p`, + ` %ascii = icmp sge i8 %byte, 0`, + ` br i1 %ascii, label %read, label %slow`, + `read:`, + ` %code = zext i8 %byte to i64`, + ` %char = getelementptr inbounds [128 x { ${sz}, ${sz}, ${sz}, [2 x i8] }], ptr @sc_ascii_chars, i64 0, i64 %code`, + ` ret ptr %char`, + `slow:`, + ` %r = call ptr @scr_str_cp_at_byte(ptr %s, i64 %b) nounwind willreturn`, + ` ret ptr %r`, + `}`, + ], + }, { // An exact integer index, as for sc_str_char_code_at_int. Requested // together with STRING_CHAR_AT_DECL, which defines the table. diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 5029abc36..702837e40 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -1081,9 +1081,7 @@ export function lowerArrayFromCall( if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0]!)) { const source = lowerer.lowerExpr(args[0]!); let src = - source.type.kind === "string" && args.length === 2 - ? strCharsCall(lowerer, source, loc) - : source; + source.type.kind === "string" && args.length === 2 ? strCharsCall(source, loc) : source; if (src.type.kind === "array") { const arrT = src.type; if (args.length === 1) { @@ -1171,7 +1169,7 @@ export function lowerArrayFromCall( } // String iteration advances by code point; the same helper serves // `[...s]` and both Array.from(s) forms. - if (src.type.kind === "string" && args.length === 1) return strCharsCall(lowerer, src, loc); + if (src.type.kind === "string" && args.length === 1) return strCharsCall(src, loc); if (src.type.kind === "dyn" && args.length === 1) { return lowerCheckedArrayFrom(lowerer, src, loc); } @@ -1318,112 +1316,9 @@ function buildArrayFromArrayFn( } /** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a - * fresh string[], through one interned helper per module. */ -export function strCharsCall(lowerer: Lowerer, src: IrExpr, loc: SrcLoc): IrExpr { - const key = "strChars"; - let helper = lowerer.arrHofHelpers.get(key); - if (!helper) { - helper = "%str.chars"; - lowerer.arrHofHelpers.set(key, helper); - lowerer.liftedFns.push(buildStrCharsFn(helper, loc)); - } - return { kind: "call", callee: helper, args: [src], type: arrayOf(STRING), loc }; -} - -/** The code-point split, from existing IR nodes — the string for-of - * desugar's UTF-16 cursor as a function: - * - * out = []; - * i = 0; - * while (i < s.length) { ch = cpAt(s, i); i += ch.length; out.push(ch); } - * return out; - */ -function buildStrCharsFn(name: string, loc: SrcLoc): IrFunction { - const outT = arrayOf(STRING); - - const sLen = (recv: IrExpr): IrExpr => ({ - kind: "strIntrinsic", - method: "length", - receiver: recv, - args: [], - type: F64, - loc, - }); - const body: IrStmt[] = [ - { - kind: "varDecl", - localId: "out.0", - init: { kind: "arrayLit", elems: [], type: outT, loc }, - loc, - }, - { kind: "varDecl", localId: "i.0", init: { kind: "numLit", value: 0, type: F64, loc }, loc }, - { - kind: "while", - cond: { - kind: "bin", - op: "<", - left: varRef("i.0", F64, loc), - right: sLen(varRef("s.0", STRING, loc)), - type: BOOL, - loc, - }, - body: [ - { - kind: "varDecl", - localId: "ch.0", - init: { - kind: "strIntrinsic", - method: "cpAt", - receiver: varRef("s.0", STRING, loc), - args: [varRef("i.0", F64, loc)], - type: STRING, - loc, - }, - loc, - }, - { - kind: "assign", - localId: "i.0", - value: { - kind: "bin", - op: "+", - left: varRef("i.0", F64, loc), - right: sLen(varRef("ch.0", STRING, loc)), - type: F64, - loc, - }, - loc, - }, - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: varRef("out.0", outT, loc), - args: [varRef("ch.0", STRING, loc)], - type: F64, - loc, - }, - loc, - }, - ], - loc, - }, - { kind: "return", value: varRef("out.0", outT, loc), loc }, - ]; - return { - name, - params: [{ localId: "s.0", name: "s", type: STRING }], - returnType: outT, - locals: [ - { id: "s.0", name: "s", type: STRING, mutable: true }, - { id: "out.0", name: "out", type: outT, mutable: false }, - { id: "i.0", name: "i", type: F64, mutable: true }, - { id: "ch.0", name: "ch", type: STRING, mutable: false }, - ], - body, - loc, - }; + * fresh string[] (scr_str_code_points). */ +export function strCharsCall(src: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "str.codePoints", args: [src], type: arrayOf(STRING), loc }; } /** The lowered `length` value of the one-property source literal, or null diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 197af2a9e..9cc96d0a5 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -156,7 +156,8 @@ const PURE_STRING = new Set([ "toUpperCase", "isWellFormed", "toWellFormed", - "cpAt", + "byteLength", + "cpAtByte", ]); function primitive(t: IrType): boolean { diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index f18291a63..bfaca6f96 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -1135,7 +1135,7 @@ function setFromSeedValue( if (source.type.kind === "set" && typeEquals(source.type, setT)) { return { kind: "setIntrinsic", method: "clone", receiver: source, args: [], type: setT, loc }; } else if (source.type.kind === "string" && setT.elem.kind === "string") { - seed = strCharsCall(lowerer, source, loc); + seed = strCharsCall(source, loc); } else if (source.type.kind === "array" && typeEquals(source.type.elem, setT.elem)) { seed = source; } else if ( diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index 903f0884d..77c16594f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -504,7 +504,7 @@ export function lowerArrayLiteral( // interned helper as Array.from(s); the result rides the array // machinery below like any string[] source. if (src.type.kind === "string") { - src = strCharsCall(lowerer, src, locOf(el)); + src = strCharsCall(src, locOf(el)); } // `[...typedArray]`: represented typed arrays are dense numeric // iterables, so drain their elements into the fresh number[] that diff --git a/packages/compiler/src/frontend/lowering/generator-delegation.ts b/packages/compiler/src/frontend/lowering/generator-delegation.ts index a606c177e..81ecc8353 100644 --- a/packages/compiler/src/frontend/lowering/generator-delegation.ts +++ b/packages/compiler/src/frontend/lowering/generator-delegation.ts @@ -577,10 +577,11 @@ export function lowerBuiltinDelegation( loc, }); } else if (sourceT.kind === "string") { + // A byte cursor over the WTF-8 data, as for-of over a string walks. const ch = lowerer.declareHiddenLocal("%delegateElement", STRING); const length = (receiver: IrExpr): IrExpr => ({ kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver, args: [], type: F64, @@ -595,7 +596,7 @@ export function lowerBuiltinDelegation( localId: ch.id, init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: src(), args: [at()], type: STRING, diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index 4909e7f45..78fcff84d 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -743,7 +743,7 @@ export function lowerStringIteratorValue( left: index, right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: text, args: [], type: F64, @@ -758,7 +758,7 @@ export function lowerStringIteratorValue( localId: "point.0", init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: text, args: [index], type: STRING, @@ -775,7 +775,7 @@ export function lowerStringIteratorValue( left: index, right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: point, args: [], type: F64, diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..9586727b3 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2668,7 +2668,7 @@ export function lowerBindingPattern( lowerer.unsupported("SC1031", el, "rest elements over a union of array or tuple layouts"); const loc = locOf(el); let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc, (text) => - strCharsCall(lowerer, text, loc), + strCharsCall(text, loc), ); if (!value) lowerer.unsupported( @@ -2798,7 +2798,7 @@ export function lowerBindingPattern( } // STRING sources (`const [a, b] = str`): array destructuring walks // the STRING ITERATOR — code points, astral characters whole — - // which is Array.from(string)'s own machinery (%str.chars), so the + // which is Array.from(string)'s own machinery (str.codePoints), so the // source splits once into a hidden chars array and the pattern // lowers as an array pattern over string[]. Positions, holes, // defaults (with the bounds test), rest (the remaining code points @@ -2817,7 +2817,7 @@ export function lowerBindingPattern( out.push({ kind: "varDecl", localId: chars.id, - init: strCharsCall(lowerer, srcRef(), loc), + init: strCharsCall(srcRef(), loc), loc, }); lowerer.lowerBindingPattern( @@ -9826,7 +9826,7 @@ function lowerArrayAssignInto( let srcType = tmpRef().type; // A STRING source (`[a, b] = s`): the declaration path's rule in // assignment position — the string iterator's code-point split - // (%str.chars, astral characters whole) into a hidden chars array, + // (str.codePoints, astral characters whole) into a hidden chars array, // then the pattern proceeds as an array pattern over string[] // (defaults with the bounds test, rest packing the remaining code // points, elisions; positions past the last code point inherit the @@ -9839,7 +9839,7 @@ function lowerArrayAssignInto( out.push({ kind: "varDecl", localId: chars.id, - init: strCharsCall(lowerer, tmpRef(), loc), + init: strCharsCall(tmpRef(), loc), loc, }); tmpRef = () => ({ kind: "varRef", localId: chars.id, type: charsT, loc }); @@ -11383,16 +11383,18 @@ function lowerForOfMatchAll( /** `for (const ch of s)` over a STRING: JS's string iterator, which walks * code POINTS — each pass binds the whole character (a two-unit string * for astral chars, where charAt/slice would return one surrogate half). - * Desugars over the UTF-16 cursor machinery: a hidden unit - * index reads the character with the cpAt intrinsic and advances by ITS - * length before the body runs (so `continue` never skips the advance): + * Desugars over a byte cursor into the WTF-8 data: a hidden byte offset + * reads the character that starts there with the cpAtByte intrinsic and + * advances by ITS byte count before the body runs (so `continue` never + * skips the advance): * * { const %s = ; let %i = 0; - * while (%i < %s.length) { const ch = cpAt(%s, %i); %i += ch.length; } } + * while (%i < byteLength(%s)) { const ch = cpAtByte(%s, %i); %i += byteLength(ch); } } * - * Strings are immutable, so the length is stable and there is no + * Strings are immutable, so the byte count is stable and there is no * iterator state to unwind on throw; a lone surrogate is a one-unit - * character, so every step lands on a character start. */ + * character with its own three-byte encoding, so every step lands on a + * character start. */ /** for-of over a CLASS ITERABLE (classIteratorOf's shape) — the * iterator protocol as ordinary method calls: * @@ -12087,7 +12089,7 @@ function lowerForOfString( try { const s = lowerer.declareHiddenLocal("%strof", STRING); const i = lowerer.declareHiddenLocal("%iterof", F64); - i.mutable = true; // the unit index reassigns (hidden locals default const) + i.mutable = true; // the byte cursor reassigns (hidden locals default const) const sRef = (): IrExpr => ({ kind: "varRef", localId: s.id, type: STRING, loc }); const iRef = (): IrExpr => ({ kind: "varRef", localId: i.id, type: F64, loc }); // `for (var c of str)`: the character mechanics stay on a hidden @@ -12118,7 +12120,7 @@ function lowerForOfString( localId: ch.id, init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: sRef(), args: [iRef()], type: STRING, @@ -12145,7 +12147,7 @@ function lowerForOfString( left: iRef(), right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: chRef, args: [], type: F64, @@ -12177,7 +12179,7 @@ function lowerForOfString( left: iRef(), right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: sRef(), args: [], type: F64, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 576386851..bf92c8da3 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -848,6 +848,7 @@ function buildLibFnSignatures() { // (subject, characters, NUL-separated replacement texts): the fused // chain of global one-character replacements. "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, + "str.codePoints": { argTypes: [STRING], result: arrayOf(STRING) }, /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the * whole span must be one StrNumericLiteral — signed decimal (Infinity diff --git a/packages/compiler/src/ir/integer-ranges.ts b/packages/compiler/src/ir/integer-ranges.ts index 1909040c4..51c0a929b 100644 --- a/packages/compiler/src/ir/integer-ranges.ts +++ b/packages/compiler/src/ir/integer-ranges.ts @@ -867,6 +867,8 @@ export function analyzeIntegerRangeFacts( ); if (e.type.kind === "f64") { if (e.method === "length") range = { min: 0, max: STRING_MAX_UNITS }; + // A string holds at most SCR_STR_MAX_CAP (0x7fffffff) bytes. + else if (e.method === "byteLength") range = { min: 0, max: STRING_MAX_UNITS }; // An empty needle is found at the clamped position, up to the length. else if (e.method === "indexOf") range = { min: -1, max: STRING_MAX_UNITS }; } diff --git a/packages/compiler/src/ir/intrinsic-signatures.ts b/packages/compiler/src/ir/intrinsic-signatures.ts index eda569883..3e26e77f2 100644 --- a/packages/compiler/src/ir/intrinsic-signatures.ts +++ b/packages/compiler/src/ir/intrinsic-signatures.ts @@ -31,7 +31,8 @@ export const STR_INTRINSIC_SIGS: Record< normalize: { argTypes: [STRING], minArgs: 1, result: STRING }, isWellFormed: { argTypes: [], minArgs: 0, result: BOOL }, toWellFormed: { argTypes: [], minArgs: 0, result: STRING }, - cpAt: { argTypes: [F64], minArgs: 1, result: STRING }, + byteLength: { argTypes: [], minArgs: 0, result: F64 }, + cpAtByte: { argTypes: [F64], minArgs: 1, result: STRING }, }; /** Per-method signature for regexIntrinsic. test/source/flags take the diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 19a5be71d..6118e4955 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -2447,10 +2447,13 @@ export type IrStrIntrinsicMethod = // surrogate, and the copy with each one replaced by U+FFFD. | "isWellFormed" | "toWellFormed" - // The string iterator's step: the full code-POINT character at a UTF-16 - // index (astral chars come back whole where charAt truncates) — for-of - // over strings desugars onto it, advancing by the result's length. - | "cpAt"; + // The string iterator over the WTF-8 bytes: the byte count of a string, + // and the code-point character that starts at a byte offset (a lone + // surrogate is a one-unit character). for-of, spread and Array.from of a + // string walk a byte cursor with them, advancing by the character's + // byte count; the cursor is always a character start below the count. + | "byteLength" + | "cpAtByte"; /** The typed-array/Buffer method surface (bytesIntrinsic). Receiver/arg * conventions (validated): `length`/`byteLength` are property reads → f64; diff --git a/packages/compiler/test/string-iteration-emission.test.ts b/packages/compiler/test/string-iteration-emission.test.ts new file mode 100644 index 000000000..7cda3da55 --- /dev/null +++ b/packages/compiler/test/string-iteration-emission.test.ts @@ -0,0 +1,47 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { compile } from "../src/index.js"; + +const directories: string[] = []; +afterEach(async () => { + await Promise.all(directories.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +async function emit(source: string): Promise { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-string-iteration-")); + directories.push(outDir); + const entry = join(outDir, "main.ts"); + await writeFile(entry, source); + const outPath = join(outDir, "program.ll"); + const result = await compile(entry, { entry, outDir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result)).toBe(true); + return readFile(outPath, "utf8"); +} + +const calls = (llvm: string, symbol: string): string[] => + llvm.split("\n").filter((line) => line.includes("call ") && line.includes(`@${symbol}(`)); + +test("for-of over a string walks a byte cursor", async () => { + const llvm = await emit(` +function count(s: string): number { + let n = 0; + for (const c of s) n += c.length; + return n; +} +console.log(count(process.argv[1] ?? "")); +`); + expect(calls(llvm, "sc_str_cp_at_byte")).toHaveLength(1); + expect(calls(llvm, "scr_str_cp_at")).toHaveLength(0); + // The helper answers ASCII bytes from the one-character table. + expect(llvm).toContain("define internal ptr @sc_str_cp_at_byte(ptr %s, i64 %b)"); +}); + +test("spread and Array.from of a string build the array in one call", async () => { + const llvm = await emit(` +const s = process.argv[1] ?? ""; +console.log([...s].length, Array.from(s).length); +`); + expect(calls(llvm, "scr_str_code_points")).toHaveLength(2); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json new file mode 100644 index 000000000..1e83f7b6f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8347-string-iteration-code-points.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 1e7802fbb..cd17da0f8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -3514,6 +3514,8 @@ ScrStr *scr_bool_to_scrstr(bool b) { * decode is a whole char; an i addressing the LOW half of an astral pair * (unreachable from the iterator, which only lands on char starts) answers * the containing char. Out of range → empty string. Borrows s; +1. */ +static ScrStr *scr_str_char_of(const char *bytes, size_t adv); + ScrStr *scr_str_cp_at(ScrStr *s, double i) { double idx = scr_to_integer_or_infinity(i); if (!(idx >= 0)) return scr_str_empty(); @@ -3528,7 +3530,75 @@ ScrStr *scr_str_cp_at(ScrStr *s, double i) { if (off >= s->len) return scr_str_empty(); /* out of range */ size_t adv; (void)scr_utf8_decode(s->data + off, &adv); - return scr_str_from_span(s->data + off, adv); + return scr_str_char_of(s->data + off, adv); +} + +/* The one-character strings of U+0080..U+07FF (two-byte sequences, lead + * bytes 0xC2..0xDF), made immortal on first use and shared by every + * thread, as the ASCII ones are static: the string iterator returns them + * instead of a new string per character. A string is immortal before its + * slot publishes it; a thread that loses the race to fill a slot keeps its + * own immortal copy, so at most one extra string per slot and thread. */ +static ScrStr *scr_two_byte_chars[0x800 - 0x80]; + +static ScrStr *scr_two_byte_char(const char *bytes) { + uint32_t cp = ((uint32_t)((unsigned char)bytes[0] & 0x1F) << 6) | ((unsigned char)bytes[1] & 0x3F); + ScrStr **slot = &scr_two_byte_chars[cp - 0x80]; + ScrStr *known = __atomic_load_n(slot, __ATOMIC_ACQUIRE); + if (known) return known; + ScrStr *made = scr_str_from_span(bytes, 2); + scr_str_hash_prime(made); + made->rc = SIZE_MAX; +#ifdef SCR_RC_AUDIT + scr_str_live_forget(); +#endif + scr_lsan_immortal(made); + ScrStr *expected = NULL; + if (__atomic_compare_exchange_n(slot, &expected, made, false, __ATOMIC_ACQ_REL, + __ATOMIC_ACQUIRE)) + return made; + return expected; +} + +/* The character whose encoding starts at bytes and is adv bytes long (+1, + * or immortal). */ +static ScrStr *scr_str_char_of(const char *bytes, size_t adv) { + unsigned char lead = (unsigned char)bytes[0]; + if (adv == 1 && lead < 0x80) return (ScrStr *)&scr_ascii1[lead]; + if (adv == 2) return scr_two_byte_char(bytes); + return scr_str_from_span(bytes, adv); +} + +ScrStr *scr_str_cp_at_byte(ScrStr *s, uint64_t b) { + if (b >= s->len) return scr_str_empty(); + size_t adv; + (void)scr_utf8_decode(s->data + b, &adv); + return scr_str_char_of(s->data + b, adv); +} + +ScrArr *scr_str_code_points(ScrStr *s) { + /* Every byte but a continuation byte starts one character; a lone + * surrogate's three bytes are one character. */ + size_t count = s->len; + if (!s->ascii) + for (size_t b = 0; b < s->len; b++) count -= ((unsigned char)s->data[b] & 0xC0) == 0x80; + ScrArr *out = scr_arr_new_inline(SCR_ELEM_STR, count, false); + size_t k = 0; + for (size_t b = 0; b < s->len;) { + unsigned char c = (unsigned char)s->data[b]; + if (c < 0x80) { + out->data[k++] = (uint64_t)(uintptr_t)&scr_ascii1[c]; + b++; + continue; + } + size_t adv; + (void)scr_utf8_decode(s->data + b, &adv); + out->data[k++] = (uint64_t)(uintptr_t)scr_str_char_of(s->data + b, adv); + b += adv; + } + out->len = (uint32_t)count; + out->filled = (uint32_t)count; + return out; } /* ── the URI component codecs (encodeURIComponent/decodeURIComponent) ── diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 01ccdd8ae..2e69ee6ac 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -150,6 +150,15 @@ ScrStr *scr_str_char_at(ScrStr *s, double i); * (two units); the consuming loop advances by the result's length. * Out of range → empty string. Returns +1. */ ScrStr *scr_str_cp_at(ScrStr *s, double i); +/* The string iterator's step over the WTF-8 bytes: the code-point + * character whose encoding starts at byte offset b, a character start + * below s->len (a lone surrogate is its own three-byte character). The + * compiler answers ASCII bytes inline and calls this for the rest. + * Returns +1. */ +ScrStr *scr_str_cp_at_byte(ScrStr *s, uint64_t b); +/* [...s] and Array.from(s): the code-point characters of s in order, as a + * fresh string array of exactly that length. Returns +1. */ +struct ScrArr *scr_str_code_points(ScrStr *s); /* trimStart()/trimEnd(): the one-sided halves of trim. Returns +1. */ ScrStr *scr_str_trim_start(ScrStr *s); diff --git a/tests/corpus/1561-forof-strings.ts b/tests/corpus/1561-forof-strings.ts index 9d16efd9a..0ea09e755 100644 --- a/tests/corpus/1561-forof-strings.ts +++ b/tests/corpus/1561-forof-strings.ts @@ -1,7 +1,7 @@ // for-of over STRINGS: JS's string iterator walks code POINTS — each pass // binds the whole character, so astral chars arrive as one two-unit string -// (where charAt/slice would truncate the halves). Desugars over the UTF-16 -// cursor with the cpAt step intrinsic. +// (where charAt/slice would truncate the halves). Desugars over a byte +// cursor with the cpAtByte step intrinsic. for (const ch of "héllo 🎉x") console.log(ch, ch.length); // The empty string iterates zero times. diff --git a/tests/corpus/8347-string-iteration-code-points.ts b/tests/corpus/8347-string-iteration-code-points.ts new file mode 100644 index 000000000..3a1286140 --- /dev/null +++ b/tests/corpus/8347-string-iteration-code-points.ts @@ -0,0 +1,96 @@ +// The string iterator: for-of, spread, Array.from, yield* and +// [Symbol.iterator]() walk code points. Astral characters come back whole, +// a lone surrogate is a one-unit character, and halves joined by +// concatenation form one character, as in Node. +function unitCodes(c: string): string { + const codes: string[] = []; + for (let k = 0; k < c.length; k++) codes.push(c.charCodeAt(k).toString(16)); + return `${c.length}:${codes.join(".")}`; +} +const show = (chars: string[]): string => chars.map(unitCodes).join(" "); + +const samples = [ + "", + "abc", + "aé😀b", + "\u0000\u007f\u0080߿ࠀ￿", + "\u{10000}\u{10ffff}", + "é ñ", + "\ud800", + "\udc00", + "x\ud83d", + "\ude00x", + "\udc00\ud800", + "\ud83d" + "\ude00", + "a\ud83d" + "\ude00b\ud83d", + "日本語テキスト", +]; +for (const s of samples) { + const looped: string[] = []; + for (const c of s) looped.push(c); + console.log(JSON.stringify(s), show(looped), show([...s]), show(Array.from(s)), [...s].length, s.length); +} + +// Loop control, heads and bindings. +const text = "a😀b\ud800c"; +let out = ""; +outer: for (const c of text) { + if (c === "b") continue; + for (const d of "xy") { + if (d === "y" && c === "c") continue outer; + out += c + d; + } + if (c === "\ud800") out += "|"; +} +console.log(out); +for (var v of "hé") out += v; +console.log(out); +let last = ""; +for (last of "q😀") out += last.length; +console.log(out, last); +for (const [first] of ["😀z", "ab"].join("")) out += first; +console.log(out); +let source = "abc"; +for (const c of source) { + source += c; + out += source.length; +} +console.log(out, source); +let n = 0; +for (const c of "stop😀here") { + if (c === "😀") break; + n += c.length; +} +console.log(n); + +// Characters as values: lengths, Map keys and code points. +const counts = new Map(); +for (const c of "abracadabra😀😀é") counts.set(c, (counts.get(c) ?? 0) + 1); +console.log([...counts].map(([k, v]) => `${k}=${v}`).join(",")); +let points = 0; +for (const c of "😀é\ud800a") points += c.codePointAt(0)!; +console.log(points); + +// Generators and explicit iterators. +function* letters(s: string): Generator { + yield* s; +} +console.log(show([...letters("x😀\udc00y")])); +const it = "o😀"[Symbol.iterator](); +let step = it.next(); +while (!step.done) { + out += step.value.length; + step = it.next(); +} +console.log(out); + +// A long string built from mixed pieces. +let long = ""; +for (let i = 0; i < 2000; i++) long += i % 3 === 0 ? "a" : i % 3 === 1 ? "é" : "😀"; +let units = 0; +let chars = 0; +for (const c of long) { + units += c.length; + chars++; +} +console.log(units, chars, long.length, [...long].length); diff --git a/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json b/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json new file mode 100644 index 000000000..3e2ae0706 --- /dev/null +++ b/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json @@ -0,0 +1 @@ +{ "plain": 2770, "san": 3750 } From 8f425a8a1b14a3dc0f832c944674f73483502fab Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:01:23 -0500 Subject: [PATCH 246/395] Wait for mailbox events in ppoll on Linux A thread waiting for its mailbox slept on a condition variable. The kernel then places the woken thread without a hint, so with every CPU busy it can queue behind a running thread until that thread is preempted or blocks: a worker program's phase barrier (post to each worker, wait for every reply) lost about 1 ms per phase, and up to 5 ms, on 4 CPUs. On Linux the wait now sleeps in ppoll(2) on the mailbox's wake pipe, which every post already writes. A pipe write is a synchronous wakeup: the woken thread can run on the poster's CPU, which the poster frees when it waits in turn. Bytes left by signals whose events were already taken are drained under the lock first. Other platforms keep the condition variable. --- packages/runtime/src/scr_mailbox.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_mailbox.c b/packages/runtime/src/scr_mailbox.c index b2af22774..6e5678378 100644 --- a/packages/runtime/src/scr_mailbox.c +++ b/packages/runtime/src/scr_mailbox.c @@ -21,6 +21,7 @@ #include #include #ifdef __linux__ +#include #include #endif #endif @@ -321,7 +322,32 @@ void scr_mailbox_wait(ScrMailbox *mailbox, double milliseconds) { if (!(milliseconds > 0)) return; if (scr_mailbox_spin(mailbox, milliseconds)) return; scr_mailbox_lock(mailbox); - if (!mailbox->head && !mailbox->closed) { + bool waiting = !mailbox->head && !mailbox->closed; +#ifdef __linux__ + /* Linux sleeps in ppoll(2) on the wake pipe rather than on the + * condition: a pipe write wakes its reader as a synchronous wakeup, + * which the scheduler can run on the writer's CPU when the writer is + * about to block. A worker that posts its result and then waits frees + * that CPU, so the thread it woke runs there at once instead of queueing + * behind another busy thread until the scheduler preempts it. Bytes left + * by signals whose events were already taken are drained first, under + * the lock: an event posted after the unlock writes a new byte. */ + if (waiting) scr_mailbox_drain(mailbox); + scr_mailbox_unlock(mailbox); + if (!waiting) return; + struct pollfd wake = {mailbox->read_fd, POLLIN, 0}; + if (isinf(milliseconds)) (void)ppoll(&wake, 1, NULL, NULL); + else { + /* The event loop rechecks its monotonic deadlines after every wake, + * including spurious ones. Bound conversion before casting time_t. */ + if (milliseconds > 86400000) milliseconds = 86400000; + struct timespec timeout = {(time_t)(milliseconds / 1000), + (long)ceil(fmod(milliseconds, 1000) * 1000000)}; + if (timeout.tv_nsec == 1000000000) { timeout.tv_sec++; timeout.tv_nsec = 0; } + (void)ppoll(&wake, 1, &timeout, NULL); + } +#else + if (waiting) { #ifdef _WIN32 DWORD timeout = milliseconds >= INFINITE ? INFINITE : (DWORD)ceil(milliseconds); (void)SleepConditionVariableSRW(&mailbox->condition, &mailbox->lock, timeout, 0); @@ -348,6 +374,7 @@ void scr_mailbox_wait(ScrMailbox *mailbox, double milliseconds) { #endif } scr_mailbox_unlock(mailbox); +#endif } int scr_mailbox_pollfd(const ScrMailbox *mailbox) { From ed50daa853aedeea1d644df779db93ada8479e80 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:01:57 -0700 Subject: [PATCH 247/395] Keep the whole union of an optional parameter that reaches a recursive placeholder An optional parameter typed by a union that is still being mapped gets a recursive placeholder: a union id whose arms arrive when the outer frame finishes. The function-type mapping then armed that placeholder with undefined, but the placeholder's arm list was still empty, so the result was a fresh union holding only `undefined`. The parameter lost its record arm, the same checker type mapped to two different layouts depending on where the mapping started, and later conversions failed (SC2001, SC2002, SC2003). This happens when an interface method takes an options object whose callbacks are typed by a subinterface of that same interface: `newSession(options?: { withSession?: (ctx: Replaced) => Promise })` with `interface Replaced extends CommandContext`. When the checker spelled the parameter with an undefined constituent, the placeholder already carries that arm, so the parameter keeps the placeholder. withUnitArm now refuses a pending placeholder instead of building the unit-only union. pi's ExtensionCommandContext is such an interface. It refused ExtensionAPI.registerCommand, and with it pi's main(). Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/type-mapper.test.ts | 14 +++ packages/compiler/src/frontend/type-mapper.ts | 24 +++- ...recursive-optional-callback-params.ts.json | 6 + .../recursive-optional-callback-params.ts | 107 ++++++++++++++++++ ...recursive-optional-callback-params.ts.json | 1 + 5 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json create mode 100644 tests/corpus/recursive-optional-callback-params.ts create mode 100644 tests/harness/corpus-costs/recursive-optional-callback-params.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.test.ts b/packages/compiler/src/frontend/type-mapper.test.ts index 163ba7b9e..6e7e339d6 100644 --- a/packages/compiler/src/frontend/type-mapper.test.ts +++ b/packages/compiler/src/frontend/type-mapper.test.ts @@ -53,6 +53,20 @@ describe("union arm lookup", () => { }); }); +describe("recursive union placeholders", () => { + test("refuse a unit arm until their arms are known", () => { + const unions = new UnionRegistry(); + const recursive = {} as Type; + const union = unions.recursiveRef(recursive, () => "Recursive"); + const placeholder: IrType = { kind: "union", unionId: union }; + expect(unions.isPendingPlaceholder(union)).toBe(true); + expect(withUndefinedArm(placeholder, unions)).toBeNull(); + unions.finalizeRecursive(recursive, [{ kind: "record", shapeId: "r1" }, UNDEFINED_T]); + expect(unions.isPendingPlaceholder(union)).toBe(false); + expect(withUndefinedArm(placeholder, unions)).toEqual(placeholder); + }); +}); + describe("nullable collection union builders", () => { test.each([mapOf(STRING, F64), setOf(STRING)])( "optional %j fields and generator results share the same union", diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..63eed378d 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -717,6 +717,12 @@ export class UnionRegistry { return id !== undefined && this.pendingRec.has(id); } + /** True when `unionId` is a recursive placeholder whose arms are not + * known yet (its outer frame is still mapping, or failed). */ + isPendingPlaceholder(unionId: string): boolean { + return this.pendingRec.has(unionId); + } + /** Completes a recursive placeholder with its canonical arm list and * registers the structural key (first writer wins, like shapes). */ finalizeRecursive(t: ts.Type, arms: IrType[], discriminant?: IrUnionDiscriminant): string { @@ -5302,8 +5308,19 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions); // Belt and braces for non-strict type worlds: an optional param's // ABI slot is always the undefined-armed union (strictNullChecks - // already spells it that way; arm it here if the world didn't). - if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn") { + // already spells it that way; arm it here if the world didn't). A + // back-reference to a union still being mapped (`options?: { then?: + // (ctx: Sub) => void }` where Sub extends the interface declaring the + // method) is a recursive placeholder with no arms yet; when the checker + // spelled the parameter `X | undefined`, the placeholder receives that + // undefined arm when its frame finishes. + const pendingOptional = + pt?.kind === "union" && + ctx.unions.isPendingPlaceholder(pt.unionId) && + ts + .constituentTypes(checker.getTypeOfSymbol(p)) + .some((part) => (part.flags & ts.TypeFlags.Undefined) !== 0); + if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn" && !pendingOptional) { const armed = withUndefinedArm(pt, ctx.unions); if (!armed) return null; pt = armed; @@ -6782,6 +6799,9 @@ export function withUnitArm( if (t.kind === "union") { const def = unions.get(t.unionId); if (!def) return null; + // A recursive placeholder's arms arrive when its outer frame finishes; + // extending its (still empty) arm list would drop every arm but the unit. + if (unions.isPendingPlaceholder(t.unionId)) return null; if (def.arms.some((a) => a.kind === kind)) return t; const arms = [...def.arms, unit]; // Date and Promise arms beside other data arms have no union home. diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json new file mode 100644 index 000000000..abf783f85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/recursive-optional-callback-params.ts" + ], + "diags": [] +} diff --git a/tests/corpus/recursive-optional-callback-params.ts b/tests/corpus/recursive-optional-callback-params.ts new file mode 100644 index 000000000..0c81235d6 --- /dev/null +++ b/tests/corpus/recursive-optional-callback-params.ts @@ -0,0 +1,107 @@ +// An optional parameter whose type reaches back to the interface that +// declares the method keeps its whole union. A command context's +// `newSession(options?)` takes callbacks typed by a subinterface of the +// context, so mapping the context passes through the options union while +// that union is still being mapped; the parameter still holds the options +// record, not only `undefined`. + +interface CommandContext { + cwd: string; + newSession(options?: { + parent?: string; + withSession?: (ctx: ReplacedContext) => Promise; + }): Promise<{ cancelled: boolean }>; + fork( + entryId: string, + options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedContext) => Promise }, + ): Promise<{ cancelled: boolean }>; +} + +interface ReplacedContext extends CommandContext { + depth: number; + send(content: string): Promise; +} + +interface Command { + description?: string; + handler: (args: string, ctx: CommandContext) => Promise; +} + +const log: string[] = []; +const commands = new Map(); + +function register(name: string, command: Command): void { + commands.set(name, command); +} + +register("new", { + description: "start a session", + handler: async (args, ctx) => { + const first = await ctx.newSession({ + parent: args, + withSession: async (next) => { + await next.send(`hello from ${next.cwd} at depth ${next.depth}`); + const nested = await next.newSession({ + withSession: async (inner) => { + await inner.send(`nested at depth ${inner.depth}`); + }, + }); + log.push(`nested cancelled=${nested.cancelled}`); + }, + }); + log.push(`first cancelled=${first.cancelled}`); + const bare = await ctx.newSession(); + log.push(`bare cancelled=${bare.cancelled}`); + }, +}); + +register("fork", { + handler: async (args, ctx) => { + const r = await ctx.fork(args, { + position: "at", + withSession: async (next) => next.send(`forked ${args} at depth ${next.depth}`), + }); + log.push(`fork cancelled=${r.cancelled}`); + const plain = await ctx.fork(args); + log.push(`plain fork cancelled=${plain.cancelled}`); + }, +}); + +function makeContext(depth: number): ReplacedContext { + return { + cwd: `/work/${depth}`, + depth, + newSession: async (options) => { + log.push(`newSession depth=${depth} parent=${options?.parent ?? "-"}`); + if (options?.withSession) { + await options.withSession(makeContext(depth + 1)); + return { cancelled: false }; + } + return { cancelled: true }; + }, + fork: async (entryId, options) => { + log.push(`fork ${entryId} position=${options?.position ?? "-"}`); + const callback = options?.withSession; + if (callback === undefined) return { cancelled: true }; + await callback(makeContext(depth + 1)); + return { cancelled: false }; + }, + send: async (content) => { + log.push(`send: ${content}`); + }, + }; +} + +async function run(): Promise { + const root: CommandContext = makeContext(0); + for (const [name, command] of commands) { + log.push(`run ${name} (${command.description ?? "no description"})`); + await command.handler(`entry-${name}`, root); + } + for (const line of log) console.log(line); +} + +run().catch((e: unknown) => { + console.log("failed", e instanceof Error ? e.message : String(e)); + process.exitCode = 1; +}); diff --git a/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json b/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json new file mode 100644 index 000000000..c33a4b32c --- /dev/null +++ b/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json @@ -0,0 +1 @@ +{ "plain": 6000 } From ffbbf1590f2208d19a14ddb2bdf237aeb7c60c5e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:11:47 -0500 Subject: [PATCH 248/395] Send every dyn kind that can be a cycle candidate through one release path The inline dyn release sorted a surviving value into three groups by kind: arrays and objects tested the candidate-buffer word and then called scr_dyn_release_survivor, capsules and handles called it directly, and functions and proxies appended to the candidate buffer inline. All six kinds now take one path: a value already in a candidate buffer, or never one, returns inline, and scr_dyn_release_survivor, which now also covers functions and proxies, decides the rest. Behavior is unchanged, and each inlined release site keeps one kind test and one call. Effect's executable text is 10% smaller and it runs 5.8% faster; zod runs 3.8% faster, and bench:runtime's dynamic-properties 14% faster. --- .../compiler/src/backend/llvm/shapes.test.ts | 56 ++++++++++++------- packages/compiler/src/backend/llvm/shapes.ts | 39 +++++++------ packages/runtime/src/scr_dyn.h | 3 +- packages/runtime/src/scr_json.c | 10 +++- 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/packages/compiler/src/backend/llvm/shapes.test.ts b/packages/compiler/src/backend/llvm/shapes.test.ts index 61c1a074d..d9e5fa641 100644 --- a/packages/compiler/src/backend/llvm/shapes.test.ts +++ b/packages/compiler/src/backend/llvm/shapes.test.ts @@ -146,11 +146,19 @@ describe("LLVM runtime RC symbols", () => { // The dyn fast path buffers surviving values by kind, as scr_dyn_release does. const ir = emitInlineRcHelpers(host).join("\n"); expect(ir).toContain("call void @scr_dyn_release_survivor(ptr %o)"); - expect(ir).toMatch( - /switch i32 %kind, label %done \[ i32 4, label %container i32 5, label %container i32 8, label %root/, + // Every kind that can be a candidate takes one path; scalars stay inline. + expect(ir).toContain( + "switch i32 %kind, label %done [ i32 4, label %candidate i32 5, label %candidate i32 8, label %candidate i32 9, label %candidate i32 12, label %candidate i32 13, label %candidate ]", ); - // Arrays and objects not yet buffered ask the runtime, which skips intact documents. + // Values not yet buffered ask the runtime, which decides per value. expect(ir).toMatch(/br i1 %cqueued, label %done, label %survivor/); + // The runtime buffers dyn candidates, so the dyn helper has no inline append. + const dynHelper = ir.slice( + ir.indexOf("@sc_rc_release_dyn("), + ir.indexOf("\n}", ir.indexOf("@sc_rc_release_dyn(")), + ); + expect(dynHelper).not.toContain("sc_cyc_append"); + expect(dynHelper).not.toContain("scr_cyc_on_release"); }); test("sanitized builds call the fast-path helpers out of line", () => { @@ -208,36 +216,42 @@ describe("LLVM runtime RC symbols", () => { }); test("optimized builds buffer candidates through the module's append helper", () => { - const emit = (inlineRc: true | "release", extra: Partial) => { + const emit = (inlineRc: true | "release", extra: Partial, kind: "union" | "dyn") => { const { host } = declarationHost(64, inlineRc); const configured: ShapeHost = { ...host, inlineAlloc: true, ...extra }; - releaseSym(configured, { kind: "dyn" }); + releaseSym(configured, kind === "union" ? ({ kind, unionId: "u0" } as never) : { kind }); return { ir: emitInlineRcHelpers(configured).join("\n"), decls: inlineRcDecls(configured), }; }; + const { ir, decls } = emit(true, {}, "union"); + expect(ir).toContain("call preserve_mostcc void @sc_cyc_append(ptr %o)"); + expect(ir).toContain("define internal preserve_mostcc void @sc_cyc_append(ptr %o) noinline"); + // The young test reads the generation byte at obj-5; the append fills + // scr_cyc_buffers' nursery buffer and the header's position at obj-4. + expect(ir).toContain("getelementptr i8, ptr %o, i64 -5"); + expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 96"); + expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 104"); + expect(ir).toContain("load i64, ptr @scr_cyc_live"); + expect(ir).toContain("or i32 %i, -2147483648"); + expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); + expect(decls).toContain( + "@scr_cyc_buffers = external global { [3 x { ptr, i64, i64, i64 }], i64, i64 }", + ); + expect(decls).toContain("@scr_cyc_live = external global i64"); + // The dyn release buffers through scr_dyn_release_survivor, so a module + // whose only headered release is dyn's has no append helper. for (const mode of [true, "release"] as const) { - const { ir, decls } = emit(mode, {}); - expect(ir).toContain("call preserve_mostcc void @sc_cyc_append(ptr %o)"); - expect(ir).toContain("define internal preserve_mostcc void @sc_cyc_append(ptr %o) noinline"); - // The young test reads the generation byte at obj-5; the append fills - // scr_cyc_buffers' nursery buffer and the header's position at obj-4. - expect(ir).toContain("getelementptr i8, ptr %o, i64 -5"); - expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 96"); - expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 104"); - expect(ir).toContain("load i64, ptr @scr_cyc_live"); - expect(ir).toContain("or i32 %i, -2147483648"); - expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); - expect(decls).toContain( - "@scr_cyc_buffers = external global { [3 x { ptr, i64, i64, i64 }], i64, i64 }", - ); - expect(decls).toContain("@scr_cyc_live = external global i64"); + const dyn = emit(mode, {}, "dyn"); + expect(dyn.ir).not.toContain("sc_cyc_append"); + expect(dyn.ir).not.toContain("scr_cyc_on_release"); + expect(dyn.decls.join("\n")).not.toContain("scr_cyc_buffers"); } // Worker executables and targets without inline allocation call // scr_cyc_on_release, which tries the same append first. for (const extra of [{ threadLocalAlloc: true }, { inlineAlloc: false }]) { - const { ir, decls } = emit(true, extra); + const { ir, decls } = emit(true, extra, "union"); expect(ir).not.toContain("sc_cyc_append"); expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); expect(decls.join("\n")).not.toContain("scr_cyc_buffers"); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index ab623f934..7498235b0 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -589,9 +589,10 @@ interface InlineRcFamily { * repeats its own checks, so the fast path never duplicates it. * * The dyn release buffers a surviving value by kind (scr_dyn_release): - * the fast path decides the always and never cases and calls - * scr_dyn_release_survivor for kinds whose candidacy is per value, - * including arrays and objects of intact documents (ScrDynRegion). + * the fast path skips scalars and values already in a candidate buffer or + * never candidates, and calls scr_dyn_release_survivor for the rest, which + * decides per value (intact documents, ScrDynRegion; capsules and handles + * without edges). * * The array release inlines the decrement of an array that stays alive * and is not headered (`a->cyc`): those are most array releases in @@ -698,9 +699,10 @@ function requestRcHelper(host: ShapeHost, family: string, op: RcOp): string { for (const decl of rcHelperDecls(family, op)) host.declare(decl); // Its body is written last (emitInlineRcHelpers), after the requests are // collected: a release that can buffer a candidate requests the append - // helper now. The jsval row (JS_RC_ROW) has no collector header; its - // cells release through dyn's helper, which makes its own request. - if (op === "release" && row.cycle === "always") requestCycleAppend(host); + // helper now. The dyn release buffers through scr_dyn_release_survivor + // instead. The jsval row (JS_RC_ROW) has no collector header; its cells + // release through dyn's helper. + if (op === "release" && row.cycle === "always" && family !== "dyn") requestCycleAppend(host); return `@${inlineRcName(family, op)}`; } @@ -820,8 +822,11 @@ function rcHelperDecls(family: string, op: RcOp): string[] { if (op === "retain" || row.release === null) return []; return [ `declare void @${row.release}(ptr)`, - ...(row.cycle === "always" ? [`declare void @scr_cyc_on_release(ptr)`] : []), - ...(family === "dyn" ? [`declare void @scr_dyn_release_survivor(ptr)`] : []), + ...(family === "dyn" + ? [`declare void @scr_dyn_release_survivor(ptr)`] + : row.cycle === "always" + ? [`declare void @scr_cyc_on_release(ptr)`] + : []), ]; } @@ -931,21 +936,21 @@ function releaseHelper(host: ShapeHost, family: string, row: InlineRcFamily): st // scr_dyn_release buffers a surviving value by kind: always for // functions and proxies, per value for arrays and objects (not // while their document is intact, ScrDynRegion), capsules and - // handles (scr_dyn_release_survivor), never for scalars. An - // array or object already in a candidate buffer stays inline. + // handles, never for scalars. Every kind that can be buffered + // takes one path, which keeps each inlined site small: a value + // already in a candidate buffer (or never one) stays inline, and + // scr_dyn_release_survivor decides the rest. ` %kp = getelementptr i8, ptr %o, ${S} ${S === "i64" ? 8 : 4} ; ScrDyn.kind`, ` %kind = load i32, ptr %kp`, - // SCR_DYN_ARR 4, OBJ 5, FUNC 8, PROXY 13; HANDLE 9, TYPED_REF 12. - ` switch i32 %kind, label %done [ i32 4, label %container i32 5, label %container i32 8, label %root i32 13, label %root i32 9, label %survivor i32 12, label %survivor ]`, - `container:`, + // SCR_DYN_ARR 4, OBJ 5, FUNC 8, HANDLE 9, TYPED_REF 12, PROXY 13. + ` switch i32 %kind, label %done [ i32 4, label %candidate i32 5, label %candidate i32 8, label %candidate i32 9, label %candidate i32 12, label %candidate i32 13, label %candidate ]`, + `candidate:`, ` %cbufp = getelementptr i8, ptr %o, ${S} -${host.cycleBufOffset}`, ` %cbuf = load i32, ptr %cbufp`, ` %cqueued = icmp slt i32 %cbuf, 0`, ` br i1 %cqueued, label %done, label %survivor`, `survivor:`, ` call void @scr_dyn_release_survivor(ptr %o)`, - ` br label %done`, - `root:`, ] : family === "box" ? [ @@ -964,7 +969,9 @@ function releaseHelper(host: ShapeHost, family: string, row: InlineRcFamily): st `root:`, ] : []), - ...(cycle === "always" ? cycleRootLines(host, "done") : [` br label %done`]), + ...(cycle === "always" && family !== "dyn" + ? cycleRootLines(host, "done") + : [` br label %done`]), `done:`, ` ret void`, `}`, diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..33cd63c05 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1941,7 +1941,8 @@ extern void scr_dyn_listener_fire_data(ScrClosure *cb, ScrBytes *chunk); extern void scr_dyn_listener_fire_err(ScrClosure *cb, ScrStr *msg); void *scr_dyn_retain_v(void *d); -/* After a compiled fast path decremented a surviving capsule or handle. */ +/* After a compiled fast path decremented a surviving value of a kind that + * can be a cycle candidate and is not one yet (scr_json.c). */ void scr_dyn_release_survivor(ScrDyn *d); void scr_dyn_release_v(void *d); void scr_dyn_trace_v(void *d, ScrTraceVisit visit, void *ctx); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..f2254cd44 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1317,9 +1317,9 @@ static void scr_dyn_gcfree(void *ptr) { scr_dyn_dispose(ptr, true); } static void scr_dyn_destroy(void *ptr) { scr_dyn_dispose(ptr, false); } /* The cycle bookkeeping of a value that survives a release. Compiled - * release fast paths decrement first and call this for the kinds whose - * candidacy depends on per-value state, matching scr_dyn_release: arrays - * and objects not yet buffered (skipped while their document is intact, + * release fast paths decrement first and call this for every kind that can + * be a candidate and is not one yet, matching scr_dyn_release: functions + * and proxies, arrays and objects (skipped while their document is intact, * ScrDynRegion), capsules and handles. */ void scr_dyn_release_survivor(ScrDyn *d) { if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) { @@ -1327,6 +1327,10 @@ void scr_dyn_release_survivor(ScrDyn *d) { if (!region || !region->intact) scr_cyc_released(d); return; } + if (d->kind == SCR_DYN_FUNC || d->kind == SCR_DYN_PROXY) { + scr_cyc_released(d); + return; + } if ((d->kind == SCR_DYN_TYPED_REF && (d->v.typed_ref.materialized || d->v.typed_ref.casts || d->v.typed_ref.traced || d->prototype || d->symbol_properties || d->symbol_keys)) || From fb17fc0715cfef4e8df42d3f4625e464ad127fcc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:15:28 -0500 Subject: [PATCH 249/395] Read fixed encrypted keys in corpus 8167's wrong-passphrase cases The cases that decrypt with a wrong passphrase used keys encrypted afresh on each run. A wrong key decrypts to random bytes, and about one run in two hundred those bytes pass the padding check, so OpenSSL fails at the ASN.1 step with a longer error than "bad decrypt" and the program's output changes. The section that reads encrypted keys now uses fixed encryptions of one P-256 key, so each case fails the same way on every run. --- tests/corpus/8167-encrypted-private-keys.cjs | 39 +++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/corpus/8167-encrypted-private-keys.cjs b/tests/corpus/8167-encrypted-private-keys.cjs index 561a0aec3..7b274e500 100644 --- a/tests/corpus/8167-encrypted-private-keys.cjs +++ b/tests/corpus/8167-encrypted-private-keys.cjs @@ -51,11 +51,40 @@ tryIt("empty passphrase", () => { return same(c.createPrivateKey({ key: out, passphrase: "" }), rsa); }); -// Reading encrypted keys. -const p8pem = ec.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase: "pw" }); -const p8der = ec.privateKey.export({ type: "pkcs8", format: "der", cipher: "aes-256-cbc", passphrase: "pw" }); -const sec1 = String(ec.privateKey.export({ type: "sec1", format: "pem", cipher: "aes-128-cbc", passphrase: "pw" })); -tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(ec.publicKey)); +// Reading encrypted keys. These are fixed encryptions of one P-256 key +// with the passphrase "pw": decrypting with a wrong passphrase gives +// random-looking bytes, which a fresh encryption would make fail at +// different steps from run to run. +const fixedKey = [ + "-----BEGIN PRIVATE KEY-----", + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgExcc5/xZ3vYL2Dpg", + "XnB7E8gQhcW1bkUcIGJWBGvrrGChRANCAAR0wxNUH1yo/U9nqFY4Gt5SVzPjvWq6", + "NzlStUC6Njk9BdR7SvBlNIGDAFlOfMwhfZbvvvMP+rLMR3RtBg3Ketxe", + "-----END PRIVATE KEY-----", +].join("\n") + "\n"; +const p8pem = [ + "-----BEGIN ENCRYPTED PRIVATE KEY-----", + "MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBBkpqs2611cRjGDZTd4", + "FmNeAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQRhL9yxS4i0ngtke+", + "frhURASBkN0dk+GQNCGU5cFC+MMswOr9XwGaQnLbJo7VnhCje3//JaVJDjexrd69", + "YZliX7pKGNghfFf+eBAeOAkwbAK39T8KQPwTJVJ+Z5O485N2fl5dPlr3gaEA3+cm", + "LQgasmL7Zv9UOH2iwvWllFYL3bnwr7VsqTCW2xe53qTB+dWVurP0cCiZNGEnUzrf", + "4bvWNrW0nA==", + "-----END ENCRYPTED PRIVATE KEY-----", +].join("\n") + "\n"; +const p8der = Buffer.from("MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAOrVavYYEvy3KUHwK0hbQnAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQmEnqHOLegUirb49LD8oNhASBkGUwe3SZciScH/tWuoK5JWujckNHRKROwu81a5AGjIKxkvqCm+Qp9Gr4rPsb2vbv3bgfojbEKvSGvtuqB/X/jpN5UMZwWI3D9Ocd1+jEq1FqDb3rb3qxcoO9jmwuFFzNeYy4sW29XFuKTxu4aJHT1sEtMzIVlRLv53aXzpp9eQ9j47EOTWFqIpkt02TRFuazCQ==", "base64"); +const sec1 = [ + "-----BEGIN EC PRIVATE KEY-----", + "Proc-Type: 4,ENCRYPTED", + "DEK-Info: AES-128-CBC,E73A95A6783C45F914B397FE018521D2", + "", + "nsuhQFMPr+QFITcpmr2JWs+Zep4wxoCWMEk1jzyyXSJddfebIXOFg4xmtK8YJXIy", + "vqa6pvAIiak0iEZjVSn20+JAg2wqQ0sLaFPhBmhcZk+xuQoLD8CcYvROO5VtVELm", + "DkgOUcJ1oEfZJIRM6U2oDNFmj2NB/SnSQUf1niy7ba8=", + "-----END EC PRIVATE KEY-----", +].join("\n") + "\n"; +const fixedPublic = c.createPublicKey(fixedKey); +tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(fixedPublic)); tryIt("public from traditional", () => c.createPublicKey({ key: sec1, passphrase: "pw" }).type); tryIt("pkcs8 pem no passphrase", () => c.createPrivateKey(p8pem)); tryIt("pkcs8 der no passphrase", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8" })); From bd1bf39a765d7b8993f25862b40225388fc5e0f8 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:18:18 -0700 Subject: [PATCH 250/395] Close recursive record knots through erased generic method members A record or union that refers back to itself only closes its recursive knot when the frame did not resolve anything through context-sensitive hooks; otherwise the same checker type could answer differently per instantiation, so the knot is refused. Erasing a generic method member (`send(message: Message): string`) resolves the method's own type parameters to `unknown`, and that resolution counted as context-sensitive. Any recursive interface declaring a generic method was therefore refused: `interface Sub extends Base { send(m: Message): string }` reached from `Base { child?: Sub }`, or a list node with a generic method beside its `next` field. The member erasure now records its own type parameters, and resolving one of them no longer counts. Consulting an enclosing binding still does, so a generic method that mentions an outer type parameter keeps the fence. The erased member's mapping is not memoized under the member's type, since the same type maps to nothing outside the erasure. pi's ReplacedSessionContext (sendMessage) extends ExtensionCommandContext, whose methods take callbacks over ReplacedSessionContext. The fence refused ExtensionAPI.registerCommand and, through it, pi's main(). Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 22 ++++++- .../recursive-generic-method-members.ts.json | 6 ++ .../recursive-generic-method-members.ts | 58 +++++++++++++++++++ .../recursive-generic-method-members.ts.json | 1 + 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json create mode 100644 tests/corpus/recursive-generic-method-members.ts create mode 100644 tests/harness/corpus-costs/recursive-generic-method-members.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..99766d0ab 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1255,6 +1255,10 @@ export interface TypeMapperCtx { * (erasedGenericMemberType): the signature's own type parameters * resolve to `unknown` instead of refusing the mapping. */ erasingSignature?: ts.Signature; + /** Set with erasingSignature: the signature's own type parameters, and + * whether the erasure consulted an enclosing binding. An erasure that + * resolved only its own parameters answers the same in every context. */ + erasedMember?: { own: ReadonlySet; outerConsulted: boolean }; /** True while an erased generic body lowers (its type parameters are * bound to `unknown`). */ isErasing?: () => boolean; @@ -1835,6 +1839,7 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { memoKey !== null && topLevel && contextResolutions === sensitivityAtEntry && + ctx.erasingSignature === undefined && (ctx.canMemoizeType?.() ?? true) ) { ctx.typeMemo?.set(memoKey, mapped); @@ -1991,7 +1996,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { !ERASURE_KEPT_CONTAINERS.has(ts.symbolNameOf(type.getSymbol()) ?? "") && mentionsErasedTypeParam(type, ctx, 0) ) { - contextResolutions++; + if (!ctx.erasedMember || ctx.erasedMember.outerConsulted) contextResolutions++; return DYN; } // Readonly over a program class (Object.freeze's result type) is the @@ -2001,7 +2006,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (resolveTypeParam && type.flags & ts.TypeFlags.TypeParameter) { const bound = resolveTypeParam(type); if (bound) { - contextResolutions++; + if (!ctx.erasedMember?.own.has(type)) contextResolutions++; return bound; } } @@ -7309,10 +7314,21 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType const sig = sigs[0]!; const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; + // Resolving only the member's own parameters does not make the erasure + // context-sensitive, so a recursive record holding a generic method + // (`interface Sub extends Base { send(m: Msg): void }` reached + // from Base) still closes its knot. Consulting an enclosing binding does. + const erasedMember = { own, outerConsulted: false }; const erased = mapType(t, { ...ctx, erasingSignature: sig, - resolveTypeParam: (param) => (own.has(param) ? DYN : (outer?.(param) ?? null)), + erasedMember, + resolveTypeParam: (param) => { + if (own.has(param)) return DYN; + if (outer === undefined) return null; + erasedMember.outerConsulted = true; + return outer(param); + }, }); return erased?.kind === "func" ? erased : undefined; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json new file mode 100644 index 000000000..6c3590ab8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/recursive-generic-method-members.ts" + ], + "diags": [] +} diff --git a/tests/corpus/recursive-generic-method-members.ts b/tests/corpus/recursive-generic-method-members.ts new file mode 100644 index 000000000..1f4d8372d --- /dev/null +++ b/tests/corpus/recursive-generic-method-members.ts @@ -0,0 +1,58 @@ +// A recursive record that declares a generic method compiles. Erasing the +// method's own type parameters does not depend on any enclosing generic +// binding, so the record still closes its recursive knot: a parent +// interface whose child is a subinterface declaring a generic method, and a +// list node whose generic method sits beside a self-referencing field. + +interface Message { + content: string; + details?: T; +} + +interface TreeNode { + name: string; + child?: SendingNode; +} + +interface SendingNode extends TreeNode { + send(message: Message): string; +} + +interface ListNode { + value: number; + next: ListNode | null; + map(f: (value: number) => U): string; +} + +function describe(node: TreeNode): string { + const inner = node.child; + return inner ? `${node.name} > ${describe(inner)}` : node.name; +} + +function leaf(name: string): SendingNode { + return { name, send: () => `${name} got a message` }; +} + +const tree: SendingNode = { + name: "root", + child: { name: "middle", child: leaf("leaf"), send: () => "middle got a message" }, + send: () => "root got a message", +}; +console.log(describe(tree)); +console.log(tree.send({ content: "hello" })); +const middle = tree.child; +if (middle !== undefined) console.log(middle.send({ content: "hi", details: 42 })); + +function cons(value: number, next: ListNode | null): ListNode { + return { value, next, map: () => `node ${value}` }; +} + +let list: ListNode | null = cons(1, cons(2, cons(3, null))); +const values: number[] = []; +let labels = ""; +while (list !== null) { + values.push(list.value); + labels += `[${list.map((v) => v * 2)}]`; + list = list.next; +} +console.log(values.join(","), labels); diff --git a/tests/harness/corpus-costs/recursive-generic-method-members.ts.json b/tests/harness/corpus-costs/recursive-generic-method-members.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/recursive-generic-method-members.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From ea599922a431abcc51210f7a07b2bc3a895721db Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:23:22 -0500 Subject: [PATCH 251/395] Iterate and read native arrays from untyped code without type-name scans Untyped code that loops over a native array (`for (const ch of checks)` in zod, over arrays the compiled code built) paid for the loop before its first element: - Every check of whether a typed capsule is a generator compared its type name against "async-generator<" with strncmp, and native array type names also start with 'a'. Iterator creation made several such checks. The test now looks at the second byte before comparing. - scr_dyn_iterator called the builtin values() through the general call path for a native array; it now creates the native iterator directly, as it already did for untyped arrays. - Read sites of `length` and `next` call scr_js_get_probe_native, which answers a native array's `length` and a native iterator's `next`, where the generic read built the array's checked view or looked the method up by name. Other read sites keep scr_js_get_probe, so their receivers that are not ordinary objects still miss without a further test. --- packages/compiler/src/backend/llvm/js-ic.ts | 8 +++-- packages/compiler/test/js-ic-emission.test.ts | 2 +- .../native-array-dynamic-reads/main.ts.json | 7 ++++ packages/runtime/src/scr_dyn.h | 3 ++ packages/runtime/src/scr_js.h | 4 +++ packages/runtime/src/scr_js_object.c | 5 +++ packages/runtime/src/scr_json.c | 31 +++++++++++++++--- .../corpus/native-array-dynamic-reads/main.ts | 32 +++++++++++++++++++ .../native-array-dynamic-reads/reads.js | 29 +++++++++++++++++ .../native-array-dynamic-reads/main.ts.json | 1 + 10 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json create mode 100644 tests/corpus/native-array-dynamic-reads/main.ts create mode 100644 tests/corpus/native-array-dynamic-reads/reads.js create mode 100644 tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index 11b894a60..d51b838bb 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -406,9 +406,13 @@ function emitGet(host: Host, e: ExprOf<"call">, callee: IrFunction, key: string) inlineLoad(host, receiver.name, ic, INLINE_WAYS, INLINE_PROTO, (cell) => cell, hits, hit, probe); B.startBlock(probe); // The read's probe also answers keys the receiver's plain chain lacks. - host.declare(`declare ptr @scr_js_get_probe(ptr, ptr, ptr)`); + // Only `length` and `next` reads can be answered for a native array or + // iterator, so only those sites take the probe that tests them. + const probeFn = + key === "length" || key === "next" ? "scr_js_get_probe_native" : "scr_js_get_probe"; + host.declare(`declare ptr @${probeFn}(ptr, ptr, ptr)`); const probed = B.tmp(); - B.line(`${probed} = call ptr @scr_js_get_probe(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); + B.line(`${probed} = call ptr @${probeFn}(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); const found = B.tmp(); B.line(`${found} = icmp ne ptr ${probed}, null`); const probeEnd = B.currentLabel(); diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 79342e99d..b350c743d 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -201,7 +201,7 @@ console.log(lib.total([1, 2, 3])); expect(fn).toContain("@scr_js_keyed_probe("); // The length read answers from the native array's length. expect(fn).toContain("icg.rest"); - expect(fn).toContain("@scr_js_get_probe("); + expect(fn).toContain("@scr_js_get_probe_native("); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json new file mode 100644 index 000000000..c3b3013ea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/native-array-dynamic-reads/reads.js", + "/tests/corpus/native-array-dynamic-reads/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..bc20be862 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1955,6 +1955,9 @@ long scr_dyn_live_count(void); * scr_json.c, which programs with dynamic-tier code link. */ /* The builtin Object.prototype node, or NULL before its first use. */ const ScrDyn *scr_dyn_tier_object_prototype(void); +/* A read site's answer for a native array's length or a native iterator's + * next (scr_js_get_probe_native): +1, or NULL. */ +ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key); /* Shared key bytes for `key` (+1, NUL-terminated), and their release. */ char *scr_dyn_tier_key_copy(const char *key, size_t key_len); void scr_dyn_tier_key_release(char *key, size_t key_len); diff --git a/packages/runtime/src/scr_js.h b/packages/runtime/src/scr_js.h index 3ff9becd3..5abab2053 100644 --- a/packages/runtime/src/scr_js.h +++ b/packages/runtime/src/scr_js.h @@ -235,6 +235,10 @@ ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); * a key that the receiver and its plain prototype chain lack, and caches * that answer. A method read uses scr_js_load_probe. */ ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); +/* scr_js_get_probe for a site whose key is `length` or `next`, which also + * answers a native array's length and a native iterator's next + * (scr_dyn_tier_unshaped_get). */ +ScrDyn *scr_js_get_probe_native(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); /* `receiver.key = value` (value borrowed) when it replaces a writable own * data property or adds one the prototype chain lacks: true when done. */ bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJsIc *ic); diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index e3d0b3a54..4cee9e7e8 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -407,6 +407,11 @@ ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { return scr_js_probe_load(receiver, key, ic, true); } +ScrDyn *scr_js_get_probe_native(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return scr_dyn_tier_unshaped_get(receiver, key); + return scr_js_probe_load(receiver, key, ic, true); +} + /* ── stores ───────────────────────────────────────────────────────────── */ /* The generic store keeps a native Error's fields in sync with these keys diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..69339b0ca 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -96,9 +96,13 @@ static SCR_TL ScrDyn *scr_iterator_symbol; static SCR_TL ScrDyn *scr_async_iterator_symbol; static SCR_TL ScrDyn *scr_to_primitive_symbol; static bool scr_dyn_generator(const ScrDyn *value) { - return value && value->kind == SCR_DYN_TYPED_REF && - (value->v.typed_ref.type_key[0] == 'g' || value->v.typed_ref.type_key[0] == 'a') && - (!strncmp(value->v.typed_ref.type_key, "generator<", 10) || !strncmp(value->v.typed_ref.type_key, "async-generator<", 16)); + if (!value || value->kind != SCR_DYN_TYPED_REF) return false; + /* Array keys also start with 'a': the second byte sets them apart before + * any comparison. */ + const char *key = value->v.typed_ref.type_key; + size_t len = value->v.typed_ref.type_key_len; + if (key[0] == 'g') return len >= 10 && !memcmp(key, "generator<", 10); + return key[0] == 'a' && key[1] == 's' && len >= 16 && !memcmp(key, "async-generator<", 16); } static bool scr_dyn_generator_symbol(const ScrDyn *value, const ScrDyn *key) { const ScrDyn *symbol = value->v.typed_ref.type_key[0] == 'a' ? scr_async_iterator_symbol : scr_iterator_symbol; @@ -7494,6 +7498,23 @@ static ScrDyn *scr_native_iterator_new(ScrDyn *source) { return result; } +#ifdef SCR_JS_TIER +/* A read site's answer for a receiver without a JS shape whose property is + * plain data the runtime holds directly: a native array's length (the + * generic read would build its checked view first) and a native iterator's + * `next` (the builtin the generic read returns). +1, or NULL for the slow + * path. */ +ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key) { + if (key->len == 6 && receiver->kind == SCR_DYN_TYPED_REF && !memcmp(key->data, "length", 6) && + scr_dyn_typed_ref_array_ops(receiver) && scr_dyn_ref_is_array(receiver)) + return scr_dyn_new_num((double)((ScrArr *)receiver->v.typed_ref.ptr)->len); + if (key->len == 4 && receiver->kind == SCR_DYN_HANDLE && + receiver->v.handle.tag == SCR_DYNH_ITERATOR && !memcmp(key->data, "next", 4)) + return scr_native_iterator_get(receiver->v.handle.ptr, key->data, key->len); + return NULL; +} +#endif + static ScrDyn *scr_native_collection_iterator(ScrDyn *source, unsigned selection) { ScrDyn *result = scr_native_iterator_new(source); ((ScrNativeIterator *)result->v.handle.ptr)->selection = selection; @@ -8384,7 +8405,9 @@ ScrDyn *scr_dyn_iterator(const ScrDyn *source, const ScrStr *spell) { } /* An array whose iterator method is the builtin values(): what calling * it with the array as `this` returns, without the call. */ - if (source->kind == SCR_DYN_ARR && method == scr_iterator_functions[1]) { + if (method == scr_iterator_functions[1] && + (source->kind == SCR_DYN_ARR || + (source->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(source)))) { scr_dyn_release(method); return scr_native_iterator_new((ScrDyn *)source); } diff --git a/tests/corpus/native-array-dynamic-reads/main.ts b/tests/corpus/native-array-dynamic-reads/main.ts new file mode 100644 index 000000000..0641114bf --- /dev/null +++ b/tests/corpus/native-array-dynamic-reads/main.ts @@ -0,0 +1,32 @@ +// Native arrays and iterators from typed code, read by untyped code: for-of +// over them, their length, an iterator's next read as a value and called, +// generators and async generators beside them, and an array whose +// iteration a Symbol.iterator use elsewhere in the program does not change. +import { asyncKinds, describe, lengths, sumAll, stepper } from "./reads.js"; + +interface Item { + sku: string; + qty: number; +} +const numbers: number[] = [1, 2, 3, 4]; +const items: Item[] = [ + { sku: "a", qty: 2 }, + { sku: "b", qty: 5 }, +]; +const empty: string[] = []; +function* counting(): Generator { + yield 10; + yield 20; +} +console.log(sumAll(numbers), sumAll(items.map((i) => i.qty)), sumAll(empty.map(Number))); +console.log(lengths([numbers, items, empty]).join()); +numbers.push(5); +console.log(lengths([numbers]).join(), sumAll(numbers)); +console.log(describe(items), describe(counting())); +console.log(stepper(numbers).join(), stepper(counting()).join()); +const iterable = { *[Symbol.iterator]() { yield "own"; } }; +console.log([...iterable].join(), sumAll(numbers)); +async function* later(): AsyncGenerator { + yield 1; +} +asyncKinds(later(), numbers).then((kinds) => console.log(kinds.join())); diff --git a/tests/corpus/native-array-dynamic-reads/reads.js b/tests/corpus/native-array-dynamic-reads/reads.js new file mode 100644 index 000000000..67e1a824a --- /dev/null +++ b/tests/corpus/native-array-dynamic-reads/reads.js @@ -0,0 +1,29 @@ +export function sumAll(values) { + let total = 0; + for (const value of values) total += value; + return total; +} +export function lengths(arrays) { + const out = []; + for (const array of arrays) out.push(array.length); + return out; +} +export function describe(values) { + const parts = []; + for (const value of values) parts.push(typeof value === "object" ? value.sku : value); + return parts.join("+"); +} +export function stepper(values) { + const iterator = values[Symbol.iterator](); + const next = iterator.next; + const out = []; + for (let step = next.call(iterator); !step.done; step = next.call(iterator)) out.push(step.value); + out.push(typeof iterator.next, iterator.next === next); + return out; +} +export async function asyncKinds(generator, array) { + const out = []; + for await (const value of generator) out.push(value); + for (const value of array) out.push(value); + return out; +} diff --git a/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json b/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json new file mode 100644 index 000000000..ac2ef5ae8 --- /dev/null +++ b/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3567, "san": 6402 } From 15a183026f1874aeca4c9cadb6220224600d7a61 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:27:58 -0500 Subject: [PATCH 252/395] Allocate the shared two-byte characters from the system allocator The one-character strings of U+0080..U+07FF that string iteration shares between threads were allocated from the calling thread's memory. On Linux a worker that exits without sharing anything returns that memory, so the table kept strings that a later read found discarded. They now come from the system allocator, as other state that outlives a thread does. --- ...string-iteration-worker-characters.ts.json | 6 +++ packages/runtime/src/scr_string.c | 15 ++++--- ...8348-string-iteration-worker-characters.ts | 44 +++++++++++++++++++ ...string-iteration-worker-characters.ts.json | 1 + .../harness/string-iteration-workers.test.ts | 44 +++++++++++++++++++ 5 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json create mode 100644 tests/corpus/8348-string-iteration-worker-characters.ts create mode 100644 tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json create mode 100644 tests/harness/string-iteration-workers.test.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json new file mode 100644 index 000000000..e767eaf65 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8348-string-iteration-worker-characters.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index cd17da0f8..64a4772e5 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -3538,7 +3538,11 @@ ScrStr *scr_str_cp_at(ScrStr *s, double i) { * thread, as the ASCII ones are static: the string iterator returns them * instead of a new string per character. A string is immortal before its * slot publishes it; a thread that loses the race to fill a slot keeps its - * own immortal copy, so at most one extra string per slot and thread. */ + * own immortal copy, so at most one extra string per slot and thread. The + * strings come from the system allocator, as other state that outlives a + * thread does: a worker's own memory goes back when it exits without + * sharing (scr_alloc.c), and the table outlives the worker that filled a + * slot. */ static ScrStr *scr_two_byte_chars[0x800 - 0x80]; static ScrStr *scr_two_byte_char(const char *bytes) { @@ -3546,12 +3550,13 @@ static ScrStr *scr_two_byte_char(const char *bytes) { ScrStr **slot = &scr_two_byte_chars[cp - 0x80]; ScrStr *known = __atomic_load_n(slot, __ATOMIC_ACQUIRE); if (known) return known; - ScrStr *made = scr_str_from_span(bytes, 2); + ScrStr *made = calloc(1, sizeof(ScrStr) + 3); + if (!made) scr_oom(); + made->len = 2; + made->cap = 2; + memcpy(made->data, bytes, 2); scr_str_hash_prime(made); made->rc = SIZE_MAX; -#ifdef SCR_RC_AUDIT - scr_str_live_forget(); -#endif scr_lsan_immortal(made); ScrStr *expected = NULL; if (__atomic_compare_exchange_n(slot, &expected, made, false, __ATOMIC_ACQ_REL, diff --git a/tests/corpus/8348-string-iteration-worker-characters.ts b/tests/corpus/8348-string-iteration-worker-characters.ts new file mode 100644 index 000000000..52a38fb78 --- /dev/null +++ b/tests/corpus/8348-string-iteration-worker-characters.ts @@ -0,0 +1,44 @@ +import { Worker, isMainThread, parentPort } from "node:worker_threads"; + +// The one-character strings of U+0080..U+07FF that string iteration returns +// are shared by every thread. A worker that is the first to iterate them +// and exits without sharing anything else leaves them in use: the main +// thread iterates the same characters after the worker's memory goes back. + +const text = "aé ñ ß ü Ω ж א ع ߿"; + +function codes(s: string): string { + const out: string[] = []; + for (const c of s) out.push(`${c.length}:${c.charCodeAt(0).toString(16)}`); + return out.join(" "); +} + +function runWorker(): Promise { + return new Promise((resolve, reject) => { + const worker = new Worker(new URL(import.meta.url)); + let count = -1; + worker.on("message", (value: number) => { + count = value; + }); + worker.on("error", reject); + worker.on("exit", () => resolve(count)); + }); +} + +async function main(): Promise { + console.log("worker", await runWorker()); + // The worker's memory goes back shortly after it exits. + await new Promise((resolve) => setTimeout(resolve, 300)); + console.log("main", codes(text)); + console.log("spread", [...text].join("|")); + console.log("from", Array.from("ñandú").map((c) => c.length).join(",")); +} + +if (isMainThread) { + void main(); +} else { + let count = 0; + for (const c of text) count += c.length; + count += [...text].length + Array.from("ñandú").length; + parentPort!.postMessage(count); +} diff --git a/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json b/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json new file mode 100644 index 000000000..4c96af583 --- /dev/null +++ b/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json @@ -0,0 +1 @@ +{ "plain": 3960, "san": 3900 } diff --git a/tests/harness/string-iteration-workers.test.ts b/tests/harness/string-iteration-workers.test.ts new file mode 100644 index 000000000..bc1feca95 --- /dev/null +++ b/tests/harness/string-iteration-workers.test.ts @@ -0,0 +1,44 @@ +/* The one-character strings that string iteration shares between threads + * outlive the worker that first made them. On Linux a worker that exits + * without sharing anything returns its memory; with SCR_SA_DISCARD=poison + * that memory becomes inaccessible instead, so a cached character + * allocated from the worker's memory would fault when the main thread + * reads it. The corpus program pins Node.js parity; this suite runs it + * with the discard poisoned and immediate. Under SCRIPTC_SAN=1 the runtime + * keeps every worker's memory, so the check does not apply. */ +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"; +import { compile } from "@scriptc/compiler"; + +const exec = promisify(execFile); +const program = join(import.meta.dirname, "../corpus/8348-string-iteration-worker-characters.ts"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test.skipIf(process.platform !== "linux" || sanitize)( + "characters a discarded worker cached stay readable", + { timeout: 240_000 }, + async () => { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-string-iteration-workers-")); + try { + const result = await compile(program, { + backend: "llvm", + outDir, + outPath: join(outDir, "program"), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + if (!result.ok) return; + const { stdout } = await exec(result.binaryPath, [], { + env: { ...process.env, SCR_SA_DISCARD: "poison", SCR_SA_DISCARD_DELAY_MS: "0" }, + timeout: 120_000, + }); + expect(stdout).toContain("main 1:61 1:e9 1:20 1:f1"); + expect(stdout).toContain("spread a|é| |ñ"); + } finally { + await rm(outDir, { recursive: true, force: true }); + } + }, +); From dec6be6ebddb41f1996ba7e71264bbc224dc07af Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:35:04 -0500 Subject: [PATCH 253/395] Run global substitutions with a literal replacement through build-time replacers A global replace or replaceAll of a regex literal called the compiled matcher once per match: each call set up its frame and seek again, and the runtime then copied the gap and the replacement. A substitution of a literal inside the compiled subset with an ASCII replacement without $ patterns now gets a replacer built for that pattern and replacement. It appends each gap and the replacement to the output and continues the search after the match, as the runtime's loop does (one past an empty match), without returning. Gaps of up to 16 bytes are one 16-byte copy, and the search after a match is a 16-byte scan instead of a memchr call. scr_regex_replace_with runs the replacer on an ASCII subject; a subject outside ASCII keeps the runtime's loop. Plain-text patterns, which the runtime searches as strings, get no replacer. New corpus program: 8351-regex-literal-replacers.ts. --- .../compiler/src/backend/llvm/emit-module.ts | 18 ++ packages/compiler/src/backend/llvm/emitter.ts | 8 + .../src/backend/llvm/expr-primitives.ts | 23 +++ .../compiler/src/backend/llvm/expr-records.ts | 45 +++- .../src/backend/llvm/regex-matchers.test.ts | 44 ++++ .../src/backend/llvm/regex-matchers.ts | 194 ++++++++++++++++-- .../compiler/test/regex-replacers.test.ts | 42 ++++ .../8351-regex-literal-replacers.ts.json | 6 + packages/runtime/src/scr_regex.c | 45 ++++ packages/runtime/src/scr_regex.h | 16 ++ tests/corpus/8351-regex-literal-replacers.ts | 72 +++++++ .../8351-regex-literal-replacers.ts.json | 1 + 12 files changed, 491 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/test/regex-replacers.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json create mode 100644 tests/corpus/8351-regex-literal-replacers.ts create mode 100644 tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index ee9089071..af00ebc2c 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -600,6 +600,23 @@ export function emitModuleParts(emitter: LlEmitter): string[] { regexMatchers.set(key, code); for (const decl of code.decls) emitter.declare(decl); } + // Replacers of global substitutions with a literal replacement; the call + // sites register only patterns inside the compiled subset. + const regexReplacers: RegexMatcherCode[] = []; + for (const rr of emitter.regexReplacers.values()) { + const code = compileRegexMatcher( + rr.sym, + rr.pattern, + rr.flags, + emitter.sizeType, + !emitter.wasi, + rr.replacement, + ); + if (code === null) + throw new InternalCompilerError(`llvm emitter bug: replacer ${rr.sym} did not compile`); + regexReplacers.push(code); + for (const decl of code.decls) emitter.declare(decl); + } const out: string[] = [ `; Generated by scriptc (LLVM backend) from ${emitter.mod.sourceFile}. Do not edit.`, @@ -882,6 +899,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] { } if (emitter.regexInstances.size > 0) out.push(``); for (const code of regexMatchers.values()) out.push(...code.lines, ``); + for (const code of regexReplacers) out.push(...code.lines, ``); for (const [, inst] of emitter.templateStringsInstances) { // One immortal ScrArr per tagged-template site: a data global of // interned cooked-string literals, and the ScrArr header over it diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 1a2c8bef8..d15a8c017 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1303,6 +1303,12 @@ export class LlEmitter { string, { sym: string; src: string; fl: string; pattern: string; flags: string } >(); + /** Build-time replacers: one per (pattern, flags, replacement) of a + * global substitution with a literal replacement (regex-matchers.ts). */ + readonly regexReplacers = new Map< + string, + { sym: string; pattern: string; flags: string; replacement: string } + >(); /** Interned tagged-template strings objects: per-site key → { symbol, * interned cooked-literal refs }. One immortal ScrArr of string slots * per template SITE (the spec's per-occurrence identity — the C @@ -5439,6 +5445,8 @@ export class LlEmitter { return addNumbered(this.threadSlotsBlocks, key, value as string, "@sc_slots_", names); case "regex": return addNumberedSymbol(this.regexInstances, key, value as never, "sc_re_", names); + case "regexReplacer": + return addNumberedSymbol(this.regexReplacers, key, value as never, "sc_rr_", names); case "templateStrings": return addNumberedSymbol( this.templateStringsInstances, diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index d3b72897e..eed138fe2 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -661,6 +661,29 @@ export function regexLiteralTemplate( return `@${re.sym}`; } +/** The build-time replacer of a global substitution of regex literal `e` + * with the literal `replacement` (regex-matchers.ts), registered on first + * use. */ +export function regexLiteralReplacer( + host: LlvmEmitterContext, + e: IrExpr & { kind: "regexLit" }, + replacement: string, +): string { + const key = `${e.flags}/${e.pattern}\0${replacement}`; + let rr = host.regexReplacers.get(key); + if (!rr) { + rr = { + sym: `sc_rr_${host.regexReplacers.size}`, + pattern: e.pattern, + flags: e.flags, + replacement, + }; + host.regexReplacers.set(key, rr); + } + host.journal?.entry("regexReplacer", key, rr); + return `@${rr.sym}`; +} + /** A string equality or comparison between `s.charAt(i)` and a literal of * exactly one UTF-16 code unit: the charAt call, the unit, and whether the * charAt is the left operand. */ diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..51688eafa 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -6,7 +6,8 @@ import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, LlValue } from "./expr-context.js"; import { f64Lit, llvmCommentText } from "./common.js"; import { borrowableInputs, emitBorrowedInputs, emitBorrowedInput } from "./borrowed-inputs.js"; -import { regexLiteralTemplate } from "./expr-primitives.js"; +import { regexLiteralReplacer, regexLiteralTemplate } from "./expr-primitives.js"; +import { canReplaceWith, regexLiteralCompiles } from "./regex-matchers.js"; import { isScalarUnionDef, loadInlineUnion, presentBits } from "./inline-unions.js"; import { preservesRegexInputs } from "./reference-effects.js"; @@ -160,6 +161,39 @@ function directRegexOperandMethod(method: string): boolean { } } +/** The build-time replacer of a substitution: a regex literal running on + * its template, with a literal replacement the replacer can write as is. + * Null when the substitution takes the runtime's loop. */ +function literalReplacer( + host: LlvmEmitterContext, + literal: IrExpr | null, + template: string | null, + replacement: IrExpr, +): string | null { + if (literal === null || template === null || literal.kind !== "regexLit") return null; + if (replacement.kind !== "strLit") return null; + if (!canReplaceWith(literal.pattern, literal.flags, replacement.value)) return null; + if (!regexLiteralCompiles(literal.pattern, literal.flags)) return null; + return regexLiteralReplacer(host, literal, replacement.value); +} + +/** scr_regex_replace_with: the runtime runs `replacer` on an ASCII subject + * and its own loop on any other. */ +function emitReplaceWith( + host: LlvmEmitterContext, + e: IrExpr, + r: LlValue, + args: LlValue[], + replacer: string, +): LlValue { + host.declare(`declare ptr @scr_regex_replace_with(ptr, ptr, ptr, ptr)`); + const t = host.B.tmp(); + host.B.line( + `${t} = call ptr @scr_regex_replace_with(ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${replacer})`, + ); + return host.own({ name: t, type: e.type }); +} + export function emitRegexIntrinsic( host: LlvmEmitterContext, e: IrExpr & { kind: "regexIntrinsic" }, @@ -234,11 +268,16 @@ export function emitRegexIntrinsic( "scr_regex_match_all_captures_into", `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, ); - case "replaceAll": + case "replaceAll": { + // A global literal (the only kind a replacer is built for) never takes + // replaceAll's non-global TypeError. + const replacer = literalReplacer(host, literal, template, e.args[1]!); + if (replacer !== null) return emitReplaceWith(host, e, r, args, replacer); return fallible( "scr_regex_replace_all", `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, ); + } case "split": host.declare(`declare ptr @scr_regex_split_limit(ptr, ptr, double)`); { @@ -323,6 +362,8 @@ export function emitRegexIntrinsic( return host.own({ name: t, type: e.type }); } case "replace": { + const replacer = literalReplacer(host, literal, template, e.args[1]!); + if (replacer !== null) return emitReplaceWith(host, e, r, args, replacer); host.declare(`declare ptr @scr_regex_replace(ptr, ptr, ptr)`); const t = B.tmp(); B.line( diff --git a/packages/compiler/src/backend/llvm/regex-matchers.test.ts b/packages/compiler/src/backend/llvm/regex-matchers.test.ts index d009de7c9..5f93f7295 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.test.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "vitest"; import { + canReplaceWith, compileRegexMatcher, regexLiteralBailReason, regexLiteralCaptures, @@ -152,3 +153,46 @@ describe("regex literal matcher code", () => { expect(text).not.toContain("constant [128 x i8]"); }); }); + +describe("regex literal replacers", () => { + test("take global, non-sticky, non-plain-text patterns and ASCII replacements without $", () => { + expect(canReplaceWith("a[bc]", "g", "<2>")).toBe(true); + expect(canReplaceWith("a[bc]", "gi", "")).toBe(true); + expect(canReplaceWith("a[bc]", "", "x")).toBe(false); + expect(canReplaceWith("a[bc]", "gy", "x")).toBe(false); + expect(canReplaceWith("a[bc]", "g", "$1")).toBe(false); + expect(canReplaceWith("a[bc]", "g", "é")).toBe(false); + // Plain text is a string search at run time. + expect(canReplaceWith("&", "g", "&")).toBe(false); + expect(canReplaceWith("a\\.b", "g", "x")).toBe(false); + // Letters under i are not plain text. + expect(canReplaceWith("ab", "gi", "x")).toBe(true); + }); + + test("append the gap and the replacement and continue the search", () => { + const code = compileRegexMatcher("r", "<[^>]*>", "g", "i64", true, "|"); + const text = code!.lines.join("\n"); + expect(text).toContain('@r.rep = private unnamed_addr constant [1 x i8] c"|"'); + expect(text).toContain("call void @scr_re_replace_out_reserve(ptr %out, i64 %m.need)"); + expect(text).toContain("load <16 x i8>, ptr %m.src, align 1"); + expect(text).toMatch(/br i1 %m\.past, label %nomatch, label %scan/); + expect(text).not.toContain("m.ret:"); + expect(text).toContain("ret i32 %n.found"); + // The search after a match is a 16-byte scan, not a memchr call. + expect(text).not.toContain("@memchr"); + expect(code!.decls).toContain("declare void @scr_re_replace_out_reserve(ptr, i64)"); + // Without vectors the gap is a memcpy. + const scalar = compileRegexMatcher("r", "<[^>]*>", "g", "i64", false, "|")!.lines.join("\n"); + expect(scalar).not.toContain("<16 x i8>"); + expect(scalar).toContain( + "call void @llvm.memcpy.p0.p0.i64(ptr %m.dst, ptr %m.src, i64 %m.gapw, i1 false)", + ); + }); + + test("escape the replacement bytes and skip the copy of an empty one", () => { + const quoted = compileRegexMatcher("r", "a[bc]", "g", "i64", true, '"\\\n')!.lines.join("\n"); + expect(quoted).toContain('c"\\22\\5C\\0A"'); + const empty = compileRegexMatcher("r", "a[bc]", "g", "i64", true, "")!.lines.join("\n"); + expect(empty).not.toContain("@r.rep"); + }); +}); diff --git a/packages/compiler/src/backend/llvm/regex-matchers.ts b/packages/compiler/src/backend/llvm/regex-matchers.ts index d83d6bae5..0491a6446 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.ts @@ -24,6 +24,8 @@ * `len`, it returns the capture count (whole match included) so the runtime * can check it against the compiled bytecode. */ +import { InternalCompilerError } from "../../errors.js"; + const ASCII = 128; const INF = -1; /** Larger patterns keep the interpreter (binary size). */ @@ -1853,10 +1855,13 @@ export function compileRegexMatcher( flags: string, sizeType: string, vector: boolean, + replacement: string | null = null, ): RegexMatcherCode | null { const parsed = parseSubset(pattern, flags); if (parsed === null) return null; const sticky = flags.includes("y"); + if (replacement !== null && !canReplaceWith(pattern, flags, replacement)) + throw new InternalCompilerError(`llvm emitter bug: no replacer for /${pattern}/${flags}`); const { root, ncap } = parsed; const gen = new RxGen(sym, ncap, vector); const end: FirstInfo = { set: emptySet(), nullable: true, bol: false, uncond: true }; @@ -1873,6 +1878,12 @@ export function compileRegexMatcher( fn.push(` %stk = alloca ptr`); fn.push(` %sbuf = alloca [64 x i32]`); for (let r = 0; r < gen.regs; r++) fn.push(` %r${r} = alloca i32`); + if (replacement !== null) { + fn.push(` %next = alloca i32`); + fn.push(` %nfound = alloca i32`); + fn.push(` store i32 0, ptr %next`); + fn.push(` store i32 0, ptr %nfound`); + } fn.push(` store ptr %sbuf, ptr %stk`); fn.push(` store i32 64, ptr %scap`); fn.push(` store i32 %start, ptr %at`); @@ -1900,7 +1911,9 @@ export function compileRegexMatcher( } else { fn.push(` br label %seek`); } - if (firstCount === 1) { + // A replacer seeks again after every match, often a few bytes on: the + // 16-byte scan avoids a memchr call per match. + if (firstCount === 1 && replacement === null) { fn.push(`seek:`); const byte = intervals(first.set)[0]![0]!; fn.push(` %rest = sub i32 %len, %a`); @@ -1944,6 +1957,98 @@ export function compileRegexMatcher( fn.push(` br i1 %over, label %nomatch, label %scan`); } + if (replacement !== null) { + emitReplaceMatch(fn, sym, replacement, vector); + } else { + emitReturnMatch(fn, ncap); + } + + const result = replacement !== null ? "%n.found" : "0"; + fn.push(`nomatch:`); + if (replacement !== null) { + fn.push(` %n.next = load i32, ptr %next`); + fn.push(` %n.nextp = getelementptr inbounds i8, ptr %out, i64 16`); + fn.push(` %n.nextw = sext i32 %n.next to i64`); + fn.push(` store i64 %n.nextw, ptr %n.nextp`); + fn.push(` %n.found = load i32, ptr %nfound`); + } + fn.push(` %n.stk = load ptr, ptr %stk`); + fn.push(` %n.heap = icmp ne ptr %n.stk, %sbuf`); + fn.push(` br i1 %n.heap, label %n.free, label %n.ret`); + fn.push(`n.free:`); + fn.push(` call void @scr_regex_stack_free(ptr %n.stk)`); + fn.push(` br label %n.ret`); + fn.push(`n.ret:`); + fn.push(` ret i32 ${result}`); + + gen.emitTail(); + for (const l of gen.body) fn.push(l); + fn.push(`}`); + + const decls = REGEX_MATCHER_DECLS.slice(); + if (gen.body.some((l) => l.includes("@llvm.cttz.i16"))) + decls.push(`declare i16 @llvm.cttz.i16(i16, i1 immarg)`); + if (!sticky && !first.nullable && firstCount === 1 && replacement === null) + decls.push(`declare ptr @memchr(ptr, i32, ${sizeType})`); + if (replacement !== null) { + decls.push(`declare void @scr_re_replace_out_reserve(ptr, i64)`); + decls.push(`declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)`); + } + const globals = [...gen.globals]; + if (replacement !== null && replacement.length > 0) + globals.push( + `@${sym}.rep = private unnamed_addr constant [${replacement.length} x i8] c"${llvmBytes(replacement)}"`, + ); + return { lines: [...globals, ...fn], decls }; +} + +/** Whether a substitution can run through a build-time replacer: a global, + * non-sticky pattern that is not plain text (the runtime searches those as + * strings) and an ASCII replacement without `$` patterns. */ +export function canReplaceWith(pattern: string, flags: string, replacement: string): boolean { + if (!flags.includes("g") || flags.includes("y") || isPlainTextPattern(pattern, flags)) + return false; + for (let i = 0; i < replacement.length; i++) { + const c = replacement.charCodeAt(i); + if (c >= ASCII || c === 0x24) return false; + } + return true; +} + +/** scr_regex_plain_text's rule: a pattern of literal characters and escaped + * metacharacters (no letters under `i`) that the runtime searches as text. */ +function isPlainTextPattern(pattern: string, flags: string): boolean { + const fold = flags.includes("i"); + if (pattern.length === 0 || pattern.length > 256) return false; + for (let i = 0; i < pattern.length; i++) { + let c = pattern[i]!; + if (c === "\\") { + if (i + 1 >= pattern.length) return false; + c = pattern[++i]!; + if (!"^$\\.*+?()[]{}|/".includes(c)) return false; + } else { + const code = c.charCodeAt(0); + if (code < 0x20 || code >= 0x7f || "^$.*+?()[]{}|".includes(c)) return false; + } + if (fold && /[a-z]/i.test(c)) return false; + } + return true; +} + +function llvmBytes(text: string): string { + let out = ""; + for (let i = 0; i < text.length; i++) { + const c = text.charCodeAt(i); + out += + c >= 0x20 && c < 0x7f && c !== 0x22 && c !== 0x5c + ? text[i] + : `\\${c.toString(16).toUpperCase().padStart(2, "0")}`; + } + return out; +} + +/** The match block of a matcher: store the capture pointers, return 1. */ +function emitReturnMatch(fn: string[], ncap: number): void { fn.push(`match:`); fn.push(` %m.end = load i32, ptr %pos`); fn.push(` %m.start = load i32, ptr %at`); @@ -1969,25 +2074,72 @@ export function compileRegexMatcher( fn.push(` br label %m.ret`); fn.push(`m.ret:`); fn.push(` ret i32 1`); +} - fn.push(`nomatch:`); - fn.push(` %n.stk = load ptr, ptr %stk`); - fn.push(` %n.heap = icmp ne ptr %n.stk, %sbuf`); - fn.push(` br i1 %n.heap, label %n.free, label %n.ret`); - fn.push(`n.free:`); - fn.push(` call void @scr_regex_stack_free(ptr %n.stk)`); - fn.push(` br label %n.ret`); - fn.push(`n.ret:`); - fn.push(` ret i32 0`); - - gen.emitTail(); - for (const l of gen.body) fn.push(l); - fn.push(`}`); - - const decls = REGEX_MATCHER_DECLS.slice(); - if (gen.body.some((l) => l.includes("@llvm.cttz.i16"))) - decls.push(`declare i16 @llvm.cttz.i16(i16, i1 immarg)`); - if (!sticky && !first.nullable && firstCount === 1) - decls.push(`declare ptr @memchr(ptr, i32, ${sizeType})`); - return { lines: [...gen.globals, ...fn], decls }; +/** The match block of a replacer: append the gap since the previous match + * and the replacement to the output (ScrReReplaceOut: len, cap, next at + * 0, 8 and 16, then data), and continue the search at the match end, or one past it for an + * empty match, as the runtime's replace loop does. */ +function emitReplaceMatch(fn: string[], sym: string, replacement: string, vector: boolean): void { + const n = replacement.length; + fn.push(`match:`); + fn.push(` %m.end = load i32, ptr %pos`); + fn.push(` %m.start = load i32, ptr %at`); + fn.push(` %m.next = load i32, ptr %next`); + fn.push(` %m.gap = sub i32 %m.start, %m.next`); + fn.push(` %m.gapw = zext i32 %m.gap to i64`); + fn.push(` %m.lenp = getelementptr inbounds i8, ptr %out, i64 0`); + fn.push(` %m.capp = getelementptr inbounds i8, ptr %out, i64 8`); + fn.push(` %m.datap = getelementptr inbounds i8, ptr %out, i64 24`); + fn.push(` %m.len0 = load i64, ptr %m.lenp`); + fn.push(` %m.cap = load i64, ptr %m.capp`); + fn.push(` %m.room = sub i64 %m.cap, %m.len0`); + // 16 bytes of slack past the gap and the replacement for the 16-byte copy. + fn.push(` %m.need = add i64 %m.gapw, ${n + 32}`); + fn.push(` %m.fits = icmp uge i64 %m.room, %m.need`); + fn.push(` br i1 %m.fits, label %m.put, label %m.grow`); + fn.push(`m.grow:`); + fn.push(` call void @scr_re_replace_out_reserve(ptr %out, i64 %m.need)`); + fn.push(` br label %m.put`); + fn.push(`m.put:`); + fn.push(` %m.data = load ptr, ptr %m.datap`); + fn.push(` %m.len = load i64, ptr %m.lenp`); + fn.push(` %m.dst = getelementptr inbounds i8, ptr %m.data, i64 %m.len`); + fn.push(` %m.nextw = sext i32 %m.next to i64`); + fn.push(` %m.src = getelementptr inbounds i8, ptr %s, i64 %m.nextw`); + if (vector) { + // A gap of at most 16 bytes with 16 readable subject bytes is one + // unaligned 16-byte copy. + fn.push(` %m.small = icmp ule i32 %m.gap, 16`); + fn.push(` %m.rest = sub i32 %len, %m.next`); + fn.push(` %m.readable = icmp sge i32 %m.rest, 16`); + fn.push(` %m.fast = and i1 %m.small, %m.readable`); + fn.push(` br i1 %m.fast, label %m.copy16, label %m.copy`); + fn.push(`m.copy16:`); + fn.push(` %m.v = load <16 x i8>, ptr %m.src, align 1`); + fn.push(` store <16 x i8> %m.v, ptr %m.dst, align 1`); + fn.push(` br label %m.rep`); + } else { + fn.push(` br label %m.copy`); + } + fn.push(`m.copy:`); + fn.push(` call void @llvm.memcpy.p0.p0.i64(ptr %m.dst, ptr %m.src, i64 %m.gapw, i1 false)`); + fn.push(` br label %m.rep`); + fn.push(`m.rep:`); + fn.push(` %m.rdst = getelementptr inbounds i8, ptr %m.dst, i64 %m.gapw`); + if (n > 0) + fn.push(` call void @llvm.memcpy.p0.p0.i64(ptr %m.rdst, ptr @${sym}.rep, i64 ${n}, i1 false)`); + fn.push(` %m.len1 = add i64 %m.len, %m.gapw`); + fn.push(` %m.len2 = add i64 %m.len1, ${n}`); + fn.push(` store i64 %m.len2, ptr %m.lenp`); + fn.push(` store i32 %m.end, ptr %next`); + fn.push(` %m.found0 = load i32, ptr %nfound`); + fn.push(` %m.found1 = add i32 %m.found0, 1`); + fn.push(` store i32 %m.found1, ptr %nfound`); + fn.push(` %m.empty = icmp eq i32 %m.end, %m.start`); + fn.push(` %m.after = add i32 %m.end, 1`); + fn.push(` %m.at = select i1 %m.empty, i32 %m.after, i32 %m.end`); + fn.push(` store i32 %m.at, ptr %at`); + fn.push(` %m.past = icmp sgt i32 %m.at, %len`); + fn.push(` br i1 %m.past, label %nomatch, label %scan`); } diff --git a/packages/compiler/test/regex-replacers.test.ts b/packages/compiler/test/regex-replacers.test.ts new file mode 100644 index 000000000..746279eb4 --- /dev/null +++ b/packages/compiler/test/regex-replacers.test.ts @@ -0,0 +1,42 @@ +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 } from "../src/index.js"; + +async function emitLlvm(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-regex-replacers-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ll"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + return await readFile(outPath, "utf8"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +test("global substitutions of regex literals with literal replacements run a replacer", async () => { + const llvm = await emitLlvm(` +const s = process.argv[2] ?? "ac"; +const rep = process.argv[3] ?? "-"; +console.log( + s.replace(/<[^>]*>/g, "|"), + s.replaceAll(/a[bc]/g, "<2>"), + s.replace(/<[^>]*>/g, "|"), + s.replace(/<[^>]*>/g, "$&$&"), + s.replace(/<[^>]*>/, "|"), + s.replace(/<[^>]*>/gy, "|"), + s.replace(/<[^>]*>/g, rep), +); +`); + const replacers = llvm.match(/^define internal i32 @sc_rr_\d+\(/gm) ?? []; + // One replacer per (pattern, flags, replacement): the repeated site shares it. + expect(replacers).toHaveLength(2); + expect(llvm.match(/call ptr @scr_regex_replace_with\(/g)).toHaveLength(3); + // $ patterns, non-global and sticky patterns, and computed replacements + // keep the runtime's loop. + expect(llvm.match(/call ptr @scr_regex_replace\(/g)!.length).toBeGreaterThanOrEqual(4); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json new file mode 100644 index 000000000..be0149287 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8351-regex-literal-replacers.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index ab323c288..2a98e92c6 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -1416,6 +1416,51 @@ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep) { return scr_replace_impl(s, re, rep); } +void scr_re_replace_out_reserve(ScrReReplaceOut *o, int64_t need) { + if (need < 0 || (uint64_t)need > SCR_STR_MAX_CAP - (uint64_t)o->len) scr_regex_oom(); + size_t cap = (size_t)o->cap * 2; + if (cap < (size_t)(o->len + need)) cap = (size_t)(o->len + need); + if (cap > SCR_STR_MAX_CAP) cap = SCR_STR_MAX_CAP; + o->str->len = (size_t)o->len; + o->str = scr_str_regrow(o->str, cap); + o->data = o->str->data; + o->cap = (int64_t)o->str->cap; +} + +/* A global substitution with a literal ASCII replacement through the + * build-time replacer of its (pattern, replacement) pair: on an ASCII + * subject the replacer writes each gap and the replacement into the output + * and continues the search after every match without returning. Every + * other subject, and a plain-text pattern (string search), takes + * scr_replace_impl. */ +ScrStr *scr_regex_replace_with(ScrStr *s, ScrRegex *re, ScrStr *rep, + ScrRegexMatcherFn replacer) { + (void)scr_regex_bc(re); + if (scr_regex_lit(re) || !scr_text_ascii(s)) return scr_replace_impl(s, re, rep); + if (s->len > (size_t)INT32_MAX - 1) scr_regex_oom(); + re->last_index = 0; + ScrReReplaceOut o; + o.str = scr_str_alloc_raw(0, s->len + 64); + o.data = o.str->data; + o.len = 0; + o.cap = (int64_t)o.str->cap; + o.next = 0; + int32_t n = replacer((const uint8_t *)s->data, (int32_t)s->len, 0, (uint8_t **)(void *)&o); + if (n == 0) { + o.str->len = 0; + scr_str_release(o.str); + return scr_str_retain(s); + } + size_t tail = s->len - (size_t)o.next; + if ((int64_t)(tail + 1) > o.cap - o.len) scr_re_replace_out_reserve(&o, (int64_t)tail + 1); + scr_copy_bytes(o.data + o.len, s->data + o.next, tail); + o.len += (int64_t)tail; + o.str->len = (size_t)o.len; + o.data[o.len] = '\0'; + scr_str_note_ascii(o.str); + return o.str; +} + typedef struct { ScrDyn *arguments; int start, end; diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index cb61e49f3..502a29266 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -80,6 +80,22 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */ ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */ /* replace: first match without /g, every match with it. */ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep); +/* The output of a build-time replacer (regex-matchers.ts): the replacer + * appends each gap and the replacement at data[len] after reserving room, + * and leaves in `next` the subject index the unmatched tail starts at. */ +typedef struct { + int64_t len; + int64_t cap; + int64_t next; + char *data; /* at offset 24 on every target */ + ScrStr *str; +} ScrReReplaceOut; +/* Grows o->str so that `need` more bytes fit at o->data[o->len]. */ +void scr_re_replace_out_reserve(ScrReReplaceOut *o, int64_t need); +/* A global substitution with a literal ASCII replacement, through the + * replacer built for that pattern and replacement. Borrows all. */ +ScrStr *scr_regex_replace_with(ScrStr *s, ScrRegex *re, ScrStr *rep, + ScrRegexMatcherFn replacer); /* replace/replaceAll with a replacer closure of `arity` (at most * SCR_REPLACER_MAX_ARITY) parameters. Parameter i receives what Node passes * at position i: the match and captures below the capture count n, the diff --git a/tests/corpus/8351-regex-literal-replacers.ts b/tests/corpus/8351-regex-literal-replacers.ts new file mode 100644 index 000000000..12968d4d9 --- /dev/null +++ b/tests/corpus/8351-regex-literal-replacers.ts @@ -0,0 +1,72 @@ +// Global substitutions of regex literals with literal replacements: matches +// at every offset around 16-character blocks and at the end of the +// subject, empty matches, alternation order, case-insensitive and +// multiline flags, word boundaries, replacements that are empty, longer +// than the match or hold quotes and backslashes, outputs that grow far past +// the subject, subjects without a match, and subjects outside ASCII (which +// take the general loop). replace and replaceAll. +const substitutions: [string, (s: string) => string][] = [ + ["<[^>]*>", (s) => s.replace(/<[^>]*>/g, "|")], + ["a[NSt]|BY", (s) => s.replace(/a[NSt]|BY/g, "<2>")], + ["tHa[Nt]", (s) => s.replace(/tHa[Nt]/g, "<4>")], + ["\\|[^|][^|]*\\|", (s) => s.replace(/\|[^|][^|]*\|/g, "-")], + ["x*", (s) => s.replace(/x*/g, "-")], + ["\\b", (s) => s.replace(/\b/g, "^")], + ["^ab", (s) => s.replace(/^ab/gm, "Z")], + ["ab|abc|b", (s) => s.replace(/ab|abc|b/g, "#")], + ["AB (i)", (s) => s.replace(/AB/gi, "")], + ["(a)(b)?", (s) => s.replace(/(a)(b)?/g, "q")], + ["\\d+", (s) => s.replace(/\d+/g, '"\\\n')], + ["[^a-z]", (s) => s.replace(/[^a-z]/g, "")], + ["a", (s) => s.replace(/a/g, "<<<
    >>>")], + ["cat (replaceAll)", (s) => s.replaceAll(/cat/g, "dog")], + ["[aeiou]{2} (replaceAll)", (s) => s.replaceAll(/[aeiou]{2}/g, "~")], +]; + +function hash(text: string, h: number): number { + for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) >>> 0; + return h; +} + +const filler = "qwertyuiop asdfghjkl zxcvbnm"; +const needles = ["<2>", "", "aN", "BY", "tHat", "|ab|", "||", "xx", "ab", "abc", "AB", "123", "cat", "aeio", "a"]; +let checksum = 0; +let lines = 0; +for (let length = 0; length <= 70; length += 7) { + for (const needle of needles) { + for (const offset of [0, 1, 14, 15, 16, 17, 31, 32, 33]) { + if (offset + needle.length > length) continue; + let subject = ""; + while (subject.length < length) subject += filler; + subject = subject.slice(0, offset) + needle + subject.slice(offset + needle.length, length); + for (const [name, sub] of substitutions) { + const out = sub(subject); + checksum = hash(out, checksum); + lines++; + if (length === 35 && (offset === 15 || offset === 16)) console.log(name, offset, JSON.stringify(out)); + } + } + } +} +console.log("lines", lines, "checksum", checksum); + +// Dense matches, matches that end the subject, and an output many times +// larger than the subject. +const dense = "<1><22><333>".repeat(40) + "tail"; +for (const [name, sub] of substitutions) console.log(name, JSON.stringify(sub(dense).slice(-40)), sub(dense).length); +const many = "a".repeat(5000); +console.log("grow", many.replace(/a/g, "<<<>>>").length, many.replace(/a/g, "<<<>>>").slice(4990)); +console.log("empty", JSON.stringify("".replace(/x*/g, "-")), JSON.stringify("".replace(/a/g, "b"))); +console.log("none", "zzzz".replace(/a[bc]/g, "!"), "zzzz".replaceAll(/a[bc]/g, "!")); + +// Subjects outside ASCII run the general loop with the same results. +for (const subject of ["héllo wörld ", "日本<語>", "a\u{1F600}b", "cat café cat"]) { + console.log(JSON.stringify(subject.replace(/<[^>]*>/g, "|")), JSON.stringify(subject.replaceAll(/cat/g, "dog")), JSON.stringify(subject.replace(/x*/g, "-"))); +} + +// A long DNA-like subject, as in regex-redux. +let dna = ""; +for (let i = 0; i < 4000; i++) dna += "acgt"[(i * 7 + (i >> 2)) % 4] + (i % 61 === 0 ? "tHaN" : i % 37 === 0 ? "aNBY" : ""); +const step1 = dna.replace(/tHa[Nt]/g, "<4>").replace(/a[NSt]|BY/g, "<2>"); +const step2 = step1.replace(/<[^>]*>/g, "|").replace(/\|[^|][^|]*\|/g, "-"); +console.log("dna", dna.length, step1.length, step2.length, hash(step2, 0)); diff --git a/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json b/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json new file mode 100644 index 000000000..56546d44c --- /dev/null +++ b/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json @@ -0,0 +1 @@ +{ "plain": 3074, "san": 44176 } From c5131cb958e4331770db8e3b23a6aec1ba733be1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:39:37 -0500 Subject: [PATCH 254/395] Name the byte order mark fixture's directory by its real path The 8211 program resolved a package written to a fresh directory under os.tmpdir() and printed the result relative to that directory. On macOS the temporary directory is a symbolic link (/var to /private/var), so the resolved real path printed relative to the linked name included the random directory suffix, which differs between the Node run and the compiled run. The fixture now names the directory by its real path, and the result is lib/entry.js on every host. --- .../node_modules/resolve-probe/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js index 4be08c224..8d581c90e 100644 --- a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { dirname, join, relative } from "node:path"; @@ -43,8 +43,10 @@ const requests = [ // A package whose package.json starts with a byte order mark, written to a // fresh directory and resolved by its absolute path: "main" is still read. +// The directory is named by its real path (the temporary directory is a +// symbolic link on macOS), so the resolved path is relative to it. function resolveWithByteOrderMark() { - const dir = mkdtempSync(join(tmpdir(), "scriptc-bom-")); + const dir = realpathSync(mkdtempSync(join(tmpdir(), "scriptc-bom-"))); try { const pkg = join(dir, "with-bom"); mkdirSync(join(pkg, "lib"), { recursive: true }); From 734ced4f4ff5ef1ca55944a127269a3433a39719 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:49:34 -0500 Subject: [PATCH 255/395] Address review feedback --- .../src/frontend/type-patches/layer.ts | 23 ++++- .../src/frontend/type-patches/locate.ts | 44 ++++++++- packages/compiler/test/type-patches.test.ts | 97 ++++++++++++++++++- 3 files changed, 156 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 48ef6ecf0..79b409110 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -59,6 +59,8 @@ interface MatchRecord extends PatchMatch { interface State { manifest: string; + /** The directory of the project's package.json. */ + projectDir: string; manifestText: string; parse: TypePatchParse; byPackage: Map; @@ -104,6 +106,7 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): } state = { manifest: config.manifest, + projectDir: dirname(config.manifest), manifestText: config.manifestText ?? "", parse, byPackage, @@ -142,6 +145,22 @@ function versionAt(dir: string): string | null { } } +/** The version of the runtime package `pkg` an `@types` twin under + * `nodeModules` describes: the copy installed beside it, else the one the + * project resolves (pnpm keeps each package in its own directory, so a + * twin has no runtime package beside it), else null. */ +function twinRuntimeVersion(s: State, nodeModules: string, pkg: string): string | null { + const beside = versionAt(nodeModules + pkg); + if (beside !== null) return beside; + for (let dir = s.projectDir; ;) { + const version = versionAt(`${dir}/node_modules/${pkg}`); + if (version !== null) return version; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + /** The installed package copy a file belongs to, with the requests that * apply to it; null for files outside node_modules or of unpatched * packages. */ @@ -162,9 +181,9 @@ function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; const requests = s.byPackage.get(pkg); if (requests !== undefined) { - // A twin describes the runtime package installed beside it. + // A twin is matched by the runtime package's version, never its own. const version = name.startsWith("@types/") - ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) + ? twinRuntimeVersion(s, path.slice(0, at + marker.length), pkg) : versionAt(root); const applicable = requests.filter( (r) => version !== null && satisfies(version, r.entry.range), diff --git a/packages/compiler/src/frontend/type-patches/locate.ts b/packages/compiler/src/frontend/type-patches/locate.ts index fc85a1634..6c762f5c3 100644 --- a/packages/compiler/src/frontend/type-patches/locate.ts +++ b/packages/compiler/src/frontend/type-patches/locate.ts @@ -596,6 +596,26 @@ function parseJsDoc(text: string, start: number, end: number): JsDocComment { return { start, end, tags }; } +/** The spans of a file's string, template and regular expression literal + * tokens. */ +function literalSpans(sf: ts.SourceFile): { start: number; end: number }[] { + const out: { start: number; end: number }[] = []; + const kinds = new Set([ + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, + ts.SyntaxKind.TemplateHead, + ts.SyntaxKind.TemplateMiddle, + ts.SyntaxKind.TemplateTail, + ts.SyntaxKind.RegularExpressionLiteral, + ]); + const visit = (n: ts.Node): void => { + if (kinds.has(n.kind)) out.push({ start: tokenStartOfNode(n), end: n.end }); + ts.forEachChild(n, visit); + }; + visit(sf); + return out.sort((a, b) => a.start - b.start); +} + const RETURN_TAGS = new Set(["returns", "return"]); const PROPERTY_TAGS = new Set(["property", "prop"]); @@ -615,15 +635,27 @@ class JsResolver { private readonly text: string, sf: ts.SourceFile, ) { + // JSDoc comments, not text inside string, template or regular + // expression literals that looks like one. + const literals = literalSpans(sf); const re = /\/\*\*(?!\/)[\s\S]*?\*\//g; - for (let m = re.exec(text); m !== null; m = re.exec(text)) + for (let m = re.exec(text); m !== null; m = re.exec(text)) { + const inside = literals.find((l) => l.start <= m!.index && m!.index < l.end); + if (inside !== undefined) { + re.lastIndex = inside.end; + continue; + } this.comments.push(parseJsDoc(text, m.index, m.index + m[0].length)); + } for (const c of this.comments) c.tags.forEach((t, i) => { if ((t.tag === "typedef" || t.tag === "callback") && t.name !== null) this.add(t.name, { kind: "typedef", comment: c, tagIndex: i, prefix: "" }); }); - for (const s of sf.statements.nodes) this.collect(s); + // Declarations first: `exports.name = name` may come before the + // function declaration it exports, which is hoisted. + for (const s of sf.statements.nodes) if (!ts.isExpressionStatement(s)) this.collect(s); + for (const s of sf.statements.nodes) if (ts.isExpressionStatement(s)) this.collect(s); } private add(name: string, pos: JsPos): void { @@ -681,9 +713,11 @@ class JsResolver { if (named !== undefined) this.exportEquals.push(...named); } } else if (ts.isIdentifier(e.right) && this.roots.has(e.right.text)) { - // `exports.name = name`: the named declaration is the export. - for (const pos of this.roots.get(e.right.text)!) - if (pos.kind === "decl") this.add(target, pos); + // `exports.name = local`: the named declaration is the export (and + // already is under its own name). + if (target !== e.right.text) + for (const pos of [...this.roots.get(e.right.text)!]) + if (pos.kind === "decl") this.add(target, pos); } else this.add(target, this.declPos(s, s, e.right)); } diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 729a0da37..b4c0af9bc 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -2,7 +2,7 @@ * packages: commander's HelpContext.helpWidth @typedef property and ms's * @return tags, patched from the fixtures' package.json; installed-version * selection; and the warnings for patches that do not apply as written. */ -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "vitest"; @@ -122,3 +122,98 @@ test("an @types twin is matched by the runtime package's installed version", () rmSync(root, { recursive: true, force: true }); } }); + +test("an @types twin in pnpm's layout is matched by the runtime package the project resolves", () => { + // pnpm keeps each package in its own directory under node_modules/.pnpm + // and links the project's dependencies from node_modules: the twin has no + // runtime package beside it. + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-pnpm-")); + try { + const store = join(root, "node_modules/.pnpm"); + const runtime = join(store, "tallyjs@0.5.0/node_modules/tallyjs"); + const types = join(store, "@types+tallyjs@0.4.0/node_modules/@types/tallyjs"); + mkdirSync(runtime, { recursive: true }); + mkdirSync(types, { recursive: true }); + writeFileSync(join(runtime, "package.json"), '{ "name": "tallyjs", "version": "0.5.0" }'); + writeFileSync(join(runtime, "index.js"), "exports.totals = () => ({});\n"); + writeFileSync(join(types, "package.json"), '{ "name": "@types/tallyjs", "version": "0.4.0" }'); + writeFileSync( + join(types, "index.d.ts"), + "export type Totals = {\n max: number;\n};\nexport declare function totals(): Totals;\n", + ); + mkdirSync(join(root, "node_modules/@types"), { recursive: true }); + symlinkSync(runtime, join(root, "node_modules/tallyjs")); + symlinkSync(types, join(root, "node_modules/@types/tallyjs")); + const patchFor = (range: string) => + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { + [`tallyjs@${range}`]: { declarations: { "Totals.max": "| undefined" } }, + }, + }, + }), + ); + // A range the runtime (0.5.0) satisfies applies, though the twin's own + // version (0.4.0) does not satisfy it. + patchFor("0.5.0"); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(types, "index.d.ts"))).toContain("max: number | undefined;"); + // A range only the twin's own version satisfies does not. + patchFor("0.4.0"); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(types, "index.d.ts"))).toBeUndefined(); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("an export before its hoisted function is patched, and string text that looks like JSDoc is not", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-js-")); + try { + const pkg = join(root, "node_modules/hoistkit"); + mkdirSync(pkg, { recursive: true }); + writeFileSync(join(pkg, "package.json"), '{ "name": "hoistkit", "version": "1.0.0" }'); + const source = [ + '"use strict";', + "exports.measure = size;", + 'exports.doc = "/** @typedef {object} Opts\\n * @property {number} width */";', + "", + "/**", + " * @returns {number}", + " */", + "function size(s) {", + " return s.length || undefined;", + "}", + "", + ].join("\n"); + writeFileSync(join(pkg, "index.js"), source); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { + "hoistkit@^1.0.0": { + declarations: { "measure()": "| undefined", "Opts.width": "| undefined" }, + }, + }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + // The export, under another name, comes before the function it exports: + // the function's @returns tag widens. + expect(typePatchedText(join(pkg, "index.js"))).toBe( + source.replace(" * @returns {number}\n", " * @returns {number | undefined}\n"), + ); + // The string's text is a value, not a typedef: the patch finds nothing. + expect( + typePatchDiagnostics()! + .diagnostics.map((d) => d.message) + .join("\n"), + ).toContain("Opts.width"); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); From 177b2fde496334d47b025951cc6c1393866b2136 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:51:24 -0700 Subject: [PATCH 256/395] Cross package values at their declared type where the JS literal type lacks members An opted-in package's JavaScript types its own values. The lowering types a consumer's view of such a value from that JavaScript, and the declaration boundary (decl-projection.ts) casts to the declared type only where the JavaScript world answers `any`. A function that returns an object literal has a precise JavaScript type, so it never crossed, even where that literal type lacks members the declarations promise: - `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read `settings["font"] ?? "x"` through the literal's fixed key set. The checker types the miss `any` (a JavaScript literal), but the keyed read took the declared fields' common type, so the miss trapped with "record has no key" and aborted the process. - an expando `out.max = ...` on a non-empty literal, which the JavaScript checker leaves out of the literal's type: reading `stats(xs).max` was refused with SC1090. A package value whose JavaScript type is an object literal that lacks a declared property or string index signature now crosses at its declared type, so missing keys read undefined through the index signature and expando members are read from the object. An index signature whose values do not project (a package interface) keeps the value untyped (`as any`), so its reads are the JavaScript reads. Only programs compiled with --npm-static whose consumers read such values are rewritten. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/decl-projection.ts | 62 +++++++++++++++++-- .../npm-static/declared-members-cli.ts | 21 +++++++ .../node_modules/declared-members/index.d.ts | 7 +++ .../node_modules/declared-members/index.js | 34 ++++++++++ .../declared-members/package.json | 6 ++ tests/harness/npm-static.test.ts | 12 ++++ 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/npm-static/declared-members-cli.ts create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/index.js create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/package.json diff --git a/packages/compiler/src/frontend/decl-projection.ts b/packages/compiler/src/frontend/decl-projection.ts index 02a300996..a999559c2 100644 --- a/packages/compiler/src/frontend/decl-projection.ts +++ b/packages/compiler/src/frontend/decl-projection.ts @@ -519,6 +519,41 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | return undefined; } }; + const stringIndex = (checker: ts.TypeChecker, type: ts.Type): boolean => { + try { + return checker + .getIndexInfosOfType(type) + .some((info) => (info.keyType.flags & ts.TypeFlags.String) !== 0); + } catch { + return false; + } + }; + /** Whether the JavaScript world types a package value as an object + * literal that lacks a member its declaration promises: a property (an + * expando `out.max = ...` on a non-empty literal, which the JavaScript + * checker does not add to the literal's type) or a string index + * signature (`Record` over a literal). Lowering the value + * at the narrower literal type would drop the member or fix the key set, + * so it crosses at its declared type instead. */ + const missesDeclaredMembers = (jsType: ts.Type, declType: ts.Type): boolean => { + if (!(jsType.flags & ts.TypeFlags.Object) || !(declType.flags & ts.TypeFlags.Object)) + return false; + const literal = jsType.getSymbol(); + if (literal === undefined || (literal.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + try { + if (declChecker.isArrayType(declType) || declChecker.isTupleType(declType)) return false; + if (stringIndex(declChecker, declType) && !stringIndex(jsChecker, jsType)) return true; + return declChecker + .getPropertiesOfType(declType) + .some( + (prop) => + !String(prop.escapedName).startsWith("__@") && + jsChecker.getPropertyOfType(jsType, String(prop.escapedName)) === undefined, + ); + } catch { + return false; + } + }; const namesPackageSymbol = (node: ts.Node): boolean => { let found = false; const visit = (n: ts.Node): void => { @@ -540,7 +575,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const fileEdits: Edit[] = []; /** Candidate expressions (by js node) with their declared type, named * only for the casts that survive suppression. */ - const exprCandidates = new Map(); + const exprCandidates = new Map(); const exprOrder: ts.Node[] = []; /** Reads whose projectable receiver the JavaScript world types `any`: * kept only when nothing types the receiver. */ @@ -610,7 +645,14 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | return; } const jsType = typeAt(jsChecker, jsNode); - if (!isAnyish(jsType)) return; + if (jsType === null) return; + // A typed JavaScript value crosses only where its object type lacks a + // member the declaration promises (missesDeclaredMembers). + const typedJs = !isAnyish(jsType); + if (typedJs) { + const declType = typeAt(declChecker, declNode); + if (declType === null || !missesDeclaredMembers(jsType, declType)) return; + } // Package-produced? const d = declNode as ts.Expression; let produced = false; @@ -686,7 +728,18 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | // no contextual type } const declType = typeAt(declChecker, d); - if (declType === null || !printer.projectable(declType)) return; + if (declType === null) return; + if (!printer.projectable(declType)) { + // An index signature over values that do not project (package + // interfaces): the literal's fixed key set would still answer for + // keys it lacks, so the value stays untyped and every read is the + // JavaScript read. + if (typedJs && stringIndex(declChecker, declType)) { + exprCandidates.set(jsNode, "any"); + exprOrder.push(jsNode); + } + return; + } exprCandidates.set(jsNode, declType); exprOrder.push(jsNode); }; @@ -890,7 +943,8 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | ) continue; } - const alias = printer.project(exprCandidates.get(e)!); + const candidate = exprCandidates.get(e)!; + const alias = candidate === "any" ? "any" : printer.project(candidate); if (alias === null) continue; const start = ts.tokenStartOfNode(e); const stmt = e.parent; diff --git a/tests/fixtures/npm-static/declared-members-cli.ts b/tests/fixtures/npm-static/declared-members-cli.ts new file mode 100644 index 000000000..107ffc54d --- /dev/null +++ b/tests/fixtures/npm-static/declared-members-cli.ts @@ -0,0 +1,21 @@ +// A consumer reads members a package's declarations promise but its +// JavaScript adds in ways the inferred literal type misses: absent keys of +// index signatures read undefined, and expando properties read their values, +// as in Node. +import { counts, fonts, settings, stats } from "declared-members"; + +const key = process.argv.length > 10 ? "size" : "missing"; + +const s = settings(); +console.log(s["font"] ?? "default font", s["size"] ?? "default size", s.family); +console.log(s["font"] === undefined, typeof s["weight"], s.weight ?? "dot miss"); +console.log(s[key] ?? "dynamic miss", s[key], Object.keys(s)); + +const c = counts(); +console.log(c["chars"] ?? -1, c["lines"], c[key] === undefined); + +const st = stats([4, 9, 2]); +console.log(st.max, st.sum, st.count, st); + +const f = fonts(); +console.log(f["title"]?.name ?? "no title", f[key] === undefined, f["body"]?.name, f.code.name); diff --git a/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts b/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts new file mode 100644 index 000000000..8061fd5cb --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts @@ -0,0 +1,7 @@ +export declare function settings(): { [key: string]: string }; +export declare function counts(): Record; +export declare function stats(xs: number[]): { count: number; sum: number; max: number }; +export interface Font { + name: string; +} +export declare function fonts(): Record; diff --git a/tests/fixtures/npm-static/node_modules/declared-members/index.js b/tests/fixtures/npm-static/node_modules/declared-members/index.js new file mode 100644 index 000000000..381bd4a46 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/index.js @@ -0,0 +1,34 @@ +"use strict"; +// Each function returns an object literal whose JavaScript-inferred type +// lacks members that index.d.ts promises. + +// An index signature over a literal built empty, then assigned. +function settings() { + const out = {}; + out.size = "12px"; + out.family = "serif"; + return out; +} + +// An index signature over a literal with fixed keys. +function counts() { + return { lines: 3, words: 12 }; +} + +// An expando on a non-empty literal: the checker's literal type has no `max`. +function stats(xs) { + const out = { count: xs.length, sum: 0 }; + for (const x of xs) out.sum += x; + out.max = Math.max(...xs); + return out; +} + +// An index signature whose values are a declared interface. +function fonts() { + return { body: { name: "serif" }, code: { name: "mono" } }; +} + +exports.settings = settings; +exports.counts = counts; +exports.stats = stats; +exports.fonts = fonts; diff --git a/tests/fixtures/npm-static/node_modules/declared-members/package.json b/tests/fixtures/npm-static/node_modules/declared-members/package.json new file mode 100644 index 000000000..4a42ebdf2 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/package.json @@ -0,0 +1,6 @@ +{ + "name": "declared-members", + "version": "1.0.0", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index f3a6225c4..076733739 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -1207,6 +1207,18 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { expect(nativeRes.exitCode).toBe(nodeRes.exitCode); }, 180_000); + test("package values cross at the members their declarations promise", async () => { + const entry = join(pilotRoot, "declared-members-cli.ts"); + const binary = await buildStatic(entry, ["declared-members"]); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + test("the published Effect Function subpath initializes and runs statically", async () => { const entry = join(pilotRoot, "effect-function-cli.ts"); const { coverage } = analyze(entry, { npmStatic: ["effect"] }); From a4bc01bafbbf5f0b9b2c8c346b1c3bae3cac6d20 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:52:19 -0700 Subject: [PATCH 257/395] Count shorthand property reads of an initializer-less JavaScript let An initializer-less JavaScript `let` whose writes all agree gets a scalar slot when every read already has that flow type (inferredEvolvingLetType). The read scan resolved each identifier with getSymbolAtLocation, which answers the property symbol for a shorthand property name, so the `width` in `return { width }` was not seen as a read. A function that assigns the let on one path only (`let width; if (tty) width = 80; return { width };`) then stored it in a number slot: the path that never assigns returned 80 or 0 instead of undefined. The capture scan missed a closure that reads the let through a shorthand the same way. Both scans now resolve a shorthand name to the binding it reads, so such a let keeps its checked-dynamic slot and reads undefined until assigned, as in Node. TypeScript lets are unaffected (the inference applies to JavaScript only). Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stmts.ts | 14 ++++--- .../evolving-let-shorthand-reads.js.json | 6 +++ tests/corpus/evolving-let-shorthand-reads.js | 41 +++++++++++++++++++ .../evolving-let-shorthand-reads.js.json | 1 + 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json create mode 100644 tests/corpus/evolving-let-shorthand-reads.js create mode 100644 tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..0563f6165 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -5422,6 +5422,12 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) const scope = decl.parent?.parent?.parent; if (symbol === undefined || (!ts.isBlock(scope) && !ts.isSourceFile(scope))) return null; + // A shorthand property's name (`{ width }`) resolves to the property + // symbol, but it reads the binding: the checker's shorthand-value symbol. + const refersToBinding = (node: ts.Identifier): boolean => + (ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node + ? lowerer.checker.getShorthandAssignmentValueSymbol(node.parent) + : lowerer.checker.getSymbolAtLocation(node)) === symbol; const isPlainAssignmentTarget = (node: ts.Identifier): boolean => { let target: ts.Expression = node; while (ts.isParenthesizedExpression(target.parent) && target.parent.expression === target) { @@ -5437,11 +5443,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) const captures = (fn: ts.Node): boolean => { let captured = false; ts.walkPreorder(fn, (node) => { - if ( - node !== fn && - ts.isIdentifier(node) && - lowerer.checker.getSymbolAtLocation(node) === symbol - ) { + if (node !== fn && ts.isIdentifier(node) && refersToBinding(node)) { captured = true; return "stop"; } @@ -5493,7 +5495,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) if ( ts.isIdentifier(node) && node !== decl.name && - lowerer.checker.getSymbolAtLocation(node) === symbol && + refersToBinding(node) && !isPlainAssignmentTarget(node) ) { reads.push(node); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json new file mode 100644 index 000000000..26d07a9f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/evolving-let-shorthand-reads.js" + ], + "diags": [] +} diff --git a/tests/corpus/evolving-let-shorthand-reads.js b/tests/corpus/evolving-let-shorthand-reads.js new file mode 100644 index 000000000..201171bcc --- /dev/null +++ b/tests/corpus/evolving-let-shorthand-reads.js @@ -0,0 +1,41 @@ +// An initializer-less JavaScript `let` read through a shorthand property +// (`{ width }`) still reads undefined on the paths that never assign it, as +// npm packages do when they build an options or size object. +function size(tty) { + let width; + if (tty) width = 80; + return { width }; +} + +/** + * @param {boolean} tty + * @returns {{ width: number | undefined }} + */ +function documented(tty) { + /** @type {number | undefined} */ + let width; + if (tty) width = 80; + return { width }; +} + +function captured(tty) { + let width; + const read = () => ({ width }); + const before = read().width; + if (tty) width = 80; + return [before, read().width]; +} + +function nested(flags) { + let columns; + let rows; + if (flags.columns) columns = 120; + if (flags.rows) rows = 40; + return { size: { columns, rows } }; +} + +console.log(size(true).width, size(false).width); +console.log(documented(true).width, documented(false).width); +console.log(JSON.stringify(size(false)), "width" in size(false)); +console.log(captured(true), captured(false)); +console.log(nested({ columns: true }), nested({ rows: true }), nested({})); diff --git a/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json b/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json new file mode 100644 index 000000000..f93ba161d --- /dev/null +++ b/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json @@ -0,0 +1 @@ +{ "plain": 3700, "san": 2800 } From 701173a2aebea76753aca83d6b4b2ae829a88a07 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:55:44 -0500 Subject: [PATCH 258/395] Read builtin globals through the global object, and support Node's navigator --- .../limitations/builtin-apis/globals.mdx | 6 +- .../limitations/builtin-function-values.mdx | 1 + .../src/generated/node-v24-compatibility.json | 24 +- .../generated/node-v24-internal.json | 34 ++- internal/compatibility/static-support.json | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 6 + .../frontend/lowering/builtins/navigator.ts | 32 +- .../lowering/expressions/identifiers.ts | 96 +++--- .../lowering/expressions/property-access.ts | 58 ++-- .../src/frontend/lowering/global-values.ts | 274 +++++++++++++++++ .../src/frontend/lowering/lower-modules.ts | 5 + .../src/frontend/lowering/lower-stmts.ts | 31 +- .../compiler/src/frontend/lowering/lowerer.ts | 18 +- .../compiler/src/ir/builtin-signatures.ts | 9 + .../8161-global-computed-reads.cjs.json | 6 + .../corpus/8162-navigator-global.cjs.json | 6 + .../corpus/8163-data-function-values.cjs.json | 6 + .../corpus/8164-atob-validation.cjs.json | 6 + packages/runtime/src/scr_dyn.h | 13 + packages/runtime/src/scr_dyn_invoke.c | 38 ++- packages/runtime/src/scr_json.c | 106 ++++--- packages/runtime/src/scr_node_builtin.c | 289 ++++++++++++++++++ tests/corpus/8161-global-computed-reads.cjs | 87 ++++++ tests/corpus/8162-navigator-global.cjs | 47 +++ tests/corpus/8163-data-function-values.cjs | 32 ++ tests/corpus/8164-atob-validation.cjs | 35 +++ tests/fixtures/node-types/fenced.ts | 8 +- tests/fixtures/node-types/navigator.ts | 6 +- .../__snapshots__/node-types-fenced.txt | 30 +- .../8161-global-computed-reads.cjs.json | 1 + .../8162-navigator-global.cjs.json | 1 + .../8163-data-function-values.cjs.json | 1 + .../8164-atob-validation.cjs.json | 1 + 34 files changed, 1132 insertions(+), 186 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/global-values.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json create mode 100644 tests/corpus/8161-global-computed-reads.cjs create mode 100644 tests/corpus/8162-navigator-global.cjs create mode 100644 tests/corpus/8163-data-function-values.cjs create mode 100644 tests/corpus/8164-atob-validation.cjs create mode 100644 tests/harness/corpus-costs/8161-global-computed-reads.cjs.json create mode 100644 tests/harness/corpus-costs/8162-navigator-global.cjs.json create mode 100644 tests/harness/corpus-costs/8163-data-function-values.cjs.json create mode 100644 tests/harness/corpus-costs/8164-atob-validation.cjs.json diff --git a/docs/content/limitations/builtin-apis/globals.mdx b/docs/content/limitations/builtin-apis/globals.mdx index f875bd761..e54530b29 100644 --- a/docs/content/limitations/builtin-apis/globals.mdx +++ b/docs/content/limitations/builtin-apis/globals.mdx @@ -1,4 +1,8 @@ - **Globals:** Stored `globalThis` references support identity and probes for absent capabilities. - Most builtin members require direct global access; access through a stored global object can raise `SC2020`. + In JavaScript, reading a builtin global through the global object by name (`globalThis[name]`, or a stored global object) answers what the global's own spelling reads, and builtin module members that are global objects in Node, such as `require("buffer").atob` and `require("timers").setTimeout`, are the same values. + `WebAssembly` read this way, and builtin members read through a global object that untyped code received, can raise `SC2020`. + `for (key in globalThis)` visits Node's enumerable globals in Node's order, then the program's own global properties. + `navigator` answers Node's `hardwareConcurrency`, `language`, `languages`, `platform`, and `userAgent`, with `language` derived from `LC_ALL`, `LC_MESSAGES`, or `LANG` as Node does. + `navigator.locks` raises `SC2020`, as does `navigator.language` for a locale outside the `language_TERRITORY.codeset@modifier` form, or one whose language or territory needs ICU's tables (a three-letter territory, for example). `Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`. Programs that name it type-check against TypeScript's `Temporal` declarations. diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 0fa62e558..24050f35d 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -7,6 +7,7 @@ Some builtins can be stored and passed as functions: In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles. Calls through these values bypass `node:test` mock timers. +In JavaScript, `atob`, `btoa`, and `structuredClone` are function values the same way, and calls through them run what the direct calls run. Other builtin globals stored in JavaScript remain opaque identity values. In JavaScript, `require("events")` and the `EventEmitter` class assigned to an untyped binding or stored in an object (`ReleaseEmitter = EventEmitter`) are the untyped `EventEmitter` constructor that constructor functions inherit from (see below): `new` through it builds such an emitter object, and identity and `instanceof` agree with the class. A class can extend `require("events")` directly. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 130da748c..a3dd6f747 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -36684,9 +36684,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "supported", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36707,9 +36707,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36730,9 +36730,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36776,9 +36776,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "supported", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index c3a0f90b3..fea2ecee7 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -52415,6 +52415,7 @@ "tests/corpus/6331-jose-root-entry/main.ts", "tests/corpus/7743-transform-stream-identity.ts", "tests/corpus/7747-text-codec-untyped-slot.js", + "tests/corpus/8162-navigator-global.cjs", "tests/corpus/text-codec-values/main.ts", "tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-dylink/main.mjs", @@ -54467,6 +54468,7 @@ "status": "partial", "evidence": "compiler-feature:globals.navigator", "tests": [ + "tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts" @@ -54492,8 +54494,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "supported", + "evidence": "compiler-feature:globals.hardwareConcurrency", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54515,8 +54521,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "partial", + "evidence": "compiler-feature:globals.language", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54538,8 +54548,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "partial", + "evidence": "compiler-feature:globals.languages", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54584,8 +54598,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "supported", + "evidence": "compiler-feature:globals.platform", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index ab626fe21..160523cc1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -215,8 +215,10 @@ { "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, { "chapter": "webcrypto", "signatures": ["Class: `Crypto`"], "status": "partial", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6293-webcrypto-digest.ts"] }, { "chapter": "webcrypto", "symbols": ["crypto.randomUUID", "crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] }, - { "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] }, + { "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] }, { "chapter": "globals", "signatures": ["`userAgent` Type: {string}"], "status": "supported", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "globals", "signatures": ["`hardwareConcurrency` Type: {number}", "`platform` Type: {string}"], "status": "supported", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts"] }, + { "chapter": "globals", "signatures": ["`language` Type: {string}", "`languages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts"] }, { "chapter": "globals", "signatures": ["Class: `CryptoKey`", "Class: `SubtleCrypto`"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, { "chapter": "errors", "parent": "Class: `SystemError`", "symbols": ["code", "errno", "syscall", "path", "dest"], "status": "partial", "evidence": ["tests/corpus/caught-system-error-fields.ts", "tests/fixtures/node-types/terminal-error-fields.ts", "tests/harness/project-config.test.ts"] }, {"chapter": "events", "symbols": ["EventTarget", "eventTarget.addEventListener", "eventTarget.removeEventListener", "eventTarget.dispatchEvent"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5131-event-target-listener-errors.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts", "tests/corpus/5135-event-target-warnings.js", "tests/corpus/5240-event-target-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5243-event-target-subclass-dynamic.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5245-event-target-subclass-signals.ts", "tests/corpus/5248-event-target-subclass-warnings.ts"]}, diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 4af8182a7..b513e17a6 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1149,6 +1149,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { case "punycode": case "constants": case "promise": + case "navigator": return host.emitGenericLibCall(e); case "test": return emitTestLibCall(host, e); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2548b75d5..4afdf8c81 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -255,6 +255,12 @@ export const LIB_FN_SYMS: Record = { "console.table": "scr_console_table", "console.dirDyn": "scr_console_dir_dyn", "global.native": "scr_global_native", + "global.installValues": "scr_global_values_install", + "global.forInObject": "scr_global_for_in_object", + "navigator.object": "scr_navigator_object", + "navigator.language": "scr_navigator_language", + "navigator.languages": "scr_navigator_languages", + "navigator.platform": "scr_navigator_platform", "process.builtinModule": "scr_process_builtin_module", "process.builtinUnsupported": "scr_process_builtin_unsupported", "module.namespaceValue": "scr_module_namespace_value", diff --git a/packages/compiler/src/frontend/lowering/builtins/navigator.ts b/packages/compiler/src/frontend/lowering/builtins/navigator.ts index f90448c1e..f4fbf2567 100644 --- a/packages/compiler/src/frontend/lowering/builtins/navigator.ts +++ b/packages/compiler/src/frontend/lowering/builtins/navigator.ts @@ -1,13 +1,13 @@ -/* Node 24's `navigator` global (lib/internal/navigator.js), the part - * libraries read to tell runtimes apart: `typeof navigator` is "object" - * and `navigator.userAgent` is `Node.js/`. jose reads exactly - * that at module load (jwks/remote.js). Both fold to string literals, so - * nothing links. The other members (hardwareConcurrency, language, - * languages, platform, locks) and the object as a value keep a named +/* Node 24's `navigator` global (lib/internal/navigator.js): `typeof + * navigator` is "object", `navigator.userAgent` is `Node.js/` (both + * fold to string literals; jose reads them at module load), and + * hardwareConcurrency, language, languages and platform read the runtime's + * answers (scr_node_builtin.c). The object as a value is the runtime's + * Navigator handle, whose getters answer the same. `locks` keeps a named * refusal. */ import * as ts from "../../ts7/adapter.js"; import { strLit } from "../../../ir/build.js"; -import type { IrExpr } from "../../../ir/ir.js"; +import { DYN, F64, STRING, type IrExpr, type SrcLoc } from "../../../ir/ir.js"; import { isJsSourceFile, locOf } from "../../program.js"; import type { Lowerer } from "../lowerer.js"; @@ -34,9 +34,18 @@ export function lowerNavigatorMember( nodeVersion: string, ): IrExpr | null { if (!isNavigatorGlobal(lowerer, access.expression)) return null; + const loc = locOf(access); if (access.name.text === "userAgent") { - return strLit(`Node.js/${nodeVersion.split(".")[0]}`, locOf(access)); + return strLit(`Node.js/${nodeVersion.split(".")[0]}`, loc); } + if (access.name.text === "hardwareConcurrency") + return { kind: "libCall", fn: "os.availableParallelism", args: [], type: F64, loc }; + if (access.name.text === "language" || access.name.text === "platform") { + const fn = access.name.text === "language" ? "navigator.language" : "navigator.platform"; + return { kind: "libCall", fn, args: [], type: STRING, loc }; + } + if (access.name.text === "languages") + return { kind: "libCall", fn: "navigator.languages", args: [], type: DYN, loc }; return refuseNavigator( lowerer, access, @@ -55,7 +64,12 @@ export function refuseNavigator( return lowerer.noLowering( what, node, - "of Node's navigator global only typeof navigator and navigator.userAgent compile", + "navigator.locks (the Web Locks API) has no lowering", symbol ?? null, ); } + +/** The navigator object as a value: the runtime's Navigator handle. */ +export function navigatorValue(loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "navigator.object", args: [], type: DYN, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 62b2c7960..6541e0788 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -1,4 +1,5 @@ /* Identifier references in expression position: lowerExprInner's identifier branch and its helpers. */ +import { globalMemberValue } from "../global-values.js"; import { predeclareForwardClass } from "../lower-stmts.js"; import { cjsMemberAliasValueOf } from "../cjs-exports-object.js"; import { builtinClassValue, builtinMemberClassKind } from "../builtins/builtin-class-dyn.js"; @@ -89,7 +90,7 @@ import { unsupportedModuleFeatureOf } from "../../builtin-modules.js"; import { lowerGlobalValue } from "../lower-global-value.js"; import { fenceEnumObjectValue } from "../lower-enums.js"; import { mixinFnOfCallee } from "../lower-mixins.js"; -import { isNavigatorGlobal, refuseNavigator } from "../builtins/navigator.js"; +import { isNavigatorGlobal, navigatorValue } from "../builtins/navigator.js"; import { wasiGuestPath } from "../../../wasi-paths.js"; export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: SrcLoc): IrExpr { @@ -597,6 +598,9 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + // A member that is a global object reads as the global does. + const global = globalMemberValue(lowerer, bi, loc); + if (global) return global; // EventEmitter and the stream classes are the runtime's untyped // constructors, so constructor functions can inherit from them. const classKind = builtinMemberClassKind(bi.module, bi.member); @@ -606,12 +610,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: return builtinFunctionFenceValue(lowerer, bi, expr, loc); return { kind: "strLit", - // node:stream/web exports the web stream globals themselves: - // the same identity token as the global spelling. - value: - bi.module === "stream/web" - ? `[builtin ${bi.member}]` - : `[builtin ${bi.module}.${bi.member}]`, + value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc, }; @@ -729,43 +728,11 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: expr.text === "decodeURI") && lowerer.isStdlibGlobal(expr, expr.text) ) { - const name = `%builtin.${expr.text}`; - const type = funcOf([DYN], STRING); - if (!lowerer.liftedFns.some((fn) => fn.name === name)) { - const value: IrExpr = { - kind: "libCall", - fn: "dyn.toStringCoerce", - args: [varRef("value", DYN, loc)], - type: STRING, - loc, - }; - const fn = - expr.text === "encodeURIComponent" - ? "str.encodeUriComponent" - : expr.text === "encodeURI" - ? "str.encodeUri" - : expr.text === "decodeURI" - ? "str.decodeUri" - : "str.decodeUriComponent"; - lowerer.liftedFns.push({ - name, - params: [{ localId: "value", name: "value", type: DYN }], - locals: [{ id: "value", name: "value", type: DYN, mutable: false }], - returnType: STRING, - body: [ - { - kind: "return", - value: { kind: "libCall", fn, args: [value], type: STRING, loc }, - loc, - }, - ], - loc, - }); - } - return { kind: "closure", fnName: name, captures: [], type, loc }; + return uriFunctionValue(lowerer, expr.text, loc); } if (lowerer.isStdlibGlobal(expr, "crypto")) return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; + if (isNavigatorGlobal(lowerer, expr)) return navigatorValue(loc); const nativeAlias = stdlibGlobalNameOf(lowerer, expr); if (nativeAlias === "Buffer" && expr.text !== "Buffer") { // An alias through a cast of globalThis carries the cast's static type, @@ -1024,14 +991,6 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: const ns = lowerNsIdentifierValue(lowerer, expr); if (ns) return ns; } - if (isNavigatorGlobal(lowerer, expr)) { - refuseNavigator( - lowerer, - expr, - "the navigator object as a value", - lowerer.checker.getSymbolAtLocation(expr), - ); - } // An import of a CommonJS member export whose value is a binding of the // exporting module (`module.exports = Client; module.exports.StatsD = // Client`): the member holds that binding's value. @@ -1167,3 +1126,42 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | } return cjsAccessorCall(lowerer, getter, locOf(ident)); } + +/** encodeURIComponent, encodeURI, decodeURI or decodeURIComponent as a + * function value: one interned closure per function. */ +export function uriFunctionValue(lowerer: Lowerer, which: string, loc: SrcLoc): IrExpr { + const name = `%builtin.${which}`; + const type = funcOf([DYN], STRING); + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const value: IrExpr = { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [varRef("value", DYN, loc)], + type: STRING, + loc, + }; + const fn = + which === "encodeURIComponent" + ? "str.encodeUriComponent" + : which === "encodeURI" + ? "str.encodeUri" + : which === "decodeURI" + ? "str.decodeUri" + : "str.decodeUriComponent"; + lowerer.liftedFns.push({ + name, + params: [{ localId: "value", name: "value", type: DYN }], + locals: [{ id: "value", name: "value", type: DYN, mutable: false }], + returnType: STRING, + body: [ + { + kind: "return", + value: { kind: "libCall", fn, args: [value], type: STRING, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 35bb16cd3..92fb2b2cd 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,4 +1,5 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ +import { jsSpecialGlobalValue } from "../global-values.js"; import { indexedUnionArms, lowerIndexedUnionMember } from "../indexed-unions.js"; import { keyedHelperName } from "../stable-names.js"; import { builtinNamespaceValue } from "../builtin-namespaces.js"; @@ -35,7 +36,6 @@ import { } from "../../type-mapper.js"; import { BOOL, - BYTES_ELEMENT_NAME, BYTES_U8, DYN, F64, @@ -60,7 +60,7 @@ import { unionContainerArmsOk, BYTES_ELEMENT_SIZE, } from "../../../ir/ir.js"; -import { lowerNavigatorMember } from "../builtins/navigator.js"; +import { lowerNavigatorMember, navigatorValue } from "../builtins/navigator.js"; import { lowerX509PublicKey } from "../builtins/x509.js"; import { NODE_COMPAT_VERSION } from "../builtins/process.js"; import { @@ -593,7 +593,10 @@ export function lowerPropertyAccessExpr( // (`(_a = globalThis).__config ?? (_a.__config = {})`) reads back from // the global object's own properties. const globalType = lowerer.typeOf(staticallySelected(lowerer, expr.expression)); - if (globalThisWrittenNames(lowerer, globalType).has(expr.name.text)) { + if ( + globalThisWrittenNames(lowerer, globalType).has(expr.name.text) || + lowerer.globalThisComputedWrite + ) { return { kind: "dynKeyGet", value: lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), DYN), @@ -602,6 +605,10 @@ export function lowerPropertyAccessExpr( loc, }; } + // Node's navigator, which JavaScript reads as a free name when no + // declaration types it (isNavigatorGlobal). + if (expr.name.text === "navigator" && isJsSourceFile(ts.getSourceFileOfNode(expr))) + return navigatorValue(loc); return dynUndefinedExpr(loc); } // `Uint8Array.prototype.toBase64` and its siblings, the base64/hex @@ -975,18 +982,8 @@ export function lowerPropertyAccessExpr( if (canonical === "globalThis") return lowerGlobalValue(lowerer, expr); // Typed for the programs that name it, absent in Node 24. if (canonical === TEMPORAL_GLOBAL) return dynUndefinedExpr(loc); - if (canonical === "ArrayBuffer") - return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; - if (canonical === "fetch") - return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc }; - if (Object.values(BYTES_ELEMENT_NAME).includes(canonical)) - return { - kind: "libCall", - fn: "bytes.constructor", - args: [{ kind: "strLit", value: canonical, type: STRING, loc }], - type: DYN, - loc, - }; + const value = jsSpecialGlobalValue(lowerer, canonical, loc); + if (value) return value; return ( builtinGlobalValueAt(lowerer, expr, canonical, loc, true) ?? { kind: "strLit", @@ -4259,9 +4256,13 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ /** The builtin global functions JavaScript sources read as interned * function values (the `cond ? setImmediate : process.nextTick` feature - * probe of bcryptjs and async libraries): typeof answers "function", - * identity holds across spellings, and calls run the timer APIs. */ + * probe of bcryptjs and async libraries, and the known globals Node's test + * harness collects): typeof answers "function", identity holds across + * spellings, and calls run what the direct calls run. */ const GLOBAL_FUNCTION_VALUES: ReadonlySet = new Set([ + "atob", + "btoa", + "structuredClone", "setTimeout", "setInterval", "setImmediate", @@ -4639,11 +4640,24 @@ function globalThisWrittenNames(lowerer: Lowerer, globalType: ts.Type): Set { + if ( + (ts.isElementAccessExpression(node) || ts.isForInStatement(node)) && + stdlibGlobalNameOf(lowerer, node.expression) === "globalThis" && + (ts.isForInStatement(node) || readsBuiltinByName(lowerer, node)) + ) { + found = node.expression; + return "stop"; + } + return undefined; + }); + lowerer.computedGlobalReads.set(sf, found); + return found; +} + +/** Whether the element access `node` of the global object can read a + * builtin global by name: its key is not a symbol (`globalThis[key]` with + * `key = Symbol.for(...)` reads the global object's symbol properties), + * and it is neither the target of `=` nor the operand of `delete`. */ +function readsBuiltinByName(lowerer: Lowerer, node: ts.ElementAccessExpression): boolean { + if (lowerer.typeOf(node.argumentExpression).flags & ts.TypeFlags.ESSymbolLike) return false; + let outer: ts.Node = node; + while (ts.isParenthesizedExpression(outer.parent)) outer = outer.parent; + const parent = outer.parent; + if (ts.isDeleteExpression(parent)) return false; + return !( + ts.isBinaryExpression(parent) && + parent.left === outer && + parent.operatorToken.kind === ts.SyntaxKind.EqualsToken + ); +} + +/** The value a JavaScript source reads for the declared global `name` + * taken as a value, whatever its spelling (`name`, `globalThis.name`, + * `const { name } = globalThis`, `globalThis[name]`), or null when it + * reads the opaque identity token. */ +export function jsGlobalValueByName(lowerer: Lowerer, name: string, loc: SrcLoc): IrExpr | null { + return ( + jsSpecialGlobalValue(lowerer, name, loc) ?? + builtinNamespaceValue(lowerer, name, loc) ?? + builtinGlobalValue(name, loc, true) + ); +} + +/** jsGlobalValueByName for the globals other than the interned builtin + * functions and constructors (builtinGlobalValue). */ +export function jsSpecialGlobalValue(lowerer: Lowerer, name: string, loc: SrcLoc): IrExpr | null { + if (name === "String" || name === "Number" || name === "Boolean" || name === "BigInt") + return primitiveCtorClosure(lowerer, name, loc, true); + if (name === "ArrayBuffer") + return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; + if (name === "Array") + return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc }; + if ((Object.values(BYTES_ELEMENT_NAME) as string[]).includes(name)) + return { + kind: "libCall", + fn: "bytes.constructor", + args: [{ kind: "strLit", value: name, type: STRING, loc }], + type: DYN, + loc, + }; + if (URI_FUNCTIONS.has(name)) return uriFunctionValue(lowerer, name, loc); + if (name === "crypto") + return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; + if (name === "navigator") return navigatorValue(loc); + if (name === "Buffer") + return { kind: "libCall", fn: "buffer.constructor", args: [], type: DYN, loc }; + if (name === "fetch") return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc }; + if (name === "console") + return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc }; + return null; +} + +const URI_FUNCTIONS = new Set([ + "encodeURIComponent", + "encodeURI", + "decodeURI", + "decodeURIComponent", +]); + +/** The statement that installs the program's lookup, built on first use + * from the names the checker declares on `receiver` (a global object). */ +export function globalValuesInstall( + lowerer: Lowerer, + receiver: ts.Expression, + loc: SrcLoc, +): IrStmt { + const fnType: IrType & { kind: "func" } = { kind: "func", params: [STRING], ret: DYN }; + if (!lowerer.liftedFnNamed(LOOKUP_FN)) { + const names = lowerer.checker + .getPropertiesOfType(lowerer.typeOf(receiver)) + .map((property) => ts.symbolName(property)) + .filter((name) => name !== TEMPORAL_GLOBAL && !RUNTIME_ANSWERED.has(name)); + const name: IrExpr = { kind: "varRef", localId: "name.0", type: STRING, loc }; + const body: IrStmt[] = []; + for (const global of [...new Set(names)].sort()) { + const value: IrExpr | null = REFUSED.has(global) + ? { kind: "unitLit", unit: "null", type: { kind: "nullT" }, loc } + : jsGlobalValueByName(lowerer, global, loc); + if (value === null) continue; + body.push({ + kind: "if", + cond: { + kind: "strEq", + negated: false, + left: name, + right: { kind: "strLit", value: global, type: STRING, loc }, + type: { kind: "bool" }, + loc, + }, + then: [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }], + else_: null, + loc, + }); + } + body.push({ + kind: "return", + value: lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + DYN, + ), + loc, + }); + lowerer.liftedFns.push({ + name: LOOKUP_FN, + params: [{ localId: "name.0", name: "name", type: STRING }], + returnType: DYN, + locals: [{ id: "name.0", name: "name", type: STRING, mutable: false }], + body, + loc, + }); + } + const lookup = lowerer.coerceToExpected( + { kind: "closure", fnName: LOOKUP_FN, captures: [], type: fnType, loc }, + DYN, + ); + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "global.installValues", + args: [lookup], + type: { kind: "void" }, + loc, + }, + loc, + }; +} + +/** Builtin module members that are the global objects of the same name in + * Node 24 (`require("buffer").atob === globalThis.atob`). */ +const GLOBAL_MEMBERS: Readonly>> = { + buffer: new Set(["atob", "btoa", "Blob", "File", "Buffer"]), + timers: new Set([ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + ]), + perf_hooks: new Set([ + "performance", + "PerformanceEntry", + "PerformanceMark", + "PerformanceMeasure", + "PerformanceObserver", + "PerformanceObserverEntryList", + "PerformanceResourceTiming", + ]), + util: new Set(["TextEncoder", "TextDecoder"]), + url: new Set(["URL", "URLSearchParams"]), + worker_threads: new Set(["BroadcastChannel", "MessageChannel", "MessagePort"]), + "stream/web": new Set([ + "ReadableStream", + "ReadableStreamDefaultReader", + "ReadableStreamBYOBReader", + "ReadableStreamBYOBRequest", + "ReadableByteStreamController", + "ReadableStreamDefaultController", + "TransformStream", + "TransformStreamDefaultController", + "WritableStream", + "WritableStreamDefaultWriter", + "WritableStreamDefaultController", + "ByteLengthQueuingStrategy", + "CountQueuingStrategy", + "TextEncoderStream", + "TextDecoderStream", + "CompressionStream", + "DecompressionStream", + ]), +}; + +/** In a JavaScript source, the value of a builtin module member that is a + * global object (GLOBAL_MEMBERS): what the global's own spelling reads, + * so identity holds across the spellings. Null for other members. */ +export function globalMemberValue( + lowerer: Lowerer, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr | null { + if (!GLOBAL_MEMBERS[bi.module]?.has(bi.member)) return null; + return ( + jsGlobalValueByName(lowerer, bi.member, loc) ?? { + kind: "strLit", + value: `[builtin ${bi.member}]`, + type: STRING, + loc, + } + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 4fe908ab4..06fc16080 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -1,3 +1,4 @@ +import { computedGlobalReadOf, globalValuesInstall } from "./global-values.js"; import { THREADS_MODULE } from "../threads-import.js"; import { positionTag } from "./structural-positions.js"; import { EXPERIMENTAL_MODULE } from "../experimental-import.js"; @@ -3022,6 +3023,10 @@ export function lowerFileInit( }); } if (sf === lowerer.entry) header.push(...lowerer.punycodeLinkWarning()); + // A module that reads builtin globals through the global object by + // computed names installs the program's lookup before its body runs. + const computedGlobalRead = computedGlobalReadOf(lowerer, sf); + if (computedGlobalRead) header.push(globalValuesInstall(lowerer, computedGlobalRead, loc0)); // Top-level `this` of a CommonJS module: its object exists before the // module's first statement (lowerCjsTopLevelThis). const exportsThis = lowerer.cjsExportsThis.get(sf); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..6024849a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,4 @@ +import { jsGlobalValueByName } from "./global-values.js"; import { lowerTupleAliasDecl, stripTypeWrappers } from "./typed-arguments.js"; import { allBytesArms, @@ -33,7 +34,6 @@ import { InternalCompilerError } from "../../errors.js"; * explicit resource management, and blocked-binding poisoning. */ import * as ts from "../ts7/adapter.js"; import { lowerOptionalIndex } from "./undefined-index.js"; -import { builtinNamespaceValue } from "./builtin-namespaces.js"; import { processDataMemberValue, programReadsExitCode } from "./builtins/process.js"; import { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; @@ -238,7 +238,6 @@ import { globalSymbolKey } from "./expressions/global-symbols.js"; import { identifiersNamed } from "./identifier-uses.js"; import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js"; import { - builtinGlobalValue, jsHoldsDynObject, lowerEnvironmentKey, lowerNativeFunctionAssignment, @@ -4326,14 +4325,9 @@ function stdlibGlobalTokenDestructure( const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member); token = `[builtin ${canonical}]`; alias = canonical; - // Builtin constructors read as their interned function values, and - // crypto as the WebCrypto object: the same values the bare and - // `globalThis.X` spellings read. - value = - canonical === "crypto" - ? { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc: locOf(el) } - : (builtinNamespaceValue(lowerer, canonical, locOf(el)) ?? - builtinGlobalValue(canonical, locOf(el), true)); + // The values the bare and `globalThis.X` spellings read + // (jsGlobalValueByName). + value = jsGlobalValueByName(lowerer, canonical, locOf(el)); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { @@ -12223,11 +12217,8 @@ function lowerForOfString( * own-keys snapshot, verified against Node) while each visit re-checks * presence (i < live length), so pops during the body skip exactly * like Node. Sparse arrays cannot exist here (hole creation traps). - * - globalThis: the harness's leaked-globals sweep — a compiled binary's - * global bindings are compile-time names, never enumerable runtime - * properties, so the enumeration is honestly EMPTY (zero iterations; - * the body typechecked but never evaluates — SEMANTICS.md documents - * the divergence). + * - globalThis: Node 24's enumerable globals in Node's order, then the + * program's own global properties, as keys of a fresh object. * - JavaScript program classes use their live property-creation table. * TypeScript and runtime-owned layouts keep the class-instance fence. * Bindings mirror for-of's: const/let declare the per-iteration string @@ -12237,10 +12228,12 @@ function lowerForOfString( function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { const labels = lowerer.takeLabels(); const loc = locOf(stmt); - if (stdlibGlobalNameOf(lowerer, stmt.expression) === "globalThis") { - return { kind: "block", body: [], loc }; - } - let receiver = lowerer.lowerExpr(stmt.expression); + // The global object visits Node's enumerable globals, then the + // program's own global properties (scr_global_for_in_object). + let receiver: IrExpr = + stdlibGlobalNameOf(lowerer, stmt.expression) === "globalThis" + ? { kind: "libCall", fn: "global.forInObject", args: [], type: DYN, loc } + : lowerer.lowerExpr(stmt.expression); if (receiver.type.kind === "object") { const info = lowerer.classes.get(receiver.type.className); let root = info; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..66e78ec02 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { globalMemberValue } from "./global-values.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js"; @@ -3105,6 +3106,9 @@ export class Lowerer { /** The property names the program assigns on the global object * (property-access.ts), collected on first use. */ globalThisWrittenNames: Set | null = null; + /** Set with globalThisWrittenNames: the program writes the global object + * by a computed name somewhere. */ + globalThisComputedWrite = false; /** Well-known symbol methods (`sym:iterator`) the program may replace * through a symbol-keyed write: an instance or prototype property, or a * reflection call. */ @@ -3339,6 +3343,9 @@ export class Lowerer { * values other than fresh literals (noteUntypedIntegrity). */ untypedIntegrity = false; readonly liftedFns: IrFunction[] = []; + /** global-values.ts's answer per JavaScript source: the receiver of its + * first read of the global object by a computed name, or null. */ + readonly computedGlobalReads = new Map(); /** The first lifted function of each name (liftedFnNamed), for the * liftedFns indexed so far. liftedFns only grows. */ private readonly liftedFnsByName = new Map(); @@ -14151,11 +14158,12 @@ export class Lowerer { if (bi.module === "crypto" && bi.member === "webcrypto") { return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; } - // node:stream/web's exports are the web stream globals themselves: in - // JavaScript sources the same identity token as the global spelling - // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). - if (bi.module === "stream/web" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { - return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; + // Members that are global objects (`require("stream/web").ReadableStream + // === globalThis.ReadableStream`) read in JavaScript sources as their + // global spelling does. + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + const global = globalMemberValue(this, bi, loc); + if (global) return global; } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 576386851..0d3d5c1ac 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3691,6 +3691,15 @@ function buildLibFnSignatures() { "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, /** A native reference to the global object; known names retain value fences. */ "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, + /** Installs the program's builtin-global lookup (global-values.ts). */ + "global.installValues": { argTypes: [DYN], result: VOID }, + /** The keys `for (key in globalThis)` visits, as a fresh object. */ + "global.forInObject": { argTypes: [], result: DYN }, + /** Node's navigator global (scr_node_builtin.c). */ + "navigator.object": { argTypes: [], result: DYN }, + "navigator.language": { argTypes: [], result: STRING }, + "navigator.languages": { argTypes: [], result: DYN }, + "navigator.platform": { argTypes: [], result: STRING }, // `X.name` through a class value: the arg is a program-dependent // classval (a null slot; the libCall case checks the kind). /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json new file mode 100644 index 000000000..7124f14a8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8161-global-computed-reads.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json new file mode 100644 index 000000000..2fd2d279e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8162-navigator-global.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json new file mode 100644 index 000000000..f76f88160 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8163-data-function-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json new file mode 100644 index 000000000..6081b76dd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8164-atob-validation.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..899c7f0ca 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -187,6 +187,7 @@ typedef enum { X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ X(MODULE_NAMESPACE, 79) /* module namespace object read through its exports (scr_module_namespace.c) */ \ + X(NAVIGATOR, 81) /* Node's navigator global (scr_node_builtin.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ @@ -1288,6 +1289,18 @@ void scr_dyn_obj_delete_symbol_field(ScrDyn *obj, ScrDyn *symbol); * object answers it; its symbol-keyed properties are the global object's * (scr_node_builtin.c). */ ScrDyn *scr_global_object(void); +/* The program's builtin-global lookup, a dyn function (borrowed); the first + * installation stands. */ +void scr_global_values_install(ScrDyn *lookup); +/* An object whose keys are what `for (key in globalThis)` visits (+1). */ +ScrDyn *scr_global_for_in_object(void); +/* Node's navigator global (+1, one box per thread) and its members: + * navigator.language (+1), navigator.languages (+1, one frozen array per + * thread) and navigator.platform (+1). */ +ScrDyn *scr_navigator_object(void); +ScrStr *scr_navigator_language(void); +ScrDyn *scr_navigator_languages(void); +ScrStr *scr_navigator_platform(void); ScrDyn *scr_dyn_global_symbol_get(ScrSym *key); /* borrowed key; +1 value */ void scr_dyn_global_symbol_set(ScrSym *key, ScrDyn *value); /* both borrowed */ bool scr_dyn_global_symbol_has(ScrSym *key); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 2fd3aa406..99d5ebad3 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1971,6 +1971,12 @@ static const char dyn_s32[] SCR_DYN_COMPAT_STR = ""; static const char dyn_s33[] SCR_DYN_COMPAT_STR = "constructing %s%s through a stored value is not supported yet"; static const char dyn_s34[] SCR_DYN_COMPAT_STR = " from a primitive"; static const char dyn_s35[] SCR_DYN_COMPAT_STR = "FinalizationRegistry"; +static const char dyn_s36[] SCR_DYN_COMPAT_STR = "atob"; +static const char dyn_s37[] SCR_DYN_COMPAT_STR = "btoa"; +static const char dyn_s38[] SCR_DYN_COMPAT_STR = "structuredClone"; +static const char dyn_s39[] SCR_DYN_COMPAT_STR = "The \"input\" argument must be specified"; +static const char dyn_s40[] SCR_DYN_COMPAT_STR = "The \"The value argument must be specified\" argument must be specified"; +static const char dyn_s41[] SCR_DYN_COMPAT_STR = "ERR_MISSING_ARGS"; static const char *const dyn_global_ctor_names[] = { dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, dyn_s35, @@ -2236,7 +2242,36 @@ static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, siz #endif +/* atob, btoa and structuredClone as values: the calls the direct + * spellings make, with Node's error for a missing argument. NULL with the + * exception pending, or the result (+1); `*handled` reports whether `name` + * is one of them. */ +static ScrDyn *dyn_builtin_data_fn_call(const ScrStr *name, ScrDyn *const *args, size_t argc, bool *handled) { + bool atob = strcmp(name->data, dyn_s36) == 0, btoa = strcmp(name->data, dyn_s37) == 0; + bool clone = strcmp(name->data, dyn_s38) == 0; + *handled = atob || btoa || clone; + if (!*handled) return NULL; + if (argc == 0) { + const char *message = clone ? dyn_s40 : dyn_s39; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), dyn_s41); + return NULL; + } + if (clone) return scr_structured_clone(args[0], argc > 1 ? args[1] : scr_dyn_undefined()); + ScrStr *text = atob ? scr_atob(args[0]) : scr_btoa(args[0]); + if (!text) return NULL; + ScrDyn *result = scr_dyn_new_str(text); /* retains */ + scr_str_release(text); + return result; +} + static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + { + ScrStr *name = scr_box_get_ref(clo->caps[0]); + bool handled; + ScrDyn *result = dyn_builtin_data_fn_call(name, args, argc, &handled); + scr_str_release(name); + if (handled) return result; + } #ifdef SCR_LIB (void)clo; (void)args; @@ -2295,7 +2330,8 @@ ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { closure->caps[0] = scr_box_new(SCR_BOX_STR); scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); const char *dot = memchr(name->data, '.', name->len); - bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0; + bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0 || + strcmp(name->data, dyn_s38) == 0; value = scr_dyn_new_func(closure, dyn_builtin_fn_call, two ? 2 : 1, dyn_s10, dot ? dot + 1 : name->data); scr_dyn_obj_set(dyn_builtin_fns, name->data, name->len, value); /* the table owns it */ } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..f2da26331 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -16723,9 +16723,10 @@ ScrError *scr_domex_clone(ScrError *e, const ScrDyn *options) { * They live HERE (not scr_string.c) because the argument is a dyn value: * WebIDL ToString runs over the dyn kind (Node's atob(null) decodes the * string "null"), and the string unit must stay linkable without the - * dyn. atob is forgiving-base64 exactly — ASCII whitespace stripped, a - * %4==0 input sheds up to two trailing '=', %4==1 refuses, leftover - * bits discard — decoding to the latin1 code points as a UTF-8 string; + * dyn. atob checks its input as Node's lib/buffer.js does (an invalid + * character or a misplaced '=' is "Invalid character", a length the + * forgiving-base64 rules refuse "not correctly encoded"), then decodes + * with leftover bits discarded to the latin1 code points as a UTF-8 string; * btoa refuses any code point over U+00FF. Malformed input throws the * catchable DOMException InvalidCharacterError with Node's exact * message. */ @@ -16741,61 +16742,70 @@ static int scr_b64_val(unsigned char c) { ScrStr *scr_atob(const ScrDyn *data) { ScrStr *s = scr_dyn_string_coerce(data); - /* Strip ASCII whitespace (the forgiving step). */ - char *buf = malloc(s->len ? s->len : 1); - if (!buf) scr_json_oom(); - size_t n = 0; + /* Node's checks (lib/buffer.js): only ASCII whitespace and the base64 + * alphabet with '=' are allowed, '=' only at the end and at most twice; + * the count of the other characters decides the rest. One pass checks + * the input and keeps the 6-bit value of each alphabet character. */ + unsigned char *values = malloc(s->len ? s->len : 1); + if (!values) scr_json_oom(); + size_t n = 0, equals = 0; + const char *message = NULL; for (size_t i = 0; i < s->len; i++) { unsigned char c = (unsigned char)s->data[i]; if (c == 0x09 || c == 0x0a || c == 0x0c || c == 0x0d || c == 0x20) continue; - buf[n++] = (char)c; + if (c == '=') { + if (++equals > 2) { + message = "Invalid character"; + break; + } + continue; + } + int v = scr_b64_val(c); + if (v < 0 || equals) { + message = "Invalid character"; + break; + } + values[n++] = (unsigned char)v; + } + if (!message) { + size_t remainder = (n + equals) % 4; + if (!remainder) remainder = n % 4; + else if (equals) message = "Invalid character"; + if (!message && remainder == 1) message = "The string to be decoded is not correctly encoded."; } scr_str_release(s); - /* A %4==0 input sheds one or two trailing '='. */ - if (n % 4 == 0 && n > 0) { - if (buf[n - 1] == '=') n--; - if (n > 0 && buf[n - 1] == '=') n--; + if (message) { + free(values); + scr_throw_domex("InvalidCharacterError", message); + return NULL; } - if (n % 4 == 1) goto invalid; /* Decode 6-bit groups; latin1 code points expand to UTF-8 (bytes over - * 0x7F become two-byte sequences). */ - { - size_t outBytes = (n / 4) * 3 + (n % 4 == 2 ? 1 : n % 4 == 3 ? 2 : 0); - char *out = malloc(outBytes * 2 ? outBytes * 2 : 1); - if (!out) scr_json_oom(); - size_t w = 0; - unsigned acc = 0; - int bits = 0; - for (size_t i = 0; i < n; i++) { - int v = scr_b64_val((unsigned char)buf[i]); - if (v < 0) { - free(out); - goto invalid; - } - acc = (acc << 6) | (unsigned)v; - bits += 6; - if (bits >= 8) { - bits -= 8; - unsigned char b = (unsigned char)((acc >> bits) & 0xff); - if (b < 0x80) { - out[w++] = (char)b; - } else { - out[w++] = (char)(0xc0 | (b >> 6)); - out[w++] = (char)(0x80 | (b & 0x3f)); - } + * 0x7F become two-byte sequences). Leftover bits discard + * (forgiving-base64's final step). */ + size_t outBytes = (n / 4) * 3 + (n % 4 == 2 ? 1 : n % 4 == 3 ? 2 : 0); + char *out = malloc(outBytes * 2 ? outBytes * 2 : 1); + if (!out) scr_json_oom(); + size_t w = 0; + unsigned acc = 0; + int bits = 0; + for (size_t i = 0; i < n; i++) { + acc = (acc << 6) | values[i]; + bits += 6; + if (bits >= 8) { + bits -= 8; + unsigned char b = (unsigned char)((acc >> bits) & 0xff); + if (b < 0x80) { + out[w++] = (char)b; + } else { + out[w++] = (char)(0xc0 | (b >> 6)); + out[w++] = (char)(0x80 | (b & 0x3f)); } } - /* Leftover bits discard (forgiving-base64's final step). */ - free(buf); - ScrStr *result = scr_str_new(out, w); - free(out); - return result; } -invalid: - free(buf); - scr_throw_domex("InvalidCharacterError", - "The string to be decoded is not correctly encoded."); - return NULL; + free(values); + ScrStr *result = scr_str_new(out, w); + free(out); + return result; } ScrStr *scr_btoa(const ScrDyn *data) { diff --git a/packages/runtime/src/scr_node_builtin.c b/packages/runtime/src/scr_node_builtin.c index c0031e67b..8604134e7 100644 --- a/packages/runtime/src/scr_node_builtin.c +++ b/packages/runtime/src/scr_node_builtin.c @@ -97,6 +97,10 @@ static SCR_TL const ScrArr *scr_global_registered; static SCR_TL bool scr_global_cleanup_registered; static SCR_TL ScrDyn *(*scr_global_console_get)(void); static SCR_TL ScrDyn *(*scr_global_fetch_get)(void); +/* The program's builtin-global lookup (scr_global_values_install), or + * NULL: a dyn function from a name to the value its static spelling reads, + * undefined for the opaque identity token, or null for a name without one. */ +static SCR_TL ScrDyn *scr_global_values; void scr_global_fetch_install(ScrDyn *(*get)(void)) { scr_global_fetch_get = get; } @@ -200,6 +204,13 @@ static void scr_global_cleanup(void) { static void *scr_global_retain(void *handle) { return handle; } static void scr_global_release(void *handle) { (void)handle; } +/* A dyn string holding `s` (+1); takes over the caller's reference. */ +static ScrDyn *scr_global_take_str(ScrStr *s) { + ScrDyn *value = scr_dyn_new_str(s); + scr_str_release(s); + return value; +} + static ScrDyn *scr_global_get(void *handle, const char *key, size_t length) { (void)handle; ScrDyn *own = scr_global_own ? scr_dyn_obj_get(scr_global_own, key, length) : NULL; @@ -210,10 +221,29 @@ static ScrDyn *scr_global_get(void *handle, const char *key, size_t length) { if (length == 7 && memcmp(key, "console", length) == 0 && scr_global_console_get) return scr_global_console_get(); if (length == 5 && memcmp(key, "fetch", length) == 0 && scr_global_fetch_get) return scr_global_fetch_get(); + if (length == 9 && memcmp(key, "navigator", length) == 0) return scr_navigator_object(); if (length == 9 && memcmp(key, "undefined", length) == 0) return scr_dyn_undefined(); if (length == 3 && memcmp(key, "NaN", length) == 0) return scr_dyn_new_num(NAN); if (length == 8 && memcmp(key, "Infinity", length) == 0) return scr_dyn_new_num(INFINITY); if (!scr_dyn_obj_get(scr_global_known, key, length)) return scr_dyn_undefined(); + if (scr_global_values) { + ScrDyn *name = scr_global_take_str(scr_str_new(key, length)); + ScrDyn *value = scr_global_values->v.fn.thunk(scr_global_values->v.fn.clo, &name, 1); + scr_dyn_release(name); + if (!value) return NULL; + if (value->kind == SCR_DYN_UNDEF) { + /* The opaque identity token a JavaScript source reads for the name. */ + scr_dyn_release(value); + ScrJsonBuf token; + scr_jb_init(&token); + scr_jb_puts(&token, "[builtin "); + for (size_t i = 0; i < length; i++) scr_jb_putc(&token, key[i]); + scr_jb_putc(&token, ']'); + return scr_global_take_str(scr_jb_finish(&token)); + } + if (value->kind != SCR_DYN_NULL) return value; + scr_dyn_release(value); + } ScrJsonBuf text; scr_jb_init(&text); scr_jb_puts(&text, "reading globalThis."); @@ -250,6 +280,47 @@ ScrDyn *scr_global_object(void) { return scr_global_box; } +/* Installs the program's builtin-global lookup (borrowed): reads of + * builtin names through the global object answer what their static + * spellings read. The first installation stands; modules that read the + * global object by computed names each install the same function. */ +static void scr_global_values_cleanup(void) { + if (scr_global_values) scr_dyn_release(scr_global_values); + scr_global_values = NULL; +} + +void scr_global_values_install(ScrDyn *lookup) { + if (scr_global_values || lookup->kind != SCR_DYN_FUNC) return; + scr_global_values = scr_dyn_retain(lookup); + scr_atexit(scr_global_values_cleanup); +} + +/* What `for (key in globalThis)` visits (+1): Node 24's enumerable global + * properties in its order, then the program's own global properties in + * creation order, as keys of a fresh object. */ +ScrDyn *scr_global_for_in_object(void) { + static const char *const node_names[] = { + "global", "clearImmediate", "setImmediate", "clearInterval", "clearTimeout", + "setInterval", "setTimeout", "queueMicrotask", "structuredClone", "atob", + "btoa", "performance", "fetch", "crypto", "navigator", + }; + ScrDyn *object = scr_dyn_new_obj(); + for (size_t i = 0; i < sizeof node_names / sizeof node_names[0]; i++) + scr_dyn_obj_set(object, node_names[i], strlen(node_names[i]), scr_dyn_undefined()); + if (scr_global_own) { + ScrDyn *keys = scr_dyn_obj_keys(scr_global_own); + double count = scr_dyn_arr_len(keys); + for (double i = 0; i < count; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + if (key->kind == SCR_DYN_STR && !scr_dyn_obj_get(object, key->v.str->data, key->v.str->len)) + scr_dyn_obj_set(object, key->v.str->data, key->v.str->len, scr_dyn_undefined()); + scr_dyn_release(key); + } + scr_dyn_release(keys); + } + return object; +} + ScrDyn *scr_global_native_init(ScrArr *known, ScrDyn *(*console_get)(void)) { /* The compiler passes one immortal name table per program: once it is * registered, later reads of the global object only take a reference. */ @@ -388,3 +459,221 @@ ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member) { scr_str_release(message); return NULL; } + +/* ── Node's navigator global (lib/internal/navigator.js) ───────────────── + * hardwareConcurrency is os.availableParallelism(), language the default + * locale as Node's ICU answers it from the environment, languages a frozen + * one-element array of it, platform Node's name for the process platform + * and architecture, and userAgent `Node.js/`. locks has no lowering. + * The members are getters on Navigator.prototype, so the object owns no + * properties: Object.keys answers [] and JSON.stringify `{}`. */ + +static SCR_TL ScrDyn *scr_navigator_box; +static SCR_TL ScrDyn *scr_navigator_langs; +static SCR_TL ScrStr *scr_navigator_lang; + +static void scr_navigator_refuse(const char *what) { + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "'"); + scr_jb_puts(&text, what); + scr_jb_puts(&text, "' is part of the standard library types but has no scriptc lowering yet [SC2020]"); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); +} + +static bool scr_navigator_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +static bool scr_navigator_digit(char c) { return c >= '0' && c <= '9'; } +static char scr_navigator_lower(char c) { return c >= 'A' && c <= 'Z' ? (char)(c + 32) : c; } +static char scr_navigator_upper(char c) { return c >= 'a' && c <= 'z' ? (char)(c - 32) : c; } + +/* The BCP 47 tag of the POSIX locale ID `id`, as ICU derives the default + * locale: `language[_TERRITORY][.codeset][@modifier]`, the codeset + * dropped, the C and POSIX locales and en_US_POSIX as en-US, the ISO 639 + * codes ICU replaces, and the modifier as a variant (5 to 8 characters) or + * an lvariant private-use subtag (1 to 4). NULL for an ID outside that + * grammar, whose tag needs ICU's tables. */ +static ScrStr *scr_navigator_tag(const char *id) { + if (!id) return scr_str_new("en-US", 5); + const char *end = id + strlen(id); + const char *at = strchr(id, '@'); + const char *dot = strchr(id, '.'); + const char *base_end = dot && (!at || dot < at) ? dot : at ? at : end; + const char *modifier = at ? at + 1 : NULL; + size_t base_len = (size_t)(base_end - id); + if (base_len == 0 && !modifier) return scr_str_new("und", 3); + if ((base_len == 1 && id[0] == 'C') || (base_len == 5 && memcmp(id, "POSIX", 5) == 0)) { + if (modifier) return NULL; + return scr_str_new("en-US", 5); + } + const char *underscore = memchr(id, '_', base_len); + size_t lang_len = underscore ? (size_t)(underscore - id) : base_len; + const char *region = underscore ? underscore + 1 : NULL; + size_t region_len = region ? (size_t)(base_end - region) : 0; + if (region && memchr(region, '_', region_len)) { + /* en_US_POSIX is ICU's name for the C locale. */ + if (lang_len == 2 && scr_navigator_lower(id[0]) == 'e' && scr_navigator_lower(id[1]) == 'n' && + region_len == 8 && scr_navigator_upper(region[0]) == 'U' && scr_navigator_upper(region[1]) == 'S' && + region[2] == '_') { + bool posix = true; + for (size_t i = 0; i < 5; i++) posix = posix && scr_navigator_upper(region[3 + i]) == "POSIX"[i]; + if (posix && !modifier) return scr_str_new("en-US", 5); + } + return NULL; + } + for (size_t i = 0; i < lang_len; i++) + if (!scr_navigator_alpha(id[i])) return NULL; + if (lang_len == 1 || lang_len == 4 || lang_len > 8) return NULL; + ScrJsonBuf tag; + scr_jb_init(&tag); + char lang[9] = {0}; + for (size_t i = 0; i < lang_len; i++) lang[i] = scr_navigator_lower(id[i]); + static const char *const replaced[][2] = { + {"in", "id"}, {"iw", "he"}, {"ji", "yi"}, {"jw", "jv"}, {"mo", "ro"}, + }; + const char *language = lang_len == 0 ? "und" : lang; + for (size_t i = 0; i < sizeof replaced / sizeof replaced[0]; i++) + if (!strcmp(lang, replaced[i][0])) language = replaced[i][1]; + scr_jb_puts(&tag, language); + if (region_len > 0) { + bool alpha2 = region_len == 2 && scr_navigator_alpha(region[0]) && scr_navigator_alpha(region[1]); + bool digits3 = region_len == 3 && scr_navigator_digit(region[0]) && scr_navigator_digit(region[1]) && + scr_navigator_digit(region[2]); + if (!alpha2 && !digits3) { + scr_str_release(scr_jb_finish(&tag)); + return NULL; + } + scr_jb_putc(&tag, '-'); + for (size_t i = 0; i < region_len; i++) scr_jb_putc(&tag, scr_navigator_upper(region[i])); + } + if (modifier && *modifier) { + size_t length = (size_t)(end - modifier); + bool alnum = length <= 8; + for (size_t i = 0; i < length && alnum; i++) + alnum = scr_navigator_alpha(modifier[i]) || scr_navigator_digit(modifier[i]); + if (!alnum || (length == 7 && !memcmp(modifier, "nynorsk", 7))) { + scr_str_release(scr_jb_finish(&tag)); + return NULL; + } + scr_jb_puts(&tag, length >= 5 ? "-" : "-x-lvariant-"); + for (size_t i = 0; i < length; i++) scr_jb_putc(&tag, scr_navigator_lower(modifier[i])); + } + return scr_jb_finish(&tag); +} + +ScrStr *scr_navigator_language(void) { + if (!scr_navigator_lang) { + /* ICU reads LC_ALL, then LC_MESSAGES, then LANG; a variable that is + * set, even to the empty string, decides. */ + const char *id = getenv("LC_ALL"); + if (!id) id = getenv("LC_MESSAGES"); + if (!id) id = getenv("LANG"); + ScrStr *tag = scr_navigator_tag(id); + if (!tag) { + scr_navigator_refuse("navigator.language for this locale"); + return NULL; + } + scr_navigator_lang = tag; + } + return scr_str_retain(scr_navigator_lang); +} + +ScrDyn *scr_navigator_languages(void) { + if (!scr_navigator_langs) { + ScrStr *language = scr_navigator_language(); + if (!language) return NULL; + ScrDyn *languages = scr_dyn_new_arr(); + scr_dyn_arr_push(languages, scr_global_take_str(language)); + scr_dyn_release(scr_dyn_freeze_data(languages)); + scr_navigator_langs = languages; + } + return scr_dyn_retain(scr_navigator_langs); +} + +ScrStr *scr_navigator_platform(void) { + ScrStr *platform = scr_process_platform(); + ScrStr *arch = scr_process_arch(); + const char *p = platform->data, *a = arch->data; + ScrJsonBuf text; + scr_jb_init(&text); + bool ia32 = !strcmp(a, "ia32"), x64 = !strcmp(a, "x64"); + if (!strcmp(p, "darwin")) scr_jb_puts(&text, "MacIntel"); + else if (!strcmp(p, "win32")) scr_jb_puts(&text, "Win32"); + else if (!strcmp(p, "linux")) { + scr_jb_puts(&text, ia32 ? "Linux i686" : x64 ? "Linux x86_64" : "Linux "); + if (!ia32 && !x64) scr_jb_puts(&text, a); + } else if (!strcmp(p, "freebsd") || !strcmp(p, "openbsd")) { + scr_jb_puts(&text, p[0] == 'f' ? "FreeBSD " : "OpenBSD "); + scr_jb_puts(&text, ia32 ? "i386" : x64 ? "amd64" : a); + } else if (!strcmp(p, "sunos")) { + scr_jb_puts(&text, "SunOS "); + scr_jb_puts(&text, ia32 ? "i86pc" : a); + } else if (!strcmp(p, "aix")) scr_jb_puts(&text, "AIX"); + else { + scr_jb_putc(&text, scr_navigator_upper(p[0])); + if (p[0]) scr_jb_puts(&text, p + 1); + scr_jb_putc(&text, ' '); + scr_jb_puts(&text, a); + } + scr_str_release(platform); + scr_str_release(arch); + return scr_jb_finish(&text); +} + +static ScrDyn *scr_navigator_get(void *handle, const char *key, size_t length) { + (void)handle; + if (length == 19 && !memcmp(key, "hardwareConcurrency", length)) + return scr_dyn_new_num(scr_os_available_parallelism()); + if (length == 8 && !memcmp(key, "language", length)) { + ScrStr *language = scr_navigator_language(); + return language ? scr_global_take_str(language) : NULL; + } + if (length == 9 && !memcmp(key, "languages", length)) return scr_navigator_languages(); + if (length == 8 && !memcmp(key, "platform", length)) return scr_global_take_str(scr_navigator_platform()); + if (length == 9 && !memcmp(key, "userAgent", length)) { + ScrStr *version = scr_process_versions_node(); + const char *dot = memchr(version->data, '.', version->len); + size_t major = dot ? (size_t)(dot - version->data) : version->len; + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Node.js/"); + for (size_t i = 0; i < major; i++) scr_jb_putc(&text, version->data[i]); + scr_str_release(version); + return scr_global_take_str(scr_jb_finish(&text)); + } + if (length == 5 && !memcmp(key, "locks", length)) { + scr_navigator_refuse("navigator.locks"); + return NULL; + } + return NULL; +} + +static ScrDyn *scr_navigator_inspect_view(void *handle) { + (void)handle; + return scr_dyn_new_obj(); +} + +static void scr_navigator_cleanup(void) { + if (scr_navigator_box) scr_dyn_release(scr_navigator_box); + if (scr_navigator_langs) scr_dyn_release(scr_navigator_langs); + if (scr_navigator_lang) scr_str_release(scr_navigator_lang); + scr_navigator_box = NULL; + scr_navigator_langs = NULL; + scr_navigator_lang = NULL; +} + +ScrDyn *scr_navigator_object(void) { + static const ScrDynHandleOps ops = { + .cls = "Navigator", .retain = scr_global_retain, .release = scr_global_release, + .get = scr_navigator_get, .inspect_view = scr_navigator_inspect_view, + .json_empty_object = true, .inspect_view_empty = true, + }; + if (!scr_navigator_box) { + scr_dyn_handle_install(SCR_DYNH_NAVIGATOR, &ops); + static SCR_TL char token; + scr_navigator_box = scr_dyn_new_handle(&token, SCR_DYNH_NAVIGATOR); + scr_atexit(scr_navigator_cleanup); + } + return scr_dyn_retain(scr_navigator_box); +} diff --git a/tests/corpus/8161-global-computed-reads.cjs b/tests/corpus/8161-global-computed-reads.cjs new file mode 100644 index 000000000..ff5883154 --- /dev/null +++ b/tests/corpus/8161-global-computed-reads.cjs @@ -0,0 +1,87 @@ +// @no-node-shims +// Builtin globals read through the global object by computed names, and +// builtin module members that are global objects: each answers what the +// global's own spelling reads, so identity holds across the spellings. +// for-in over the global object visits Node's enumerable globals first and +// the program's own global properties after them. +"use strict"; +const { atob: bufferAtob, btoa: bufferBtoa } = require("buffer"); +const timers = require("timers"); +const { performance: perf } = require("perf_hooks"); +const util = require("util"); +const url = require("url"); +const { MessageChannel } = require("worker_threads"); +const web = require("stream/web"); + +const names = [ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + "queueMicrotask", + "global", + "globalThis", + "fetch", + "Map", + "Object", + "Promise", + "Buffer", + "crypto", +]; +for (const name of names) { + const value = globalThis[name]; + console.log(name, typeof value, value === globalThis[name]); +} +console.log( + globalThis["setTimeout"] === setTimeout, + globalThis["Map"] === Map, + globalThis["crypto"] === crypto, + globalThis["Buffer"] === Buffer, +); +console.log(timers.setTimeout === setTimeout, timers.clearInterval === globalThis["clearInterval"]); +console.log( + bufferAtob === atob, + bufferBtoa === globalThis["btoa"], + perf === performance, + util.TextEncoder === TextEncoder, + url.URL === URL, + MessageChannel === globalThis["MessageChannel"], + web.ReadableStream === ReadableStream, +); +console.log(globalThis["noSuchGlobal"], typeof globalThis["Temporal"]); +const pick = (name) => globalThis[name]; +console.log(pick("Math") === Math, pick("JSON") === JSON, pick("Reflect") === Reflect, pick("Math").max(1, 3)); + +const before = []; +for (const key in globalThis) before.push(key); +globalThis.ownA = 1; +globalThis["own" + "B"] = { b: 2 }; +const after = []; +for (const key in globalThis) after.push(key); +console.log(after.length - before.length, after.slice(-2).join(",")); +console.log(before.slice(0, 9).join(",")); +console.log(globalThis.ownA, globalThis.ownB.b, globalThis["ownB"] === globalThis.ownB); + +// The harness's leak check: every enumerated global is a known value. +const known = new Set([ + global, + clearImmediate, + setImmediate, + clearInterval, + clearTimeout, + setInterval, + setTimeout, + queueMicrotask, + structuredClone, + bufferAtob, + bufferBtoa, + performance, + fetch, + crypto, + globalThis.ownA, + globalThis.ownB, +]); +const leaked = after.filter((key) => key !== "navigator" && !known.has(globalThis[key])); +console.log("leaked", leaked.length); diff --git a/tests/corpus/8162-navigator-global.cjs b/tests/corpus/8162-navigator-global.cjs new file mode 100644 index 000000000..3944d6efd --- /dev/null +++ b/tests/corpus/8162-navigator-global.cjs @@ -0,0 +1,47 @@ +// @no-node-shims +// Node 24's navigator global: its members are getters on +// Navigator.prototype answering the process's values, and the object +// itself owns no properties. It is one of the global object's enumerable +// properties. +"use strict"; +const os = require("os"); +const util = require("util"); + +console.log(typeof navigator, navigator.userAgent); +console.log(navigator.hardwareConcurrency === os.availableParallelism()); +console.log(typeof navigator.language, navigator.language.length > 0); +console.log(navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(navigator.languages === navigator.languages, Object.isFrozen(navigator.languages)); +const platforms = { + linux: { x64: "Linux x86_64", arm64: "Linux arm64" }, + darwin: { x64: "MacIntel", arm64: "MacIntel" }, +}; +console.log(navigator.platform === platforms[process.platform]?.[process.arch]); + +const value = navigator; +console.log(value === navigator, value === globalThis.navigator, value === globalThis["navigator"]); +console.log(value.userAgent, value.platform === navigator.platform, value.missing); +console.log(Object.keys(value), JSON.stringify(value), util.inspect(value)); +console.log(String(value), Object.prototype.toString.call(value)); + +const keys = []; +for (const key in globalThis) keys.push(key); +console.log(keys.join(",")); +const known = new Set([ + global, + clearImmediate, + setImmediate, + clearInterval, + clearTimeout, + setInterval, + setTimeout, + queueMicrotask, + structuredClone, + atob, + btoa, + performance, + fetch, + crypto, + navigator, +]); +console.log("leaked", keys.filter((key) => !known.has(globalThis[key])).length); diff --git a/tests/corpus/8163-data-function-values.cjs b/tests/corpus/8163-data-function-values.cjs new file mode 100644 index 000000000..d752f8405 --- /dev/null +++ b/tests/corpus/8163-data-function-values.cjs @@ -0,0 +1,32 @@ +// atob, btoa and structuredClone as JavaScript values: function values +// whose calls run what the direct calls run, the same value through every +// spelling. +"use strict"; +const buffer = require("buffer"); + +const fns = [atob, btoa, structuredClone]; +for (const fn of fns) console.log(typeof fn, fn.name, fn.length); +console.log(buffer.atob === atob, buffer.btoa === globalThis.btoa, globalThis["structuredClone"] === structuredClone); + +const pick = (name) => ({ atob, btoa, structuredClone })[name]; +const encode = pick("btoa"); +const decode = pick("atob"); +console.log(encode("scriptc"), decode(encode("scriptc")), decode("aGk=")); +const clone = pick("structuredClone"); +const original = { list: [1, { deep: true }], text: "t" }; +const copy = clone(original); +console.log(copy !== original, copy.list[1].deep, copy.list !== original.list, copy.text); + +const report = (label, run) => { + try { + console.log(label, run()); + } catch (error) { + console.log(label, error.name, error.code, error.message); + } +}; +report("atob()", () => decode()); +report("btoa()", () => encode()); +report("structuredClone()", () => clone()); +report("atob invalid", () => decode("a")); +report("btoa wide", () => encode("Ā")); +report("clone symbol", () => clone(Symbol("s"))); diff --git a/tests/corpus/8164-atob-validation.cjs b/tests/corpus/8164-atob-validation.cjs new file mode 100644 index 000000000..89d46e3b1 --- /dev/null +++ b/tests/corpus/8164-atob-validation.cjs @@ -0,0 +1,35 @@ +// atob checks its input as Node does: only ASCII whitespace and the base64 +// alphabet are allowed, '=' only at the end and at most twice, and a +// length the forgiving-base64 rules refuse is a different error. +"use strict"; +const inputs = [ + "@@@", + "a", + "ab", + "ab=", + "ab==", + "ab=c", + "abc==", + "a===", + "ab===", + " Y W J j ", + "YQ", + "Y Q = =", + "YWJjZA==", + "YWJjZA=", + "\tYW\nJj\fZA\r==", + "é", + "YW-J", + "", +]; +const decode = (label, input) => { + try { + console.log(label, JSON.stringify(atob(input))); + } catch (error) { + console.log(label, error.name, error.code, error.message); + } +}; +for (const input of inputs) decode(JSON.stringify(input), input); +decode("null", null); +decode("undefined", undefined); +decode("12", 12); diff --git a/tests/fixtures/node-types/fenced.ts b/tests/fixtures/node-types/fenced.ts index 0d90e8cac..d94c7c19a 100644 --- a/tests/fixtures/node-types/fenced.ts +++ b/tests/fixtures/node-types/fenced.ts @@ -108,8 +108,8 @@ async function publicKeyWithWebCrypto(key: import("node:crypto").webcrypto.Crypt await crypto.subtle.getPublicKey(key, ["verify"]); } void publicKeyWithWebCrypto; -/* Node's navigator global: typeof navigator and navigator.userAgent - * compile; the other members fence by name. */ -const cores = navigator.hardwareConcurrency; -void cores; +/* Node's navigator global: navigator.locks (the Web Locks API) fences + * by name. */ +const locks = navigator.locks; +void locks; // End of the declared-but-not-lowered surface. diff --git a/tests/fixtures/node-types/navigator.ts b/tests/fixtures/node-types/navigator.ts index 2f1ee2d9f..901f0e290 100644 --- a/tests/fixtures/node-types/navigator.ts +++ b/tests/fixtures/node-types/navigator.ts @@ -1,6 +1,10 @@ /* Node 24's navigator global under @types/node: the runtime probe - * libraries use (jose's user-agent check) folds to Node's values. */ + * libraries use (jose's user-agent check) folds to Node's values, and the + * other members read the process's values. */ const browser = !navigator.userAgent.startsWith("Mozilla/5.0 "); console.log(typeof navigator, navigator.userAgent, browser); console.log(typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers"); console.log(navigator.userAgent.split("/")[0], navigator.userAgent.length); +console.log(navigator.hardwareConcurrency > 0, Number.isInteger(navigator.hardwareConcurrency)); +console.log(typeof navigator.language, navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(navigator.platform.length > 0, navigator.languages === navigator.languages); diff --git a/tests/harness/__snapshots__/node-types-fenced.txt b/tests/harness/__snapshots__/node-types-fenced.txt index 32eb08fba..a7618fa58 100644 --- a/tests/harness/__snapshots__/node-types-fenced.txt +++ b/tests/harness/__snapshots__/node-types-fenced.txt @@ -232,11 +232,29 @@ fenced.ts:108:9 - error SC2020: 'crypto.subtle.getPublicKey' is part of the stan hint: the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle -fenced.ts:113:15 - error SC2020: 'navigator.hardwareConcurrency' is typed by @types/node but has no scriptc lowering yet +fenced.ts:113:7 - error SC2020: 'LockManager' is typed by @types/node but has no scriptc lowering yet - 112 | * compile; the other members fence by name. */ - 113 | const cores = navigator.hardwareConcurrency; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 114 | void cores; + 112 | * by name. */ + 113 | const locks = navigator.locks; + | ^~~~~ + 114 | void locks; - hint: of Node's navigator global only typeof navigator and navigator.userAgent compile \ No newline at end of file + hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations) + +fenced.ts:113:15 - error SC2020: 'navigator.locks' is typed by @types/node but has no scriptc lowering yet + + 112 | * by name. */ + 113 | const locks = navigator.locks; + | ^~~~~~~~~~~~~~~ + 114 | void locks; + + hint: navigator.locks (the Web Locks API) has no lowering + +fenced.ts:114:6 - error SC2004: uses of 'locks' inherit the blocker on its declaration + + 113 | const locks = navigator.locks; + 114 | void locks; + | ^~~~~ + 115 | // End of the declared-but-not-lowered surface. + + hint: the declaration of 'locks' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file diff --git a/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json b/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json new file mode 100644 index 000000000..24129b327 --- /dev/null +++ b/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json @@ -0,0 +1 @@ +{ "plain": 3708, "san": 12618 } diff --git a/tests/harness/corpus-costs/8162-navigator-global.cjs.json b/tests/harness/corpus-costs/8162-navigator-global.cjs.json new file mode 100644 index 000000000..433a92b5d --- /dev/null +++ b/tests/harness/corpus-costs/8162-navigator-global.cjs.json @@ -0,0 +1 @@ +{ "plain": 3686, "san": 6899 } diff --git a/tests/harness/corpus-costs/8163-data-function-values.cjs.json b/tests/harness/corpus-costs/8163-data-function-values.cjs.json new file mode 100644 index 000000000..5c1fdff5e --- /dev/null +++ b/tests/harness/corpus-costs/8163-data-function-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3362, "san": 7680 } diff --git a/tests/harness/corpus-costs/8164-atob-validation.cjs.json b/tests/harness/corpus-costs/8164-atob-validation.cjs.json new file mode 100644 index 000000000..5fd135960 --- /dev/null +++ b/tests/harness/corpus-costs/8164-atob-validation.cjs.json @@ -0,0 +1 @@ +{ "plain": 3065, "san": 4575 } From 317d724a7b439cf8056eee530bf9cf9458227b16 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:46:52 -0700 Subject: [PATCH 259/395] Keep bound alias instantiations on the lowering pass The compiler is compiled by scriptc and keeps caches on their owners instead of weak collections (compiler-weak-collections.test.ts). Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 25 ++----------------- .../compiler/src/frontend/lowering/lowerer.ts | 15 ++++++++--- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 7d7124027..a2eda839a 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -921,27 +921,6 @@ function bindDefaultTypeParams( }); } -/** Generic alias references of a call-keyed instance's declared signature - * (`Catalog`), each paired with the same alias as the call's resolved - * signature instantiates it (`Catalog<{ g1: … }, "acme">`). The checker - * keeps the alias symbolic inside the generic body and offers no way to - * instantiate it with the bound arguments, but alias instantiations are - * interned: the body's `x as Catalog` is the very type the signature - * declares, so the pair recorded during unification is that re-instantiation. - * Keyed by the instance's ts-level bindings map, which every scope that - * lowers the instance installs (typeParamTsBindings). */ -const boundAliasInstances = new WeakMap, Map>(); - -/** The concrete instantiation of a generic alias reference inside the - * instance whose ts-level bindings are `tsBindings` (boundAliasInstances), - * or null when its signature never instantiated that reference. */ -export function boundAliasInstance( - tsBindings: Map, - type: ts.Type, -): ts.Type | null { - return boundAliasInstances.get(tsBindings)?.get(type) ?? null; -} - /** The structural half of binding inference: unify the DECLARED signature * (whose types mention the type parameters) against a TARGET signature * with the substitution applied — a call's resolved signature, or the @@ -1062,8 +1041,8 @@ function unifySignatureBindings( iAliasArgs.length === dAliasArgs.length ) { if (tsBindings && declared !== inst) { - let pairs = boundAliasInstances.get(tsBindings); - if (!pairs) boundAliasInstances.set(tsBindings, (pairs = new Map())); + let pairs = lowerer.boundAliasInstances.get(tsBindings); + if (!pairs) lowerer.boundAliasInstances.set(tsBindings, (pairs = new Map())); // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 52f3567fb..216851c9c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -456,7 +456,6 @@ import { inferTypeParamBindings, lowerGenericInstance, genericLocalClassBinding, - boundAliasInstance, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; @@ -2234,6 +2233,16 @@ export class Lowerer { * the body needs — indexed accesses (`T[K]` needs K's literal key) and * keyed record reads (`o[k]` where k's type is a literal-bound K). */ typeParamTsBindings: Map | null = null; + /** Generic alias references of a call-keyed instance's declared signature + * (`Catalog`), each paired with the same alias as the call's + * resolved signature instantiates it (`Catalog<{ g1: … }, "acme">`), + * keyed by the instance's ts-level bindings (typeParamTsBindings). The + * checker keeps the alias symbolic inside the generic body and offers no + * way to instantiate it with the bound arguments, but alias + * instantiations are interned: the body's `x as Catalog` is the + * very type the signature declares, so the pair recorded during binding + * unification is that re-instantiation. */ + readonly boundAliasInstances = new Map, Map>(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -9554,10 +9563,10 @@ export class Lowerer { /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares - * (boundAliasInstance). Inert outside generic function instantiation. */ + * (boundAliasInstances). Inert outside generic function instantiation. */ readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; - return boundAliasInstance(this.typeParamTsBindings, t); + return this.boundAliasInstances.get(this.typeParamTsBindings)?.get(t) ?? null; }; irTypeOf(node: ts.Node): IrType { From 6000485a17155e71dba5a33c482a347309aadbba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:04:10 -0500 Subject: [PATCH 260/395] Report either decryption error of a wrong passphrase in corpus 8183 With a fresh key and IV each run, Node's OpenSSL now and then accepts the padding of a wrong-passphrase decryption and rejects the decrypted structure instead: ERR_OSSL_UNSUPPORTED rather than ERR_OSSL_BAD_DECRYPT, in about 1 run in 100. The program now prints both as one decryption error. --- tests/corpus/8183-key-passphrase-encoding.cjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/corpus/8183-key-passphrase-encoding.cjs b/tests/corpus/8183-key-passphrase-encoding.cjs index e52fa6c1c..ca817f386 100644 --- a/tests/corpus/8183-key-passphrase-encoding.cjs +++ b/tests/corpus/8183-key-passphrase-encoding.cjs @@ -9,12 +9,17 @@ const hex = Buffer.from(pass).toString("hex"); const pem = privateKey.export({ format: "pem", type: "pkcs8", cipher: "aes-256-cbc", passphrase: hex, encoding: "hex" }); const der = privateKey.export({ format: "der", type: "pkcs8", cipher: "aes-128-cbc", passphrase: pass }); +// A wrong passphrase usually fails the decryption's padding check +// (ERR_OSSL_BAD_DECRYPT); with a fresh key and IV each run, Node's +// OpenSSL now and then accepts the padding and rejects the decrypted +// structure instead (ERR_OSSL_UNSUPPORTED). Both are reported as one. function attempt(label, options) { try { const key = c.createPrivateKey(options); console.log(label, key.asymmetricKeyType, key.equals(privateKey)); } catch (e) { - console.log(label, "throws", e.code); + const wrong = e.code === "ERR_OSSL_BAD_DECRYPT" || e.code === "ERR_OSSL_UNSUPPORTED"; + console.log(label, "throws", wrong ? "a decryption error" : e.code); } } From a2be7d8bc03ca9be9a3e1f941bbc4d759e449bd3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 12:05:14 -0700 Subject: [PATCH 261/395] Answer Object.prototype's methods on typed records Records inherit toString, toLocaleString, valueOf, hasOwnProperty, isPrototypeOf and propertyIsEnumerable. Calls of these methods by name run an own callable entry when the record's type allows one and the record holds it, and otherwise the built-in with the record as receiver; toLocaleString, valueOf and isPrototypeOf on records were refused, and toString ignored an own entry. Keyed reads of records of functions answer the built-in as a native function value when the key misses: bound to the record when called at once, with an undefined `this` otherwise. The backend fuses the read with its fallback, so hits cost nothing extra. Records of other value types keep reading undefined for these names; the limitations page says so. Co-Authored-By: Claude Opus 5.5 --- .../record-identity-and-property-order.mdx | 7 + .../compiler/src/backend/llvm/expr-control.ts | 22 +- .../compiler/src/backend/llvm/expr-records.ts | 40 +- .../lowering/expressions/element-access.ts | 13 +- .../lowering/expressions/property-access.ts | 7 + .../src/frontend/lowering/lower-calls.ts | 4 + .../src/frontend/lowering/record-inherited.ts | 815 ++++++++++++++++++ ...8360-record-prototype-method-calls.ts.json | 6 + ...61-record-function-table-inherited.ts.json | 6 + .../8360-record-prototype-method-calls.ts | 56 ++ .../8361-record-function-table-inherited.ts | 40 + ...8360-record-prototype-method-calls.ts.json | 1 + ...61-record-function-table-inherited.ts.json | 1 + 13 files changed, 1014 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/record-inherited.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json create mode 100644 tests/corpus/8360-record-prototype-method-calls.ts create mode 100644 tests/corpus/8361-record-function-table-inherited.ts create mode 100644 tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json create mode 100644 tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json diff --git a/docs/content/limitations/record-identity-and-property-order.mdx b/docs/content/limitations/record-identity-and-property-order.mdx index dd180cb7b..fc0b2be92 100644 --- a/docs/content/limitations/record-identity-and-property-order.mdx +++ b/docs/content/limitations/record-identity-and-property-order.mdx @@ -25,3 +25,10 @@ Index-signature records list declared fields before the other keys. Optional fields and properties added through a record's `unknown` view can be deleted. A required field has no absent state, so deleting it through the view throws `TypeError: Cannot delete property 'x' of #` and leaves the record unchanged, where Node.js removes the property. + +Records inherit `toString`, `toLocaleString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf` and `propertyIsEnumerable` from `Object.prototype`. +A call of one of these methods by name runs the built-in with the record as its receiver, unless the record holds an own function under that name. +A keyed read such as `handlers[name]` of a record whose values are functions answers the built-in when the record lacks the key, so truthiness, `typeof` and calls match Node.js. +A keyed read of a record whose values are not functions, such as `counts["toString"]` on a `Record`, answers `undefined` where Node.js answers the inherited function, so `counts[word] ?? 0` counts such a word as new. +Calling a built-in read from a record of functions throws a `TypeError` when its answer does not fit the record's function type, for example `valueOf` read from a `Record number>`. +Other inherited names, such as `constructor`, read `undefined` from typed records. diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 2f7c31813..5ffa54eb5 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -2,11 +2,12 @@ import { emitBorrowedInput } from "./borrowed-inputs.js"; /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { undefinedArmTag } from "../../ir/analysis.js"; -import { isRefCounted, isUnitType, typeEquals, type IrExpr } from "../../ir/ir.js"; +import { isRefCounted, isUnitType, typeEquals, type IrExpr, type IrType } from "../../ir/ir.js"; import { DYN_KIND } from "./dyn.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; import { f64Lit } from "./common.js"; +import { emitRecordKeyGetOr } from "./expr-records.js"; import { NULLABLE_NULL, SHAPE_WORD_INDEX, shapeWordHeader } from "./nullable-unions.js"; import { emitUnionTag } from "./union-repr.js"; @@ -397,6 +398,13 @@ export function emitControlExpr( throw new InternalCompilerError("llvm emitter bug: nullish union lacks unit arms"); const fused = emitNullishOptionalChain(host, e); if (fused) return fused; + if ( + e.left.kind === "recordKeyGet" && + e.left.missNaN !== true && + typeEquals(e.type, e.left.type) && + storesNoUnit(host, e.left) + ) + return emitRecordKeyGetOr(host, e.left, e.right); if ( !typeEquals(e.type, e.left.type) && (e.left.kind === "varRef" || @@ -700,3 +708,15 @@ function emitUnionTagSet(host: LlvmEmitterContext, set: TagSet): string { B.line(`${t} = phi i1 [ true, %${yes} ], [ false, %${no} ]`); return t; } + +/** Every value a keyed read can hit is present (no stored undefined or + * null), so only a missing key yields the read's unit arm. */ +function storesNoUnit(host: LlvmEmitterContext, e: ExprOf<"recordKeyGet">): boolean { + const shape = host.recordShape(e.shapeId); + const holdsUnit = (t: IrType): boolean => + isUnitType(t) || + t.kind === "dyn" || + (t.kind === "union" && (host.unionsById.get(t.unionId)?.arms ?? []).some(isUnitType)); + if (shape.indexValue !== undefined && holdsUnit(shape.indexValue)) return false; + return e.overflowOnly === true || !shape.fields.some((f) => holdsUnit(f.type)); +} diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..86f20fd45 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -338,6 +338,39 @@ export function emitRegexIntrinsic( } } +/** `r[k] ?? d` in its pass-through shape: a missing key evaluates `d` + * where the read would store undefined, so a hit costs nothing extra. */ +export function emitRecordKeyGetOr( + host: LlvmEmitterContext, + e: IrExpr & { kind: "recordKeyGet" }, + fallback: IrExpr, +): LlValue { + const B = host.B; + const obj = host.emitExpr(e.obj); + const k = host.emitExpr(e.key); + const ty = host.llType(e.type); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ${ty}`); + const join = B.newLabel("rkg.j"); + keyedRecordReadInto( + host, + slot, + join, + obj.name, + k.name, + e.shapeId, + e.type, + e.overflowOnly === true, + e.loc, + false, + () => host.emitBranchInto(slot, fallback), + ); + B.startBlock(join); + const t = B.tmp(); + B.line(`${t} = load ${ty}, ptr ${slot}`); + return host.own({ name: t, type: e.type }); +} + export function emitRecordKeyGet( host: LlvmEmitterContext, e: IrExpr & { kind: "recordKeyGet" }, @@ -485,6 +518,7 @@ export function keyedRecordReadInto( overflowOnly: boolean, loc?: SrcLoc, missNaN = false, + missInto: (() => void) | null = null, ): void { const B = host.B; const shape = host.recordShape(shapeId); @@ -687,8 +721,10 @@ export function keyedRecordReadInto( } } if (def !== null && resultType.kind === "union" && undefTag >= 0) { - // The miss path: the result union's undefined arm. - B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); + // The miss path: the result union's undefined arm, or the caller's + // own value for a miss (missInto stores it into the slot). + if (missInto !== null) missInto(); + else B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); B.br(join); return; } diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index a2f2ebdb6..565d0f843 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -69,6 +69,7 @@ import { InternalCompilerError } from "../../../errors.js"; import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../../ir/build.js"; import { npmStaticPackageOfPath } from "../../npm-static.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; +import { withInheritedMethods } from "../record-inherited.js"; import { lowerClassPrototypeComputedAssignment } from "../class-prototypes.js"; import { isClassCallback } from "../class-callbacks.js"; import { isKeyedFieldAccess } from "./keyed-field-targets.js"; @@ -1444,7 +1445,17 @@ export function lowerRecordKeyRead( loc, }; if (nanCoded) return read; - return includeUndefined || optionalRead ? read : lowerer.maybeNarrow(read, expr); + const answered = withInheritedMethods(lowerer, read, litKey, isCalledAccess(expr)); + return includeUndefined || optionalRead ? answered : lowerer.maybeNarrow(answered, expr); +} + +/** The access is the callee of a call (`o[k](…)`, `o[k]?.(…)`, `o[k]!(…)`), + * which passes the object as `this`. */ +function isCalledAccess(node: ts.Expression): boolean { + let child: ts.Expression = node; + while (ts.isParenthesizedExpression(child.parent) || ts.isNonNullExpression(child.parent)) + child = child.parent; + return ts.isCallExpression(child.parent) && child.parent.expression === child; } /** ToPropertyKey for represented record keys. Missing array-derived string diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 35bb16cd3..f213b6cbf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -115,6 +115,7 @@ import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../. import { typedArrayLacksMember } from "../builtins/typed-array-members.js"; import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; import { tryLowerExpression } from "./try-lower-expression.js"; +import { lowerRecordInheritedMember } from "../record-inherited.js"; import { DATE_METHOD_HINT } from "../builtins/dates.js"; import { cjsClassExprWholeExportOf, @@ -3188,6 +3189,12 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi loc: locOf(expr), }; } + // Object.prototype's methods read as values: an own entry of the + // member's function type, else the built-in. + if (actual?.type.kind === "record") { + const inherited = lowerRecordInheritedMember(lowerer, actual, expr); + if (inherited) return inherited; + } // A member the checker declares on the receiver, stored in a pure // index-signature record (an object literal that spread one, see // dictSpreadLiteralShape): the key is always present, so the keyed diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c8f02562e..aeb1993d4 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -280,6 +280,7 @@ import { } from "./generic-functions.js"; import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; +import { lowerRecordInheritedCall } from "./record-inherited.js"; const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); @@ -1643,6 +1644,9 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // Union receivers whose every arm has a text — the ngrok // `(chunk: Buffer | string) => chunk.toString()` idiom. lowerUnionToStringCall(lowerer, expr, expr.expression) ?? + // Object.prototype's methods called by name on records: an own + // callable entry, else the built-in with the record as receiver. + lowerRecordInheritedCall(lowerer, expr, expr.expression) ?? // Object.prototype.toString's default answer on records and // override-free program classes — "[object Object]", folded. lowerDefaultToStringCall(lowerer, expr, expr.expression) ?? diff --git a/packages/compiler/src/frontend/lowering/record-inherited.ts b/packages/compiler/src/frontend/lowering/record-inherited.ts new file mode 100644 index 000000000..2c01ccd0a --- /dev/null +++ b/packages/compiler/src/frontend/lowering/record-inherited.ts @@ -0,0 +1,815 @@ +/* Object.prototype's methods on typed records. + * + * A record is an ordinary object, so `r.toString()`, `r.hasOwnProperty(k)` + * and the other Object.prototype methods answer from its prototype unless + * the record holds an own entry under that name. Method calls with a + * constant name call an own entry when the record's type lets one be + * callable and otherwise run the built-in natively with the record as its + * receiver. Keyed reads of records whose values are functions or checked + * values answer the built-in as a native function value on the miss path, + * so truthiness, typeof and calls behave as in Node; hits are untouched. + * Records of other value types keep reading undefined for these names + * (a documented limitation). No dynamic-tier unit is involved: every + * built-in is a small lifted function over the record. */ +import * as ts from "../ts7/adapter.js"; +import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; +import { + BOOL, + DYN, + STRING, + UNDEFINED_T, + isUnitType, + typeEquals, + typeKey, + type IrExpr, + type IrLocal, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { locOf } from "../program.js"; +import { keyedHelperName } from "./stable-names.js"; +import { isSafeToRepeat } from "./expressions/evaluation-safety.js"; +import { nullProtoRecordShape, recordNullProtoTest } from "./null-prototype.js"; +import { recordHasOwnHelper } from "./lower-calls.js"; +import { OTHER_ARM_FAILURE_MESSAGE } from "./coercions/unions.js"; + +/** Object.prototype's methods a record answers from its prototype. */ +export const RECORD_INHERITED_METHODS: ReadonlySet = new Set([ + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", +]); + +type FuncType = IrType & { kind: "func" }; + +/** Declares a hidden local in the function being built: the lowerer's + * current function, or a lifted helper's own local list. */ +type DeclareLocal = (name: string, type: IrType) => string; + +function liftedLocals(): { declare: DeclareLocal; locals: IrLocal[] } { + const locals: IrLocal[] = []; + return { + locals, + declare: (name, type) => { + const id = `${name}.${locals.length}`; + locals.push({ id, name, type, mutable: false }); + return id; + }, + }; +} + +/** The form a read of `type` gives a built-in method value: the union's one + * function arm (beside an undefined arm), a checked value, or null when the + * type cannot hold a function. */ +function methodValueForm(lowerer: Lowerer, type: IrType): FuncType | "dyn" | null { + if (type.kind === "dyn") return "dyn"; + if (type.kind !== "union") return null; + const arms = lowerer.unions.get(type.unionId)?.arms ?? []; + if (!arms.some((arm) => arm.kind === "undefinedT")) return null; + const funcs = arms.filter((arm): arm is FuncType => arm.kind === "func"); + if (funcs.length !== 1 || funcs[0]!.restAbi !== undefined) return null; + return funcs[0]!; +} + +/** A value of `type` may be a function that a call can enter. */ +function callableType(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "func" || type.kind === "dyn") return true; + if (type.kind !== "union") return false; + return (lowerer.unions.get(type.unionId)?.arms ?? []).some( + (arm) => arm.kind === "func" || arm.kind === "dyn", + ); +} + +/** A keyed record read (`r[k]`, `r["toString"]`) whose miss answers the + * inherited Object.prototype method for those names. Hits keep the + * ordinary read; only the miss path tests the key. `bound`: the read is + * the callee of a call (`r[k](…)`, `r[k]?.(…)`), whose `this` is the + * record; any other read yields the method as a plain value, whose calls + * see an undefined `this` as in Node. */ +export function withInheritedMethods( + lowerer: Lowerer, + read: IrExpr & { kind: "recordKeyGet" }, + litKey: string | null, + bound: boolean, +): IrExpr { + if (litKey !== null && !RECORD_INHERITED_METHODS.has(litKey)) return read; + if (read.missNaN === true) return read; + const form = methodValueForm(lowerer, read.type); + const shape = lowerer.shapes.get(read.shapeId); + if (form === null || !shape || shape.tuple) return read; + const loc = read.loc; + const stmts: IrStmt[] = []; + const stable = (value: IrExpr, name: string): IrExpr => { + if (isSafeToRepeat(value) || value.kind === "strLit") return value; + const local = lowerer.declareHiddenLocal(name, value.type); + stmts.push({ kind: "varDecl", localId: local.id, init: value, loc }); + return varRef(local.id, value.type, loc); + }; + const obj = stable(read.obj, "%inheritedRecv"); + const key = stable(read.key, "%inheritedKey"); + const helper = inheritedReadHelper(lowerer, read.shapeId, read.type, form, bound, loc); + const fallback: IrExpr = { kind: "call", callee: helper, args: [obj, key], type: read.type, loc }; + if ( + form !== "dyn" && + read.type.kind === "union" && + !(lowerer.unions.get(read.type.unionId)?.arms ?? []).some((arm) => arm.kind === "nullT") + ) { + // `r[k] ?? inherited(r, k)`: the hit is the read's own value, untouched. + const coalesced: IrExpr = { + kind: "nullish", + left: { ...read, obj, key }, + right: fallback, + type: read.type, + loc, + }; + return stmts.length === 0 + ? coalesced + : { kind: "seqExpr", stmts, result: coalesced, type: read.type, loc }; + } + const value = lowerer.declareHiddenLocal("%inheritedRead", read.type); + stmts.push({ kind: "varDecl", localId: value.id, init: { ...read, obj, key }, loc }); + const found = varRef(value.id, read.type, loc); + let missing: IrExpr; + if (form === "dyn") { + missing = { kind: "dynTest", test: "undefined", value: found, type: BOOL, loc } as IrExpr; + } else { + const unionId = (read.type as IrType & { kind: "union" }).unionId; + const tag = (lowerer.unions.get(unionId)?.arms ?? []).findIndex( + (arm) => arm.kind === "undefinedT", + ); + missing = { kind: "unionIsTag", unionId, tag, negated: false, value: found, type: BOOL, loc }; + } + return { + kind: "seqExpr", + stmts, + result: { + kind: "ternary", + cond: missing, + then: fallback, + else_: found, + type: read.type, + loc, + }, + type: read.type, + loc, + }; +} + +/** `%rec.inherited.(o, k)`: the inherited method `k` names as a value + * of `readType` (bound to `o`), or undefined. */ +function inheritedReadHelper( + lowerer: Lowerer, + shapeId: string, + readType: IrType, + form: FuncType | "dyn", + bound: boolean, + loc: SrcLoc, +): string { + const key = `rec.inherited:${bound ? "bound" : "value"}:${shapeId}:${typeKey(readType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%rec.inherited", key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + const o = varRef("o.0", recT, loc); + const k = varRef("k.0", STRING, loc); + const fnType: FuncType = + form === "dyn" ? ({ kind: "func", params: [DYN], ret: DYN } as FuncType) : form; + const undefinedValue: IrExpr = + form === "dyn" + ? dynUndefinedExpr(loc) + : lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + readType, + ); + const named = (method: string): IrExpr => ({ + kind: "strEq", + negated: false, + left: k, + right: { kind: "strLit", value: method, type: STRING, loc }, + type: BOOL, + loc, + }); + const methods = [...RECORD_INHERITED_METHODS]; + let anyName: IrExpr = named(methods[0]!); + for (const method of methods.slice(1)) + anyName = { kind: "logical", op: "||", left: anyName, right: named(method), type: BOOL, loc }; + const body: IrStmt[] = [ + // Most misses name no inherited method: one comparison chain, nothing else. + { + kind: "if", + cond: { kind: "unary", op: "!", operand: anyName, type: BOOL, loc }, + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }, + // An own key holding undefined shadows the prototype like any own key. + { + kind: "if", + cond: { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, k], + type: BOOL, + loc, + }, + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }, + ]; + if (nullProtoRecordShape(lowerer, recT)) + body.push({ + kind: "if", + cond: recordNullProtoTest(o, shapeId, loc), + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }); + for (const method of methods) { + const value: IrExpr = bound + ? { + kind: "call", + callee: boundMethodFactory(lowerer, shapeId, method, fnType, loc), + args: [o], + type: fnType, + loc, + } + : { + kind: "closure", + fnName: unboundMethodImpl(lowerer, method, fnType, loc), + captures: [], + type: fnType, + loc, + }; + body.push({ + kind: "if", + cond: named(method), + then: [ + { + kind: "return", + value: lowerer.coerceToExpected(value, form === "dyn" ? DYN : readType), + loc, + }, + ], + else_: null, + loc, + }); + } + body.push({ kind: "return", value: { ...undefinedValue }, loc }); + lowerer.liftedFns.push({ + name, + params: [ + { localId: "o.0", name: "o", type: recT }, + { localId: "k.0", name: "k", type: STRING }, + ], + returnType: readType, + locals: [ + { id: "o.0", name: "o", type: recT, mutable: false }, + { id: "k.0", name: "k", type: STRING, mutable: false }, + ], + body, + loc, + }); + return name; +} + +/** `%rec.bind.(o)`: the built-in `method` bound to `o` as a closure. */ +function boundMethodFactory( + lowerer: Lowerer, + shapeId: string, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.bind:${shapeId}:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%rec.bind", key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + lowerer.liftedFns.push({ + name, + params: [{ localId: "o.0", name: "o", type: recT }], + returnType: fnType, + locals: [{ id: "o.0", name: "o", type: recT, mutable: false, boxed: true }], + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: inheritedMethodImpl(lowerer, shapeId, method, fnType, loc), + captures: ["o.0"], + type: fnType, + loc, + }, + loc, + }, + ], + loc, + }); + return name; +} + +/** The lifted body of one built-in bound to its record, with the read's + * function signature: the built-in's answer converted to the signature's + * return type, or the checked-conversion TypeError where it cannot be. */ +function inheritedMethodImpl( + lowerer: Lowerer, + shapeId: string, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.inheritedImpl:${shapeId}:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, `%rec.${method}`, key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + const o = varRef("o.0", recT, loc); + const params = fnType.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + const args = params.map((p) => varRef(p.localId, p.type, loc)); + const body: IrStmt[] = []; + const hidden = liftedLocals(); + const answer = builtinAnswer(lowerer, hidden.declare, o, shapeId, method, args, fnType.ret, loc); + body.push(...returnAs(lowerer, answer, fnType.ret, loc)); + lowerer.liftedFns.push({ + name, + params, + returnType: fnType.ret, + captures: [{ localId: "o.0", name: "o", type: recT }], + locals: [ + { id: "o.0", name: "o", type: recT, mutable: false, boxed: true }, + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...hidden.locals, + ], + body, + loc, + }); + return name; +} + +/** One built-in as a plain function value (no receiver): its answer for an + * undefined `this`, converted like inheritedMethodImpl's. */ +function unboundMethodImpl( + lowerer: Lowerer, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.unboundImpl:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, `%obj.${method}`, key); + lowerer.arrHofHelpers.set(key, name); + const params = fnType.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + const arg: IrExpr | undefined = params[0] && varRef(params[0].localId, params[0].type, loc); + const coerce = "Cannot convert undefined or null to object"; + let answer: IrExpr; + switch (method) { + case "toString": + answer = { kind: "strLit", value: "[object Undefined]", type: STRING, loc }; + break; + case "toLocaleString": + answer = nodeThrowExpr( + 1, + "", + "Object.prototype.toLocaleString called on null or undefined", + STRING, + loc, + ); + break; + case "isPrototypeOf": { + // A primitive argument answers false before `this` converts. + const no: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; + const kind = arg?.type.kind; + if ( + arg === undefined || + isUnitType(arg.type) || + kind === "string" || + kind === "f64" || + kind === "bool" || + kind === "bigint" + ) + answer = no; + else if (kind === "dyn") + answer = { + kind: "ternary", + cond: { + kind: "logical", + op: "||", + left: { kind: "dynTest", test: "object", value: arg, type: BOOL, loc }, + right: { kind: "dynTest", test: "function", value: arg, type: BOOL, loc }, + type: BOOL, + loc, + } as IrExpr, + then: nodeThrowExpr(1, "", coerce, BOOL, loc), + else_: no, + type: BOOL, + loc, + }; + else answer = nodeThrowExpr(1, "", coerce, BOOL, loc); + break; + } + default: + answer = nodeThrowExpr(1, "", coerce, method === "valueOf" ? DYN : BOOL, loc); + } + const body: IrStmt[] = + answer.kind === "libCall" && answer.fn === "error.nodeThrow" + ? [ + { + kind: "return", + value: nodeThrowExpr( + 1, + "", + (answer.args[2] as IrExpr & { kind: "strLit" }).value, + fnType.ret, + loc, + ), + loc, + }, + ] + : returnAs(lowerer, answer, fnType.ret, loc); + lowerer.liftedFns.push({ + name, + params, + returnType: fnType.ret, + locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + body, + loc, + }); + return name; +} + +/** The built-in's answer with `o` as its receiver. `want` is the type the + * caller needs (valueOf answers the record itself). */ +function builtinAnswer( + lowerer: Lowerer, + declare: DeclareLocal, + o: IrExpr, + shapeId: string, + method: string, + args: readonly IrExpr[], + want: IrType, + loc: SrcLoc, +): IrExpr { + switch (method) { + case "toString": + return { kind: "strLit", value: "[object Object]", type: STRING, loc }; + case "toLocaleString": + // Object.prototype.toLocaleString calls this.toString(). + return callOwnOrBuiltin( + lowerer, + declare, + o, + shapeId, + "toString", + [], + STRING, + "toString", + loc, + ); + case "valueOf": + return want.kind === "dyn" ? lowerer.coerceToExpected(o, DYN) : o; + case "isPrototypeOf": + // No object is created with a typed record as its prototype. + return { kind: "boolLit", value: false, type: BOOL, loc }; + default: { + const key = propertyKeyOf(lowerer, args[0], loc); + return { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, key], + type: BOOL, + loc, + }; + } + } +} + +/** ToPropertyKey of a built-in's key argument (absent: "undefined"). */ +function propertyKeyOf(lowerer: Lowerer, arg: IrExpr | undefined, loc: SrcLoc): IrExpr { + if (arg === undefined || isUnitType(arg.type)) + return { + kind: "strLit", + value: arg?.type.kind === "nullT" ? "null" : "undefined", + type: STRING, + loc, + }; + if (arg.type.kind === "string") return arg; + if (arg.type.kind === "f64" || arg.type.kind === "bool") + return { kind: "toString", operand: arg, type: STRING, loc }; + return { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [lowerer.coerceToExpected(arg, DYN)], + type: STRING, + loc, + }; +} + +/** `value` returned as `ret`: as is, wrapped into a union arm or a checked + * value, discarded for void, or the checked-conversion TypeError. */ +function returnAs(lowerer: Lowerer, value: IrExpr, ret: IrType, loc: SrcLoc): IrStmt[] { + const converted = convertTo(lowerer, value, ret); + if (converted !== null) return [{ kind: "return", value: converted, loc }]; + if (ret.kind === "void") + return [ + { kind: "exprStmt", expr: value, loc }, + { kind: "return", value: null, loc }, + ]; + return [ + { kind: "exprStmt", expr: value, loc }, + { kind: "return", value: nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, ret, loc), loc }, + ]; +} + +function convertTo(lowerer: Lowerer, value: IrExpr, want: IrType): IrExpr | null { + if (typeEquals(value.type, want)) return value; + if (want.kind === "dyn" && lowerer.dynConvertible(value.type)) + return lowerer.coerceToExpected(value, DYN); + if (want.kind === "union" && lowerer.armTag(want.unionId, value.type) >= 0) + return lowerer.coerceToExpected(value, want); + return null; +} + +/** `o.method(...args)` on a record: an own callable entry when the record + * holds one, else the built-in. `calleeText` names the callee for Node's + * "is not a function". */ +function callOwnOrBuiltin( + lowerer: Lowerer, + declare: DeclareLocal, + o: IrExpr, + shapeId: string, + method: string, + args: readonly IrExpr[], + want: IrType, + calleeText: string, + loc: SrcLoc, +): IrExpr { + const shape = lowerer.shapes.get(shapeId)!; + const field = shape.fields.find((f) => f.name === method); + const ownType = field ? field.type : shape.indexValue; + let builtin = builtinAnswer(lowerer, declare, o, shapeId, method, args, want, loc); + const recT: IrType = { kind: "record", shapeId }; + if (nullProtoRecordShape(lowerer, recT)) + builtin = { + kind: "ternary", + cond: recordNullProtoTest(o, shapeId, loc), + then: nodeThrowExpr(1, "", `${calleeText} is not a function`, builtin.type, loc), + else_: builtin, + type: builtin.type, + loc, + }; + const builtinAsWant = convertTo(lowerer, builtin, want) ?? builtin; + if (ownType === undefined || !callableType(lowerer, ownType)) return builtinAsWant; + const own: IrExpr = field + ? { kind: "recordGet", obj: o, shapeId, field: method, type: field.type, loc } + : { + kind: "recordKeyGet", + obj: o, + shapeId, + key: { kind: "strLit", value: method, type: STRING, loc }, + overflowOnly: true, + type: lowerer.withUndefinedArmOf(shape.indexValue!) ?? shape.indexValue!, + loc, + }; + const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); + return { + kind: "ternary", + cond: { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, { kind: "strLit", value: method, type: STRING, loc }], + type: BOOL, + loc, + }, + then: called, + else_: builtinAsWant, + type: builtinAsWant.type, + loc, + }; +} + +/** Call the own entry `own` (present, of any stored type) with `args`. A + * non-function throws Node's "is not a function"; a function whose + * parameters or result cannot carry the call throws the checked-conversion + * TypeError (the program's types claimed otherwise). */ +function callOwn( + lowerer: Lowerer, + declare: DeclareLocal, + own: IrExpr, + args: readonly IrExpr[], + want: IrType, + calleeText: string, + loc: SrcLoc, +): IrExpr { + const notFunction = nodeThrowExpr(1, "", `${calleeText} is not a function`, want, loc); + const lie = nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, want, loc); + const localId = declare("%ownMethod", own.type); + const value = varRef(localId, own.type, loc); + const enter = (fn: IrExpr): IrExpr => { + if (fn.type.kind === "dyn") { + const result: IrExpr = { + kind: "dynCall", + callee: fn, + calleeName: calleeText, + args: args.map((a) => lowerer.coerceToExpected(a, DYN)), + type: DYN, + loc, + } as IrExpr; + return convertTo(lowerer, result, want) ?? sequence(result, lie, loc); + } + const sig = fn.type as FuncType; + if (sig.restAbi !== undefined) return lie; + const passed: IrExpr[] = []; + for (let i = 0; i < sig.params.length; i++) { + const param = sig.params[i]!; + const arg = args[i]; + const given = + arg === undefined + ? convertTo( + lowerer, + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + param, + ) + : convertTo(lowerer, arg, param); + if (given === null) return lie; + passed.push(given); + } + const result: IrExpr = { kind: "callValue", callee: fn, args: passed, type: sig.ret, loc }; + if (sig.ret.kind === "void") return sequence(result, lie, loc); + return convertTo(lowerer, result, want) ?? sequence(result, lie, loc); + }; + let call: IrExpr; + if (own.type.kind === "func" || own.type.kind === "dyn") call = enter(value); + else if (own.type.kind === "union") { + const unionId = own.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + call = notFunction; + for (let tag = arms.length - 1; tag >= 0; tag--) { + const arm = arms[tag]!; + if (arm.kind !== "func" && arm.kind !== "dyn") continue; + call = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value, type: BOOL, loc }, + then: enter({ kind: "unionNarrow", unionId, tag, value, type: arm, loc }), + else_: call, + type: want, + loc, + }; + } + } else call = notFunction; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId, init: own, loc }], + result: call, + type: want, + loc, + }; +} + +function sequence(first: IrExpr, result: IrExpr, loc: SrcLoc): IrExpr { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: first, loc }], + result, + type: result.type, + loc, + }; +} + +/** `r.toString()`, `r.hasOwnProperty(k)` and the other Object.prototype + * methods called by name on a typed record (the checker resolved the + * member to Object.prototype's). Null for other calls. */ +export function lowerRecordInheritedCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + const method = access.name.text; + if ( + call.questionDotToken || + access.questionDotToken || + !RECORD_INHERITED_METHODS.has(method) || + call.arguments.nodes.some(ts.isSpreadElement) || + !lowerer.isStdlibMember(access) + ) + return null; + const recvT = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (recvT?.kind !== "record") return null; + const shape = lowerer.shapes.get(recvT.shapeId); + if (!shape || shape.tuple) return null; + const want = + method === "valueOf" + ? (lowerer.mapTypeOf(lowerer.typeOf(call)) ?? recvT) + : method === "toString" || method === "toLocaleString" + ? STRING + : BOOL; + // Without a possible own function, toString and the own-key tests keep + // their existing lowerings (with the special record views they know). + if ( + (method === "hasOwnProperty" || method === "propertyIsEnumerable" || method === "toString") && + !shape.fields.some((f) => f.name === method && callableType(lowerer, f.type)) && + !(shape.indexValue && callableType(lowerer, shape.indexValue)) + ) + return null; + const loc = locOf(call); + const stmts: IrStmt[] = []; + const receiver = lowerer.lowerExpr(access.expression); + if (receiver.type.kind !== "record") return null; + const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); + stmts.push({ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }); + const o = varRef(recvLocal.id, receiver.type, loc); + const args: IrExpr[] = []; + for (const node of call.arguments.nodes) { + const arg = lowerer.lowerExpr(node); + const local = lowerer.declareHiddenLocal("%protoArg", arg.type); + stmts.push({ kind: "varDecl", localId: local.id, init: arg, loc }); + args.push(varRef(local.id, arg.type, loc)); + } + const result = callOwnOrBuiltin( + lowerer, + (name, type) => lowerer.declareHiddenLocal(name, type).id, + o, + receiver.type.shapeId, + method, + args, + want, + ts.getTextOfNode(access), + loc, + ); + return { kind: "seqExpr", stmts, result, type: result.type, loc }; +} + +/** `r.toString` and the other Object.prototype methods read by name (not + * called) on an index-signature record: an own entry of the member's + * function type when the record holds one, else the built-in as a plain + * function value. Null when the checker's member type is not a function. */ +export function lowerRecordInheritedMember( + lowerer: Lowerer, + receiver: IrExpr, + expr: ts.PropertyAccessExpression, +): IrExpr | null { + const method = expr.name.text; + if (!RECORD_INHERITED_METHODS.has(method) || !lowerer.isStdlibMember(expr)) return null; + if (receiver.type.kind !== "record") return null; + const want = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (want?.kind !== "func" || want.restAbi !== undefined) return null; + const shapeId = receiver.type.shapeId; + const shape = lowerer.shapes.get(shapeId); + if (!shape || shape.tuple) return null; + const loc = locOf(expr); + const builtin: IrExpr = { + kind: "closure", + fnName: unboundMethodImpl(lowerer, method, want, loc), + captures: [], + type: want, + loc, + }; + const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); + const o = varRef(recvLocal.id, receiver.type, loc); + const stmts: IrStmt[] = [{ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }]; + let result: IrExpr = builtin; + if (shape.indexValue && typeEquals(shape.indexValue, want)) { + const ownType = lowerer.withUndefinedArmOf(want); + if (ownType?.kind === "union") { + const read: IrExpr = { + kind: "recordKeyGet", + obj: o, + shapeId, + key: { kind: "strLit", value: method, type: STRING, loc }, + overflowOnly: true, + type: ownType, + loc, + }; + const tag = lowerer.armTag(ownType.unionId, want); + const ownLocal = lowerer.declareHiddenLocal("%ownMethod", ownType); + const own = varRef(ownLocal.id, ownType, loc); + stmts.push({ kind: "varDecl", localId: ownLocal.id, init: read, loc }); + result = { + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId: ownType.unionId, + tag, + negated: false, + value: own, + type: BOOL, + loc, + }, + then: { kind: "unionNarrow", unionId: ownType.unionId, tag, value: own, type: want, loc }, + else_: builtin, + type: want, + loc, + }; + } + } + return { kind: "seqExpr", stmts, result, type: want, loc }; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json new file mode 100644 index 000000000..2bbaaaa88 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8360-record-prototype-method-calls.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json new file mode 100644 index 000000000..a9d8bf4b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8361-record-function-table-inherited.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8360-record-prototype-method-calls.ts b/tests/corpus/8360-record-prototype-method-calls.ts new file mode 100644 index 000000000..980e4b5d2 --- /dev/null +++ b/tests/corpus/8360-record-prototype-method-calls.ts @@ -0,0 +1,56 @@ +// Object.prototype's methods called by name on typed records: the built-in +// with the record as its receiver, or an own entry that shadows it. +interface Point { + x: number; + y: number; +} +const counts: Record = { apple: 2, pear: 0 }; +const point: Point = { x: 1, y: 2 }; +const handlers: Record number> = { double: (n) => n * 2 }; +const own: Record string> = { + toString: () => "own toString", + valueOf: () => "own valueOf", +}; + +function line(...parts: unknown[]): void { + console.log(parts.map(String).join(" | ")); +} + +line("toString", counts.toString(), point.toString(), handlers.toString(), String(counts)); +line("toLocaleString", counts.toLocaleString(), point.toLocaleString()); +line("valueOf", counts.valueOf() === counts, point.valueOf() === point); +line( + "hasOwnProperty", + counts.hasOwnProperty("apple"), + counts.hasOwnProperty("pear"), + counts.hasOwnProperty("plum"), + counts.hasOwnProperty("toString"), + point.hasOwnProperty("x"), + point.hasOwnProperty("z"), + handlers.hasOwnProperty("double"), + handlers.hasOwnProperty("valueOf"), +); +line("propertyIsEnumerable", counts.propertyIsEnumerable("apple"), point.propertyIsEnumerable("toString")); +line("isPrototypeOf", counts.isPrototypeOf(point), point.isPrototypeOf(counts)); + +// Own entries shadow the built-ins; toLocaleString calls the own toString. +line("own", own.toString(), own.toLocaleString(), own.valueOf(), own.hasOwnProperty("toString")); +delete own["toString"]; +line("own deleted", own.toString(), own.toLocaleString()); +own.toString = () => "own again"; +line("own again", own.toString(), `${String(own.toString())}`); + +// Keys added at run time are own keys too. +const later: Record = {}; +for (const word of "toString valueOf plain toString".split(" ")) { + later[word] = (later.hasOwnProperty(word) ? later[word]! : 0) + 1; +} +line("later", JSON.stringify(later), later.hasOwnProperty("toString"), later.hasOwnProperty("constructor")); + +// A null-prototype record has no inherited methods. +const groups = Object.groupBy([1, 2, 3], (n): string => (n % 2 ? "odd" : "even")); +try { + line("groups", groups.toString()); +} catch (e) { + line("groups threw", (e as Error).name + ": " + (e as Error).message); +} diff --git a/tests/corpus/8361-record-function-table-inherited.ts b/tests/corpus/8361-record-function-table-inherited.ts new file mode 100644 index 000000000..eecf74f02 --- /dev/null +++ b/tests/corpus/8361-record-function-table-inherited.ts @@ -0,0 +1,40 @@ +// Records of functions used as dispatch tables meet Object.prototype's method +// names: a keyed read answers the inherited method, bound to the record when +// the read is called at once and a plain function value otherwise. +type Command = (arg: string) => unknown; +const commands: Record = { + echo: (arg) => arg, + shout: (arg) => arg.toUpperCase(), +}; +const inputs = process.argv.length > 99 ? [] : ["echo", "toString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "shout", "nope"]; +function show(v: unknown): string { + return v === commands ? "" : typeof v + ":" + String(v); +} +for (const name of inputs) { + const handler = commands[name]; + if (!handler) { + console.log(name, "unknown handler"); + continue; + } + console.log(name, typeof handler, "direct", show(commands[name](name === "hasOwnProperty" ? "echo" : "x"))); + console.log(name, "optional", show(commands[name]?.("shout"))); + try { + console.log(name, "detached", show(handler("echo"))); + } catch (e) { + console.log(name, "detached threw", (e as Error).name + ": " + (e as Error).message); + } +} +console.log(typeof commands["toString"], typeof commands["valueOf"], commands["toString"] === undefined, commands["toString"] === commands["toString"]); +console.log(String(commands["toString"]("x")), commands["hasOwnProperty"]("shout"), commands["hasOwnProperty"]("nope")); +const literal: Record = {}; +console.log(Object.keys(literal).length, literal["isPrototypeOf"]?.("x"), typeof literal.toString); + +// A word count whose words are not Object.prototype names keeps `?? 0`. +const freq: Record = {}; +for (const w of "a rose is a rose is a rose".split(" ")) freq[w] = (freq[w] ?? 0) + 1; +console.log(JSON.stringify(freq), freq["tulip"] ?? 0); + +// Read by name without a call: a plain function value. +const counts: Record = { a: 1 }; +const named = counts.toString; +console.log(typeof counts.toString, typeof commands.valueOf, String(named())); diff --git a/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json b/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json new file mode 100644 index 000000000..1b2d59a51 --- /dev/null +++ b/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 3200, "san": 3900 } diff --git a/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json b/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json new file mode 100644 index 000000000..1b2d59a51 --- /dev/null +++ b/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json @@ -0,0 +1 @@ +{ "plain": 3200, "san": 3900 } From f4825e05a7ad5fc48d6bf188ba4d272ec6401d2a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:33:12 -0700 Subject: [PATCH 262/395] Replace a package's declaration files or supply its declarations with type patches A scriptc.typePatches entry can now replace a declaration file of the package (or of its @types twin, written @types//) with a local .d.ts ("files"), or give the package a declaration entry ("types"). A supplied entry answers the package's types file and the declarations TypeScript probes beside its JavaScript entries, and exists for the program host, the resolver and npm-static's untyped-package test, so a package without declarations becomes a typed package. Declaration patches still apply on top. A files target the installed copy does not ship is SC8031. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 19 +++ packages/compiler/src/frontend/npm-static.ts | 3 +- packages/compiler/src/frontend/resolve.ts | 4 +- .../compiler/src/frontend/ts7/program-host.ts | 4 +- .../src/frontend/type-patches/layer.ts | 157 +++++++++++++++--- packages/compiler/src/project-config.test.ts | 44 ++++- packages/compiler/src/project-config.ts | 68 +++++++- .../npm/cases/type-patch-files/main.ts.json | 25 +++ .../cases/type-patch-types-entry/main.ts.json | 16 ++ packages/compiler/test/type-patches.test.ts | 55 ++++++ .../node_modules/@effect/platform-node | 1 + tests/fixtures/effect4/node_modules/effect | 1 + .../npm/cases/type-patch-files/main.ts | 10 ++ .../node_modules/eventlog/dist/entry.d.ts | 8 + .../node_modules/eventlog/dist/index.d.ts | 4 + .../node_modules/eventlog/dist/index.js | 17 ++ .../node_modules/eventlog/package.json | 1 + .../npm/cases/type-patch-files/package.json | 13 ++ .../type-patch-files/type-patches/entry.d.ts | 7 + .../npm/cases/type-patch-types-entry/main.ts | 9 + .../node_modules/pointjs/lib/point.js | 17 ++ .../node_modules/pointjs/package.json | 1 + .../cases/type-patch-types-entry/package.json | 10 ++ .../type-patches/pointjs.d.ts | 4 + tests/harness/type-patches.test.ts | 48 +++++- 25 files changed, 513 insertions(+), 33 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json create mode 120000 tests/fixtures/effect4/node_modules/@effect/platform-node create mode 120000 tests/fixtures/effect4/node_modules/effect create mode 100644 tests/fixtures/npm/cases/type-patch-files/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-files/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index ec1eb0601..9b660c76f 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -172,6 +172,25 @@ Each key names a package and a semver range of the installed versions the patch A declaration is named by its path: `Options.name` for a member of a type, `parse()` for a function's return type, `parse(text)` for a parameter, `list()[]` for an array's elements, and `Env[string]` for an index signature. `export=()` names the return type of the function the module exports, `default` its default export, and a file prefix such as `index.js:parse()` names a declaration inside one of the package's files. One patch applies to the package's own declarations, its JSDoc, and its `@types/` package. The patched type then holds everywhere, so your code has to handle the `undefined` that a widened declaration allows. An optional `"note"` string can link the upstream issue. +For larger fixes, replace a whole declaration file, or give a package without declarations its own: + +```json:package.json +{ + "scriptc": { + "typePatches": { + "some-client@^3.2.0": { + "files": { "dist/types/options.d.ts": "./type-patches/options.d.ts" } + }, + "untyped-helper@1.x": { + "types": "./type-patches/untyped-helper.d.ts" + } + } + } +} +``` + +`files` maps a declaration file inside the package, or inside its `@types/` package written as `@types//`, to a local `.d.ts` file. `types` supplies the package's declaration entry. Paths of local files are relative to the `package.json`, and a replaced file can import the package's other declaration files with the same relative paths as the file it replaces. + Patches change only declarations, never the package's code, and values are still checked against the patched types. A build warns when a patch doesn't apply as written: when no range covers the installed version, when a declaration path matches nothing, when the declaration already has the type, or when a path is ambiguous. Report wrong declarations to the package too, so the patch can be removed later. ## Provenance sources (experimental) diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index ed442be79..8e26e3058 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -23,7 +23,7 @@ import { trackedRealpath, } from "./input-tracker.js"; import { npmBuiltinAliasOf } from "./builtin-modules.js"; -import { packageSourceText } from "./type-patches/layer.js"; +import { packageSourceText, typePatchSuppliesTypes } from "./type-patches/layer.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -374,6 +374,7 @@ function packageIsUntyped(path: string): boolean { /* no package.json — keep probing */ } if (untyped && trackedExists(`${pkgDir}/index.d.ts`)) untyped = false; + if (untyped && typePatchSuppliesTypes(pkgDir)) untyped = false; if (untyped) { // the @types twin, hoisted anywhere up the realm chain const mangled = mangledTypesName(dirName); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 5b035b6cb..86252499f 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -37,9 +37,11 @@ import { trackedRealpath, } from "./input-tracker.js"; import { isRelativeSpecifier, packageNameOfSpecifier } from "./workspace-registry.js"; +import { typePatchFileExists } from "./type-patches/layer.js"; function isFile(path: string): boolean { - return trackedFileExists(path); + // A declaration entry a project type patch supplies (type-patches/layer.ts). + return trackedFileExists(path) || typePatchFileExists(path); } function isDirectory(path: string): boolean { diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..303092acf 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -46,7 +46,7 @@ import { trackedRealpath, } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; -import { typePatchedText } from "../type-patches/layer.js"; +import { typePatchedText, typePatchFileExists } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; @@ -190,7 +190,7 @@ export class Ts7Host { fileExists: (fileName) => { if (virtualFiles.has(tsgoPath(fileName))) return true; if (shadow !== null && shadow.hideFile(fileName)) return false; - return trackedFileExists(fileName); + return trackedFileExists(fileName) || typePatchFileExists(fileName); }, directoryExists: (path) => trackedDirectoryExists(path), realpath: (path) => diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 48ef6ecf0..5fb41f968 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -27,7 +27,7 @@ import { dirname, relative } from "node:path"; import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; import { typePatchDiag } from "../../diagnostics/diagnostic.js"; import { readProjectConfig, type TypePatchEntry } from "../../project-config.js"; -import { trackedReadFile } from "../input-tracker.js"; +import { trackedFileExists, trackedReadFile } from "../input-tracker.js"; import type { SourceFile } from "../ts7/syntax.js"; import { applyPatchEdits, patchFile, type PatchMatch, type PatchRequest } from "./locate.js"; import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; @@ -49,6 +49,11 @@ interface CopyInfo { root: string; /** Requests whose entry's range the version satisfies. */ requests: Request[]; + /** Replaced declaration files: package-relative path → local file. */ + files: Map; + /** Declaration entries a `types` patch supplies: package-relative path + * → local file (existing entries are replaced, missing ones appear). */ + entries: Map; } interface MatchRecord extends PatchMatch { @@ -61,9 +66,11 @@ interface State { manifest: string; manifestText: string; parse: TypePatchParse; - byPackage: Map; + byPackage: Map; copies: Map; files: Map; + /** `files` targets that do not exist in a copy. */ + missingFiles: { entry: TypePatchEntry; root: string; rel: string }[]; /** Package copies whose version no patch range covers. */ unmatchedCopies: Map; /** Requests applicable to a copy this load read files of. */ @@ -84,22 +91,23 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): state = null; return; } - const byPackage = new Map(); + const byPackage = new Map(); let id = 0; for (const entry of config.typePatches) { + const pkg = byPackage.get(entry.pkg) ?? { entries: [], requests: [] }; + byPackage.set(entry.pkg, pkg); + pkg.entries.push(entry); for (const decl of entry.declarations) { const selector = parseSelector(decl.selector); const op = parseTypeText(decl.type); if ("error" in selector || "error" in op) continue; // reported as SC8004 - const list = byPackage.get(entry.pkg) ?? []; - list.push({ + pkg.requests.push({ id: id++, selector: selector as Selector, op: op as TypePatchOp, entry, offset: decl.offset, }); - byPackage.set(entry.pkg, list); } } state = { @@ -109,6 +117,7 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): byPackage, copies: new Map(), files: new Map(), + missingFiles: [], unmatchedCopies: new Map(), touched: new Set(), matches: [], @@ -145,7 +154,7 @@ function versionAt(dir: string): string | null { /** The installed package copy a file belongs to, with the requests that * apply to it; null for files outside node_modules or of unpatched * packages. */ -function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null { +function copyOf(s: State, path: string, touch = true): { copy: CopyInfo; rel: string } | null { const marker = "/node_modules/"; const at = path.lastIndexOf(marker); if (at < 0) return null; @@ -159,33 +168,120 @@ function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null let copy = s.copies.get(root); if (copy === undefined) { copy = null; - const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; - const requests = s.byPackage.get(pkg); - if (requests !== undefined) { + const twin = name.startsWith("@types/"); + const pkg = twin ? unmangleTypesName(name) : name; + const patches = s.byPackage.get(pkg); + if (patches !== undefined) { // A twin describes the runtime package installed beside it. - const version = name.startsWith("@types/") + const version = twin ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) : versionAt(root); - const applicable = requests.filter( - (r) => version !== null && satisfies(version, r.entry.range), + const applicable = patches.entries.filter( + (e) => version !== null && satisfies(version, e.range), ); - // Later entries win on the same selector. - const bySelector = new Map(); - for (const r of applicable) bySelector.set(r.selector.text, r); - const chosen = [...bySelector.values()]; - if (chosen.length === 0) { - const entries = [...new Set(requests.map((r) => r.entry))]; - s.unmatchedCopies.set(root, { pkg, version, entries }); + if (applicable.length === 0) { + s.unmatchedCopies.set(root, { pkg, version, entries: patches.entries }); + } else { + // Later entries win on the same selector, file or entry. + const bySelector = new Map(); + for (const r of patches.requests) + if (applicable.includes(r.entry)) bySelector.set(r.selector.text, r); + const files = new Map(); + let types: string | null = null; + const prefix = `${name}/`; + for (const e of applicable) { + for (const [target, local] of e.files) { + // `@types//` names the twin's file; plain paths the + // package's own. + const forTwin = target.startsWith("@types/"); + if (forTwin !== twin) continue; + const rel = forTwin + ? target.startsWith(prefix) + ? target.slice(prefix.length) + : null + : target; + if (rel === null) continue; + if (!trackedFileExists(`${root}/${rel}`)) { + s.missingFiles.push({ entry: e, root, rel }); + continue; + } + files.set(rel, local); + } + if (e.types !== null && !twin) types = e.types; + } + copy = { + pkg, + version, + root, + requests: [...bySelector.values()], + files, + entries: types === null ? new Map() : declarationEntries(root, types), + }; } - copy = chosen.length === 0 ? null : { pkg, version, root, requests: chosen }; } s.copies.set(root, copy); } if (copy === null) return null; - for (const r of copy.requests) s.touched.add(r); + if (touch) for (const r of copy.requests) s.touched.add(r); return { copy, rel }; } +/** The declaration entry paths of a package a `types` patch stands for: + * its `types`/`typings` file and every declaration TypeScript probes + * beside a JavaScript entry (`main`, the `exports` targets of "."). */ +function declarationEntries(root: string, local: string): Map { + const out = new Map(); + let pkg: Record = {}; + try { + pkg = JSON.parse(trackedReadFile(`${root}/package.json`) ?? "{}") as Record; + } catch { + pkg = {}; + } + const add = (target: unknown): void => { + if (typeof target !== "string") return; + const rel = target.replace(/^\.\//, ""); + if (/\.d\.[cm]?ts$/.test(rel)) out.set(rel, local); + else if (/\.[cm]?js$/.test(rel)) out.set(rel.replace(/\.([cm]?)js$/, ".d.$1ts"), local); + else if (!/\.\w+$/.test(rel)) out.set(`${rel}.d.ts`, local); + }; + add(pkg["types"]); + add(pkg["typings"]); + add(typeof pkg["main"] === "string" ? pkg["main"] : "index.js"); + const exportsField = pkg["exports"]; + const dot = + exportsField !== null && typeof exportsField === "object" && "." in exportsField + ? (exportsField as Record)["."] + : exportsField; + const visit = (v: unknown): void => { + if (typeof v === "string") add(v); + else if (v !== null && typeof v === "object") for (const x of Object.values(v)) visit(x); + }; + // Subpath exports ("./x") are not the entry. + if (dot !== null && typeof dot === "object" && Object.keys(dot).some((k) => k.startsWith("."))) + return out; + visit(dot); + return out; +} + +/** True for a declaration entry a `types` patch supplies that does not + * exist on disk (a package without declarations gains one). */ +export function typePatchFileExists(fileName: string): boolean { + const s = state; + if (s === null) return false; + const path = fileName.split("\\").join("/"); + if (!/\.d\.[cm]?ts$/.test(path)) return false; + const located = copyOf(s, path, false); + return located !== null && located.copy.entries.has(located.rel); +} + +/** True when a `types` patch gives the package at `pkgDir` declarations. */ +export function typePatchSuppliesTypes(pkgDir: string): boolean { + const s = state; + if (s === null) return false; + const located = copyOf(s, `${pkgDir.split("\\").join("/")}/package.json`, false); + return located !== null && located.copy.entries.size > 0; +} + /** Names a request's root can appear under in a file's text. */ function rootWords(sel: Selector): string[] { if (sel.root === "export=") return ["exports", "export"]; @@ -205,7 +301,12 @@ export function typePatchedText(fileName: string): string | undefined { let result: string | null = null; const located = copyOf(s, path); if (located !== null) { - const text = trackedReadFile(path); + // A replaced file or a supplied entry reads as the local file; the + // declaration patches then apply to it like to the package's own. + const local = located.copy.files.get(located.rel) ?? located.copy.entries.get(located.rel); + const replaced = local === undefined ? null : trackedReadFile(local); + const text = replaced ?? trackedReadFile(path); + if (replaced !== null) result = replaced; const requests = located.copy.requests.filter( (r) => (r.selector.file === null || r.selector.file === located.rel) && @@ -314,6 +415,16 @@ export function typePatchDiagnostics(): { ); } } + for (const missing of s.missingFiles) { + out.push( + typePatchDiag( + "SC8031", + `type patch '${missing.entry.key}': ${missing.rel} is not a file of ${missing.entry.pkg} at ${relativeToManifest(s, missing.root)}`, + "name a declaration file the installed package ships; the package may have moved it", + loc(s, missing.entry.offset), + ), + ); + } for (const r of s.touched) { const mine = s.matches.filter((m) => m.id === r.id); // A selector that matched somewhere (the .d.ts) is not a problem where diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 04a5d2e0b..51d9520a0 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -214,7 +214,7 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = "A.c": "string\nnumber", "A.d": "| undefined", }, - files: {}, + patches: {}, }, "other@^1.0.0": [], }, @@ -225,7 +225,7 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = `package.json 'scriptc.typePatches.ms@^banana' has the version range "^banana", which is not a semver range`, "package.json 'scriptc.typePatches.@types/node@*' patches @types/node, whose declarations ship with scriptc or TypeScript", "package.json 'scriptc.typePatches.typescript@5' patches typescript, whose declarations ship with scriptc or TypeScript", - "package.json 'scriptc.typePatches.lib@^1.0.0.files' is not a known type patch setting", + "package.json 'scriptc.typePatches.lib@^1.0.0.patches' is not a known type patch setting", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.1bad' is not a selector: it must start with a declared name, 'export=' or 'default'", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.b' must map to a type string, not 3", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.c' has a type that must be one line", @@ -241,3 +241,43 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = }); expect(partial.typePatches[0]!.declarations.map((d) => d.selector)).toEqual(["A.d"]); }); + +test("typePatches: files and types name local declaration files", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-project-config-")); + try { + mkdirSync(join(root, "patches")); + writeFileSync(join(root, "patches", "entry.d.ts"), "export {};\n"); + const text = JSON.stringify({ + scriptc: { + typePatches: { + "lib@^1.0.0": { + files: { + "dist/entry.d.ts": "./patches/entry.d.ts", + "@types/lib/index.d.ts": "patches/entry.d.ts", + "dist/index.js": "./patches/entry.d.ts", + "../outside.d.ts": "./patches/entry.d.ts", + "dist/other.d.ts": "./patches/missing.d.ts", + }, + types: "./patches/entry.d.ts", + }, + "other@^1.0.0": { types: "./patches/entry.ts" }, + }, + }, + }); + const c = parseProjectConfig(join(root, "package.json"), text); + expect(c.diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.typePatches.lib@^1.0.0.files.dist/index.js' must name a declaration file inside the package", + "package.json 'scriptc.typePatches.lib@^1.0.0.files.../outside.d.ts' must name a declaration file inside the package", + `package.json 'scriptc.typePatches.lib@^1.0.0.files.dist/other.d.ts' names "./patches/missing.d.ts", which does not exist`, + `package.json 'scriptc.typePatches.other@^1.0.0.types' names "./patches/entry.ts", which is not a declaration file`, + ]); + const entry = c.typePatches.find((e) => e.pkg === "lib")!; + expect([...entry.files]).toEqual([ + ["dist/entry.d.ts", join(root, "patches/entry.d.ts")], + ["@types/lib/index.d.ts", join(root, "patches/entry.d.ts")], + ]); + expect(entry.types).toBe(join(root, "patches/entry.d.ts")); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index c43a6bd2c..4aad9cbc3 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -27,7 +27,7 @@ * to their own readers. */ import { dirname, join, resolve } from "node:path"; import { projectConfigDiag, type ScrDiagnostic } from "./diagnostics/diagnostic.js"; -import { trackedReadFile } from "./frontend/input-tracker.js"; +import { trackedFileExists, trackedReadFile } from "./frontend/input-tracker.js"; import { parseSelector, parseTypeText } from "./frontend/type-patches/selector.js"; import { isValidRange } from "./frontend/type-patches/semver.js"; @@ -79,6 +79,13 @@ export interface TypePatchEntry { pkg: string; range: string; declarations: TypePatchDeclaration[]; + /** `files`: declaration files of the package (or of its `@types/` twin, + * written `@types//`) replaced by local files, by + * package-relative path. The local paths are absolute. */ + files: Map; + /** `types`: a local declaration file that becomes the package's + * declaration entry (absolute), or null. */ + types: string | null; /** Offset of the key in the manifest. */ offset: number; } @@ -341,7 +348,8 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi return config; } -const TYPE_PATCH_KEYS = ["declarations", "note"]; +const TYPE_PATCH_KEYS = ["declarations", "files", "types", "note"]; +const DECLARATION_FILE = /\.d\.[cm]?ts$/; /** Packages whose declarations ship with scriptc or TypeScript: a wrong * one is a bug to report, not a project-local patch. */ const UNPATCHABLE = /^(?:@types\/node|typescript|@typescript\/.*)$/; @@ -404,8 +412,64 @@ function readTypePatches( pkg, range, declarations: [], + files: new Map(), + types: null, offset: locate.offsetOf(["scriptc", "typePatches", key]), }; + const base = dirname(config.manifest!); + const localFile = (path: string[], v: unknown): string | null => { + if (typeof v !== "string" || v === "") { + problem( + path, + `must be the path of a local declaration file, not ${shown(v)}`, + `write "./type-patches/${pkg}.d.ts"`, + ); + return null; + } + const abs = resolve(base, v); + if (!DECLARATION_FILE.test(abs)) { + problem( + path, + `names "${v}", which is not a declaration file`, + "use a .d.ts, .d.mts or .d.cts file", + ); + return null; + } + if (!trackedFileExists(abs)) { + problem( + path, + `names "${v}", which does not exist`, + "give the path relative to this package.json", + ); + return null; + } + return abs; + }; + const files = value["files"]; + if (files !== undefined && !isRecord(files)) { + problem( + ["typePatches", key, "files"], + `must be an object mapping declaration files of the package to local files, not ${shown(files)}`, + `write "files": { "dist/index.d.ts": "./type-patches/${pkg}.d.ts" }`, + ); + } else if (files !== undefined) { + for (const target of Object.keys(files)) { + const path = ["typePatches", key, "files", target]; + const rel = target.replace(/^\.\//, ""); + if (!DECLARATION_FILE.test(rel) || rel.startsWith("/") || rel.split("/").includes("..")) { + problem( + path, + "must name a declaration file inside the package", + `write a package-relative .d.ts path such as "dist/index.d.ts", or "@types//index.d.ts" for its @types package`, + ); + continue; + } + const abs = localFile(path, files[target]); + if (abs !== null) entry.files.set(rel, abs); + } + } + if (value["types"] !== undefined) + entry.types = localFile(["typePatches", key, "types"], value["types"]); let valid = true; for (const k of Object.keys(value)) { if (!TYPE_PATCH_KEYS.includes(k)) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json new file mode 100644 index 000000000..4a15a1975 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json @@ -0,0 +1,25 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-files/main.ts" + ], + "diags": [ + { + "code": "SC0001", + "message": "Property 'toUpperCase' does not exist on type 'number'.", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-files/main.ts", + "start": 346, + "end": 357 + } + }, + { + "code": "SC0001", + "message": "This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-files/main.ts", + "start": 429, + "end": 447 + } + } + ] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json new file mode 100644 index 000000000..bc1622724 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json @@ -0,0 +1,16 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-types-entry/main.ts" + ], + "diags": [ + { + "code": "SC0001", + "message": "Could not find a declaration file for module 'pointjs'. '/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js' implicitly has an 'any' type.\n Try `npm i --save-dev @types/pointjs` if it exists or add a new declaration (.d.ts) file containing `declare module 'pointjs';`", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-types-entry/main.ts", + "start": 246, + "end": 255 + } + } + ] +} diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 729a0da37..116715db5 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -12,7 +12,9 @@ import { typePatchDiagnostics, typePatchedSpans, typePatchedText, + typePatchFileExists, typePatchMatches, + typePatchSuppliesTypes, } from "../src/frontend/type-patches/layer.js"; const fixtures = join(import.meta.dirname, "../../../tests/fixtures"); @@ -122,3 +124,56 @@ test("an @types twin is matched by the runtime package's installed version", () rmSync(root, { recursive: true, force: true }); } }); + +test("files replaces a declaration file and declarations still apply on top", () => { + const root = join(fixtures, "npm/cases/type-patch-files"); + configureTypePatches(join(root, "main.ts"), parse); + const entry = typePatchedText(join(root, "node_modules/eventlog/dist/entry.d.ts")); + expect(entry).toBe(readFileSync(join(root, "type-patches/entry.d.ts"), "utf8")); + expect(typePatchedText(join(root, "node_modules/eventlog/dist/index.d.ts"))).toContain( + "{ count: number; last: string | undefined }", + ); + expect(typePatchDiagnostics()!.diagnostics).toEqual([]); +}); + +test("types gives a package without declarations an entry beside its main", () => { + const root = join(fixtures, "npm/cases/type-patch-types-entry"); + configureTypePatches(join(root, "main.ts"), parse); + const pkg = join(root, "node_modules/pointjs"); + expect(typePatchFileExists(join(pkg, "lib/point.d.ts"))).toBe(true); + expect(typePatchFileExists(join(pkg, "index.d.ts"))).toBe(false); + expect(typePatchFileExists(join(pkg, "lib/other.d.ts"))).toBe(false); + expect(typePatchSuppliesTypes(pkg)).toBe(true); + expect(typePatchedText(join(pkg, "lib/point.d.ts"))).toBe( + readFileSync(join(root, "type-patches/pointjs.d.ts"), "utf8"), + ); + // Without patches nothing exists that is not on disk. + configureTypePatches(join(tmpdir(), "none", "main.ts"), parse); + expect(typePatchFileExists(join(pkg, "lib/point.d.ts"))).toBe(false); + expect(typePatchSuppliesTypes(pkg)).toBe(false); +}); + +test("a files target the installed package does not ship warns", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const twin = join(fixtures, "npm/cases/type-patch-files"); + writeFileSync(join(root, "local.d.ts"), "export type Entry = {};\n"); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { "eventlog@^2.0.0": { files: { "dist/gone.d.ts": "./local.d.ts" } } }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(twin, "node_modules/eventlog/dist/index.d.ts"))).toBeUndefined(); + const diags = typePatchDiagnostics()!.diagnostics; + expect(diags.map((d) => d.code)).toEqual(["SC8031"]); + expect(diags[0]!.message).toMatch( + /^type patch 'eventlog@\^2\.0\.0': dist\/gone\.d\.ts is not a file of eventlog at .*type-patch-files\/node_modules\/eventlog$/, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/tests/fixtures/effect4/node_modules/@effect/platform-node b/tests/fixtures/effect4/node_modules/@effect/platform-node new file mode 120000 index 000000000..aaa396705 --- /dev/null +++ b/tests/fixtures/effect4/node_modules/@effect/platform-node @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@effect+platform-node@4.0.0_effect@4.0.0_redis@6.3.0/node_modules/@effect/platform-node \ No newline at end of file diff --git a/tests/fixtures/effect4/node_modules/effect b/tests/fixtures/effect4/node_modules/effect new file mode 120000 index 000000000..1b933cdef --- /dev/null +++ b/tests/fixtures/effect4/node_modules/effect @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/effect@4.0.0/node_modules/effect \ No newline at end of file diff --git a/tests/fixtures/npm/cases/type-patch-files/main.ts b/tests/fixtures/npm/cases/type-patch-files/main.ts new file mode 100644 index 000000000..32e041ce0 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/main.ts @@ -0,0 +1,10 @@ +// A package declaration file replaced by a local one (scriptc.typePatches +// files), plus a declaration patch on another file of the same package. +import { parseLog, summarize } from "eventlog"; + +const entries = parseLog("10 info ada started\n12 warn - disk low\n15 error lin crashed hard\n"); +for (const e of entries) console.log(e.at, e.level.toUpperCase(), e.user ?? "(system)", e.message); +console.log(entries.filter((e) => e.level !== "info").length, JSON.stringify(entries[1])); +const all = summarize(entries); +const none = summarize([]); +console.log(all.count, all.last ?? "-", none.count, none.last ?? "-"); diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts new file mode 100644 index 000000000..0a42b298f --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts @@ -0,0 +1,8 @@ +// Out of date: the JavaScript reports levels as strings and has no +// `user` for system entries. +export type Entry = { + at: number; + level: number; + user: string; + message: string; +}; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts new file mode 100644 index 000000000..c16d59d46 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts @@ -0,0 +1,4 @@ +export type { Entry } from "./entry"; +import type { Entry } from "./entry"; +export declare function parseLog(text: string): Entry[]; +export declare function summarize(entries: Entry[]): { count: number; last: string }; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js new file mode 100644 index 000000000..4c10d88f4 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js @@ -0,0 +1,17 @@ +"use strict"; +function parseLog(text) { + const rows = String(text) + .split("\n") + .filter(Boolean) + .map((line) => { + const [at, level, user, ...rest] = line.split(" "); + return { at: Number(at), level: level, user: user === "-" ? undefined : user, message: rest.join(" ") }; + }); + // Entries travel as JSON (the log format's own wire form). + return JSON.parse(JSON.stringify(rows)); +} +function summarize(entries) { + return { count: entries.length, last: entries.length === 0 ? undefined : entries[entries.length - 1].message }; +} +exports.parseLog = parseLog; +exports.summarize = summarize; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json new file mode 100644 index 000000000..27ebf4625 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json @@ -0,0 +1 @@ +{ "name": "eventlog", "version": "2.3.1", "main": "dist/index.js", "types": "dist/index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-files/package.json b/tests/fixtures/npm/cases/type-patch-files/package.json new file mode 100644 index 000000000..92a38c108 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/package.json @@ -0,0 +1,13 @@ +{ + "name": "type-patch-files-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "eventlog@^2.0.0": { + "files": { "dist/entry.d.ts": "./type-patches/entry.d.ts" }, + "declarations": { "summarize().last": "| undefined" } + } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts b/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts new file mode 100644 index 000000000..380338e96 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts @@ -0,0 +1,7 @@ +// The project's replacement for eventlog's dist/entry.d.ts. +export type Entry = { + at: number; + level: "info" | "warn" | "error"; + user?: string; + message: string; +}; diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/main.ts b/tests/fixtures/npm/cases/type-patch-types-entry/main.ts new file mode 100644 index 000000000..3d0ab5ac9 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/main.ts @@ -0,0 +1,9 @@ +// A package without declarations, typed by a local declaration file +// (scriptc.typePatches types): user code is checked against it, and the +// values cross the declaration boundary as native records. +import { centroid, point, type Point } from "pointjs"; + +const points: Point[] = [point(0, 0), point(4, 0), point(4, 3)]; +const c = centroid(points); +console.log(c === null ? "none" : `${c.x.toFixed(2)},${c.y.toFixed(2)}`); +console.log(centroid([]) === null, JSON.stringify(points.map((p) => p.x + p.y))); diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js new file mode 100644 index 000000000..f324e9acb --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js @@ -0,0 +1,17 @@ +"use strict"; +// Plain JavaScript without declarations. +function point(x, y) { + return { x: x, y: y }; +} +function centroid(points) { + if (points.length === 0) return null; + var sx = 0; + var sy = 0; + for (var i = 0; i < points.length; i++) { + sx += points[i].x; + sy += points[i].y; + } + return point(sx / points.length, sy / points.length); +} +exports.point = point; +exports.centroid = centroid; diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json new file mode 100644 index 000000000..917576875 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json @@ -0,0 +1 @@ +{ "name": "pointjs", "version": "1.4.0", "main": "lib/point.js" } diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/package.json b/tests/fixtures/npm/cases/type-patch-types-entry/package.json new file mode 100644 index 000000000..06846a01d --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/package.json @@ -0,0 +1,10 @@ +{ + "name": "type-patch-types-entry-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "pointjs@1.x": { "types": "./type-patches/pointjs.d.ts" } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts b/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts new file mode 100644 index 000000000..30ae94a5e --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts @@ -0,0 +1,4 @@ +// The project's declarations for pointjs, which ships none. +export type Point = { x: number; y: number }; +export declare function point(x: number, y: number): Point; +export declare function centroid(points: Point[]): Point | null; diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index ecd8ee923..816418a96 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -4,8 +4,10 @@ * value (the shape of commander's HelpContext.helpWidth), and ms's @return * tags, compile natively and byte-match Node once patched; without the * patches the same declarations throw where Node runs. Patches that do not - * apply as written warn (SC8030-SC8033). The `.d.ts` and `@types` cases - * run in npm.test.ts (type-patch-dts, type-patch-types-twin), and + * apply as written warn (SC8030-SC8033). The `.d.ts`, `@types`, replaced + * file and supplied entry cases run in npm.test.ts (type-patch-dts, + * type-patch-types-twin, type-patch-files, type-patch-types-entry); here + * their unpatched copies show the patches are what makes them compile. And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -121,6 +123,48 @@ describe("type patches", () => { }, ); + test( + "without its patch, code using the out-of-date declaration file does not type-check", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-files")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "files-unpatched"), + outDir, + sanitize, + }); + expect(result.ok).toBe(false); + if (!result.ok) + expect(result.diagnostics.map((d) => d.message)).toContain( + "Property 'toUpperCase' does not exist on type 'number'.", + ); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "without its patch, a package without declarations is not compiled for a typed import", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-types-entry")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "types-entry-unpatched"), + outDir, + sanitize, + }); + expect(result.ok).toBe(false); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From be0e13aa1ef057c04ab397aa801a00b7577a1378 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:28:23 -0500 Subject: [PATCH 263/395] Run paired row sums two columns per step A paired row-sum nest now also takes two columns per step: each row adds the first column's term and then the second's, and an odd column count runs its last column on its own, so every sum keeps its order of additions. The nest runs paired when all four bounds are integers in [0, 2^25 - 1]; it reads them masked to that range, which proves both counters' ranges, so its counted loops need no run-time guard or second copy of the body. Corpus 8355 covers the column counts and bounds, and corpus 8356 covers worker loops over shared views that run past a view's end or write overlapping views of one buffer. --- packages/compiler/src/ir/row-pairs.test.ts | 37 +++- packages/compiler/src/ir/row-pairs.ts | 200 +++++++++++++----- .../corpus/8355-row-pair-columns.ts.json | 6 + .../8356-shared-view-loop-versions.ts.json | 6 + tests/corpus/8355-row-pair-columns.ts | 73 +++++++ .../corpus/8356-shared-view-loop-versions.ts | 93 ++++++++ 6 files changed, 352 insertions(+), 63 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json create mode 100644 tests/corpus/8355-row-pair-columns.ts create mode 100644 tests/corpus/8356-shared-view-loop-versions.ts diff --git a/packages/compiler/src/ir/row-pairs.test.ts b/packages/compiler/src/ir/row-pairs.test.ts index f9db5b89d..8ef01c665 100644 --- a/packages/compiler/src/ir/row-pairs.test.ts +++ b/packages/compiler/src/ir/row-pairs.test.ts @@ -93,12 +93,39 @@ test("a row-sum nest runs two rows per pass behind its bounds and overlap test", // from the array the term reads. expect(JSON.stringify(go.init)).toContain('"fn":"bytes.disjoint"'); expect(branch.else_).toBeNull(); - const pairs = branch.then[0] as IrStmt & { kind: "for" }; + // The four bounds, masked to the range the test proved, then the pairs. + expect(branch.then.slice(0, 4)).toMatchObject( + Array(4).fill({ kind: "varDecl", init: { op: "&", right: { value: 2 ** 25 - 1 } } }), + ); + const pairs = branch.then[4] as IrStmt & { kind: "for" }; expect(pairs.body.filter((s) => s.kind === "bytesSet")).toHaveLength(2); - const inner = pairs.body[2] as IrStmt & { kind: "for" }; - // One read of u[j] for both rows, then the two sums. - expect(inner.body).toHaveLength(3); + const [, , inner, last] = pairs.body as [ + IrStmt, + IrStmt, + IrStmt & { kind: "for" }, + IrStmt & { kind: "if" }, + ]; + const column = inner.init as IrStmt & { kind: "varDecl" }; + // Two columns per step: for each, one read of u[j] for both rows, then + // the two sums; the second column reads `column + 1`. + expect(inner.body).toHaveLength(6); expect(inner.body[0]).toMatchObject({ kind: "varDecl", init: { kind: "bytesIntrinsic" } }); + expect(inner.body[3]).toMatchObject({ + kind: "varDecl", + init: { + kind: "bytesIntrinsic", + args: [{ op: "+", left: { localId: column.localId }, right: { value: 1 } }], + }, + }); + expect(inner.cond).toMatchObject({ op: "<", right: { op: "-", right: { value: 1 } } }); + expect(inner.update).toMatchObject({ value: { op: "+", right: { value: 2 } } }); + // The last column of an odd count, `to - 1`. + expect(last.then).toHaveLength(3); + expect(last.then[0]).toMatchObject({ + kind: "varDecl", + init: { args: [{ op: "-", right: { value: 1 } }] }, + }); + expect(last.else_).toBeNull(); // The second row's term reads `row + 1`. const row = pairs.init?.kind === "varDecl" ? pairs.init.localId : ""; expect((inner.body[2] as IrStmt & { kind: "assign" }).value).toMatchObject({ @@ -130,7 +157,7 @@ test("a row-sum nest runs two rows per pass behind its bounds and overlap test", expect(top.body).toHaveLength(4); expect( result.functions[0]!.locals.map((l) => l.id).filter((id) => id.startsWith("%pair.")), - ).toHaveLength(7); + ).toHaveLength(13); }); test("nests whose term calls, writes or reads the sum keep their loops", () => { diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index 2e189f42e..e6b3889a5 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -8,10 +8,17 @@ import { type IrStmt, } from "./ir.js"; import { everyExpr, everyExprChild, mapExprChildren, transformStmtList } from "./traverse.js"; +import { PRODUCT_COUNTER_BOUND } from "./integer-loops.js"; /** Inner-loop expressions above this many nodes keep the loop as written. */ const MAX_TERM_NODES = 64; +/** The largest bound a paired nest runs with. Counters below + * PRODUCT_COUNTER_BOUND keep products of their sums exact, so the nest's + * counted loops take integer counters without a run-time guard (and its + * copy of the body). */ +const SMALL_BOUND = PRODUCT_COUNTER_BOUND - 1; + /** The number operators a term may use: they run no other code and cannot * throw. */ const PURE_BINARY = new Set([ @@ -210,7 +217,8 @@ const bin = ( }); /** Runs the row sums of `match` two rows per pass when the test holds and - * as written otherwise. */ + * as written otherwise. Each pass takes two columns per step, then the + * last column when their count is odd. */ function pairRows( s: IrStmt & { kind: "for" }, match: RowSum, @@ -225,42 +233,93 @@ function pairRows( sum = fresh("sum"), next = fresh("next"), column = fresh("column"); - const atColumn = (r: IrExpr & { kind: "varRef" }): IrExpr => - r.localId === match.column ? { ...r, localId: column } : { ...r }; // Element reads that do not depend on the row are read once per column // and shared by both rows. - const shared = new Map(); - const elements: IrStmt[] = []; + const sharedReads: IrExpr[] = []; const collect = (e: IrExpr): boolean => { if (e.kind === "bytesIntrinsic" && e.method === "get" && !readsLocal(e, match.row)) { - const id = fresh("element"); - shared.set(e, id); - elements.push({ kind: "varDecl", localId: id, init: copy(e, atColumn), loc }); + sharedReads.push(e); return true; } return everyExprChild(e, collect, () => true); }; collect(match.term); - // The second row's term reads `row + 1`, so its counter sums stay visible - // to the counted-loop analysis. - const termAt = (rowExpr: () => IrExpr): IrExpr => - copy( - match.term, - (r) => (r.localId === match.row ? rowExpr() : atColumn(r)), - (node) => { - const id = shared.get(node); - return id === undefined ? null : { kind: "varRef", localId: id, type: node.type, loc }; + /** Both rows' additions at the column `columnAt()`: its shared element + * reads, then each row's sum. The second row's term reads `row + 1`, and + * the second column's `column + 1`, so their counter sums stay visible to + * the counted-loop analysis. */ + const step = (columnAt: () => IrExpr): IrStmt[] => { + const at = (r: IrExpr & { kind: "varRef" }): IrExpr => + r.localId === match.column ? columnAt() : { ...r }; + const shared = new Map(); + const elements: IrStmt[] = sharedReads.map((read) => { + const id = fresh("element"); + shared.set(read, id); + return { kind: "varDecl", localId: id, init: copy(read, at), loc }; + }); + const termAt = (rowExpr: () => IrExpr): IrExpr => + copy( + match.term, + (r) => (r.localId === match.row ? rowExpr() : at(r)), + (node) => { + const id = shared.get(node); + return id === undefined ? null : { kind: "varRef", localId: id, type: node.type, loc }; + }, + ); + return [ + ...elements, + { + kind: "assign", + localId: sum, + value: bin( + "+", + ref(sum), + termAt(() => ref(row)), + loc, + ), + loc, }, - ); + { + kind: "assign", + localId: next, + value: bin( + "+", + ref(next), + termAt(() => bin("+", ref(row), num(1), loc)), + loc, + ), + loc, + }, + ]; + }; const innerCond = match.inner.cond as IrExpr & { kind: "bin" }; + const columnStart = (match.inner.init as IrStmt & { kind: "varDecl" }).init!; + // The bounds, as the paired nest reads them (see `small` below). + const low = fresh("low"), + high = fresh("high"), + first = fresh("first"), + last = fresh("last"); + const bounds: IrStmt[] = ( + [ + [low, match.start], + [high, match.end], + [first, columnStart], + [last, innerCond.right], + ] as const + ).map(([id, bound]) => ({ + kind: "varDecl", + localId: id, + init: bin("&", again(bound), num(SMALL_BOUND), loc), + loc, + })); const pairs: IrStmt = { kind: "for", - init: { kind: "varDecl", localId: row, init: again(match.start), loc }, + init: { kind: "varDecl", localId: row, init: ref(low), loc }, cond: { kind: "bin", op: "<", left: ref(row), - right: bin("-", again(match.end), num(1), loc), + right: bin("-", ref(high), num(1), loc), type: BOOL, loc, }, @@ -268,41 +327,40 @@ function pairRows( body: [ { kind: "varDecl", localId: sum, init: { ...match.init }, loc }, { kind: "varDecl", localId: next, init: { ...match.init }, loc }, + // Both column bounds are int32, so `column < to - 1` is + // `column + 1 < to`. Each row adds its columns in order. { kind: "for", - init: { - kind: "varDecl", - localId: column, - init: again((match.inner.init as IrStmt & { kind: "varDecl" }).init!), + init: { kind: "varDecl", localId: column, init: ref(first), loc }, + cond: { + ...innerCond, + left: ref(column), + right: bin("-", ref(last), num(1), loc), + }, + update: { kind: "assign", localId: column, value: bin("+", ref(column), num(2), loc), loc }, + body: [...step(() => ref(column)), ...step(() => bin("+", ref(column), num(1), loc))], + loc, + }, + // An odd column count leaves the last column, `to - 1`. + { + kind: "if", + cond: { + kind: "logical", + op: "&&", + left: { ...innerCond, left: ref(first), right: ref(last) }, + right: { + kind: "bin", + op: "===", + left: bin("&", bin("-", ref(last), ref(first), loc), num(1), loc), + right: num(1), + type: BOOL, + loc, + }, + type: BOOL, loc, }, - cond: { ...innerCond, left: ref(column), right: again(innerCond.right) }, - update: { kind: "assign", localId: column, value: bin("+", ref(column), num(1), loc), loc }, - body: [ - ...elements, - { - kind: "assign", - localId: sum, - value: bin( - "+", - ref(sum), - termAt(() => ref(row)), - loc, - ), - loc, - }, - { - kind: "assign", - localId: next, - value: bin( - "+", - ref(next), - termAt(() => bin("+", ref(row), num(1), loc)), - loc, - ), - loc, - }, - ], + then: step(() => bin("-", ref(last), num(1), loc)), + else_: null, loc, }, { kind: "bytesSet", arr: { ...match.out }, index: ref(row), value: ref(sum), loc }, @@ -316,9 +374,12 @@ function pairRows( ], loc, }; - // Both bounds are int32, so `row < end - 1` is `row + 1 < end`, and the - // nest has at least two rows. The overlap tests run only then, so they never - // read an array the program assigns only when there are rows. Rows are + // Every bound is an integer in [0, SMALL_BOUND], so `row < end - 1` is + // `row + 1 < end` (and the same for the columns), and the masks in + // `bounds` leave the bounds unchanged while proving both counters' ranges. + // The nest has at least two rows. The column bounds' tests and the + // overlap tests run only then, so they never read an array the program + // assigns only when there are rows. Rows are // independent when the written array shares no byte with an array the // term reads; an array that is not there answers false. const int32 = (e: IrExpr): IrExpr => ({ @@ -329,9 +390,22 @@ function pairRows( type: BOOL, loc, }); + const compare = (op: "<=" | ">=", e: IrExpr, value: number): IrExpr => ({ + kind: "bin", + op, + left: again(e), + right: num(value), + type: BOOL, + loc, + }); + const small = (e: IrExpr): IrExpr[] => [ + int32(e), + compare(">=", e, 0), + compare("<=", e, SMALL_BOUND), + ]; const tests: IrExpr[] = [ - int32(match.start), - int32(match.end), + ...small(match.start), + ...small(match.end), { kind: "bin", op: "<", @@ -340,6 +414,8 @@ function pairRows( type: BOOL, loc, }, + ...small(columnStart), + ...small(innerCond.right), ...match.reads.map((read): IrExpr => ({ kind: "libCall", fn: "bytes.disjoint", @@ -394,7 +470,7 @@ function pairRows( }, loc, }, - { kind: "if", cond: { ...paired }, then: [pairs], else_: null, loc }, + { kind: "if", cond: { ...paired }, then: [...bounds, pairs], else_: null, loc }, rest, ], loc, @@ -413,7 +489,15 @@ function pairRows( * store into shares no byte with any array the term reads, so no row reads * another row's result. An odd row count runs its last row as written. * Otherwise the nest runs as written. Two independent sums take turns in - * the inner loop, so neither waits on the other's addition. */ + * the inner loop, so neither waits on the other's addition. + * + * The inner loop takes two columns per step, each row adding the first + * column's term and then the second's, and the last column on its own + * when the count is odd, so the additions keep their order. Both column + * bounds must also be int32 (so `column < to - 1` is `column + 1 < to`). + * Neighboring rows and columns share counter sums (`row + 1 + column` and + * `row + (column + 1)`), and the step's divisions have no dependence + * between them. */ export function pairRowSums(mod: IrModule): IrModule { let changed = false; const functions = mod.functions.map((fn): IrFunction => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json new file mode 100644 index 000000000..900de6fb0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8355-row-pair-columns.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json new file mode 100644 index 000000000..76cd99065 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8356-shared-view-loop-versions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8355-row-pair-columns.ts b/tests/corpus/8355-row-pair-columns.ts new file mode 100644 index 000000000..96ba26153 --- /dev/null +++ b/tests/corpus/8355-row-pair-columns.ts @@ -0,0 +1,73 @@ +// Row sums whose inner loop runs two columns per step: even, odd, zero, one +// and two columns, column ranges that start above zero or at or past their +// end, fractional, negative, NaN and very large column bounds, a column +// bound read from an array's length, terms that read two elements per +// column, and row bounds at and past 2^25. Every sum adds its columns in +// order, so each has the bits of the loop as written. +function rows(u: Float64Array, v: Float64Array, start: number, end: number, from: number, to: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = from; j < to; j++) t += u[j]! / ((((i + j) * (i + j + 1)) >>> 1) + i + 1); + v[i] = t; + } +} + +function pairs(u: Float64Array, w: Float32Array, v: Float64Array, end: number): void { + for (let i = 0; i < end; i++) { + let t = -0.25; + for (let j = 0; j < u.length; j++) t += u[j]! * w[j + 1]! - (i + 1) / (j + 3); + v[i] = t; + } +} + +const show = (label: string, v: Float64Array): void => { + const parts: string[] = []; + for (const x of v) parts.push(Object.is(x, -0) ? "-0" : x.toPrecision(17)); + console.log(label, parts.join(" ")); +}; + +const n = 9; +const u = new Float64Array(n).map((_, k) => 1 / (k + 1) + k); +for (const [from, to] of [ + [0, n], + [0, n - 1], + [0, 0], + [0, 1], + [0, 2], + [3, n], + [4, n], + [5, 5], + [7, 2], + [0.5, 6], + [0, 6.5], + [-2, 5], + [-0, 4], + [NaN, 4], + [0, NaN], + [0, n + 3], + [2 ** 25, 2 ** 25 + 3], +]) { + const v = new Float64Array(6); + rows(u, v, 0, 6, from!, to!); + show(`columns ${from} ${to}`, v); +} +for (const [start, end] of [ + [2 ** 25 - 3, 2 ** 25 + 1], + [2 ** 25, 2 ** 25 + 2], + [1, 6], +]) { + const v = new Float64Array(8); + rows(u, v, start!, end!, 0, n); + show(`rows ${start} ${end}`, v); +} + +const w = new Float32Array(n + 1).map((_, k) => (k % 4) - 1.5); +for (const end of [0, 1, 4, 5]) { + const v = new Float64Array(5); + pairs(u, w, v, end); + show(`two reads ${end}`, v); +} +const short = new Float64Array(4).map((_, k) => k * 2.5); +const out = new Float64Array(5); +pairs(short, w, out, 5); +show("two reads short", out); diff --git a/tests/corpus/8356-shared-view-loop-versions.ts b/tests/corpus/8356-shared-view-loop-versions.ts new file mode 100644 index 000000000..28f4307fb --- /dev/null +++ b/tests/corpus/8356-shared-view-loop-versions.ts @@ -0,0 +1,93 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// Loops over typed-array views of shared memory in a worker program: row +// sums and element-wise passes whose accesses are in bounds, the same loops +// running past a view's end, a loop that mixes a shared view with one the +// thread owns, and views of one SharedArrayBuffer that overlap, written in +// an order that shows whether each read sees the previous write. The worker +// writes its results into shared memory, which the main thread reads after +// the worker reports. + +interface Job { + sab: SharedArrayBuffer; + n: number; +} + +function rowSums(u: Float64Array, v: Float64Array, start: number, end: number, n: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = 0; j < n; j++) t = t + u[j]! / (((i + j) * (i + j + 1)) / 2 + i + 1); + v[i] = t; + } +} + +function scaled(src: Float64Array, dst: Float64Array, k: number, count: number): void { + for (let i = 0; i < count; i++) dst[i] = src[i]! * k + 1; +} + +function mixed(shared: Int32Array, owned: Int32Array, count: number): number { + let sum = 0; + for (let i = 0; i < count; i++) { + owned[i] = shared[i]! * 3; + shared[i] = owned[i]! - i; + sum = (sum + shared[i]! + owned[i]!) | 0; + } + return sum; +} + +function shift(a: Float64Array, b: Float64Array, count: number): void { + for (let i = 0; i < count; i++) a[i + 1] = b[i]! + 1; +} + +function checksum(values: Float64Array): string { + let h = 0; + for (let i = 0; i < values.length; i++) h = (Math.imul(h, 31) + Math.round(values[i]! * 1e6)) >>> 0; + return h.toString(16); +} + +function work({ sab, n }: Job): string[] { + const out: string[] = []; + const u = new Float64Array(sab, 0, n); + const v = new Float64Array(sab, n * 8, n); + for (let i = 0; i < n; i++) u[i] = 1 + (i % 5) / 4; + rowSums(u, v, 0, n, n); + out.push(`rows ${checksum(v)}`); + // Past the end of u: the reads beyond it are undefined, so the sums are NaN. + rowSums(u, v, 0, 4, n + 3); + out.push(`past ${v[0]} ${v[3]} ${checksum(v.subarray(4))}`); + scaled(u, v, 0.5, n); + out.push(`scaled ${checksum(v)}`); + scaled(u, v, 2, n + 2); + out.push(`scaled past ${checksum(v)}`); + const ints = new Int32Array(sab, 2 * n * 8, 32); + for (let i = 0; i < 32; i++) ints[i] = i * 7 - 50; + const own = new Int32Array(32); + out.push(`mixed ${mixed(ints, own, 32)} ${mixed(ints, own, 34)} ${ints[5]} ${own[31]}`); + // a[i + 1] lands on b[i + 1] when b starts one element after a. + const a = new Float64Array(sab, 0, 24); + const b = new Float64Array(sab, 8, 24); + for (let i = 0; i < 25; i++) u[i] = i; + shift(a, b, 20); + out.push(`overlap ${Array.from(a.subarray(0, 8)).join(",")}`); + shift(b, a, 20); + out.push(`overlap back ${Array.from(b.subarray(0, 8)).join(",")}`); + return out; +} + +if (isMainThread) { + const n = 40; + const sab = new SharedArrayBuffer(2 * n * 8 + 32 * 4); + const worker = new Worker(new URL(import.meta.url), { workerData: { sab, n } satisfies Job }); + worker.on("message", (lines: string[]) => { + for (const line of lines) console.log("worker", line); + const v = new Float64Array(sab, n * 8, n); + const ints = new Int32Array(sab, 2 * n * 8, 32); + console.log("main sees", checksum(v), ints[0], ints[31]); + // The same passes on the main thread, over a fresh buffer. + const again = new SharedArrayBuffer(2 * n * 8 + 32 * 4); + for (const line of work({ sab: again, n })) console.log("main", line); + }); + worker.on("exit", (code: number) => console.log("exit", code)); +} else { + parentPort!.postMessage(work(workerData as Job)); +} From 7d458303a632d9b3842e52cea373c3325cff3b19 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:32:00 -0500 Subject: [PATCH 264/395] Check type patch files again on a cached configuration, and supply .jsx entries - The project configuration records the local files a type patch names, with whether each existed. A configuration read again from an unchanged package.json is reused only while every answer still holds, so creating or deleting a local declaration file between builds in one process validates again. - A types patch supplies the declaration TypeScript probes for a .jsx JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs. - Remove the two tests/fixtures/effect4/node_modules links the previous commit added; that directory is ignored and pnpm creates them. --- .../src/frontend/type-patches/layer.ts | 2 ++ packages/compiler/src/project-config.test.ts | 25 +++++++++++++++++ packages/compiler/src/project-config.ts | 16 +++++++++-- packages/compiler/test/type-patches.test.ts | 28 ++++++++++++++++++- .../node_modules/@effect/platform-node | 1 - tests/fixtures/effect4/node_modules/effect | 1 - 6 files changed, 68 insertions(+), 5 deletions(-) delete mode 120000 tests/fixtures/effect4/node_modules/@effect/platform-node delete mode 120000 tests/fixtures/effect4/node_modules/effect diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 5fb41f968..9c156b68c 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -242,6 +242,8 @@ function declarationEntries(root: string, local: string): Map { const rel = target.replace(/^\.\//, ""); if (/\.d\.[cm]?ts$/.test(rel)) out.set(rel, local); else if (/\.[cm]?js$/.test(rel)) out.set(rel.replace(/\.([cm]?)js$/, ".d.$1ts"), local); + // TypeScript probes x.tsx and then x.d.ts for x.jsx. + else if (rel.endsWith(".jsx")) out.set(`${rel.slice(0, -".jsx".length)}.d.ts`, local); else if (!/\.\w+$/.test(rel)) out.set(`${rel}.d.ts`, local); }; add(pkg["types"]); diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 51d9520a0..f12a8ea89 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -281,3 +281,28 @@ test("typePatches: files and types name local declaration files", () => { rmSync(root, { recursive: true, force: true }); } }); + +test("readProjectConfig checks local type patch files again for an unchanged package.json", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-project-config-")); + try { + writeFileSync( + join(root, "package.json"), + JSON.stringify({ scriptc: { typePatches: { "lib@^1.0.0": { types: "./lib.d.ts" } } } }), + ); + const entry = join(root, "main.ts"); + const missing = readProjectConfig(entry); + expect(missing.diagnostics.map((d) => d.message)).toEqual([ + `package.json 'scriptc.typePatches.lib@^1.0.0.types' names "./lib.d.ts", which does not exist`, + ]); + expect(readProjectConfig(entry)).toBe(missing); + writeFileSync(join(root, "lib.d.ts"), "export {};\n"); + const created = readProjectConfig(entry); + expect(created.diagnostics).toEqual([]); + expect(created.typePatches[0]!.types).toBe(join(root, "lib.d.ts")); + expect(readProjectConfig(entry)).toBe(created); + rmSync(join(root, "lib.d.ts")); + expect(readProjectConfig(entry).diagnostics.map((d) => d.code)).toEqual(["SC8004"]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index 4aad9cbc3..b87b67838 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -101,6 +101,10 @@ export interface ProjectConfig { wasm: string[]; /** `scriptc.typePatches`, in manifest order. */ typePatches: TypePatchEntry[]; + /** The local files the configuration checked for existence, with the + * answer. A configuration read again from an unchanged package.json is + * reused only while every answer still holds. */ + localFiles: { path: string; exists: boolean }[]; /** SC8004 for every unknown key or value. */ diagnostics: ScrDiagnostic[]; } @@ -153,6 +157,7 @@ export function emptyProjectConfig(): ProjectConfig { nodeIslands: defaultNodeIslands(), wasm: [], typePatches: [], + localFiles: [], diagnostics: [], }; } @@ -435,7 +440,9 @@ function readTypePatches( ); return null; } - if (!trackedFileExists(abs)) { + const exists = trackedFileExists(abs); + config.localFiles.push({ path: abs, exists }); + if (!exists) { problem( path, `names "${v}", which does not exist`, @@ -546,7 +553,12 @@ export function readProjectConfig(entryPath: string): ProjectConfig { const manifest = join(dir, "package.json"); const text = trackedReadFile(manifest); if (text !== null) { - if (cachedConfig !== null && cachedManifest === manifest && cachedText === text) + if ( + cachedConfig !== null && + cachedManifest === manifest && + cachedText === text && + cachedConfig.localFiles.every((file) => trackedFileExists(file.path) === file.exists) + ) return cachedConfig; // A manifest without a `scriptc` key (none spelled out, none behind a // \u escape) has the empty configuration; builds of such projects diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 116715db5..ddcd5b88d 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -2,7 +2,7 @@ * packages: commander's HelpContext.helpWidth @typedef property and ms's * @return tags, patched from the fixtures' package.json; installed-version * selection; and the warnings for patches that do not apply as written. */ -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "vitest"; @@ -153,6 +153,32 @@ test("types gives a package without declarations an entry beside its main", () = expect(typePatchSuppliesTypes(pkg)).toBe(false); }); +test("types gives a package whose main is a .jsx file an entry beside it", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const pkg = join(root, "node_modules/viewjs"); + mkdirSync(join(pkg, "lib"), { recursive: true }); + writeFileSync( + join(pkg, "package.json"), + JSON.stringify({ name: "viewjs", version: "1.0.0", main: "lib/view.jsx" }), + ); + writeFileSync(join(pkg, "lib/view.jsx"), "export const view = 1;\n"); + writeFileSync(join(root, "viewjs.d.ts"), "export declare const view: number;\n"); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ scriptc: { typePatches: { "viewjs@^1.0.0": { types: "./viewjs.d.ts" } } } }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchFileExists(join(pkg, "lib/view.d.ts"))).toBe(true); + expect(typePatchSuppliesTypes(pkg)).toBe(true); + expect(typePatchedText(join(pkg, "lib/view.d.ts"))).toBe( + "export declare const view: number;\n", + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("a files target the installed package does not ship warns", () => { const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); try { diff --git a/tests/fixtures/effect4/node_modules/@effect/platform-node b/tests/fixtures/effect4/node_modules/@effect/platform-node deleted file mode 120000 index aaa396705..000000000 --- a/tests/fixtures/effect4/node_modules/@effect/platform-node +++ /dev/null @@ -1 +0,0 @@ -../../../../../node_modules/.pnpm/@effect+platform-node@4.0.0_effect@4.0.0_redis@6.3.0/node_modules/@effect/platform-node \ No newline at end of file diff --git a/tests/fixtures/effect4/node_modules/effect b/tests/fixtures/effect4/node_modules/effect deleted file mode 120000 index 1b933cdef..000000000 --- a/tests/fixtures/effect4/node_modules/effect +++ /dev/null @@ -1 +0,0 @@ -../../../../node_modules/.pnpm/effect@4.0.0/node_modules/effect \ No newline at end of file From 6d34f4f0a5c23fe2001d8c454af7de5217e52104 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 12:41:21 -0700 Subject: [PATCH 265/395] Leave withUnitArm unchanged for pending placeholders The lowerer's optional-union cache relies on withUnitArm answering for a placeholder before it completes and recomputing after the revision moves (lowerer.test.ts). Only the optional-parameter site needs to keep the placeholder, so the refusal goes back out. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.test.ts | 14 -------------- packages/compiler/src/frontend/type-mapper.ts | 3 --- 2 files changed, 17 deletions(-) diff --git a/packages/compiler/src/frontend/type-mapper.test.ts b/packages/compiler/src/frontend/type-mapper.test.ts index 6e7e339d6..163ba7b9e 100644 --- a/packages/compiler/src/frontend/type-mapper.test.ts +++ b/packages/compiler/src/frontend/type-mapper.test.ts @@ -53,20 +53,6 @@ describe("union arm lookup", () => { }); }); -describe("recursive union placeholders", () => { - test("refuse a unit arm until their arms are known", () => { - const unions = new UnionRegistry(); - const recursive = {} as Type; - const union = unions.recursiveRef(recursive, () => "Recursive"); - const placeholder: IrType = { kind: "union", unionId: union }; - expect(unions.isPendingPlaceholder(union)).toBe(true); - expect(withUndefinedArm(placeholder, unions)).toBeNull(); - unions.finalizeRecursive(recursive, [{ kind: "record", shapeId: "r1" }, UNDEFINED_T]); - expect(unions.isPendingPlaceholder(union)).toBe(false); - expect(withUndefinedArm(placeholder, unions)).toEqual(placeholder); - }); -}); - describe("nullable collection union builders", () => { test.each([mapOf(STRING, F64), setOf(STRING)])( "optional %j fields and generator results share the same union", diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 63eed378d..d819d30a2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6799,9 +6799,6 @@ export function withUnitArm( if (t.kind === "union") { const def = unions.get(t.unionId); if (!def) return null; - // A recursive placeholder's arms arrive when its outer frame finishes; - // extending its (still empty) arm list would drop every arm but the unit. - if (unions.isPendingPlaceholder(t.unionId)) return null; if (def.arms.some((a) => a.kind === kind)) return t; const arms = [...def.arms, unit]; // Date and Promise arms beside other data arms have no union home. From 7222c3e900320b99c906b61f0a2987ed7e008d13 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:44:05 -0500 Subject: [PATCH 266/395] Skip module-pass work that changes nothing in the functions it walks The LLVM emitter's module passes walk every function, and on large programs most of them find nothing to change in most functions. Each pass now avoids work whose result it does not use, with the same output: - scalarizeLiteralResults built, for every function, a set of all locals and all module globals before looking for a producer call. It now collects the names a fresh binding must avoid when it makes the first one. - specializeCallbackCalls walked every body five times: rebindings, node counts and definitions, call keys, untouched bodies, and callers. One walk now records each body's node count, rebindings, written values and helper calls in walk order; the later steps read those records and walk only the bodies the rewrite changed and the copies it made. - holdCharLocalsAsUnits skips its two object walks in functions without a candidate local, and its mention count where no candidate is declared from a charAt or an optional read (the only ones it can hold). - resolveSelfCaptures only asks CaptureFlow whether bindings are unchanged, so its flow collects the facts of the bodies those questions reach instead of scanning every body. - copyStableCaptures reads the function references of the bodies it kept from its CaptureFlow facts instead of walking them again, and makes the set of local ids a copy must avoid only when it makes a copy. - versionLoopBoundParameters counts call sites only when a function has loop-bound parameters. - pairRowSums and inlineExpressionFunctions look for a row sum or an inlinable call before rebuilding a body. --- .../src/ir/callback-specialization.ts | 168 ++++++++++++------ packages/compiler/src/ir/capture-values.ts | 32 +++- packages/compiler/src/ir/char-unit-locals.ts | 10 +- .../compiler/src/ir/expression-inlining.ts | 19 +- .../compiler/src/ir/parameter-versions.ts | 9 +- packages/compiler/src/ir/row-pairs.ts | 18 +- packages/compiler/src/ir/scalar-records.ts | 12 +- packages/compiler/src/ir/self-captures.ts | 3 +- 8 files changed, 195 insertions(+), 76 deletions(-) diff --git a/packages/compiler/src/ir/callback-specialization.ts b/packages/compiler/src/ir/callback-specialization.ts index f4421f34b..ba579061c 100644 --- a/packages/compiler/src/ir/callback-specialization.ts +++ b/packages/compiler/src/ir/callback-specialization.ts @@ -33,6 +33,22 @@ const REBINDING_KINDS: readonly IrNodeKind[] = [ "closure", ]; +/** The rebindings one function's body makes: locals it writes after + * declaring them, and the closures it creates over captured bindings. */ +interface FunctionRebindings { + ids: Set; + sites: { owner: string; fnName: string; captures: string[] }[]; +} + +/** Records `node`'s rebinding into `into` (owner `fnName`). */ +function noteRebinding(fnName: string, node: IrExpr | IrStmt, into: FunctionRebindings): void { + if (node.kind === "assign" || node.kind === "forOf") into.ids.add(node.localId); + else if (node.kind === "tryCatch" && node.catchLocalId !== null) into.ids.add(node.catchLocalId); + else if (node.kind === "assignExpr" || node.kind === "incDec") into.ids.add(node.localId); + else if (node.kind === "closure" && node.captures.length) + into.sites.push({ owner: fnName, fnName: node.fnName, captures: node.captures }); +} + function rebindings( mod: { readonly functions: readonly IrFunction[] }, nodes?: IrNodeIndex, @@ -41,21 +57,31 @@ function rebindings( /** Bindings of each function that a closure it creates writes. */ closureWritten: Map>; } { + const scan = nodes?.scan(REBINDING_KINDS); + const each = mod.functions.map((fn) => { + const own: FunctionRebindings = { ids: new Set(), sites: [] }; + eachNodeOfKinds(fn, scan, (node) => noteRebinding(fn.name, node, own)); + return own; + }); + return closeRebindings(mod, each); +} + +/** The module's rebindings from each function's own (`each`, in module + * order): + * a binding a closure writes through its environment is written by the + * function that created the closure, transitively. */ +function closeRebindings( + mod: { readonly functions: readonly IrFunction[] }, + each: readonly FunctionRebindings[], +): { written: Map>; closureWritten: Map> } { const written = new Map>(); const closureWritten = new Map>(); const sites: { owner: string; fnName: string; captures: string[] }[] = []; - const scan = nodes?.scan(REBINDING_KINDS); - for (const fn of mod.functions) { - const ids = new Set(); - eachNodeOfKinds(fn, scan, (node) => { - if (node.kind === "assign" || node.kind === "forOf") ids.add(node.localId); - else if (node.kind === "tryCatch" && node.catchLocalId !== null) ids.add(node.catchLocalId); - else if (node.kind === "assignExpr" || node.kind === "incDec") ids.add(node.localId); - else if (node.kind === "closure" && node.captures.length) - sites.push({ owner: fn.name, fnName: node.fnName, captures: node.captures }); - }); - written.set(fn.name, ids); - } + mod.functions.forEach((fn, index) => { + const own = each[index]!; + written.set(fn.name, own.ids); + for (const site of own.sites) sites.push(site); + }); const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); for (let changed = true; changed;) { changed = false; @@ -130,9 +156,10 @@ export function specializeCallbackCalls( symbolName: (name: string) => string = (name) => name, ): IrModule { const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); - const { written, closureWritten } = rebindings(mod); const copies = new Map(); - const candidates = new Map(); + // Helpers that may take callbacks: their callback parameters are final + // once the walk below finds which of them the body rebinds. + const helpers = new Map(); for (const fn of mod.functions) { if ( !(fn.ownsPrototype || fn.name.startsWith("%arr.")) || @@ -142,19 +169,13 @@ export function specializeCallbackCalls( fn.captures !== undefined || fn.classCaptures?.length || fn.fastRead !== undefined || - fn.speculativeDispatch + fn.speculativeDispatch || + !fn.params.some((param) => param.type.kind === "func") ) continue; - const nodes = countNodes(fn); - if (nodes > MAX_FUNCTION_NODES) continue; - const rebound = written.get(fn.name)!; - const slots: number[] = []; - fn.params.forEach((param, index) => { - if (param.type.kind === "func" && !rebound.has(param.localId)) slots.push(index); - }); - if (slots.length) candidates.set(fn.name, { fn, slots, nodes }); + helpers.set(fn.name, fn); } - if (!candidates.size) return mod; + if (!helpers.size) return mod; // Function values, exports, entry points and methods: an original // reachable this way stays even when every direct call moves to a copy. @@ -162,16 +183,17 @@ export function specializeCallbackCalls( for (const cls of mod.classes ?? []) for (const method of ["constructor", ...(cls.methods ?? [])]) referenced.add(`%${cls.name}.${method}`); - // Single definitions of function-typed locals and globals. - const definitions = new Map>(); const globalIds = new Set(mod.globals?.map((global) => global.id)); const globalWrites = new Map(); - // The sum of countNodes over every function, counted by the walk below. - let totalNodes = 0; - for (const fn of mod.functions) { + // One walk of every body records what the steps below read: its node + // count (countNodes), its rebindings, the values written into each of its + // bindings, and its direct calls of helpers, in walk order. + const each = mod.functions.map((fn) => { let nodes = 0; + const own: FunctionRebindings = { ids: new Set(), sites: [] }; const values = new Map(); const counts = new Map(); + const calls: (IrExpr & { kind: "call" })[] = []; const record = (id: string, value: IrExpr | null, weight: number): void => { counts.set(id, (counts.get(id) ?? 0) + weight); if (value) { @@ -183,6 +205,7 @@ export function specializeCallbackCalls( everyStmtList(fn.body, { stmt: (stmt) => { nodes++; + noteRebinding(fn.name, stmt, own); if (stmt.kind === "varDecl") record(stmt.localId, stmt.init, stmt.init ? 1 : 0); if (stmt.kind === "assign") { if (globalIds.has(stmt.localId)) { @@ -199,15 +222,42 @@ export function specializeCallbackCalls( }, expr: (expr) => { nodes++; + noteRebinding(fn.name, expr, own); if (expr.kind === "assignExpr" || expr.kind === "incDec") { record(expr.localId, null, 2); if (globalIds.has(expr.localId)) globalWrites.set(expr.localId, []); } + if (expr.kind === "call" && helpers.has(expr.callee)) calls.push(expr); const name = exprFunctionReference(expr); if (name !== undefined) referenced.add(name); return true; }, }); + return { fn, nodes: Math.min(nodes, MAX_FUNCTION_NODES + 1), own, values, counts, calls }; + }); + const { written, closureWritten } = closeRebindings( + mod, + each.map((entry) => entry.own), + ); + const nodesOf = new Map(each.map((entry) => [entry.fn.name, entry.nodes])); + const candidates = new Map(); + for (const fn of helpers.values()) { + const nodes = nodesOf.get(fn.name)!; + if (nodes > MAX_FUNCTION_NODES) continue; + const rebound = written.get(fn.name)!; + const slots: number[] = []; + fn.params.forEach((param, index) => { + if (param.type.kind === "func" && !rebound.has(param.localId)) slots.push(index); + }); + if (slots.length) candidates.set(fn.name, { fn, slots, nodes }); + } + if (!candidates.size) return mod; + + // Single definitions of function-typed locals and globals. + const definitions = new Map>(); + // The sum of countNodes over every function. + let totalNodes = 0; + for (const { fn, nodes, values, counts } of each) { const single = new Map(); const locals = new Set(fn.locals.map((local) => local.id)); const shared = closureWritten.get(fn.name); @@ -221,7 +271,7 @@ export function specializeCallbackCalls( ) single.set(id, list[0]!); definitions.set(fn.name, single); - totalNodes += Math.min(nodes, MAX_FUNCTION_NODES + 1); + totalNodes += nodes; } const globalDefinitions = new Map(); for (const global of mod.globals ?? []) @@ -288,23 +338,18 @@ export function specializeCallbackCalls( // unknown callbacks or function-value entries. const keysOf = new Map>(); const unknown = new Set(); - for (const fn of mod.functions) - everyStmtList(fn.body, { - stmt: () => true, - expr: (expr) => { - if (expr.kind !== "call") return true; - const candidate = candidates.get(expr.callee); - if (!candidate) return true; - const key = callKey(fn, new Map(), expr, candidate); - if (key === "") unknown.add(expr.callee); - else if (key !== null) { - const keys = keysOf.get(expr.callee) ?? new Set(); - keys.add(key); - keysOf.set(expr.callee, keys); - } - return true; - }, - }); + for (const { fn, calls } of each) + for (const call of calls) { + const candidate = candidates.get(call.callee); + if (!candidate) continue; + const key = callKey(fn, new Map(), call, candidate); + if (key === "") unknown.add(call.callee); + else if (key !== null) { + const keys = keysOf.get(call.callee) ?? new Set(); + keys.add(key); + keysOf.set(call.callee, keys); + } + } const specialized = new Set(); for (const [name, keys] of keysOf) if (keys.size > 1 || unknown.has(name) || referenced.has(name)) specialized.add(name); @@ -419,12 +464,9 @@ export function specializeCallbackCalls( return owner.body.map(stmt); } - const functionsOut = mod.functions.map((fn) => { + const functionsOut = each.map(({ fn, calls }) => { // Most bodies call no specialized original; keep their nodes. - const untouched = everyStmtList(fn.body, { - stmt: () => true, - expr: (expr) => expr.kind !== "call" || !specialized.has(expr.callee), - }); + const untouched = calls.every((call) => !specialized.has(call.callee)); return untouched ? fn : { ...fn, body: rewriteBody(fn, new Map(), new Set(), false) }; }); for (let i = 0; i < pending.length; i++) { @@ -437,19 +479,29 @@ export function specializeCallbackCalls( // Drop originals no remaining code reaches: every direct call moved to a // copy and nothing names them as a value. const callers = new Map>(); - for (const fn of functionsOut) + const call = (callee: string, caller: string): void => { + if (!specialized.has(callee)) return; + const set = callers.get(callee) ?? new Set(); + set.add(caller); + callers.set(callee, set); + }; + functionsOut.forEach((fn, index) => { + // A body the rewrite kept has the calls the walk recorded, and its + // closures are already in `referenced`. + const kept = each[index]; + if (kept !== undefined && kept.fn === fn) { + for (const site of kept.calls) call(site.callee, fn.name); + return; + } everyStmtList(fn.body, { stmt: () => true, expr: (expr) => { - if (expr.kind === "call" && specialized.has(expr.callee)) { - const set = callers.get(expr.callee) ?? new Set(); - set.add(fn.name); - callers.set(expr.callee, set); - } + if (expr.kind === "call") call(expr.callee, fn.name); if (expr.kind === "closure" && specialized.has(expr.fnName)) referenced.add(expr.fnName); return true; }, }); + }); const live = new Set(); const work: string[] = []; for (const fn of functionsOut) diff --git a/packages/compiler/src/ir/capture-values.ts b/packages/compiler/src/ir/capture-values.ts index bcc587f1b..19ec41363 100644 --- a/packages/compiler/src/ir/capture-values.ts +++ b/packages/compiler/src/ir/capture-values.ts @@ -86,7 +86,12 @@ export function copyStableCaptures(mod: IrModule): IrModule { // A closure body whose every use became a direct call is only reachable // through its copy now. const referenced = new Set(moduleFunctionReferences(mod)); - for (const fn of functions) + functions.forEach((fn, index) => { + // A body the rewrite kept names what the flow recorded of it. + if (fn === mod.functions[index]) { + for (const name of flow.references(fn)) referenced.add(name); + return; + } everyStmtList(fn.body, { stmt: () => true, expr: (e) => { @@ -95,6 +100,7 @@ export function copyStableCaptures(mod: IrModule): IrModule { return true; }, }); + }); const clones = flow.directClones(); return { ...mod, @@ -251,6 +257,9 @@ interface FunctionFacts { captureIndex: Map; classCaptures: Set; params: Set; + /** Functions the body names: direct call callees and function values + * (exprFunctionReference). */ + references: Set; } interface CreationSite { @@ -273,17 +282,26 @@ export class CaptureFlow { /** Environment-free bodies called directly, and their copies' names. */ private readonly clones = new Map(); - constructor(functions: readonly IrFunction[]) { + /** With `scan` false, each body's facts are collected when a question + * first needs them, and only unchanged() may be asked: the other + * questions read creation sites, which come from scanning every body. */ + constructor(functions: readonly IrFunction[], scan = true) { for (const fn of functions) this.functions.set(fn.name, fn); this.receivers = new AmbientReceiverReaders(this.functions); // Creation sites are collected while scanning each body. - for (const fn of functions) this.factsOf(fn); + if (scan) for (const fn of functions) this.factsOf(fn); } directClones(): ReadonlyMap { return this.clones; } + /** The functions the body of `fn`, one of the functions this flow was + * made from, names (FunctionFacts.references). */ + references(fn: IrFunction): ReadonlySet { + return this.factsOf(fn).references; + } + /** A lifted body that captures nothing and can be entered like a declared * function: synchronous, never naming its own closure, and unable to * observe the ambient receiver a closure call would bracket. */ @@ -459,6 +477,7 @@ export class CaptureFlow { captureIndex: new Map(), classCaptures: new Set((fn.classCaptures ?? []).map((c) => c.localId)), params: new Set(fn.params.map((p) => p.localId)), + references: new Set(), }; (fn.captures ?? []).forEach((c, index) => { if (!facts.captureIndex.has(c.localId)) facts.captureIndex.set(c.localId, index); @@ -511,6 +530,8 @@ export class CaptureFlow { facts.writes.set(id, (facts.writes.get(id) ?? 0) + 1); }; const expr = (e: IrExpr): boolean => { + const referenced = e.kind === "call" ? e.callee : exprFunctionReference(e); + if (referenced !== undefined) facts.references.add(referenced); switch (e.kind) { case "varRef": facts.reads.set(e.localId, (facts.reads.get(e.localId) ?? 0) + 1); @@ -707,8 +728,11 @@ export class CaptureFlow { const splits = new Map(); const entry: IrStmt[] = []; const locals: IrLocal[] = []; - const used = new Set(fn.locals.map((l) => l.id)); + // The local ids a copy must avoid, collected for the first copy: most + // functions make none. + let used: Set | null = null; const fresh = (local: IrLocal): IrLocal => { + used ??= new Set(fn.locals.map((l) => l.id)); let k = 0; while (used.has(`${local.name}.${k}`)) k++; const id = `${local.name}.${k}`; diff --git a/packages/compiler/src/ir/char-unit-locals.ts b/packages/compiler/src/ir/char-unit-locals.ts index aff952909..fc1d5873b 100644 --- a/packages/compiler/src/ir/char-unit-locals.ts +++ b/packages/compiler/src/ir/char-unit-locals.ts @@ -300,6 +300,8 @@ function rewriteFunction(fn: IrFunction): IrFunction { // or as copies of another such local (a switch's discriminant temp). const reads = new Map(); const copyOf = new Map(); + // Whether a candidate string is declared from a charAt. + let charAtDeclared = false; const collect = (value: unknown): void => { if (Array.isArray(value)) for (const item of value) collect(item); else if (value !== null && typeof value === "object") { @@ -307,6 +309,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { if (node.kind === "varDecl" && candidates.has(node.localId as string) && node.init) { const id = node.localId as string; const init = node.init as IrExpr; + if (locals.get(id)?.type.kind === "string" && isCharAt(init)) charAtDeclared = true; const read = optionalCharRead(init); if (read) reads.set(id, read); else if (init.kind === "varRef" && reads.has(init.localId)) { @@ -317,7 +320,8 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const key in node) if (key !== "loc") collect(node[key]); } }; - collect(fn.body); + // Both walks only record candidates: a function without one holds none. + if (candidates.size > 0) collect(fn.body); const localTags = (operand: IrExpr): OptionalCharRead | null => operand.kind === "varRef" ? (reads.get(operand.localId) ?? null) : null; @@ -344,7 +348,9 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const key in node) if (key !== "loc") count(node[key]); } }; - count(fn.body); + // Only candidates declared from a charAt or an optional read can be held + // (understoodMention), and count() only decides which are. + if (reads.size > 0 || charAtDeclared) count(fn.body); // A copy is held only with its source, and the source's mention in the // copy's initializer is understood only while the copy is held. const held = new Set(); diff --git a/packages/compiler/src/ir/expression-inlining.ts b/packages/compiler/src/ir/expression-inlining.ts index 377cfaf3f..1282cdb79 100644 --- a/packages/compiler/src/ir/expression-inlining.ts +++ b/packages/compiler/src/ir/expression-inlining.ts @@ -128,15 +128,24 @@ export function inlineExpressionFunctions(mod: IrModule): IrModule { const functions = mod.functions.map((fn) => { const locals = new Map(fn.locals.map((local) => [local.id, local])); const inLoops = callsInLoops(fn.body); - if (inLoops.size === 0) return fn; + // Each call the rewrite inlines: of an expression function, in a loop, + // with arguments the copy may repeat. + const inlinable = (e: IrExpr & { kind: "call" }): boolean => { + if (e.callee === fn.name) return false; + const target = expressions.get(e.callee); + return ( + target !== undefined && + target.fn.params.length === e.args.length && + e.args.every((arg) => duplicableArgument(arg, locals)) + ); + }; + if (![...inLoops].some((e) => e.kind === "call" && inlinable(e))) return fn; let inlined = false; const body = transformStmtList(fn.body, { stmt: (s) => s, expr: (e) => { - if (e.kind !== "call" || e.callee === fn.name || !inLoops.has(e)) return e; - const target = expressions.get(e.callee); - if (target === undefined || target.fn.params.length !== e.args.length) return e; - if (!e.args.every((arg) => duplicableArgument(arg, locals))) return e; + if (e.kind !== "call" || !inLoops.has(e) || !inlinable(e)) return e; + const target = expressions.get(e.callee)!; const args = new Map(target.fn.params.map((param, i) => [param.localId, e.args[i]!])); inlined = true; return copy(target.value, (ref) => { diff --git a/packages/compiler/src/ir/parameter-versions.ts b/packages/compiler/src/ir/parameter-versions.ts index 88232251c..5156d4bff 100644 --- a/packages/compiler/src/ir/parameter-versions.ts +++ b/packages/compiler/src/ir/parameter-versions.ts @@ -184,7 +184,8 @@ export function versionLoopBoundParameters( symbolName: (name: string) => string = (name) => name, ): IrModule { const candidates: { fn: IrFunction; params: Set }[] = []; - const sites = callSites(mod); + // Call sites are read only for functions with loop-bound parameters. + let sites: ReturnType | null = null; const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); const members = new Set((mod.classes ?? []).flatMap((cls) => cls.methods ?? [])); for (const fn of mod.functions) { @@ -207,7 +208,11 @@ export function versionLoopBoundParameters( const local = locals.get(id); if (!local || local.boxed || local.tdz) params.delete(id); } - if (params.size === 0 || size(fn) > MAX_FUNCTION_NODES || keepsSingleBody(fn, sites, functions)) + if ( + params.size === 0 || + size(fn) > MAX_FUNCTION_NODES || + keepsSingleBody(fn, (sites ??= callSites(mod)), functions) + ) continue; candidates.push({ fn, params }); } diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index 2e189f42e..d5342d113 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -7,7 +7,13 @@ import { type IrModule, type IrStmt, } from "./ir.js"; -import { everyExpr, everyExprChild, mapExprChildren, transformStmtList } from "./traverse.js"; +import { + everyExpr, + everyExprChild, + everyStmtList, + mapExprChildren, + transformStmtList, +} from "./traverse.js"; /** Inner-loop expressions above this many nodes keep the loop as written. */ const MAX_TERM_NODES = 64; @@ -419,6 +425,16 @@ export function pairRowSums(mod: IrModule): IrModule { const functions = mod.functions.map((fn): IrFunction => { if (fn.async || fn.generator) return fn; const locals = new Map(fn.locals.map((local) => [local.id, local])); + // Most bodies hold no row sum: find one before copying the body. + let found = false; + everyStmtList(fn.body, { + expr: () => !found, + stmt: (s) => { + if (s.kind === "for" && matchRowSum(s, locals) !== null) found = true; + return !found; + }, + }); + if (!found) return fn; const added: IrLocal[] = []; const ids = new Set(fn.locals.map((local) => local.id)); const fresh = (name: string): string => { diff --git a/packages/compiler/src/ir/scalar-records.ts b/packages/compiler/src/ir/scalar-records.ts index 5d57cb3ec..088fd8461 100644 --- a/packages/compiler/src/ir/scalar-records.ts +++ b/packages/compiler/src/ir/scalar-records.ts @@ -554,17 +554,23 @@ export function scalarizeLiteralResults(mod: IrModule): IrModule { } if (producers.size === 0) return mod; let changed = false; + const globalIds = new Set((mod.globals ?? []).map((g) => g.id)); const functions = mod.functions.map((fn): IrFunction => { if (fn.async || fn.generator) return fn; const locals = new Map(fn.locals.map((l) => [l.id, l])); - const used = new Set([...locals.keys(), ...(mod.globals ?? []).map((g) => g.id)]); - collectLabels(fn.body, used); + // The names a fresh binding must avoid, collected when the first one is + // made: most functions inline no producer. + let used: Set | null = null; let next = 0; const fresh = (): string => { + if (used === null) { + used = new Set(locals.keys()); + collectLabels(fn.body, used); + } let id: string; do { id = `%literal.${next++}`; - } while (used.has(id)); + } while (used.has(id) || globalIds.has(id)); used.add(id); return id; }; diff --git a/packages/compiler/src/ir/self-captures.ts b/packages/compiler/src/ir/self-captures.ts index 74f634d4a..70242cb8b 100644 --- a/packages/compiler/src/ir/self-captures.ts +++ b/packages/compiler/src/ir/self-captures.ts @@ -107,7 +107,8 @@ export function resolveSelfCaptures(mod: IrModule): IrModule { if (creator.captures?.some((c) => c.localId === source)) return false; if (creator.classCaptures?.some((c) => c.localId === source)) return false; if (declarations.get(owner)!.get(source) !== 1) return false; - flow ??= new CaptureFlow(mod.functions); + // Only unchanged() is asked: the flow reads just the bodies it needs. + flow ??= new CaptureFlow(mod.functions, false); return flow.unchanged(owner, source, definition.writes); }); if (qualifies) { From 6b51aa883f99337b45a7e854f0c27eef1f6b2f65 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:46:01 -0500 Subject: [PATCH 267/395] Keep -0 row and column starts out of the paired row-sum nest The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a -0 start into +0, and a term can tell the two apart (1 / i on the first row). The bounds test now requires 1 / b > 0, which -0 fails, so such a nest runs as written. Corpus 8355 adds row and column starts of -0 with terms that see the sign. --- packages/compiler/src/ir/row-pairs.ts | 13 ++++++++----- tests/corpus/8355-row-pair-columns.ts | 25 +++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index e6b3889a5..d54f37b0b 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -203,7 +203,7 @@ function copy( } const bin = ( - op: "+" | "-" | "&" | "|", + op: "+" | "-" | "/" | "&" | "|", left: IrExpr, right: IrExpr, loc: IrExpr["loc"], @@ -374,9 +374,10 @@ function pairRows( ], loc, }; - // Every bound is an integer in [0, SMALL_BOUND], so `row < end - 1` is - // `row + 1 < end` (and the same for the columns), and the masks in - // `bounds` leave the bounds unchanged while proving both counters' ranges. + // Every bound is an integer in [0, SMALL_BOUND] and not -0, so `row < + // end - 1` is `row + 1 < end` (and the same for the columns), and the + // masks in `bounds` leave the bounds unchanged while proving both + // counters' ranges. // The nest has at least two rows. The column bounds' tests and the // overlap tests run only then, so they never read an array the program // assigns only when there are rows. Rows are @@ -398,9 +399,11 @@ function pairRows( type: BOOL, loc, }); + // `1 / e > 0` holds for +0 and every positive bound, but not for -0: + // masking would turn a -0 start into +0, which a term can tell apart. const small = (e: IrExpr): IrExpr[] => [ int32(e), - compare(">=", e, 0), + { kind: "bin", op: ">", left: bin("/", num(1), again(e), loc), right: num(0), type: BOOL, loc }, compare("<=", e, SMALL_BOUND), ]; const tests: IrExpr[] = [ diff --git a/tests/corpus/8355-row-pair-columns.ts b/tests/corpus/8355-row-pair-columns.ts index 96ba26153..dccd9ca6d 100644 --- a/tests/corpus/8355-row-pair-columns.ts +++ b/tests/corpus/8355-row-pair-columns.ts @@ -2,8 +2,9 @@ // and two columns, column ranges that start above zero or at or past their // end, fractional, negative, NaN and very large column bounds, a column // bound read from an array's length, terms that read two elements per -// column, and row bounds at and past 2^25. Every sum adds its columns in -// order, so each has the bits of the loop as written. +// column, row bounds at and past 2^25, and row and column starts of -0 +// with terms that tell -0 from +0. Every sum adds its columns in order, +// so each has the bits of the loop as written. function rows(u: Float64Array, v: Float64Array, start: number, end: number, from: number, to: number): void { for (let i = start; i < end; i++) { let t = 0; @@ -20,6 +21,16 @@ function pairs(u: Float64Array, w: Float32Array, v: Float64Array, end: number): } } +// 1 / (i * 4 - j) is -Infinity at a -0 row and +0 column, and +// 1 / (j - i * 4) at a -0 column and +0 row: the first pass sees the sign. +function signs(v: Float64Array, start: number, end: number, from: number, to: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = from; j < to; j++) t += 1 / (i * 4 - j) + 1 / (j - i * 4) + 1 / (i * 4 + j + 1); + v[i] = t; + } +} + const show = (label: string, v: Float64Array): void => { const parts: string[] = []; for (const x of v) parts.push(Object.is(x, -0) ? "-0" : x.toPrecision(17)); @@ -71,3 +82,13 @@ const short = new Float64Array(4).map((_, k) => k * 2.5); const out = new Float64Array(5); pairs(short, w, out, 5); show("two reads short", out); +for (const [start, from] of [ + [0, 0], + [-0, 0], + [0, -0], + [-0, -0], +]) { + const v = new Float64Array(4); + signs(v, start!, 4, from!, 5); + show(`signs ${Object.is(start, -0) ? "-0" : start} ${Object.is(from, -0) ? "-0" : from}`, v); +} From f791c1d0061b946c4f1d2f24ca9673ca50e6efba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:47:24 -0500 Subject: [PATCH 268/395] Address review feedback --- .../src/frontend/lowering/record-inherited.ts | 144 +++++++++++++++--- .../8362-record-inherited-order.ts.json | 6 + tests/corpus/8362-record-inherited-order.ts | 40 +++++ .../record-inherited/member-signature.ts | 12 ++ .../8362-record-inherited-order.ts.json | 1 + tests/harness/record-inherited-member.test.ts | 32 ++++ 6 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json create mode 100644 tests/corpus/8362-record-inherited-order.ts create mode 100644 tests/fixtures/record-inherited/member-signature.ts create mode 100644 tests/harness/corpus-costs/8362-record-inherited-order.ts.json create mode 100644 tests/harness/record-inherited-member.test.ts diff --git a/packages/compiler/src/frontend/lowering/record-inherited.ts b/packages/compiler/src/frontend/lowering/record-inherited.ts index 2c01ccd0a..65d070f1d 100644 --- a/packages/compiler/src/frontend/lowering/record-inherited.ts +++ b/packages/compiler/src/frontend/lowering/record-inherited.ts @@ -104,14 +104,19 @@ export function withInheritedMethods( if (form === null || !shape || shape.tuple) return read; const loc = read.loc; const stmts: IrStmt[] = []; - const stable = (value: IrExpr, name: string): IrExpr => { - if (isSafeToRepeat(value) || value.kind === "strLit") return value; + const repeatable = (value: IrExpr): boolean => isSafeToRepeat(value) || value.kind === "strLit"; + const stable = (value: IrExpr, name: string, keep: boolean): IrExpr => { + if (keep) return value; const local = lowerer.declareHiddenLocal(name, value.type); stmts.push({ kind: "varDecl", localId: local.id, init: value, loc }); return varRef(local.id, value.type, loc); }; - const obj = stable(read.obj, "%inheritedRecv"); - const key = stable(read.key, "%inheritedKey"); + // The receiver is evaluated before the key: when computing the key runs + // code (it may reassign the receiver's variable), the receiver is held + // first, even when reading it again would be safe on its own. + const keyRepeatable = repeatable(read.key); + const obj = stable(read.obj, "%inheritedRecv", repeatable(read.obj) && keyRepeatable); + const key = stable(read.key, "%inheritedKey", keyRepeatable); const helper = inheritedReadHelper(lowerer, read.shapeId, read.type, form, bound, loc); const fallback: IrExpr = { kind: "call", callee: helper, args: [obj, key], type: read.type, loc }; if ( @@ -481,19 +486,52 @@ function builtinAnswer( // No object is created with a typed record as its prototype. return { kind: "boolLit", value: false, type: BOOL, loc }; default: { - const key = propertyKeyOf(lowerer, args[0], loc); - return { + // hasOwnProperty and propertyIsEnumerable: ToPropertyKey keeps a + // symbol a symbol, and a record's own keys are strings, so a symbol + // key is no entry of it. + const no: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; + const arg = args[0]; + if (arg?.type.kind === "symbol") return sequence(arg, no, loc); + const has = (key: IrExpr): IrExpr => ({ kind: "call", callee: recordHasOwnHelper(lowerer, shapeId, loc), args: [o, key], type: BOOL, loc, + }); + if (arg === undefined || !mayBeSymbol(arg.type)) return has(propertyKeyOf(lowerer, arg, loc)); + const id = declare("%protoKey", DYN); + const held = varRef(id, DYN, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: id, init: lowerer.coerceToExpected(arg, DYN), loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "symbol", value: held, type: BOOL, loc } as IrExpr, + then: no, + else_: has(propertyKeyOf(lowerer, held, loc)), + type: BOOL, + loc, + }, + type: BOOL, + loc, }; } } } -/** ToPropertyKey of a built-in's key argument (absent: "undefined"). */ +/** Whether a value of `type` may be a symbol at run time. */ +function mayBeSymbol(type: IrType): boolean { + return !( + isUnitType(type) || + type.kind === "string" || + type.kind === "f64" || + type.kind === "bool" + ); +} + +/** ToPropertyKey of a built-in's key argument (absent: "undefined"), for + * keys that are not symbols. */ function propertyKeyOf(lowerer: Lowerer, arg: IrExpr | undefined, loc: SrcLoc): IrExpr { if (arg === undefined || isUnitType(arg.type)) return { @@ -552,6 +590,7 @@ function callOwnOrBuiltin( want: IrType, calleeText: string, loc: SrcLoc, + resolved?: { has: IrExpr; own: IrExpr }, ): IrExpr { const shape = lowerer.shapes.get(shapeId)!; const field = shape.fields.find((f) => f.name === method); @@ -569,7 +608,47 @@ function callOwnOrBuiltin( }; const builtinAsWant = convertTo(lowerer, builtin, want) ?? builtin; if (ownType === undefined || !callableType(lowerer, ownType)) return builtinAsWant; - const own: IrExpr = field + const own = resolved?.own ?? ownEntryRead(lowerer, o, shapeId, method, loc); + const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); + return { + kind: "ternary", + cond: resolved?.has ?? ownEntryTest(lowerer, o, shapeId, method, loc), + then: called, + else_: builtinAsWant, + type: builtinAsWant.type, + loc, + }; +} + +/** Whether the record holds an own entry named `method`. */ +function ownEntryTest( + lowerer: Lowerer, + o: IrExpr, + shapeId: string, + method: string, + loc: SrcLoc, +): IrExpr { + return { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, { kind: "strLit", value: method, type: STRING, loc }], + type: BOOL, + loc, + }; +} + +/** The record's own entry named `method`: its field, or its overflow entry + * (undefined when absent). */ +function ownEntryRead( + lowerer: Lowerer, + o: IrExpr, + shapeId: string, + method: string, + loc: SrcLoc, +): IrExpr { + const shape = lowerer.shapes.get(shapeId)!; + const field = shape.fields.find((f) => f.name === method); + return field ? { kind: "recordGet", obj: o, shapeId, field: method, type: field.type, loc } : { kind: "recordKeyGet", @@ -580,21 +659,6 @@ function callOwnOrBuiltin( type: lowerer.withUndefinedArmOf(shape.indexValue!) ?? shape.indexValue!, loc, }; - const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); - return { - kind: "ternary", - cond: { - kind: "call", - callee: recordHasOwnHelper(lowerer, shapeId, loc), - args: [o, { kind: "strLit", value: method, type: STRING, loc }], - type: BOOL, - loc, - }, - then: called, - else_: builtinAsWant, - type: builtinAsWant.type, - loc, - }; } /** Call the own entry `own` (present, of any stored type) with `args`. A @@ -727,6 +791,25 @@ export function lowerRecordInheritedCall( const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); stmts.push({ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }); const o = varRef(recvLocal.id, receiver.type, loc); + // `o.m(...)` reads the member before its arguments run: an argument that + // adds, deletes or replaces the own method does not change the callee. + const field = shape.fields.find((f) => f.name === method); + const ownType = field ? field.type : shape.indexValue; + let resolved: { has: IrExpr; own: IrExpr } | undefined; + if (ownType !== undefined && callableType(lowerer, ownType)) { + const shapeId = receiver.type.shapeId; + const hasLocal = lowerer.declareHiddenLocal("%protoHas", BOOL); + stmts.push({ + kind: "varDecl", + localId: hasLocal.id, + init: ownEntryTest(lowerer, o, shapeId, method, loc), + loc, + }); + const read = ownEntryRead(lowerer, o, shapeId, method, loc); + const ownLocal = lowerer.declareHiddenLocal("%protoOwn", read.type); + stmts.push({ kind: "varDecl", localId: ownLocal.id, init: read, loc }); + resolved = { has: varRef(hasLocal.id, BOOL, loc), own: varRef(ownLocal.id, read.type, loc) }; + } const args: IrExpr[] = []; for (const node of call.arguments.nodes) { const arg = lowerer.lowerExpr(node); @@ -744,6 +827,7 @@ export function lowerRecordInheritedCall( want, ts.getTextOfNode(access), loc, + resolved, ); return { kind: "seqExpr", stmts, result, type: result.type, loc }; } @@ -751,7 +835,10 @@ export function lowerRecordInheritedCall( /** `r.toString` and the other Object.prototype methods read by name (not * called) on an index-signature record: an own entry of the member's * function type when the record holds one, else the built-in as a plain - * function value. Null when the checker's member type is not a function. */ + * function value. An own entry of another type than the member's (a + * function of another signature) throws the checked-conversion TypeError: + * the program's types claimed the member. Null when the checker's member + * type is not a function. */ export function lowerRecordInheritedMember( lowerer: Lowerer, receiver: IrExpr, @@ -810,6 +897,15 @@ export function lowerRecordInheritedMember( loc, }; } + } else if (shape.indexValue && callableType(lowerer, shape.indexValue)) { + result = { + kind: "ternary", + cond: ownEntryTest(lowerer, o, shapeId, method, loc), + then: nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, want, loc), + else_: builtin, + type: want, + loc, + }; } return { kind: "seqExpr", stmts, result, type: want, loc }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json new file mode 100644 index 000000000..e080ae4d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8362-record-inherited-order.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8362-record-inherited-order.ts b/tests/corpus/8362-record-inherited-order.ts new file mode 100644 index 000000000..33a72eb91 --- /dev/null +++ b/tests/corpus/8362-record-inherited-order.ts @@ -0,0 +1,40 @@ +// Object.prototype's methods on typed records keep JavaScript's evaluation +// order and key conversions. + +// A function-table key that reassigns the receiver reads the table +// evaluated before the key. +let table: Record string> = { a: () => "first" }; +const other: Record string> = { a: () => "second" }; +function key(): string { + table = other; + return "a"; +} +console.log(String(table[key()]())); + +// hasOwnProperty and propertyIsEnumerable with a symbol key on a record of +// functions test the symbol key. +const fns: Record number> = { double: (n) => n * 2 }; +const sym = Symbol("s"); +console.log(fns.hasOwnProperty(sym as unknown as string), fns.propertyIsEnumerable(sym as unknown as string)); + +// A call by name reads the method before its arguments run: an argument +// that adds, replaces or deletes the own method does not change the callee. +type Has = Record boolean>; +function add(r: Has): string { + r.hasOwnProperty = () => true; + return "zzz"; +} +function swap(r: Has): string { + r.hasOwnProperty = (key) => key === "b"; + return "a"; +} +function drop(r: Has): string { + delete r["hasOwnProperty"]; + return "zzz"; +} +const added: Has = {}; +console.log(added.hasOwnProperty(add(added)), added.hasOwnProperty("zzz")); +const swapped: Has = { hasOwnProperty: (key) => key === "a" }; +console.log(swapped.hasOwnProperty(swap(swapped)), swapped.hasOwnProperty("a")); +const removed: Has = { hasOwnProperty: () => true }; +console.log(removed.hasOwnProperty(drop(removed)), removed.hasOwnProperty("zzz")); diff --git a/tests/fixtures/record-inherited/member-signature.ts b/tests/fixtures/record-inherited/member-signature.ts new file mode 100644 index 000000000..c9c65f8ad --- /dev/null +++ b/tests/fixtures/record-inherited/member-signature.ts @@ -0,0 +1,12 @@ +// A dot read of a prototype-named member whose own entry has another +// signature than the member's (tests/harness/record-inherited-member.test.ts). +const plain: Record number> = { double: (x) => x * 2 }; +const builtin = plain.toString; +console.log(typeof builtin); +const numeric: Record number> = { toString: (x: number) => x + 1 }; +try { + const own = numeric.toString; + console.log(typeof own); +} catch (e) { + console.log(e instanceof TypeError, (e as Error).message); +} diff --git a/tests/harness/corpus-costs/8362-record-inherited-order.ts.json b/tests/harness/corpus-costs/8362-record-inherited-order.ts.json new file mode 100644 index 000000000..8bcdf4a18 --- /dev/null +++ b/tests/harness/corpus-costs/8362-record-inherited-order.ts.json @@ -0,0 +1 @@ +{ "plain": 2000, "san": 2000 } diff --git a/tests/harness/record-inherited-member.test.ts b/tests/harness/record-inherited-member.test.ts new file mode 100644 index 000000000..ed14524d2 --- /dev/null +++ b/tests/harness/record-inherited-member.test.ts @@ -0,0 +1,32 @@ +/* Object.prototype's methods on typed records (frontend/lowering/ + * record-inherited.ts): a dot read of a prototype-named member answers the + * built-in when the record has no own entry, and throws the checked + * conversion's TypeError when its own entry is a function of another + * signature than the member's, which the program's types claimed. Node reads + * the own function there, so the program cannot be a corpus case. */ +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; +import { OTHER_ARM_FAILURE_MESSAGE } from "../../packages/compiler/src/frontend/lowering/coercions/unions.js"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const entry = join(repoRoot, "tests/fixtures/record-inherited/member-signature.ts"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/record-inherited-member"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test( + "a prototype-named member read with an own function of another signature throws", + { timeout: 300_000 }, + async () => { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, "main"), outDir, sanitize }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const { stdout } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" }); + expect(stdout).toBe(`function\ntrue ${OTHER_ARM_FAILURE_MESSAGE}\n`); + }, +); From c21fc0120180fb89bf6ff2251bba30d6739f3c7b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:49:55 -0500 Subject: [PATCH 269/395] Key bound alias instances by the declared alias The compiler compiles itself, and a Map keyed by another Map is outside the key domains it compiles (SC2009 in the self-hosted frontend). The Lowerer's table is now keyed by the declared alias reference: each entry lists the instances that instantiate it with their ts-level bindings maps, and the resolver picks the current instance's by identity. --- .../frontend/lowering/generic-functions.ts | 7 ++-- .../compiler/src/frontend/lowering/lowerer.ts | 32 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index a2eda839a..1769e8562 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1041,11 +1041,12 @@ function unifySignatureBindings( iAliasArgs.length === dAliasArgs.length ) { if (tsBindings && declared !== inst) { - let pairs = lowerer.boundAliasInstances.get(tsBindings); - if (!pairs) lowerer.boundAliasInstances.set(tsBindings, (pairs = new Map())); + let entries = lowerer.boundAliasInstances.get(declared); + if (!entries) lowerer.boundAliasInstances.set(declared, (entries = [])); // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. - if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); + if (!entries.some((entry) => entry.tsBindings === tsBindings)) + entries.push({ tsBindings, instance: lowerer.boundAliasTsResolver(inst) ?? inst }); } dAliasArgs.forEach((da, i) => { const ia = iAliasArgs[i]; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 216851c9c..c77741997 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2233,16 +2233,19 @@ export class Lowerer { * the body needs — indexed accesses (`T[K]` needs K's literal key) and * keyed record reads (`o[k]` where k's type is a literal-bound K). */ typeParamTsBindings: Map | null = null; - /** Generic alias references of a call-keyed instance's declared signature - * (`Catalog`), each paired with the same alias as the call's - * resolved signature instantiates it (`Catalog<{ g1: … }, "acme">`), - * keyed by the instance's ts-level bindings (typeParamTsBindings). The - * checker keeps the alias symbolic inside the generic body and offers no - * way to instantiate it with the bound arguments, but alias - * instantiations are interned: the body's `x as Catalog` is the - * very type the signature declares, so the pair recorded during binding - * unification is that re-instantiation. */ - readonly boundAliasInstances = new Map, Map>(); + /** Generic alias references of call-keyed instances' declared signatures + * (`Catalog`), each with the same alias as every call's resolved + * signature instantiates it (`Catalog<{ g1: … }, "acme">`), together with + * that instance's ts-level bindings map (typeParamTsBindings), which every + * scope that lowers the instance installs. The checker keeps the alias + * symbolic inside the generic body and offers no way to instantiate it + * with the bound arguments, but alias instantiations are interned: the + * body's `x as Catalog` is the very type the signature declares, so + * the pair recorded during unification is that re-instantiation. */ + readonly boundAliasInstances = new Map< + ts.Type, + { tsBindings: Map; instance: ts.Type }[] + >(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -9563,10 +9566,15 @@ export class Lowerer { /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares - * (boundAliasInstances). Inert outside generic function instantiation. */ + * (boundAliasInstances), or null when its signature never instantiated + * that reference. Inert outside generic function instantiation. */ readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; - return this.boundAliasInstances.get(this.typeParamTsBindings)?.get(t) ?? null; + const tsBindings = this.typeParamTsBindings; + return ( + this.boundAliasInstances.get(t)?.find((entry) => entry.tsBindings === tsBindings)?.instance ?? + null + ); }; irTypeOf(node: ts.Node): IrType { From da616a27cd64135bf76401162630d0817a8df1b4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:57:45 -0500 Subject: [PATCH 270/395] Install the global lookup for function-local snapshots, and release navigator values read alone A computed read through a snapshot of the global object taken in a function (`const g = globalThis; return g[name];`) threw SC2020 for builtin globals: the scan that decides whether a module installs the builtin-global lookup runs before function bodies are lowered, when the snapshot's alias is not registered yet. The scan now also recognizes a variable whose only declaration initializes it with the global object. navigator.language and navigator.languages cache their answers, but only taking the navigator object registered their release at exit, so a program that read just those members left them live and a sanitized build exited with status 99. The first cached value now registers the release. Corpus 8161 reads through a function-local snapshot, and 8168 reads navigator.language, navigator.languages and navigator.platform without the navigator object. --- .../src/frontend/lowering/global-values.ts | 23 ++++++++++++++++++- .../8168-navigator-language-values.cjs.json | 6 +++++ packages/runtime/src/scr_node_builtin.c | 17 +++++++++++++- tests/corpus/8161-global-computed-reads.cjs | 11 +++++++++ .../corpus/8168-navigator-language-values.cjs | 8 +++++++ .../8168-navigator-language-values.cjs.json | 1 + 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json create mode 100644 tests/corpus/8168-navigator-language-values.cjs create mode 100644 tests/harness/corpus-costs/8168-navigator-language-values.cjs.json diff --git a/packages/compiler/src/frontend/lowering/global-values.ts b/packages/compiler/src/frontend/lowering/global-values.ts index 44e8639f4..264c99337 100644 --- a/packages/compiler/src/frontend/lowering/global-values.ts +++ b/packages/compiler/src/frontend/lowering/global-values.ts @@ -63,7 +63,7 @@ export function computedGlobalReadOf(lowerer: Lowerer, sf: ts.SourceFile): ts.Ex ts.walkPreorder(sf, (node) => { if ( (ts.isElementAccessExpression(node) || ts.isForInStatement(node)) && - stdlibGlobalNameOf(lowerer, node.expression) === "globalThis" && + isGlobalObjectRead(lowerer, node.expression) && (ts.isForInStatement(node) || readsBuiltinByName(lowerer, node)) ) { found = node.expression; @@ -75,6 +75,27 @@ export function computedGlobalReadOf(lowerer: Lowerer, sf: ts.SourceFile): ts.Ex return found; } +/** Whether `expr` reads the global object: a spelling of it, or a variable + * whose only declaration initializes it with one (`const g = globalThis`). + * The scan runs before function bodies are lowered, so the alias of a + * variable declared in a function is not registered yet. */ +function isGlobalObjectRead(lowerer: Lowerer, expr: ts.Expression): boolean { + if (stdlibGlobalNameOf(lowerer, expr) === "globalThis") return true; + let inner = expr; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + if (!ts.isIdentifier(inner)) return false; + const declarations = lowerer.checker.getSymbolAtLocation(inner)?.declarations ?? []; + const declaration = declarations[0]; + return ( + declarations.length === 1 && + declaration !== undefined && + ts.isVariableDeclaration(declaration) && + ts.isIdentifier(declaration.name) && + declaration.initializer !== undefined && + stdlibGlobalNameOf(lowerer, declaration.initializer) === "globalThis" + ); +} + /** Whether the element access `node` of the global object can read a * builtin global by name: its key is not a symbol (`globalThis[key]` with * `key = Symbol.for(...)` reads the global object's symbol properties), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json new file mode 100644 index 000000000..616b0a1b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8168-navigator-language-values.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_node_builtin.c b/packages/runtime/src/scr_node_builtin.c index 8604134e7..37efa8822 100644 --- a/packages/runtime/src/scr_node_builtin.c +++ b/packages/runtime/src/scr_node_builtin.c @@ -471,6 +471,18 @@ ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member) { static SCR_TL ScrDyn *scr_navigator_box; static SCR_TL ScrDyn *scr_navigator_langs; static SCR_TL ScrStr *scr_navigator_lang; +static SCR_TL bool scr_navigator_cleanup_registered; + +static void scr_navigator_cleanup(void); + +/* Registers the release of the cached navigator values at exit, once per + * thread: a program can read navigator.language or navigator.languages + * without ever taking the navigator object. */ +static void scr_navigator_keep(void) { + if (scr_navigator_cleanup_registered) return; + scr_navigator_cleanup_registered = true; + scr_atexit(scr_navigator_cleanup); +} static void scr_navigator_refuse(const char *what) { ScrJsonBuf text; @@ -575,6 +587,7 @@ ScrStr *scr_navigator_language(void) { return NULL; } scr_navigator_lang = tag; + scr_navigator_keep(); } return scr_str_retain(scr_navigator_lang); } @@ -587,6 +600,7 @@ ScrDyn *scr_navigator_languages(void) { scr_dyn_arr_push(languages, scr_global_take_str(language)); scr_dyn_release(scr_dyn_freeze_data(languages)); scr_navigator_langs = languages; + scr_navigator_keep(); } return scr_dyn_retain(scr_navigator_langs); } @@ -661,6 +675,7 @@ static void scr_navigator_cleanup(void) { scr_navigator_box = NULL; scr_navigator_langs = NULL; scr_navigator_lang = NULL; + scr_navigator_cleanup_registered = false; } ScrDyn *scr_navigator_object(void) { @@ -673,7 +688,7 @@ ScrDyn *scr_navigator_object(void) { scr_dyn_handle_install(SCR_DYNH_NAVIGATOR, &ops); static SCR_TL char token; scr_navigator_box = scr_dyn_new_handle(&token, SCR_DYNH_NAVIGATOR); - scr_atexit(scr_navigator_cleanup); + scr_navigator_keep(); } return scr_dyn_retain(scr_navigator_box); } diff --git a/tests/corpus/8161-global-computed-reads.cjs b/tests/corpus/8161-global-computed-reads.cjs index ff5883154..19dfdc3e7 100644 --- a/tests/corpus/8161-global-computed-reads.cjs +++ b/tests/corpus/8161-global-computed-reads.cjs @@ -51,6 +51,17 @@ console.log( web.ReadableStream === ReadableStream, ); console.log(globalThis["noSuchGlobal"], typeof globalThis["Temporal"]); +// A snapshot of the global object in a function reads the same values. +function readThroughSnapshot(name) { + const g = globalThis; + return g[name]; +} +console.log( + readThroughSnapshot("setTimeout") === setTimeout, + readThroughSnapshot("Map") === Map, + typeof readThroughSnapshot("structuredClone"), + readThroughSnapshot("noSuchGlobal"), +); const pick = (name) => globalThis[name]; console.log(pick("Math") === Math, pick("JSON") === JSON, pick("Reflect") === Reflect, pick("Math").max(1, 3)); diff --git a/tests/corpus/8168-navigator-language-values.cjs b/tests/corpus/8168-navigator-language-values.cjs new file mode 100644 index 000000000..41368a1ef --- /dev/null +++ b/tests/corpus/8168-navigator-language-values.cjs @@ -0,0 +1,8 @@ +// @no-node-shims +// navigator.language, navigator.languages and navigator.platform read +// without the navigator object ever being taken as a value: the runtime +// caches the answers and releases them at exit. +"use strict"; +console.log(typeof navigator.language, navigator.language.length > 0); +console.log(navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(Object.isFrozen(navigator.languages), typeof navigator.platform); diff --git a/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json b/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json new file mode 100644 index 000000000..e86b2692e --- /dev/null +++ b/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3104, "san": 4714 } From aead30fc2635a67d62764fbd1a61147cec16e592 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:10:32 -0500 Subject: [PATCH 271/395] Mark emitted functions sanitize_address only in sanitized builds The sanitized lane's -fsanitize=address link needs the attribute to instrument the emitted functions. Without a sanitizer pass it is not inert: LLVM does not shrink-wrap functions that carry it, so every call saved and restored all of a function's callee-saved registers on entry, including on paths that return before any call, and it blocks speculative loads. The LLVM helper already dropped it for builds that import runtime bitcode (speed); release builds kept it. - The new sanitizeAttributes option (default true for direct emission) is set from --sanitize wherever objectAudit is. - Without it and without a target CPU level, attribute group #0 holds a string attribute LLVM ignores, since a group cannot be empty. --- packages/cli/test/target-cpu.test.ts | 4 +++- .../compiler/src/backend/llvm/emit-module.ts | 20 ++++++++++--------- .../compiler/src/backend/llvm/emitter.test.ts | 13 ++++++++++++ packages/compiler/src/backend/llvm/emitter.ts | 9 +++++++++ packages/compiler/src/backend/llvm/shapes.ts | 5 +++-- packages/compiler/src/index.ts | 3 +++ packages/compiler/src/native/prepare.ts | 2 ++ .../compiler/test/generator-emission.test.ts | 2 +- 8 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/cli/test/target-cpu.test.ts b/packages/cli/test/target-cpu.test.ts index ca5243564..5bc6717c3 100644 --- a/packages/cli/test/target-cpu.test.ts +++ b/packages/cli/test/target-cpu.test.ts @@ -50,9 +50,11 @@ test.runIf(process.platform === "linux" && process.arch === "x64")( const expected = `${(await execFileAsync(process.execPath, [entry])).stdout}`; const baseline = await build([]); expect(baseline).not.toContain('"target-cpu"'); + // Only --sanitize builds mark the functions for ASan instrumentation. + expect(baseline).not.toContain("sanitize_address"); expect(await build(["--target-cpu=baseline"])).toBe(baseline); const v3 = await build(["--target-cpu=x86-64-v3"]); - expect(v3).toMatch(/^attributes #0 = \{ sanitize_address "target-cpu"="x86-64-v3" \}$/m); + expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); if (runsV3) expect((await execFileAsync(outPath)).stdout).toBe(expected); // The bootstrap's cached route builds without a level. expect(await build([], true)).toBe(baseline); diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2c..8145429f1 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -196,15 +196,17 @@ function readRange(dir: string, range: number): { result: ChunkResult; texts: st /** The attribute groups of the emitted functions: #0 for every function, * #1 for coroutine bodies and #2 for out-of-line record clones. Each carries - * the build's `--target-cpu` level. */ + * the build's `--target-cpu` level, and `sanitize_address` in sanitized + * builds. A group needs one attribute: without either, #0 holds a string + * attribute that LLVM ignores. */ function functionAttributes(emitter: LlEmitter, hasNoInlineRecordClone: boolean): string[] { const cpu = targetCpuAttribute(emitter.targetCpu); + const asan = emitter.sanitizeAttributes ? " sanitize_address" : ""; + const base = `${asan}${cpu}` || ` "scriptc-function"`; return [ - `attributes #0 = { sanitize_address${cpu} }`, - ...(emitter.usesCoroutines - ? [`attributes #1 = { sanitize_address presplitcoroutine${cpu} }`] - : []), - ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address${cpu} }`] : []), + `attributes #0 = {${base} }`, + ...(emitter.usesCoroutines ? [`attributes #1 = {${asan} presplitcoroutine${cpu} }`] : []), + ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline${asan}${cpu} }`] : []), ]; } @@ -1179,9 +1181,9 @@ export function emitModuleParts(emitter: LlEmitter): string[] { ); for (const line of emitter.inlineRcTail(flushedDecls)) out.push(line); out.push( - // sanitize_address is inert under the plain pipeline; the sanitized - // lane's -fsanitize=address link activates instrumentation over the - // emitted functions too (the runtime TUs get theirs from clang). + // The sanitized lane's -fsanitize=address link instruments the + // functions marked sanitize_address (the runtime TUs get theirs from + // clang). ...functionAttributes(emitter, hasNoInlineRecordClone), ``, ); diff --git a/packages/compiler/src/backend/llvm/emitter.test.ts b/packages/compiler/src/backend/llvm/emitter.test.ts index 876476cc1..880685bf2 100644 --- a/packages/compiler/src/backend/llvm/emitter.test.ts +++ b/packages/compiler/src/backend/llvm/emitter.test.ts @@ -211,6 +211,19 @@ test("a target CPU level reaches every function attribute group and nothing else expect(emitLlvmModule(module, { targetCpu: "baseline" })).toBe(plain); }); +test("only sanitized builds mark the emitted functions sanitize_address", () => { + const module = moduleFor([call()]); + const sanitized = emitLlvmModule(module); + expect(emitLlvmModule(module, { sanitizeAttributes: true })).toBe(sanitized); + expect(sanitized).toMatch(/^attributes #0 = \{ sanitize_address \}$/m); + const plain = emitLlvmModule(module, { sanitizeAttributes: false }); + expect(plain).not.toContain("sanitize_address"); + // A group needs an attribute: an ignored string attribute stands in. + expect(plain).toMatch(/^attributes #0 = \{ "scriptc-function" \}$/m); + const v3 = emitLlvmModule(module, { sanitizeAttributes: false, targetCpu: "x86-64-v3" }); + expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); +}); + test("inline RC inlines every fast path at speed and keeps box releases as calls in release", () => { for (const boxed of [false, true]) { const module = moduleFor(Array.from({ length: 4 }, call), { boxed }); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 5569599ee..f1f1fa7d6 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -511,6 +511,12 @@ export interface LlvmTargetOptions { inlineRc?: InlineRcMode; /** Define the inline RC fast-path helpers `noinline` (sanitized builds). */ rcHelpersNoinline?: boolean; + /** Mark every emitted function `sanitize_address`, which the sanitized + * lane's `-fsanitize=address` link needs to instrument it. Without a + * sanitizer pass the attribute still turns off shrink-wrapping and + * speculative loads, so plain builds pass false. Defaults to true for + * direct emission. */ + sanitizeAttributes?: boolean; /** Define `scr_detached_exit` so the Linux runtime hands the address-space * teardown after `process.exit` to a helper process (`--detached-exit`). * Executables only; the runtime's weak default keeps it off. */ @@ -1269,6 +1275,8 @@ export class LlEmitter { readonly runtimeAbiMarker: boolean; readonly detachedExit: boolean; readonly targetCpu: TargetCpu | undefined; + /** Emitted functions carry `sanitize_address` (LlvmTargetOptions). */ + readonly sanitizeAttributes: boolean; readonly wasmBoundsChecks: boolean; /** Interned string literals: UTF-8 text → { symbol, byte length } — * first-use order, the runtime ABI’s determinism discipline. */ @@ -1971,6 +1979,7 @@ export class LlEmitter { this.runtimeAbiMarker = options.runtimeAbiMarker === true; this.detachedExit = options.detachedExit === true; this.targetCpu = options.targetCpu; + this.sanitizeAttributes = options.sanitizeAttributes !== false; this.wasmBoundsChecks = options.wasmBoundsChecks === true || wasmBoundsChecks({ diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 7498235b0..6d03db222 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -216,8 +216,9 @@ export interface RegionHooks { classDefinitions(cls: IrClassDef, sizeOf: string): string[]; } -/** Every emitted function/helper carries #0 = { sanitize_address } — see - * the emitter header. */ +/** Every emitted function/helper carries attribute group #0: the build's + * target CPU level, and `sanitize_address` in sanitized builds (see + * emit-module.ts functionAttributes). */ export const FN_ATTRS = "#0"; export { computeTraced } from "../cycle-analysis.js"; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index ffd56a1bf..11b0f6d13 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -854,6 +854,7 @@ async function prepareExecutableInput( // the pending flag, since their link may not add an unwinder. unwindAbi: buildPlatform === "linux" && outputKind !== "obj", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), @@ -925,6 +926,7 @@ async function prepareExecutableInput( runtimeAbiMarker: useRuntimePack, unwindAbi: buildPlatform === "linux", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), @@ -2357,6 +2359,7 @@ async function compileLibraryTracked( pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, ...inlineRcOption(profile.optimization, opts.sanitize === true), }); timing("llvm-emit", { diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 11b8f9dd1..749027b80 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -431,6 +431,7 @@ export function prepareNativeExecutable( (outputKind === "exe" || outputKind === "llvm" || outputKind === "asm") && toolchain.target.platform === "linux", objectAudit: options.sanitize === true, + sanitizeAttributes: options.sanitize === true, lowRegion: lowRegionTarget(toolchain.target.platform, options.sanitize === true), functionWorkers: emitWorkers(), forkPrimitives: emitForkPrimitives(), @@ -545,6 +546,7 @@ export function prepareNativeLibrary( pointerBits: toolchain.target.pointerBits, wasi: toolchain.target.platform === "wasi", objectAudit: options.sanitize === true, + sanitizeAttributes: options.sanitize === true, ...inlineRcOption(profile.optimization, options.sanitize === true), }); timing("llvm-emit"); diff --git a/packages/compiler/test/generator-emission.test.ts b/packages/compiler/test/generator-emission.test.ts index 8c43925ea..47e5f16c5 100644 --- a/packages/compiler/test/generator-emission.test.ts +++ b/packages/compiler/test/generator-emission.test.ts @@ -50,7 +50,7 @@ console.log(sum); expect(definition(llvm, "sc_f_range")).not.toContain("@scr_coro_yield_f64("); expect(llvm).not.toContain("@scr_gen_take_out_f64("); expect(llvm).toContain("%ScrGenHead = type { i64, ptr, ptr, i32, %ScrGenSlot }"); - expect(llvm).toContain("attributes #1 = { sanitize_address presplitcoroutine }"); + expect(llvm).toContain("attributes #1 = { presplitcoroutine }"); expect(definition(llvm, "sc_gs_range")).toContain("call ptr @scr_gen_new_stackless("); expect(llvm).not.toContain("@scr_gen_yield_f64("); }); From a4ae5f9e27f89e238ca24d6dd91273674a7ba092 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:17:20 -0500 Subject: [PATCH 272/395] Run the linker job directly for host Linux links with lld A Linux executable link runs `clang -fuse-ld=lld ...`, and the driver only starts ld.lld with a job it derives from its arguments, its environment and the installed toolchain. Starting the driver costs about 10 ms of a small program's 29 ms link. LinkerJobs (native/linker-job.ts) records that job in the native cache, with the link's own inputs and output as positions. A traced link (the Linux executable cache's `-v -Wl,-t` link) records it from the job line the driver prints with -v; a link the executable cache does not trace lists it with -v -### first. The entry also keeps the identities of the files and directories the job and the driver's -v lines name (ld.lld, startup objects, library directories, the GCC installation, the driver's directory): the set the Linux executable cache records from its traced link. It is keyed by the driver's identity, the link's arguments, the toolchain environment, PATH and the working directory. A later link with the same key whose recorded paths still have those identities runs ld.lld with the job directly, and the executable cache traces it with the job's -t output and the recorded identities. Every other link runs through the driver as before: cross-target links, links without lld, a driver that is not clang, SCRIPTC_LINKER, sanitizer links, a toolchain environment that names mutable inputs, a listing without exactly one ld.lld job naming each of the link's paths once, a recorded path whose identity changed, and a job that fails (the driver then reports the error). The linker gets the same arguments, so the executables are identical. --- packages/compiler/src/native/cache.ts | 1 + packages/compiler/src/native/compiler.ts | 8 +- .../src/native/executable-cache.test.ts | 42 +- .../compiler/src/native/executable-cache.ts | 28 +- .../compiler/src/native/linker-job.test.ts | 408 ++++++++++++++++++ packages/compiler/src/native/linker-job.ts | 286 ++++++++++++ 6 files changed, 760 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/src/native/linker-job.test.ts create mode 100644 packages/compiler/src/native/linker-job.ts diff --git a/packages/compiler/src/native/cache.ts b/packages/compiler/src/native/cache.ts index e204b8a6b..0250e9618 100644 --- a/packages/compiler/src/native/cache.ts +++ b/packages/compiler/src/native/cache.ts @@ -168,6 +168,7 @@ export class NativeCache { "sanitizer", "executable", "kept", + "linker-job", "runtime-pack", "binary", "dsym", diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index ff49e2ac9..0b56b0a53 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -70,6 +70,7 @@ import { prepareNativeExecutable, prepareNativeLibrary } from "./prepare.js"; import { buildSanitizedRuntime, sanitizerDriver, sanitizerFlags } from "./sanitizer.js"; import { lldLinkerArgs } from "../backend/lld.js"; import { openNativeExecutableCache } from "./executable-cache.js"; +import { LinkerJobs } from "./linker-job.js"; import { nativeFileIdentity } from "./file-identity.js"; /** The first partition keeps the whole-object key used before partitioning. */ @@ -530,11 +531,14 @@ export class NativeCompiler { "-o", stagedOutput, ]; - const cacheable = executableCache?.trace(linkArgs, stage) ?? false; + // Host Linux links with lld run the linker job the driver would. + const jobs = LinkerJobs.open(this.cache, toolchain, lld, options.sanitize ?? false); + const job = jobs?.link([...plan.inputs, stagedOutput], stage); + const cacheable = executableCache?.trace(linkArgs, stage, job) ?? false; timing("link-inputs"); // A successful dependency trace already produced this executable. // Keep its verified output instead of asking the linker to repeat it. - if (!cacheable) { + if (!cacheable && (job?.run(linkArgs, true) ?? null) === null) { const driver = options.sanitize ? sanitizerDriver(toolchain) : toolchain.linker; try { runNativeTool(driver, linkArgs); diff --git a/packages/compiler/src/native/executable-cache.test.ts b/packages/compiler/src/native/executable-cache.test.ts index 8bec9916e..4f950e5b5 100644 --- a/packages/compiler/src/native/executable-cache.test.ts +++ b/packages/compiler/src/native/executable-cache.test.ts @@ -15,7 +15,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test, vi } from "vitest"; import { contentDigest, NativeCache } from "./cache.js"; -import { NativeExecutableCache } from "./executable-cache.js"; +import { identity, NativeExecutableCache } from "./executable-cache.js"; vi.mock("node:child_process", async (original) => ({ ...(await original()), @@ -331,3 +331,43 @@ test("a Linux build that keeps its output does not read the deferred inputs", () expect(open().restore(f.destination)).toBe(true); expect(deferred).toHaveBeenCalledTimes(3); }); + +test("a Linux link that runs as its linker job is traced from the job's run, and otherwise records it", () => { + const f = fixture(); + const lib = join(f.root, "lib"); + mkdirSync(lib); + const crt = join(lib, "Scrt1.o"); + writeFileSync(crt, "startup object"); + const program = join(f.stage, "program.o"); + writeFileSync(program, "program"); + const job = { + run: vi.fn((_args: string[], _list: boolean) => ({ + output: [crt, program].join("\n") + "\n", + driverInputs: [identity(crt), identity(lib)], + })), + record: vi.fn(), + }; + const open = () => new NativeExecutableCache(f.cache, f.key, "clang", false, [f.runtime], true); + const entry = open(); + expect(entry.trace(["-o", f.output], f.stage, job)).toBe(true); + // The job runs the traced link, and the driver does not. + expect(job.run.mock.calls[0]).toEqual([["-o", f.output, "-v", "-Wl,-t"], false]); + expect(spawnSync).not.toHaveBeenCalled(); + expect(job.record).not.toHaveBeenCalled(); + entry.recordOutput(f.output); + const recorded = ( + JSON.parse(f.cache.read("kept", f.key)!.toString()) as { inputs: { path: string }[] } + ).inputs.map((saved) => saved.path); + expect(recorded).toEqual(expect.arrayContaining([crt, lib])); + expect(recorded).not.toContain(program); + expect(open().keep(f.output)).toBe(true); + // A link without a job runs through the driver, and records the job from + // the driver's output. + const record = vi.fn(); + expect(open().trace(["-o", f.output], f.stage, { run: () => null, record })).toBe(true); + expect(spawnSync).toHaveBeenCalledTimes(1); + expect(record).toHaveBeenCalledWith(["-o", f.output, "-v", "-Wl,-t"], f.library + "\n\n", [ + identity(f.sdk), + identity(f.library), + ]); +}); diff --git a/packages/compiler/src/native/executable-cache.ts b/packages/compiler/src/native/executable-cache.ts index bafd2c020..a26bc1d87 100644 --- a/packages/compiler/src/native/executable-cache.ts +++ b/packages/compiler/src/native/executable-cache.ts @@ -14,8 +14,9 @@ import type { NativeOptimization } from "../backend/optimization.js"; import type { NativeRuntimeSelection } from "../backend/runtime-pack-native.js"; import type { NativeToolchain } from "./toolchain.js"; import { contentDigest, NativeCache } from "./cache.js"; +import type { LinkerJobLink } from "./linker-job.js"; -interface InputIdentity { +export interface InputIdentity { path: string; canonical: string; kind: "file" | "directory"; @@ -33,7 +34,7 @@ interface ExecutableEntry { symbols: string | null; } -function identity(path: string): InputIdentity { +export function identity(path: string): InputIdentity { path = resolve(path); const info = statSync(path); if (!info.isFile() && !info.isDirectory()) @@ -63,7 +64,7 @@ function sameIdentity(a: InputIdentity, b: InputIdentity): boolean { ); } -function stillMatches(inputs: InputIdentity[]): boolean { +export function stillMatches(inputs: InputIdentity[]): boolean { try { return inputs.every((input) => sameIdentity(identity(input.path), input)); } catch { @@ -71,7 +72,7 @@ function stillMatches(inputs: InputIdentity[]): boolean { } } -function commandPath(command: string): string { +export function commandPath(command: string): string { if (command.includes("/") || command.includes("\\")) return resolve(command); for (const directory of (process.env["PATH"] ?? "/usr/bin:/bin").split(delimiter)) { const candidate = join(directory || process.cwd(), command); @@ -92,7 +93,7 @@ function toolOutput(executable: string, args: string[]): string { return result.stdout + "\n" + result.stderr; } -function tracePaths(output: string, driver: boolean, roots: string[]): string[] { +export function tracePaths(output: string, driver: boolean, roots: string[]): string[] { const paths = new Set(); for (const line of output.split(/\r?\n/)) { for (const candidate of driver ? driverTraceCandidates(line) : linkTraceCandidate(line)) { @@ -134,7 +135,7 @@ interface KeptEntry { inputs: InputIdentity[]; } -function validIdentity(input: InputIdentity): boolean { +export function validIdentity(input: InputIdentity): boolean { return ( input !== null && typeof input === "object" && @@ -290,8 +291,10 @@ export class NativeExecutableCache { * Private build inputs already belong to the LLVM/runtime cache identity. * A true result means the requested output was linked successfully and its * inputs remained stable. The caller can publish that output directly. On - * false, the caller links the output itself. */ - trace(args: string[], stage: string): boolean { + * false, the caller links the output itself. On Linux, `job` runs the + * traced link as its recorded linker job when it can, and otherwise records + * the job the driver ran (see LinkerJobs). */ + trace(args: string[], stage: string, job?: LinkerJobLink): boolean { this.traced = false; try { this.addDeferredInputs(); @@ -300,8 +303,13 @@ export class NativeExecutableCache { ? [] : tracePaths(toolOutput(this.linker, [...args, "-###"]), true, roots).map(identity); // On Linux, -v prints the jobs that -### would, while the link runs. - const linked = toolOutput(this.linker, [...args, ...(this.linux ? ["-v"] : []), "-Wl,-t"]); - if (this.linux) driverInputs = tracePaths(linked, true, roots).map(identity); + const traced = [...args, ...(this.linux ? ["-v"] : []), "-Wl,-t"]; + const direct = this.linux ? (job?.run(traced, false) ?? null) : null; + const linked = direct?.output ?? toolOutput(this.linker, traced); + if (this.linux) { + driverInputs = direct?.driverInputs ?? tracePaths(linked, true, roots).map(identity); + if (direct === null) job?.record(traced, linked, driverInputs); + } const paths = tracePaths(linked, false, roots); if (paths.length === 0 || !stillMatches(this.inputs) || !stillMatches(driverInputs)) return false; diff --git a/packages/compiler/src/native/linker-job.test.ts b/packages/compiler/src/native/linker-job.test.ts new file mode 100644 index 000000000..b9d718c03 --- /dev/null +++ b/packages/compiler/src/native/linker-job.test.ts @@ -0,0 +1,408 @@ +import { spawnSync } from "node:child_process"; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readFileSync, + renameSync, + rmSync, + statSync, + utimesSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { + LINUX_ARM64_GNU_TARGET, + LINUX_X64_GNU_TARGET, + MACOS_ARM64_TARGET, + type NativeTargetSpec, +} from "../backend/targets.js"; +import { NativeCache } from "./cache.js"; +import { LinkerJobs } from "./linker-job.js"; +import type { NativeToolchain } from "./toolchain.js"; + +const directories: string[] = []; +const saved = new Map(); +const variables = [ + "SCRIPTC_LINKER", + "COMPILER_PATH", + "LOG", + "LD", + "CRT", + "LIB", + "FAIL", + "BROKEN", + "DOUBLE", +]; +beforeEach(() => { + for (const name of variables) { + saved.set(name, process.env[name]); + delete process.env[name]; + } +}); +afterEach(() => { + for (const [name, value] of saved) { + if (value === undefined) delete process.env[name]; + else process.env[name] = value; + } + saved.clear(); + for (const path of directories.splice(0)) rmSync(path, { recursive: true, force: true }); +}); + +function script(path: string, text: string): string { + writeFileSync(path, text); + chmodSync(path, 0o755); + return path; +} + +/** A driver named `name` and an ld.lld that log their arguments to $LOG. The + * driver's job runs $LD with `-pie`, the startup object $CRT, `-L$LIB` and + * its own arguments (without `-fuse-ld=lld` and `-v`, `-Wl,` removed). With + * -### it prints that job as clang does, every argument quoted; with -v it + * prints the job it runs as clang does, only the program quoted. With + * $BROKEN, -### prints the job twice; with $DOUBLE, it names the path $DOUBLE + * once more. The linker writes its inputs to its output, prints them with + * `-t`, and fails with $FAIL. */ +function fixture(name = "clang") { + const root = mkdtempSync(join(tmpdir(), "scriptc-linker-job-")); + directories.push(root); + const bin = join(root, "bin"); + const lib = join(root, "lib"); + // The recorded inputs include the parents of the toolchain's paths (here + // `root`); stages are elsewhere, as a build's are. + const stages = join(root, "stages"); + for (const directory of [bin, lib, stages]) mkdirSync(directory); + const log = join(root, "log"); + writeFileSync(log, ""); + const driver = script( + join(bin, name), + `#!/bin/sh +echo "driver $*" >> "$LOG" +job=" \\"$LD\\" \\"-pie\\" \\"$CRT\\" \\"-L$LIB\\"" +raw=" \\"$LD\\" -pie $CRT -L$LIB" +listing=0; verbose=0 +for a; do + case "$a" in + -'###') listing=1 ;; + -v) verbose=1 ;; + -fuse-ld=lld) ;; + -Wl,*) job="$job \\"\${a#-Wl,}\\""; raw="$raw \${a#-Wl,}" ;; + *) job="$job \\"$a\\""; raw="$raw $a" ;; + esac +done +if [ $listing = 1 ]; then + echo "InstalledDir: $(dirname "$0")" >&2 + [ -z "$DOUBLE" ] && echo "$job" >&2 + [ -n "$BROKEN" ] && echo "$job" >&2 + [ -n "$DOUBLE" ] && echo "$job \\"$DOUBLE\\"" >&2 && exit 0 + exit 0 +fi +[ $verbose = 1 ] && echo "$raw" >&2 +eval "exec $job" +`, + ); + const ld = script( + join(bin, "ld.lld"), + `#!/bin/sh +echo "ld.lld $*" >> "$LOG" +[ -n "$FAIL" ] && exit 1 +out=""; prev="" +for a; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; done +printf '%s\\n' "$@" > "$out" +for a; do [ "$a" = "-t" ] && printf '%s\\n' "$@" | grep '^/'; done +exit 0 +`, + ); + const crt = join(lib, "Scrt1.o"); + writeFileSync(crt, "startup object"); + Object.assign(process.env, { LOG: log, LD: ld, CRT: crt, LIB: lib }); + // A directory's identity is its stat: an old time makes a later change visible. + utimesSync(lib, 0, 0); + const cache = new NativeCache(join(root, "cache")); + const toolchain = (target: NativeTargetSpec = LINUX_X64_GNU_TARGET, helper = target) => + ({ + target, + helper: helper.helper, + linker: driver, + }) as unknown as NativeToolchain; + let count = 0; + /** A stage with a program object, the link's paths and its driver arguments. */ + const stage = () => { + const directory = join(stages, `stage-${++count}`); + mkdirSync(directory); + const program = join(directory, "program.o"); + const output = join(directory, "out"); + writeFileSync(program, "program"); + return { + directory, + output, + paths: [program, output], + args: ["-fuse-ld=lld", "-pthread", program, "-lm", "-o", output], + }; + }; + const calls = () => + readFileSync(log, "utf8") + .split("\n") + .filter((line) => line !== ""); + return { root, bin, lib, crt, ld, driver, cache, toolchain, stage, calls }; +} + +test.skipIf(process.platform === "win32")( + "a link records the driver's job once, and later links run ld.lld with their own paths", + () => { + const f = fixture(); + const jobs = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(jobs).not.toBeNull(); + const first = f.stage(); + const run = jobs.run(first.args, first.paths, first.directory, true); + expect(run).not.toBeNull(); + // The driver only listed the job; ld.lld linked. + expect(f.calls()).toEqual([ + `driver ${first.args.join(" ")} -v -###`, + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${first.paths[0]} -lm -o ${first.output}`, + ]); + expect(readFileSync(first.output, "utf8")).toContain(first.paths[0]); + const recorded = run!.driverInputs.map((input) => input.path); + expect(recorded).toEqual(expect.arrayContaining([f.ld, f.bin, f.crt, f.lib])); + expect(recorded.some((path) => path.startsWith(first.directory))).toBe(false); + // Another build, with other paths, runs the recorded job without the driver. + const second = f.stage(); + const again = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(again.run(second.args, second.paths, second.directory, true)).not.toBeNull(); + expect(f.calls().slice(2)).toEqual([ + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${second.paths[0]} -lm -o ${second.output}`, + ]); + // Other arguments are another job. + const third = f.stage(); + expect( + again.run([...third.args, "-Wl,-t"], third.paths, third.directory, true)?.output, + ).toContain(third.paths[0]); + expect(f.calls().slice(3)[0]).toBe(`driver ${third.args.join(" ")} -Wl,-t -v -###`); + }, +); + +test.skipIf(process.platform === "win32")( + "a driver link records its job from its -v output, and later links run it", + () => { + const f = fixture(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + const traced = [...first.args, "-v", "-Wl,-t"]; + // Without an entry, a link that is not listed runs through the driver. + const link = open().link(first.paths, first.directory); + expect(link.run(traced, false)).toBeNull(); + expect(f.calls()).toEqual([]); + const linked = spawnSync(f.driver, traced, { encoding: "utf8" }); + expect(linked.status).toBe(0); + link.record(traced, linked.stdout + "\n" + linked.stderr); + const second = f.stage(); + const run = open() + .link(second.paths, second.directory) + .run([...second.args, "-v", "-Wl,-t"], false); + expect(run?.output).toContain(second.paths[0]); + expect(run!.driverInputs.map((input) => input.path)).toEqual( + expect.arrayContaining([f.ld, f.bin, f.crt, f.lib]), + ); + expect(f.calls().slice(2)).toEqual([ + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${second.paths[0]} -lm -o ${second.output} -t`, + ]); + }, +); + +test.skipIf(process.platform === "win32")( + "a changed startup object, library directory or linker sends the link to the driver", + () => { + for (const change of ["crt", "lib", "ld"] as const) { + const f = fixture(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + expect(open().run(first.args, first.paths, first.directory, true)).not.toBeNull(); + if (change === "lib") writeFileSync(join(f.lib, "libm.so"), "new library"); + else { + const path = change === "crt" ? f.crt : f.ld; + const before = statSync(path); + writeFileSync(path + ".new", readFileSync(path)); + chmodSync(path + ".new", 0o755); + utimesSync(path + ".new", before.atime, before.mtime); + renameSync(path + ".new", path); + } + const second = f.stage(); + const count = f.calls().length; + expect(open().run(second.args, second.paths, second.directory, true)).toBeNull(); + expect(f.calls().length).toBe(count); + } + }, +); + +test.skipIf(process.platform === "win32")("a changed driver records the job again", () => { + const f = fixture(); + const first = f.stage(); + expect( + LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!.run( + first.args, + first.paths, + first.directory, + true, + ), + ).not.toBeNull(); + writeFileSync(f.driver, readFileSync(f.driver, "utf8") + "\n"); + const second = f.stage(); + expect( + LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!.run( + second.args, + second.paths, + second.directory, + true, + ), + ).not.toBeNull(); + expect(f.calls()[2]).toBe(`driver ${second.args.join(" ")} -v -###`); +}); + +test.skipIf(process.platform === "win32")( + "a job that fails sends this and the build's later links to the driver", + () => { + const f = fixture(); + const first = f.stage(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(open().run(first.args, first.paths, first.directory, true)).not.toBeNull(); + process.env["FAIL"] = "1"; + const jobs = open(); + const second = f.stage(); + expect(jobs.run(second.args, second.paths, second.directory, true)).toBeNull(); + const count = f.calls().length; + expect(jobs.run(second.args, second.paths, second.directory, true)).toBeNull(); + expect(f.calls().length).toBe(count); + }, +); + +test.skipIf(process.platform === "win32")( + "a listing without one ld.lld job naming each path once keeps the driver, and is not asked again", + () => { + for (const listing of ["two jobs", "another linker", "a path twice"] as const) { + const f = fixture(); + if (listing === "two jobs") process.env["BROKEN"] = "1"; + if (listing === "another linker") + process.env["LD"] = script(join(f.bin, "ld.bfd"), readFileSync(f.ld, "utf8")); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + if (listing === "a path twice") process.env["DOUBLE"] = first.paths[0]; + expect(open().run(first.args, first.paths, first.directory, true)).toBeNull(); + expect(f.calls()).toEqual([`driver ${first.args.join(" ")} -v -###`]); + // The answer is stored: the next build does not list the job again. + expect(open().run(first.args, first.paths, first.directory, true)).toBeNull(); + expect(f.calls().length).toBe(1); + delete process.env["BROKEN"]; + delete process.env["DOUBLE"]; + } + }, +); + +test.skipIf(process.platform === "win32")( + "arguments that name the stage besides the link's paths keep the driver", + () => { + const f = fixture(); + const first = f.stage(); + const jobs = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const script = `-Wl,--version-script=${join(first.directory, "exports")}`; + expect(jobs.run([...first.args, script], first.paths, first.directory, true)).toBeNull(); + expect(f.calls()).toEqual([]); + }, +); + +test.skipIf(process.platform === "win32")( + "only host Linux links through a clang driver with lld run the job", + () => { + const f = fixture(); + const lld = ["-fuse-ld=lld"]; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).not.toBeNull(); + // A cross-target link: the arm64 target with the x64 host's helper. + expect( + LinkerJobs.open( + f.cache, + f.toolchain(LINUX_ARM64_GNU_TARGET, LINUX_X64_GNU_TARGET), + lld, + false, + ), + ).toBeNull(); + expect( + LinkerJobs.open( + f.cache, + f.toolchain(LINUX_X64_GNU_TARGET, LINUX_ARM64_GNU_TARGET), + lld, + false, + ), + ).toBeNull(); + expect(LinkerJobs.open(f.cache, f.toolchain(MACOS_ARM64_TARGET), lld, false)).toBeNull(); + // The driver's default linker, a sanitizer link, no cache. + expect(LinkerJobs.open(f.cache, f.toolchain(), [], false)).toBeNull(); + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, true)).toBeNull(); + expect(LinkerJobs.open(null, f.toolchain(), lld, false)).toBeNull(); + // A linker that is not a clang driver. + const gcc = fixture("gcc"); + expect(LinkerJobs.open(gcc.cache, gcc.toolchain(), lld, false)).toBeNull(); + // An explicit linker, or an environment that names mutable toolchain inputs. + process.env["SCRIPTC_LINKER"] = f.driver; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).toBeNull(); + delete process.env["SCRIPTC_LINKER"]; + process.env["COMPILER_PATH"] = f.bin; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).toBeNull(); + }, +); + +const hostTarget = + process.arch === "x64" + ? LINUX_X64_GNU_TARGET + : process.arch === "arm64" + ? LINUX_ARM64_GNU_TARGET + : null; +const hasLld = + process.platform === "linux" && + spawnSync("clang", ["-fuse-ld=lld", "-###", "-x", "c", "/dev/null"], { + encoding: "utf8", + }).stderr?.includes("ld.lld") === true; + +test.skipIf(hostTarget === null || !hasLld)("the job links the executable the driver links", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-linker-job-clang-")); + directories.push(root); + const stage = join(root, "stage"); + mkdirSync(stage); + const program = join(stage, "program.o"); + const compiled = spawnSync("clang", ["-c", "-x", "c", "-", "-o", program], { + input: '#include \nint main(void) { puts("linked"); return 0; }\n', + }); + expect(compiled.status).toBe(0); + const toolchain = { + target: hostTarget, + helper: hostTarget!.helper, + linker: "clang", + } as unknown as NativeToolchain; + const args = (output: string) => ["-fuse-ld=lld", "-pthread", program, "-lm", "-o", output]; + const viaDriver = join(stage, "driver"); + expect(spawnSync("clang", args(viaDriver)).status).toBe(0); + const cache = new NativeCache(join(root, "cache")); + // Recorded from the -v output of a traced link, and listed with -###. + const traced = (output: string) => [...args(output), "-v", "-Wl,-t"]; + const tracedOutput = join(stage, "traced"); + const linked = spawnSync("clang", traced(tracedOutput), { encoding: "utf8" }); + expect(linked.status).toBe(0); + LinkerJobs.open(cache, toolchain, ["-fuse-ld=lld"], false)! + .link([program, tracedOutput], stage) + .record(traced(tracedOutput), linked.stdout + "\n" + linked.stderr); + for (const [name, list] of [ + ["first", false], + ["second", true], + ["third", true], + ] as const) { + const output = join(stage, name); + const link = LinkerJobs.open(cache, toolchain, ["-fuse-ld=lld"], false)!.link( + [program, output], + stage, + ); + const run = list ? link.run(args(output), true) : link.run(traced(output), false); + expect(run).not.toBeNull(); + expect(readFileSync(output).equals(readFileSync(viaDriver))).toBe(true); + expect(spawnSync(output, { encoding: "utf8" }).stdout).toBe("linked\n"); + } +}); diff --git a/packages/compiler/src/native/linker-job.ts b/packages/compiler/src/native/linker-job.ts new file mode 100644 index 000000000..e1195e4c4 --- /dev/null +++ b/packages/compiler/src/native/linker-job.ts @@ -0,0 +1,286 @@ +import { spawnSync } from "node:child_process"; +import { realpathSync } from "node:fs"; +import { basename, extname, isAbsolute, resolve } from "node:path"; +import { + toolchainEnvironmentCachePolicy, + toolchainEnvironmentFingerprint, +} from "../backend/toolchain-environment.js"; +import type { NativeToolchain } from "./toolchain.js"; +import { contentDigest, type NativeCache } from "./cache.js"; +import { + commandPath, + identity, + type InputIdentity, + stillMatches, + tracePaths, + validIdentity, +} from "./executable-cache.js"; + +/** A recorded linker job. In `job`, a number is the index of one of the link's + * own paths (its inputs and output); `inputs` are the identities of the + * other files and directories the job and the driver's `-v` lines name. */ +interface LinkerJobEntry { + schema: "scriptc.linker-job.v1"; + /** null when the driver's job could not be recorded: the link runs through + * the driver. */ + job: (string | number)[] | null; + inputs: InputIdentity[]; +} + +/** A link that ran as its recorded job: the linker's output, and the + * identities the job depends on besides the link's own paths. */ +export interface LinkerJobRun { + output: string; + driverInputs: InputIdentity[]; +} + +/** One link's use of LinkerJobs, with its own paths and stage. */ +export interface LinkerJobLink { + run(args: string[], list: boolean): LinkerJobRun | null; + record(args: string[], output: string, driverInputs?: InputIdentity[]): void; +} + +const MAX_ENTRY_BYTES = 1024 * 1024; + +function validEntry(value: unknown, paths: number): value is LinkerJobEntry { + if (value === null || typeof value !== "object") return false; + const entry = value as Partial; + if (entry.schema !== "scriptc.linker-job.v1" || !Array.isArray(entry.inputs)) return false; + if (!entry.inputs.every(validIdentity)) return false; + if (entry.job === null) return true; + if (!Array.isArray(entry.job) || typeof entry.job[0] !== "string") return false; + const used = new Set(); + for (const token of entry.job) { + if (typeof token === "string") continue; + if (!Number.isInteger(token) || token < 0 || token >= paths || used.has(token)) return false; + used.add(token); + } + return used.size === paths; +} + +function underRoots(text: string, roots: string[]): boolean { + return roots.some((root) => text.includes(root)); +} + +/** The arguments of one job line as the driver prints them: with `-###` + * each argument is quoted, with `-v` only the program and the arguments that + * contain a space, `"`, `\` or `$`; a quoted argument escapes `"`, `\` and + * `$` with `\`. Null for any other line. */ +function jobArguments(line: string): string[] | null { + const pattern = /\s*(?:"((?:\\.|[^"\\])*)"|([^\s"\\]+))/y; + const args: string[] = []; + let end = 0; + let match: RegExpExecArray | null; + while ((match = pattern.exec(line)) !== null) { + args.push(match[1] === undefined ? match[2]! : match[1].replace(/\\(.)/g, "$1")); + end = pattern.lastIndex; + } + return args.length === 0 || line.slice(end).trim() !== "" ? null : args; +} + +/** + * Linux links through a clang driver with lld. The driver only starts ld.lld + * with a job it derives from its arguments, its environment, its own + * installation and the system's (startup objects, the GCC installation, + * library directories). The job is recorded once per driver identity, + * arguments and environment, with the identities of the files and + * directories it and the driver's `-v` lines name. A later link whose recorded + * paths still have those identities runs ld.lld with the job directly; every + * other link runs through the driver. + */ +export class LinkerJobs { + /** A job run that failed in this build: later links run through the + * driver, which reports the error. */ + private failed = false; + + private constructor( + private readonly cache: NativeCache, + private readonly driver: string, + private readonly driverIdentity: InputIdentity, + ) {} + + /** Null unless the link is a host Linux link through a clang driver with + * `-fuse-ld=lld` (`lld`, the link's lld arguments), outside the sanitizer, + * with a native cache and a toolchain environment that allows complete + * artifacts. */ + static open( + cache: NativeCache | null, + toolchain: NativeToolchain, + lld: readonly string[], + sanitize: boolean, + ): LinkerJobs | null { + const target = toolchain.target; + if ( + cache === null || + sanitize || + target.platform !== "linux" || + // A cross-target link keeps the driver. + target.llvmTriple !== toolchain.helper.defaultTarget || + !lld.includes("-fuse-ld=lld") || + process.env["SCRIPTC_LINKER"] !== undefined || + !toolchainEnvironmentCachePolicy().completeArtifacts + ) + return null; + try { + const driver = commandPath(toolchain.linker); + if (!/^clang(-\d+)?$/.test(basename(realpathSync(driver)))) return null; + return new LinkerJobs(cache, driver, identity(driver)); + } catch { + return null; + } + } + + /** The link whose own inputs and output are `paths`, in `stage`. */ + link(paths: readonly string[], stage: string): LinkerJobLink { + return { + run: (args, list) => this.run(args, paths, stage, list), + record: (args, output, driverInputs) => this.record(args, paths, stage, output, driverInputs), + }; + } + + /** The entry key of the link `args` with its own `paths`, or null when + * the arguments name the stage besides those paths. */ + private key(args: readonly string[], paths: readonly string[], roots: string[]): string | null { + const index = new Map(paths.map((path, i) => [path, i])); + if (index.size !== paths.length || !paths.every((path) => args.includes(path))) return null; + if (args.some((arg) => !index.has(arg) && underRoots(arg, roots))) return null; + const shape = args.map((arg) => { + const at = index.get(arg); + return at === undefined ? arg : { path: at, extension: extname(arg) }; + }); + return contentDigest( + JSON.stringify({ + schema: 1, + driver: this.driverIdentity, + args: shape, + environment: toolchainEnvironmentFingerprint(), + cwd: process.cwd(), + path: process.env["PATH"] ?? null, + }), + ); + } + + /** + * Runs the link that the driver arguments `args` describe as its recorded + * job. `paths` are the link's own inputs and output among `args`, which may + * differ from build to build; `stage` is the directory they are in. Without + * an entry, `list` records the job first with the driver's `-v -###`; + * otherwise the caller links through the driver and passes its `-v` output + * to record(). Null when the link has to run through the driver: no job is + * recorded, a recorded path changed, or the job failed. + */ + run( + args: readonly string[], + paths: readonly string[], + stage: string, + list: boolean, + ): LinkerJobRun | null { + if (this.failed) return null; + try { + const roots = [resolve(stage), realpathSync(stage)]; + const key = this.key(args, paths, roots); + if (key === null) return null; + let entry = this.read(key, paths.length); + if (entry === null && list) { + const result = spawnSync(this.driver, [...args, "-v", "-###"], { + encoding: "utf8", + stdio: "pipe", + }); + if (result.error !== undefined || result.status !== 0 || result.signal !== null) + return null; + entry = this.store(key, result.stdout + "\n" + result.stderr, paths, roots); + } + if (entry === null || entry.job === null || !stillMatches(entry.inputs)) return null; + const job = entry.job.map((token) => (typeof token === "number" ? paths[token]! : token)); + const result = spawnSync(job[0]!, job.slice(1), { + encoding: "utf8", + stdio: "pipe", + }); + if (result.error !== undefined || result.status !== 0 || result.signal !== null) { + this.failed = true; + return null; + } + return { output: result.stdout + "\n" + result.stderr, driverInputs: entry.inputs }; + } catch { + return null; + } + } + + private read(key: string, paths: number): LinkerJobEntry | null { + const bytes = this.cache.read("linker-job", key, MAX_ENTRY_BYTES); + if (bytes === null) return null; + try { + const entry: unknown = JSON.parse(bytes.toString("utf8")); + return validEntry(entry, paths) ? entry : null; + } catch { + return null; + } + } + + /** Records the job of the link `args` from `output`, the `-v` output of + * the driver's successful link, unless an entry exists. `driverInputs` + * are the identities of the paths `output` names, when the caller has + * them. */ + record( + args: readonly string[], + paths: readonly string[], + stage: string, + output: string, + driverInputs?: InputIdentity[], + ): void { + try { + const roots = [resolve(stage), realpathSync(stage)]; + const key = this.key(args, paths, roots); + if (key !== null && this.read(key, paths.length) === null) + this.store(key, output, paths, roots, driverInputs); + } catch { + /* The next link records it. */ + } + } + + /** Stores the entry for `output`, the driver's `-v` lines and job. */ + private store( + key: string, + output: string, + paths: readonly string[], + roots: string[], + driverInputs?: InputIdentity[], + ): LinkerJobEntry { + const lines = output.split(/\r?\n/).filter((line) => line.startsWith(' "')); + const job = lines.length === 1 ? this.jobOf(jobArguments(lines[0]!), paths, roots) : null; + const entry: LinkerJobEntry = { + schema: "scriptc.linker-job.v1", + job, + inputs: job === null ? [] : (driverInputs ?? tracePaths(output, true, roots).map(identity)), + }; + this.cache.write("linker-job", key, JSON.stringify(entry)); + return entry; + } + + /** `job` with each of `paths` as its index: null unless the job runs + * ld.lld and names each of the paths once, as one whole argument, and names + * nothing else in the stage. */ + private jobOf( + job: string[] | null, + paths: readonly string[], + roots: string[], + ): (string | number)[] | null { + if (job === null || job.length === 0) return null; + if (!isAbsolute(job[0]!) || basename(job[0]!) !== "ld.lld") return null; + const index = new Map(paths.map((path, i) => [path, i])); + const used = new Set(); + const tokens: (string | number)[] = []; + for (const arg of job) { + const at = index.get(arg); + if (at === undefined) { + if (underRoots(arg, roots)) return null; + tokens.push(arg); + } else { + if (used.has(at)) return null; + used.add(at); + tokens.push(at); + } + } + return used.size === paths.length ? tokens : null; + } +} From e842da2ceb7bcb8e7c914e2d20203624497ff06a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:24:38 -0500 Subject: [PATCH 273/395] Batch the program's identifier symbols before collection --- .../compiler/src/frontend/lowering/lowerer.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 2a04b33c3..d272b7014 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -5221,11 +5221,11 @@ export class Lowerer { const sourceFiles = parts.map((p) => p.sf); const fnDecls = parts.flatMap((p) => p.fnDecls); // This fixed-point scan intentionally sees deferred bodies before - // reachability is known. Batch their binding queries through the - // facade's panic fence, then keep lookup free of lowering side effects: - // resolveValueSymbol also flushes deferred diagnostics and applies - // merged-namespace fences, whose authority remains ordinary lowering. - this.checker.prefetchSymbolRoots(sourceFiles, true); + // reachability is known. Its binding queries were batched through the + // facade's panic fence before collection (prefetchProgramSymbols); keep + // lookup free of lowering side effects: resolveValueSymbol also flushes + // deferred diagnostics and applies merged-namespace fences, whose + // authority remains ordinary lowering. // The facts each fixed-point site reads, by the symbols, nodes and // element keys they belong to (created with the site list below). let siteDependencies: SiteDependencies | null = null; @@ -8027,6 +8027,7 @@ export class Lowerer { if (this.remainder) { for (const fp of parts) this.checker.prefetchSourceFile(fp.sf); } + this.prefetchProgramSymbols(parts); this.collectProgram(parts); this.speculativeTypeQueries(() => { extendAssertedRecordMembers( @@ -8486,6 +8487,7 @@ export class Lowerer { this.onEdge = (name) => { collectionEdges.add(name); }; + this.prefetchProgramSymbols(parts); this.collectProgram(parts); this.speculativeTypeQueries(() => { extendAssertedRecordMembers( @@ -12183,6 +12185,17 @@ export class Lowerer { return lowerClassExpressionInfo(this, expr); } + /** Batch-prefetches the symbols of every runtime identifier of the + * program's files. Collection's early-binding scan and the runtime- + * optional analysis look them up across every body, reached or not; + * one batch answers both, instead of one request per identifier. */ + private prefetchProgramSymbols(parts: FileParts[]): void { + this.checker.prefetchSymbolRoots( + parts.map((fp) => fp.sf), + true, + ); + } + /** Depth of the whole-program analyses that query types across every * file before reachability is known (speculativeTypeQueries). */ private speculativeTypeQueryDepth = 0; From f0f91ac1ff5a5540479d9dab10c8a69fb52631d9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:36:08 -0500 Subject: [PATCH 274/395] Load a Node module's fallback declarations only when the program names it Without @types/node, every program loaded scriptc-node-fallback.d.ts whole: the console and process globals and 47 Node modules, about 8,100 lines, parsed, bound and checked in every cold build whether or not the program imported them. - The fallback file keeps the globals and the modules they name (console, readline, readline/promises, perf_hooks, tty). Every other module, under both spellings, has its own file in ambient/node-fallback/, which references the files of the modules its declarations name. - A module's file joins the program when one of the program's files names the module in a string literal: imports, requires, import types and JSDoc types all name modules that way. The entry is read before the first program; the program-root discovery passes add modules that other files name. Files are read as tsgo reads them, and the TypeScript libraries and scriptc's own declarations are skipped. - The checks that recognized the fallback file by its path recognize every fallback file. --- .oxlintrc.jsonc | 14 +- .../ambient/node-fallback/assert-strict.d.ts | 47 + .../ambient/node-fallback/assert.d.ts | 86 + .../ambient/node-fallback/async_hooks.d.ts | 41 + .../ambient/node-fallback/buffer.d.ts | 23 + .../ambient/node-fallback/child_process.d.ts | 223 + .../ambient/node-fallback/cluster.d.ts | 17 + .../ambient/node-fallback/constants.d.ts | 15 + .../ambient/node-fallback/crypto.d.ts | 837 +++ .../compiler/ambient/node-fallback/dgram.d.ts | 58 + .../node-fallback/diagnostics_channel.d.ts | 87 + .../ambient/node-fallback/dns-promises.d.ts | 27 + .../compiler/ambient/node-fallback/dns.d.ts | 65 + .../ambient/node-fallback/events.d.ts | 60 + .../ambient/node-fallback/fs-promises.d.ts | 225 + .../compiler/ambient/node-fallback/fs.d.ts | 577 ++ .../ambient/node-fallback/graceful-fs.d.ts | 9 + .../compiler/ambient/node-fallback/http.d.ts | 326 + .../compiler/ambient/node-fallback/http2.d.ts | 816 ++ .../compiler/ambient/node-fallback/https.d.ts | 59 + .../ambient/node-fallback/module.d.ts | 10 + .../compiler/ambient/node-fallback/net.d.ts | 368 + .../compiler/ambient/node-fallback/os.d.ts | 288 + .../ambient/node-fallback/path-posix.d.ts | 22 + .../ambient/node-fallback/path-win32.d.ts | 22 + .../compiler/ambient/node-fallback/path.d.ts | 55 + .../ambient/node-fallback/process.d.ts | 11 + .../ambient/node-fallback/punycode.d.ts | 18 + .../ambient/node-fallback/querystring.d.ts | 55 + .../node-fallback/stream-consumers.d.ts | 21 + .../node-fallback/stream-promises.d.ts | 28 + .../ambient/node-fallback/stream-web.d.ts | 17 + .../ambient/node-fallback/stream.d.ts | 644 ++ .../ambient/node-fallback/string_decoder.d.ts | 20 + .../compiler/ambient/node-fallback/test.d.ts | 236 + .../node-fallback/timers-promises.d.ts | 31 + .../ambient/node-fallback/timers.d.ts | 28 + .../compiler/ambient/node-fallback/tls.d.ts | 169 + .../compiler/ambient/node-fallback/url.d.ts | 95 + .../ambient/node-fallback/util-types.d.ts | 50 + .../compiler/ambient/node-fallback/util.d.ts | 270 + .../compiler/ambient/node-fallback/v8.d.ts | 43 + .../ambient/node-fallback/worker_threads.d.ts | 113 + .../compiler/ambient/node-fallback/zlib.d.ts | 422 ++ .../ambient/scriptc-node-fallback.d.ts | 6557 ----------------- packages/compiler/src/frontend/dts-paths.ts | 139 +- .../compiler/src/frontend/fork-target.test.ts | 32 +- packages/compiler/src/frontend/fork-target.ts | 8 +- .../compiler/src/frontend/lowering/lowerer.ts | 5 +- packages/compiler/src/frontend/program.ts | 46 +- .../compiler/src/frontend/ts7/program-host.ts | 5 +- .../src/frontend/worker-target.test.ts | 17 +- .../test/node-fallback-modules.test.ts | 43 + packages/compiler/test/node-pin.test.ts | 4 +- packages/compiler/test/ts7/bench.test.ts | 10 +- packages/compiler/test/ts7/harness.ts | 4 +- scripts/native-cli-assets.mts | 5 + 57 files changed, 6924 insertions(+), 6599 deletions(-) create mode 100644 packages/compiler/ambient/node-fallback/assert-strict.d.ts create mode 100644 packages/compiler/ambient/node-fallback/assert.d.ts create mode 100644 packages/compiler/ambient/node-fallback/async_hooks.d.ts create mode 100644 packages/compiler/ambient/node-fallback/buffer.d.ts create mode 100644 packages/compiler/ambient/node-fallback/child_process.d.ts create mode 100644 packages/compiler/ambient/node-fallback/cluster.d.ts create mode 100644 packages/compiler/ambient/node-fallback/constants.d.ts create mode 100644 packages/compiler/ambient/node-fallback/crypto.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dgram.d.ts create mode 100644 packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dns-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dns.d.ts create mode 100644 packages/compiler/ambient/node-fallback/events.d.ts create mode 100644 packages/compiler/ambient/node-fallback/fs-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/fs.d.ts create mode 100644 packages/compiler/ambient/node-fallback/graceful-fs.d.ts create mode 100644 packages/compiler/ambient/node-fallback/http.d.ts create mode 100644 packages/compiler/ambient/node-fallback/http2.d.ts create mode 100644 packages/compiler/ambient/node-fallback/https.d.ts create mode 100644 packages/compiler/ambient/node-fallback/module.d.ts create mode 100644 packages/compiler/ambient/node-fallback/net.d.ts create mode 100644 packages/compiler/ambient/node-fallback/os.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path-posix.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path-win32.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path.d.ts create mode 100644 packages/compiler/ambient/node-fallback/process.d.ts create mode 100644 packages/compiler/ambient/node-fallback/punycode.d.ts create mode 100644 packages/compiler/ambient/node-fallback/querystring.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-consumers.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-web.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream.d.ts create mode 100644 packages/compiler/ambient/node-fallback/string_decoder.d.ts create mode 100644 packages/compiler/ambient/node-fallback/test.d.ts create mode 100644 packages/compiler/ambient/node-fallback/timers-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/timers.d.ts create mode 100644 packages/compiler/ambient/node-fallback/tls.d.ts create mode 100644 packages/compiler/ambient/node-fallback/url.d.ts create mode 100644 packages/compiler/ambient/node-fallback/util-types.d.ts create mode 100644 packages/compiler/ambient/node-fallback/util.d.ts create mode 100644 packages/compiler/ambient/node-fallback/v8.d.ts create mode 100644 packages/compiler/ambient/node-fallback/worker_threads.d.ts create mode 100644 packages/compiler/ambient/node-fallback/zlib.d.ts create mode 100644 packages/compiler/test/node-fallback-modules.test.ts diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index b0acea28a..55c8d0353 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -283,10 +283,22 @@ }, { // Ambient declarations describe an existing constructor and its prototype methods. - "files": ["packages/compiler/ambient/scriptc-node-fallback.d.ts"], + "files": [ + "packages/compiler/ambient/scriptc-node-fallback.d.ts", + "packages/compiler/ambient/node-fallback/*.d.ts", + ], "rules": { "typescript/no-unsafe-declaration-merging": "off", }, }, + { + // Each module's fallback declarations are a script that declares the + // module ambiently; it names the files of the modules it uses by + // reference, since a script cannot import them. + "files": ["packages/compiler/ambient/node-fallback/*.d.ts"], + "rules": { + "typescript/triple-slash-reference": "off", + }, + }, ], } diff --git a/packages/compiler/ambient/node-fallback/assert-strict.d.ts b/packages/compiler/ambient/node-fallback/assert-strict.d.ts new file mode 100644 index 000000000..ee0eaf8fa --- /dev/null +++ b/packages/compiler/ambient/node-fallback/assert-strict.d.ts @@ -0,0 +1,47 @@ +/// + +/* node:assert/strict — the same surface with the loose names bound to the + * strict comparisons (equal IS strictEqual, ...), exactly Node's aliasing; + * the module object is callable like assert's. */ +declare module "assert/strict" { + function strict(value: unknown, message?: string): void; + namespace strict { + function ok(value: unknown, message?: string): void; + function equal(actual: unknown, expected: unknown, message?: string): void; + function notEqual(actual: unknown, expected: unknown, message?: string): void; + function deepEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + function strictEqual(actual: unknown, expected: unknown, message?: string): void; + function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function fail(message?: string): never; + function throws(fn: () => unknown, expected?: unknown, message?: string): void; + function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; + function rejects( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function doesNotReject( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function match(value: string, regExp: RegExp, message?: string): void; + function doesNotMatch(value: string, regExp: RegExp, message?: string): void; + function ifError(value: unknown): void; + const strict: typeof import("assert/strict"); + type AssertOptions = import("assert").AssertOptions; + type Assert = import("assert").Assert; + type AssertStrict = import("assert").AssertStrict; + const Assert: typeof import("assert").Assert; + } + export = strict; +} + +declare module "node:assert/strict" { + import strict = require("assert/strict"); + export = strict; +} diff --git a/packages/compiler/ambient/node-fallback/assert.d.ts b/packages/compiler/ambient/node-fallback/assert.d.ts new file mode 100644 index 000000000..830d33e01 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/assert.d.ts @@ -0,0 +1,86 @@ +/// + +declare module "assert" { + function assert(value: unknown, message?: string): void; + namespace assert { + interface AssertionErrorOptions { + message?: string | undefined; + actual?: unknown; + expected?: unknown; + operator?: string | undefined; + stackStartFn?: Function | undefined; + diff?: "simple" | "full" | undefined; + } + class AssertionError extends Error { + constructor(options: AssertionErrorOptions); + actual: unknown; + expected: unknown; + generatedMessage: boolean; + code: "ERR_ASSERTION"; + operator: string; + } + function ok(value: unknown, message?: string): void; + function strictEqual(actual: unknown, expected: unknown, message?: string): void; + function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function equal(actual: unknown, expected: unknown, message?: string): void; + function notEqual(actual: unknown, expected: unknown, message?: string): void; + function deepEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + function fail(message?: string): never; + function throws(fn: () => unknown, expected?: unknown, message?: string): void; + function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; + function rejects( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function doesNotReject( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function match(value: string, regExp: RegExp, message?: string): void; + function doesNotMatch(value: string, regExp: RegExp, message?: string): void; + function ifError(value: unknown): void; + const strict: typeof import("assert/strict"); + interface AssertOptions { + diff?: "simple" | "full" | undefined; + strict?: boolean | undefined; + skipPrototype?: boolean | undefined; + } + type AssertMethodNames = + | "deepEqual" + | "deepStrictEqual" + | "doesNotMatch" + | "doesNotReject" + | "doesNotThrow" + | "equal" + | "fail" + | "ifError" + | "match" + | "notDeepEqual" + | "notDeepStrictEqual" + | "notEqual" + | "notStrictEqual" + | "ok" + | "partialDeepStrictEqual" + | "rejects" + | "strictEqual" + | "throws"; + interface Assert extends Pick {} + interface AssertStrict extends Pick {} + const Assert: { + new (options?: AssertOptions & { strict?: true | undefined }): AssertStrict; + new (options: AssertOptions): Assert; + }; + } + export = assert; +} + +declare module "node:assert" { + import assert = require("assert"); + export = assert; +} diff --git a/packages/compiler/ambient/node-fallback/async_hooks.d.ts b/packages/compiler/ambient/node-fallback/async_hooks.d.ts new file mode 100644 index 000000000..91082ee12 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/async_hooks.d.ts @@ -0,0 +1,41 @@ +/* node:async_hooks — declared surface without lowering: async-hook + * instrumentation is guarded behind env flags in harness code; a reached + * use fences at its site. */ +declare module "async_hooks" { + /* AsyncLocalStorage: fiber-carried context snapshots — run/getStore/ + * exit/enterWith/disable lower onto the runtime's active-slot machinery + * (als.* libCalls); values cross as dyn values. bind and snapshot bind + * functions to the active context; the options carry the store's + * default value and name. */ + export interface AsyncLocalStorageOptions { + defaultValue?: any; + name?: string | undefined; + } + export class AsyncLocalStorage { + constructor(options?: AsyncLocalStorageOptions); + static bind any>(fn: Func): Func; + static snapshot(): (fn: (...args: TArgs) => R, ...args: TArgs) => R; + readonly name: string; + run(store: T, callback: (...args: any[]) => R, ...args: any[]): R; + exit(callback: (...args: any[]) => R, ...args: any[]): R; + getStore(): T | undefined; + enterWith(store: T): void; + disable(): void; + } + export interface AsyncHook { + enable(): AsyncHook; + disable(): AsyncHook; + } + export function createHook(callbacks: { + init?: (asyncId: number, type: string, triggerAsyncId: number, resource: unknown) => void; + before?: (asyncId: number) => void; + after?: (asyncId: number) => void; + destroy?: (asyncId: number) => void; + }): AsyncHook; + export function executionAsyncId(): number; + export function triggerAsyncId(): number; +} + +declare module "node:async_hooks" { + export * from "async_hooks"; +} diff --git a/packages/compiler/ambient/node-fallback/buffer.d.ts b/packages/compiler/ambient/node-fallback/buffer.d.ts new file mode 100644 index 000000000..c37b91c70 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/buffer.d.ts @@ -0,0 +1,23 @@ +/* node:buffer — the module spelling of the Buffer/atob/btoa globals. + * Buffer re-exports the GLOBAL declaration, so an imported binding + * resolves to the same symbol the lowerings already answer for (and + * third-party .d.ts files that `import { Buffer } from "node:buffer"` — + * file-entry-cache in a typed-JS source graph — typecheck). */ +declare module "buffer" { + export function isAscii(input: ArrayBuffer | ArrayBufferView): boolean; + export function isUtf8(input: ArrayBuffer | ArrayBufferView): boolean; + export function transcode(source: Uint8Array, fromEncoding: string, toEncoding: string): Buffer; + export function atob(data: string): string; + export function btoa(data: string): string; + export const kMaxLength: number; + export const kStringMaxLength: number; + export const Buffer: BufferConstructor; + export type Blob = globalThis.Blob; + export type File = globalThis.File; + export const Blob: typeof globalThis.Blob; + export const File: typeof globalThis.File; +} + +declare module "node:buffer" { + export * from "buffer"; +} diff --git a/packages/compiler/ambient/node-fallback/child_process.d.ts b/packages/compiler/ambient/node-fallback/child_process.d.ts new file mode 100644 index 000000000..c9e6483c4 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/child_process.d.ts @@ -0,0 +1,223 @@ +/* node:child_process — the synchronous slice: spawnSync(command, args?) + * runs the child to completion (posix_spawn + waitpid) with stdout/stderr + * captured as utf8 strings. `status` is the exit code, or null when the + * child died to a signal OR could not be spawned at all (nonexistent + * binary, permission denied) — Node reports spawn failure through the + * result's `error` property, which this surface does not carry, and types + * stdout/stderr null there where scriptc returns "" (SEMANTICS.md). + * No shell, no options object: the command runs directly (PATH-searched, + * like Node). */ +declare module "child_process" { + export interface SpawnSyncReturns { + readonly status: number | null; + readonly stdout: string; + readonly stderr: string; + /* The termination signal's name ("SIGTERM", ...) when a signal + * killed the child (a timeout's killSignal included), null otherwise + * — Node's shape. */ + readonly signal: string | null; + /* Node's spawn-failure carrier: a real Error ("spawnSync + * ENOENT", `code` stamped) when the spawn itself failed — status is + * null and both outputs "" then — or ETIMEDOUT after a timeout kill; + * undefined otherwise. */ + readonly error?: Error; + } + /* scriptc always captures utf8 strings; pass { encoding: "utf8" } + * whenever stdout/stderr is READ so plain Node agrees (without it Node + * hands Buffers). The other supported options: timeout (ms — sends + * killSignal at the deadline and the result carries error: ETIMEDOUT + + * the signal, never a throw), killSignal (a signal NAME literal), + * stdio ("ignore"/"inherit"/"pipe" or a 3-tuple of those — captures + * read "" for non-piped streams), env (replaces the child's + * environment), and windowsHide (a POSIX no-op). */ + export function spawnSync( + command: string, + args?: string[], + options?: { + encoding?: "utf8" | "utf-8"; + timeout?: number; + killSignal?: string; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + env?: { [k: string]: string | undefined }; + windowsHide?: boolean; + }, + ): SpawnSyncReturns; + + /* The asynchronous slice: spawn with stdio "ignore"/"inherit"/fd/"pipe" + * tuples (piped stdin is writable through child.stdin; piped stdout/stderr + * deliver through child.stdout/stderr) and the terminal events. "exit" + * fires once with the exit code, or + * null when the child died to a signal; "error" fires ONLY when the + * child could not be spawned at all (Node's split: a spawn failure + * emits "error" and never "exit"). "close" fires after exit/error and + * every piped stdio handle reaches EOF. Exit/close listeners may take + * Node's code and signal parameters. `on`/`once` return void here (Node + * returns the child; chaining is fenced). The event loop keeps the process + * alive until every spawned child is reaped, like Node — reaping polls at + * loop quiescence (SEMANTICS.md documents the divergence). An "error" + * event with no registered listener prints the error and exits 1, + * exactly the unhandled-'error' EventEmitter behavior. */ + export interface ChildStdin { + readonly writable: boolean; + write(chunk: string | Uint8Array): boolean; + end(): void; + destroy(): void; + on(event: "drain" | "finish", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: string, listener: (...args: any[]) => void): void; + once(event: "drain" | "finish", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: string, listener: (...args: any[]) => void): void; + } + + export interface ChildProcess extends Disposable { + ref(): void; + on(event: "spawn", listener: () => void): this; + once(event: "spawn", listener: () => void): this; + /* The exit listener may also take Node's second parameter — the + * terminating signal's name, null for a normal exit. */ + on(event: "exit", listener: (code: number | null, signal: string | null) => void): this; + on(event: "close", listener: (code: number | null, signal: string | null) => void): this; + on(event: "error", listener: (err: Error) => void): this; + once(event: "exit", listener: (code: number | null, signal: string | null) => void): this; + once(event: "close", listener: (code: number | null, signal: string | null) => void): this; + once(event: "error", listener: (err: Error) => void): this; + on(event: "message", listener: (message: any) => void): this; + on(event: "disconnect", listener: () => void): this; + once(event: "message", listener: (message: any) => void): this; + once(event: "disconnect", listener: () => void): this; + /* The lifecycle members, Node's exact shapes: pid is undefined exactly + * when the spawn failed; exitCode is null while running, the code + * after a normal exit, null for a signal death, and -errno once a + * spawn failure's "error" event has fired; kill resolves signal names + * through Node's table (unknown names throw the Unknown signal + * TypeError), answers true when the signal was sent, false once the + * child was reaped (or never spawned), and sets `killed` on success; + * unref() drops the child from the event loop's keep-alive set (it is + * still reaped while the loop runs for other reasons — SEMANTICS.md + * has the reap story). */ + readonly pid: number | undefined; + readonly exitCode: number | null; + readonly killed: boolean; + kill(signal?: string | number): boolean; + unref(): void; + readonly connected: boolean; + send(message: object, callback?: (error: Error | null) => void): boolean; + disconnect(): void; + [Symbol.dispose](): void; + /* The piped streams — non-null exactly when the matching stdio slot + * was "pipe" (Node's shape). */ + readonly stdin: ChildStdin | null; + readonly stdout: NodeJS.ReadableStream | null; + readonly stderr: NodeJS.ReadableStream | null; + } + export interface SpawnOptions { + /* The 3-tuple form admits number fds in the stdout/stderr slots — + * openSync results dup2'd into the child (the daemon-log idiom + * ["ignore", logFd, logFd]) — and "pipe" there too (child.stdout/ + * child.stderr streams); "pipe" in the stdin slot exposes + * child.stdin. */ + stdio?: "ignore" | "inherit" | "pipe" | ("ignore" | "inherit" | "pipe" | number)[]; + /* detached gives the child its own session and process group + * (POSIX_SPAWN_SETSID); env REPLACES the child environment; cwd + * sets its working directory; windowsHide is a POSIX no-op; shell + * runs the complete command string through /bin/sh or cmd.exe. */ + detached?: boolean; + env?: { [k: string]: string | undefined }; + cwd?: string; + windowsHide?: boolean; + shell?: boolean; + } + export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; + export function spawn(command: string, options?: SpawnOptions): ChildProcess; + + export function fork( + modulePath: string | URL, + args?: string[], + options?: { + cwd?: string; + env?: { [k: string]: string | undefined }; + execArgv?: string[]; + silent?: boolean; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit" | "ipc")[]; + serialization?: "json" | "advanced"; + windowsHide?: boolean; + }, + ): ChildProcess; + + /* The synchronous exec pair: execFileSync runs a file directly (no + * shell), execSync runs a command through /bin/sh -c. Both capture + * stdout as a utf8 string (pass { encoding: "utf8" } so plain Node + * agrees — without it Node returns a Buffer) and THROW on a non-zero + * exit / signal death — the Error's message is Node's "Command failed: + * " (with the captured stderr appended). Supported options: + * encoding ("utf8"), cwd, env (REPLACES the child environment), input + * (fed to stdin), timeout (ms — SIGTERMs the child, then throws + * ETIMEDOUT), stdio ("pipe"/"ignore" or a 3-tuple of those — the + * default captures stderr AND echoes it to the parent, like Node), and + * maxBuffer (accepted, not enforced). Node's status/stdout/stderr error + * properties are not carried (SEMANTICS.md). */ + interface ExecSyncOptions { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + input?: string; + timeout?: number; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + shell?: boolean; + } + /* The named options interface for the string-encoding exec form — + * @types/node's ExecFileSyncOptionsWithStringEncoding, narrowed to the + * honestly-implemented members. VALUES of this type are a real record + * (type-mapper.ts interns the shape), so a typed const or a runner function's + * options parameter flows to execFileSync at runtime — the windows-ca + * command-runner idiom. */ + export interface ExecFileSyncOptionsWithStringEncoding { + encoding: "utf8" | "utf-8"; + cwd?: string; + input?: string; + timeout?: number; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + maxBuffer?: number; + windowsHide?: boolean; + } + export function execFileSync(file: string, args?: string[], options?: ExecSyncOptions): string; + export function execSync(command: string, options?: ExecSyncOptions): string; + export type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void; + /* The callback form starts an asynchronous child and captures utf8 + * stdout/stderr. The no-callback overload remains for util.promisify's + * static target recognition; reached no-callback calls still fence. */ + export function execFile(file: string, callback: ExecFileCallback): ChildProcess; + export function execFile( + file: string, + args: string[] | null, + callback: ExecFileCallback, + ): ChildProcess; + export function execFile( + file: string, + args: string[] | null, + options: ExecSyncOptions, + callback: ExecFileCallback, + ): ChildProcess; + export function execFile( + file: string, + args?: string[] | null, + options?: ExecSyncOptions, + ): unknown; + /* The async shell form. The callback forms lower like execFile's, with + * the command line run by /bin/sh -c, and util.promisify recognizes it + * statically, as it does execFile; reached calls without a callback + * fence at their site. */ + export function exec( + command: string, + options?: ExecSyncOptions | ((error: Error | null, stdout: string, stderr: string) => void), + callback?: (error: Error | null, stdout: string, stderr: string) => void, + ): ChildProcess; +} + +declare module "node:child_process" { + export * from "child_process"; +} diff --git a/packages/compiler/ambient/node-fallback/cluster.d.ts b/packages/compiler/ambient/node-fallback/cluster.d.ts new file mode 100644 index 000000000..7cb89b09f --- /dev/null +++ b/packages/compiler/ambient/node-fallback/cluster.d.ts @@ -0,0 +1,17 @@ +/* node:cluster — the primary-probe slice. A compiled binary never runs + * as a cluster worker (cluster forks the node binary itself — the + * mechanism is na for compiled programs), so isPrimary lowers to `true` + * and isWorker to `false`; everything else is undeclared. */ +declare module "cluster" { + const cluster: { + readonly isPrimary: boolean; + readonly isMaster: boolean; + readonly isWorker: boolean; + }; + export = cluster; +} + +declare module "node:cluster" { + import cluster = require("cluster"); + export = cluster; +} diff --git a/packages/compiler/ambient/node-fallback/constants.d.ts b/packages/compiler/ambient/node-fallback/constants.d.ts new file mode 100644 index 000000000..8ff410bfa --- /dev/null +++ b/packages/compiler/ambient/node-fallback/constants.d.ts @@ -0,0 +1,15 @@ +/* node:constants (deprecated, DEP0008) — one frozen object: os.constants' + * dlopen, errno, priority and signals groups, fs.constants, and + * crypto.constants. A platform's missing constant reads as undefined. */ +declare module "constants" { + const constants: { readonly [name: string]: number } & { + readonly defaultCoreCipherList: string; + readonly defaultCipherList: string; + }; + export = constants; +} + +declare module "node:constants" { + import constants = require("constants"); + export = constants; +} diff --git a/packages/compiler/ambient/node-fallback/crypto.d.ts b/packages/compiler/ambient/node-fallback/crypto.d.ts new file mode 100644 index 000000000..05ed6c69e --- /dev/null +++ b/packages/compiler/ambient/node-fallback/crypto.d.ts @@ -0,0 +1,837 @@ +/// +/// + +/* node:crypto: the static hashing/MAC/random/key-derivation utility slice. */ +declare module "crypto" { + import { Transform } from "stream"; + export type KeyObjectType = "secret" | "public" | "private"; + export interface JsonWebKey { + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + k?: string; + kty?: string; + n?: string; + p?: string; + q?: string; + qi?: string; + x?: string; + y?: string; + [key: string]: unknown; + } + export interface JwkKeyExportOptions { + format: "jwk"; + } + export interface KeyExportOptions { + format?: "buffer" | undefined; + } + export type KeyFormat = "pem" | "der" | "jwk"; + export interface AsymmetricKeyExportOptions { + type: "pkcs1" | "spki" | "pkcs8" | "sec1"; + format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; + } + export interface AsymmetricKeyDetails { + modulusLength?: number; + publicExponent?: bigint; + namedCurve?: string; + } + /* Secret keys, and RSA and EC private and public keys. */ + export class KeyObject { + private constructor(); + type: KeyObjectType; + symmetricKeySize?: number; + asymmetricKeyType?: string; + asymmetricKeyDetails?: AsymmetricKeyDetails; + export(options: AsymmetricKeyExportOptions<"pem">): string | Buffer; + export(options: AsymmetricKeyExportOptions<"der">): Buffer; + export(options?: KeyExportOptions): Buffer; + export(options: JwkKeyExportOptions): JsonWebKey; + equals(otherKeyObject: KeyObject): boolean; + } + export interface PrivateKeyInput { + key: string | Buffer; + format?: "pem" | "der" | undefined; + type?: "pkcs1" | "pkcs8" | "sec1" | undefined; + encoding?: string | undefined; + passphrase?: string | Buffer | undefined; + } + export interface PublicKeyInput { + key: string | Buffer; + format?: "pem" | "der" | undefined; + type?: "pkcs1" | "spki" | undefined; + encoding?: string | undefined; + } + export interface JsonWebKeyInput { + key: JsonWebKey; + format: "jwk"; + } + export function createPrivateKey( + key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, + ): KeyObject; + export function createPublicKey( + key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, + ): KeyObject; + export interface KeyPairKeyObjectResult { + publicKey: KeyObject; + privateKey: KeyObject; + } + export interface KeyPairSyncResult { + publicKey: T1; + privateKey: T2; + } + export interface RSAKeyPairKeyObjectOptions { + modulusLength: number; + publicExponent?: number | undefined; + } + export interface ECKeyPairKeyObjectOptions { + namedCurve: string; + } + export interface RSAKeyPairOptions { + modulusLength: number; + publicExponent?: number | undefined; + publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export interface ECKeyPairOptions { + namedCurve: string; + publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; + privateKeyEncoding: { + type: "sec1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + ): KeyPairKeyObjectResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairKeyObjectOptions, + ): KeyPairKeyObjectResult; + export interface ED25519KeyPairOptions { + publicKeyEncoding: { type: "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; + export interface X25519KeyPairOptions { + publicKeyEncoding: { type: "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync(type: "x25519", options?: {}): KeyPairKeyObjectResult; + export function generateKeyPair( + type: "x25519", + options: {} | undefined, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "ed25519", + options: {} | undefined, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "ec", + options: ECKeyPairKeyObjectOptions, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export interface SigningOptions { + padding?: number | undefined; + saltLength?: number | undefined; + dsaEncoding?: "der" | "ieee-p1363" | undefined; + } + export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} + export interface SignKeyObjectInput extends SigningOptions { + key: KeyObject; + } + export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} + export interface VerifyKeyObjectInput extends SigningOptions { + key: KeyObject; + } + export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject }): Buffer; + export function diffieHellman( + options: { privateKey: KeyObject; publicKey: KeyObject }, + callback: (err: Error | null, secret: Buffer) => void, + ): void; + export function sign( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + ): Buffer; + export function verify( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + /* Sign and Verify accumulate update() data; they are not usable as + * writable streams. */ + export class Sign { + private constructor(); + update(data: string | ArrayBufferView): this; + update(data: string, inputEncoding: Encoding): this; + sign( + privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + ): Buffer; + sign( + privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + outputFormat: BinaryToTextEncoding, + ): string; + } + export class Verify { + private constructor(); + update(data: string | ArrayBufferView): this; + update(data: string, inputEncoding: Encoding): this; + verify( + object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + verify( + object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: string, + signatureEncoding?: BinaryToTextEncoding, + ): boolean; + } + export function createSign(algorithm: string): Sign; + export function createVerify(algorithm: string): Verify; + export function createSecretKey(key: ArrayBufferView | ArrayBufferLike): KeyObject; + export function createSecretKey(key: string, encoding: BufferEncoding): KeyObject; + export function generateKeySync(type: "hmac" | "aes", options: { length: number }): KeyObject; + export function generateKey( + type: "hmac" | "aes", + options: { length: number }, + callback: (err: Error | null, key: KeyObject) => void, + ): void; + export interface RandomUUIDOptions { + disableEntropyCache?: boolean | undefined; + } + export function randomUUID(options?: RandomUUIDOptions): string; + export function randomUUIDv7(options?: RandomUUIDOptions): string; + export function randomBytes(size: number): Buffer; + export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; + export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "utf-16le" | "latin1"; + export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; + export type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding; + export interface HashOptions { + outputLength?: number | undefined; + } + /* Hash and Hmac are Transforms: written chunks update the digest, which + * the readable side delivers once the writable side ends. */ + export interface Hash extends Transform { + copy(options?: HashOptions): Hash; + update(data: string | ArrayBufferView): Hash; + update(data: string, inputEncoding: Encoding): Hash; + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + } + export interface Hmac extends Transform { + update(data: string | ArrayBufferView): Hmac; + update(data: string, inputEncoding: Encoding): Hmac; + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + } + export function createHash(algorithm: string, options?: HashOptions): Hash; + export function createHmac( + algorithm: string, + key: BinaryLike | KeyObject, + options?: { encoding?: BufferEncoding | undefined }, + ): Hmac; + export interface OneShotDigestOptions { + outputEncoding?: BinaryToTextEncoding | "buffer" | undefined; + outputLength?: number | undefined; + } + export interface OneShotDigestOptionsWithStringEncoding extends OneShotDigestOptions { + outputEncoding?: BinaryToTextEncoding | undefined; + } + export interface OneShotDigestOptionsWithBufferEncoding extends OneShotDigestOptions { + outputEncoding: "buffer"; + } + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options?: OneShotDigestOptionsWithStringEncoding | BinaryToTextEncoding, + ): string; + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options: OneShotDigestOptionsWithBufferEncoding | "buffer", + ): Buffer; + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options: OneShotDigestOptions | BinaryToTextEncoding | "buffer", + ): string | Buffer; + export function timingSafeEqual( + a: ArrayBufferView | ArrayBufferLike, + b: ArrayBufferView | ArrayBufferLike, + ): boolean; + /* WebCrypto's getRandomValues (also the webcrypto object's): integer + * typed arrays, filled in place and returned. */ + type RandomValuesArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | BigInt64Array + | BigUint64Array; + export function getRandomValues(typedArray: T): T; + /* The WebCrypto global's object and its SubtleCrypto. */ + export const webcrypto: Crypto; + export const subtle: SubtleCrypto; + export function randomFillSync( + buffer: T, + offset?: number, + size?: number, + ): T; + export function randomFill( + buffer: T, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomFill( + buffer: T, + offset: number, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomFill( + buffer: T, + offset: number, + size: number, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomInt(max: number): number; + export function randomInt(min: number, max: number): number; + export function randomInt( + max: number, + callback: (err: Error | null, value: number) => void, + ): void; + export function randomInt( + min: number, + max: number, + callback: (err: Error | null, value: number) => void, + ): void; + export function pbkdf2Sync( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + ): Buffer; + export function pbkdf2( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function randomBytes( + size: number, + callback: (error: Error | null, buffer: Buffer) => void, + ): void; + export type BinaryLike = string | ArrayBuffer | ArrayBufferView; + /* Cipheriv/Decipheriv: aes-128/192/256 in cbc, ctr, gcm, ccm, ecb, cfb, + * cfb8, cfb1, and ofb modes and their key wraps, des-ede3 and des-ede, + * chacha20, and chacha20-poly1305. Both are Transforms: written chunks go + * through update(), and the end pushes final(). */ + export type CipherCCMTypes = "aes-128-ccm" | "aes-192-ccm" | "aes-256-ccm"; + export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; + export type CipherChaCha20Poly1305Types = "chacha20-poly1305"; + export type CipherKey = BinaryLike | KeyObject; + export interface CipherCCMOptions { + authTagLength: number; + } + export interface CipherGCMOptions { + authTagLength?: number; + } + export interface CipherChaCha20Poly1305Options { + authTagLength?: number; + } + export interface Cipheriv extends Transform { + update(data: string | ArrayBufferView): Buffer; + update(data: string, inputEncoding: BufferEncoding): Buffer; + update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; + update( + data: string, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + ): string; + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + setAutoPadding(autoPadding?: boolean): this; + } + export interface CipherCCM extends Cipheriv { + setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface CipherGCM extends Cipheriv { + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface CipherChaCha20Poly1305 extends Cipheriv { + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface Decipheriv extends Transform { + update(data: ArrayBufferView): Buffer; + update(data: string, inputEncoding: BufferEncoding): Buffer; + update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; + update( + data: string, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + ): string; + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + setAutoPadding(autoPadding?: boolean): this; + } + export interface DecipherCCM extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; + } + export interface DecipherGCM extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + } + export interface DecipherChaCha20Poly1305 extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + } + export function createCipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, + ): CipherCCM; + export function createCipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, + ): CipherGCM; + export function createCipheriv( + algorithm: CipherChaCha20Poly1305Types, + key: CipherKey, + iv: BinaryLike, + options?: CipherChaCha20Poly1305Options, + ): CipherChaCha20Poly1305; + export function createCipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: { authTagLength?: number }, + ): Cipheriv; + export function createDecipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, + ): DecipherCCM; + export function createDecipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, + ): DecipherGCM; + export function createDecipheriv( + algorithm: CipherChaCha20Poly1305Types, + key: CipherKey, + iv: BinaryLike, + options?: CipherChaCha20Poly1305Options, + ): DecipherChaCha20Poly1305; + export function createDecipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: { authTagLength?: number }, + ): Decipheriv; + export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; + export class ECDH { + private constructor(); + static convertKey( + key: BinaryLike, + curve: string, + inputEncoding?: BinaryToTextEncoding, + outputEncoding?: "latin1" | "hex" | "base64" | "base64url", + format?: ECDHKeyFormat, + ): Buffer | string; + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret(otherPublicKey: string, inputEncoding: BinaryToTextEncoding): Buffer; + computeSecret(otherPublicKey: ArrayBufferView, outputEncoding: BinaryToTextEncoding): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPublicKey(encoding?: null, format?: ECDHKeyFormat): Buffer; + getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; + } + export function createECDH(curveName: string): ECDH; + export class DiffieHellman { + private constructor(); + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + computeSecret( + otherPublicKey: ArrayBufferView, + inputEncoding?: null, + outputEncoding?: null, + ): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding?: null, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + inputEncoding: null, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + setPublicKey(publicKey: ArrayBufferView): void; + setPublicKey(publicKey: string, encoding: BufferEncoding): void; + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BufferEncoding): void; + verifyError: number; + } + export type DiffieHellmanGroup = Omit; + export function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman; + export function createDiffieHellman( + prime: ArrayBuffer | ArrayBufferView, + generator?: number | ArrayBuffer | ArrayBufferView, + ): DiffieHellman; + export function createDiffieHellman( + prime: ArrayBuffer | ArrayBufferView, + generator: string, + generatorEncoding: BinaryToTextEncoding, + ): DiffieHellman; + export function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator?: number | ArrayBuffer | ArrayBufferView, + ): DiffieHellman; + export function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator: string, + generatorEncoding: BinaryToTextEncoding, + ): DiffieHellman; + export function getDiffieHellman(groupName: string): DiffieHellmanGroup; + export function createDiffieHellmanGroup(name: string): DiffieHellmanGroup; + export type LargeNumberLike = ArrayBufferView | SharedArrayBuffer | ArrayBuffer | bigint; + export interface GeneratePrimeOptions { + add?: LargeNumberLike | undefined; + rem?: LargeNumberLike | undefined; + safe?: boolean | undefined; + bigint?: boolean | undefined; + } + export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { + bigint: true; + } + export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { + bigint?: false | undefined; + } + export function generatePrime( + size: number, + callback: (err: Error | null, prime: ArrayBuffer) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptionsBigInt, + callback: (err: Error | null, prime: bigint) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + callback: (err: Error | null, prime: ArrayBuffer) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptions, + callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, + ): void; + export function generatePrimeSync(size: number): ArrayBuffer; + export function generatePrimeSync(size: number, options: GeneratePrimeOptionsBigInt): bigint; + export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + ): ArrayBuffer; + export function generatePrimeSync( + size: number, + options: GeneratePrimeOptions, + ): ArrayBuffer | bigint; + export interface CheckPrimeOptions { + checks?: number | undefined; + } + export function checkPrime( + value: LargeNumberLike, + callback: (err: Error | null, result: boolean) => void, + ): void; + export function checkPrime( + value: LargeNumberLike, + options: CheckPrimeOptions, + callback: (err: Error | null, result: boolean) => void, + ): void; + export function checkPrimeSync(candidate: LargeNumberLike, options?: CheckPrimeOptions): boolean; + export interface ScryptOptions { + N?: number; + r?: number; + p?: number; + cost?: number; + blockSize?: number; + parallelization?: number; + maxmem?: number; + } + export function scryptSync( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + options?: ScryptOptions, + ): Buffer; + export function scrypt( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function scrypt( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + options: ScryptOptions, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function hkdfSync( + digest: string, + ikm: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + keylen: number, + ): ArrayBuffer; + export function hkdf( + digest: string, + ikm: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + keylen: number, + callback: (error: Error | null, derivedKey: ArrayBuffer) => void, + ): void; + /* The lowered X509Certificate surface is the data-record slice: the + * fields below, all computed at construction (raw decodes the input, + * and the others read raw). validFrom/validTo are Node's + * ASN1_TIME_print strings ("Jul 1 00:00:00 2026 GMT"). Unparseable + * input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ + export class X509Certificate { + constructor(buffer: Uint8Array | string); + readonly ca: boolean; + readonly fingerprint: string; + readonly fingerprint256: string; + readonly fingerprint512: string; + readonly subject: string; + readonly subjectAltName: string | undefined; + readonly infoAccess: string | undefined; + readonly issuer: string; + readonly raw: Buffer; + readonly serialNumber: string; + readonly signatureAlgorithm: string | undefined; + readonly signatureAlgorithmOid: string; + readonly validFrom: string; + readonly validFromDate: Date; + readonly validTo: string; + readonly validToDate: Date; + /* the certificate's public key; every read answers the same KeyObject */ + readonly publicKey: KeyObject; + verify(publicKey: KeyObject): boolean; + checkPrivateKey(privateKey: KeyObject): boolean; + checkIssued(otherCert: X509Certificate): boolean; + checkHost(name: string, options?: X509CheckOptions): string | undefined; + checkEmail(email: string, options?: X509CheckOptions): string | undefined; + checkIP(ip: string): string | undefined; + toLegacyObject(): import("tls").PeerCertificate; + } + export interface X509CheckOptions { + subject?: "always" | "default" | "never"; + wildcards?: boolean; + partialWildcards?: boolean; + multiLabelWildcards?: boolean; + singleLabelSubdomains?: boolean; + } + /* The crypto introspection statics: getFips() answers 0 (a compiled + * binary is never a FIPS build), the three name lists bake as fresh + * string[] literals of Node v24 answers, and constants (below) bakes + * per member like http2.constants. */ + export function getFips(): 1 | 0; + export function getCiphers(): string[]; + export type CipherMode = + | "cbc" + | "ccm" + | "cfb" + | "ctr" + | "ecb" + | "gcm" + | "ocb" + | "ofb" + | "stream" + | "wrap" + | "xts"; + export interface CipherInfoOptions { + keyLength?: number | undefined; + ivLength?: number | undefined; + } + export interface CipherInfo { + name: string; + nid: number; + blockSize?: number | undefined; + ivLength?: number | undefined; + keyLength: number; + mode: CipherMode; + } + export function getCipherInfo( + nameOrNid: string | number, + options?: CipherInfoOptions, + ): CipherInfo | undefined; + export function getHashes(): string[]; + export function getCurves(): string[]; + /* Node v24 crypto.constants, literal-typed (the Http2Constants + * stance). Every ACCESS bakes as its literal at lowering; the object + * itself never materializes (bare crypto.constants value uses fence). */ + export interface CryptoConstants { + readonly OPENSSL_VERSION_NUMBER: 810549376; + readonly SSL_OP_ALL: 2147485776; + readonly SSL_OP_ALLOW_NO_DHE_KEX: 1024; + readonly SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144; + readonly SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304; + readonly SSL_OP_CISCO_ANYCONNECT: 32768; + readonly SSL_OP_COOKIE_EXCHANGE: 8192; + readonly SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648; + readonly SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048; + readonly SSL_OP_LEGACY_SERVER_CONNECT: 4; + readonly SSL_OP_NO_COMPRESSION: 131072; + readonly SSL_OP_NO_ENCRYPT_THEN_MAC: 524288; + readonly SSL_OP_NO_QUERY_MTU: 4096; + readonly SSL_OP_NO_RENEGOTIATION: 1073741824; + readonly SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536; + readonly SSL_OP_NO_SSLv2: 0; + readonly SSL_OP_NO_SSLv3: 33554432; + readonly SSL_OP_NO_TICKET: 16384; + readonly SSL_OP_NO_TLSv1: 67108864; + readonly SSL_OP_NO_TLSv1_1: 268435456; + readonly SSL_OP_NO_TLSv1_2: 134217728; + readonly SSL_OP_NO_TLSv1_3: 536870912; + readonly SSL_OP_PRIORITIZE_CHACHA: 2097152; + readonly SSL_OP_TLS_ROLLBACK_BUG: 8388608; + readonly ENGINE_METHOD_RSA: 1; + readonly ENGINE_METHOD_DSA: 2; + readonly ENGINE_METHOD_DH: 4; + readonly ENGINE_METHOD_RAND: 8; + readonly ENGINE_METHOD_EC: 2048; + readonly ENGINE_METHOD_CIPHERS: 64; + readonly ENGINE_METHOD_DIGESTS: 128; + readonly ENGINE_METHOD_PKEY_METHS: 512; + readonly ENGINE_METHOD_PKEY_ASN1_METHS: 1024; + readonly ENGINE_METHOD_ALL: 65535; + readonly ENGINE_METHOD_NONE: 0; + readonly DH_CHECK_P_NOT_SAFE_PRIME: 2; + readonly DH_CHECK_P_NOT_PRIME: 1; + readonly DH_UNABLE_TO_CHECK_GENERATOR: 4; + readonly DH_NOT_SUITABLE_GENERATOR: 8; + readonly RSA_PKCS1_PADDING: 1; + readonly RSA_SSLV23_PADDING: 2; + readonly RSA_NO_PADDING: 3; + readonly RSA_PKCS1_OAEP_PADDING: 4; + readonly RSA_X931_PADDING: 5; + readonly RSA_PKCS1_PSS_PADDING: 6; + readonly RSA_PSS_SALTLEN_DIGEST: -1; + readonly RSA_PSS_SALTLEN_MAX_SIGN: -2; + readonly RSA_PSS_SALTLEN_AUTO: -2; + readonly defaultCoreCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; + readonly TLS1_VERSION: 769; + readonly TLS1_1_VERSION: 770; + readonly TLS1_2_VERSION: 771; + readonly TLS1_3_VERSION: 772; + readonly POINT_CONVERSION_COMPRESSED: 2; + readonly POINT_CONVERSION_UNCOMPRESSED: 4; + readonly POINT_CONVERSION_HYBRID: 6; + readonly defaultCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; + } + export const constants: CryptoConstants; +} + +declare module "node:crypto" { + export * from "crypto"; +} diff --git a/packages/compiler/ambient/node-fallback/dgram.d.ts b/packages/compiler/ambient/node-fallback/dgram.d.ts new file mode 100644 index 000000000..27a98b26d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dgram.d.ts @@ -0,0 +1,58 @@ +/* node:dgram — UDP sockets over the event loop (scr_dgram.c, linked only + * into using binaries). createSocket takes "udp4" or the + * { type: "udp4", reuseAddr? } literal; bind/connect bind NOW and defer + * 'listening'/'connect' to the next loop turn; send takes one string or + * Buffer datagram with an explicit port + address (the connected-send + * and callback forms have no lowering); address() returns the real + * {address, family, port} record and THROWS "Not running" before bind, + * like Node. The socket options throw Node's errors (EBADF before bind); + * add/dropMembership before bind is refused at run time (SC2020). 'error' + * with no listener exits 1 (the unhandled EventEmitter 'error' behavior). */ +declare module "dgram" { + export interface AddressInfo { + address: string; + family: string; + port: number; + } + export interface RemoteInfo { + address: string; + family: string; + port: number; + size: number; + } + export interface Socket { + bind(port: number, address?: string, callback?: () => void): void; + connect(port: number, address?: string, callback?: () => void): void; + send(msg: string | Uint8Array, port: number, address: string): void; + address(): AddressInfo; + close(callback?: () => void): void; + disconnect(): void; + unref(): void; + ref(): void; + setBroadcast(flag: boolean): void; + setTTL(ttl: number): number; + setMulticastTTL(ttl: number): number; + setMulticastLoopback(flag: boolean): boolean; + setMulticastInterface(multicastInterface: string): void; + addMembership(multicastAddress: string, multicastInterface?: string): void; + dropMembership(multicastAddress: string, multicastInterface?: string): void; + getRecvBufferSize(): number; + getSendBufferSize(): number; + setRecvBufferSize(size: number): void; + setSendBufferSize(size: number): void; + getSendQueueSize(): number; + getSendQueueCount(): number; + on(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; + on(event: "listening" | "close" | "connect", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + once(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; + once(event: "listening" | "close" | "connect", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export function createSocket(type: "udp4"): Socket; + export function createSocket(options: { type: "udp4"; reuseAddr?: boolean }): Socket; +} + +declare module "node:dgram" { + export * from "dgram"; +} diff --git a/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts b/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts new file mode 100644 index 000000000..53e195e89 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts @@ -0,0 +1,87 @@ +/// + +/* node:diagnostics_channel — the in-process pub/sub core: named channels, + * subscribe/unsubscribe (module-level and channel-level), publish with any + * dyn-convertible message. Channel values are f64 handles into the + * runtime's channel registry (the readline.Interface precedent — channels + * are process-lived in Node too, its WeakRef machinery aside). + * The store-binding surface (bindStore/unbindStore/runStores) lowers onto + * the AsyncLocalStorage integration (dc.chan* libCalls). + * + * TracingChannel (tracingChannel) is the five-event-channel collection: + * an f64 handle into the runtime's tracing registry (type-mapper.ts maps the + * name like Channel). subscribe/unsubscribe walk a handlers object; + * traceSync/traceCallback run Node's publish choreography in the runtime + * (context defaults to a fresh {}); tracePromise is declared (the member + * exists in Node) but fences at the call site — the traced function's + * promise cannot cross the checked-dynamic tree boundary yet. */ +declare module "node:diagnostics_channel" { + export type ChannelListener = (message: unknown, name: string) => void; + export interface Channel { + readonly name: string; + readonly hasSubscribers: boolean; + publish(message: unknown): void; + subscribe(onMessage: ChannelListener): void; + unsubscribe(onMessage: ChannelListener): boolean; + bindStore( + store: import("async_hooks").AsyncLocalStorage, + transform?: (context: any) => any, + ): void; + unbindStore(store: import("async_hooks").AsyncLocalStorage): boolean; + runStores(context: unknown, fn: (...args: any[]) => any, thisArg?: any, ...args: any[]): any; + } + export function channel(name: string): Channel; + export function subscribe(name: string, onMessage: ChannelListener): void; + export function unsubscribe(name: string, onMessage: ChannelListener): boolean; + export function hasSubscribers(name: string): boolean; + export interface TracingChannelSubscribers { + start?: (message: any) => void; + end?: (message: any) => void; + asyncStart?: (message: any) => void; + asyncEnd?: (message: any) => void; + error?: (message: any) => void; + } + export interface TracingChannelCollection { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + } + export interface TracingChannel { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + readonly hasSubscribers: boolean; + subscribe(subscribers: TracingChannelSubscribers): void; + /** True when every present handler was found and removed (Node's + * all-found conjunction; @types/node under-declares this as void). */ + unsubscribe(subscribers: TracingChannelSubscribers): boolean; + traceSync( + fn: (this: ThisArg, ...args: Args) => Result, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Result; + tracePromise( + fn: (this: ThisArg, ...args: Args) => Promise, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Promise; + traceCallback( + fn: (this: ThisArg, ...args: Args) => Result, + position?: number, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Result; + } + export function tracingChannel(nameOrChannels: string | TracingChannelCollection): TracingChannel; +} + +declare module "diagnostics_channel" { + export * from "node:diagnostics_channel"; +} diff --git a/packages/compiler/ambient/node-fallback/dns-promises.d.ts b/packages/compiler/ambient/node-fallback/dns-promises.d.ts new file mode 100644 index 000000000..29fadd6d4 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dns-promises.d.ts @@ -0,0 +1,27 @@ +/// + +/* node:dns/promises — lookup and lookupService as promises over the same + * resolution as the callback forms: argument errors throw synchronously, + * failures reject with the callbacks' errors. */ +declare module "dns/promises" { + import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; + export function lookup(hostname: string, family: number): Promise; + export function lookup(hostname: string, options: LookupOneOptions): Promise; + export function lookup(hostname: string, options: LookupAllOptions): Promise; + export function lookup( + hostname: string, + options: LookupOptions, + ): Promise; + export function lookup(hostname: string): Promise; + export function lookupService( + address: string, + port: number, + ): Promise<{ hostname: string; service: string }>; + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; +} + +declare module "node:dns/promises" { + export * from "dns/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/dns.d.ts b/packages/compiler/ambient/node-fallback/dns.d.ts new file mode 100644 index 000000000..234f03463 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dns.d.ts @@ -0,0 +1,65 @@ +/// + +/* node:dns — the lookup slice (scr_dgram.c hosts it; getaddrinfo runs AT + * CALL TIME and the callback defers to the next loop turn — SEMANTICS.md + * documents the blocking divergence). Only the { family: 4 } options + * form lowers; failures deliver Node's error shape ("getaddrinfo + * ENOTFOUND ") as the callback's Error argument, with "" for + * the address where Node passes undefined. */ +declare module "dns" { + export interface LookupAddress { + address: string; + family: number; + } + export interface LookupOptions { + family?: number | "IPv4" | "IPv6"; + hints?: number; + all?: boolean; + order?: "ipv4first" | "ipv6first" | "verbatim"; + verbatim?: boolean; + } + export interface LookupOneOptions extends LookupOptions { + all?: false; + } + export interface LookupAllOptions extends LookupOptions { + all: true; + } + /* The callback comes with the other lookup deliveries (literal + * addresses on the next tick); the options follow lib/dns.js. */ + export function lookup( + hostname: string, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + family: number, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + options: LookupOneOptions, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + options: LookupAllOptions, + callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void, + ): void; + /* getnameinfo with NI_NAMEREQD: the host's name and the port's service + * name (the number when it has none). */ + export function lookupService( + address: string, + port: number, + callback: (err: NodeJS.ErrnoException | null, hostname: string, service: string) => void, + ): void; + /* The nameservers /etc/resolv.conf lists. */ + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; + /* The promise forms (node:dns/promises). */ + export const promises: typeof import("dns/promises"); +} + +declare module "node:dns" { + export * from "dns"; +} diff --git a/packages/compiler/ambient/node-fallback/events.d.ts b/packages/compiler/ambient/node-fallback/events.d.ts new file mode 100644 index 000000000..dda1cd086 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/events.d.ts @@ -0,0 +1,60 @@ +/* node:events — the EventEmitter class (the module object IS the class, + * like Node's `module.exports = EventEmitter`). Listener/emit signatures + * mirror @types/node's untyped `...args: any[]` surface; the compiler's + * lowering unifies each event name's argument tuple program-wide and + * checks annotated listener parameters against it (unannotated non-empty + * parameter lists have no static types and fence at the registration). */ +declare module "events" { + class EventEmitter { + constructor(); + static defaultMaxListeners: number; + /* Listeners registered under this symbol observe 'error' emits first + * (an emit with no 'error' listener still throws). */ + static readonly errorMonitor: unique symbol; + static addAbortListener(signal: AbortSignal, listener: (event: Event) => void): Disposable; + on(eventName: string | symbol, listener: (...args: any[]) => void): this; + addListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + once(eventName: string | symbol, listener: (...args: any[]) => void): this; + prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + off(eventName: string | symbol, listener: (...args: any[]) => void): this; + removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + removeAllListeners(eventName?: string | symbol): this; + emit(eventName: string | symbol, ...args: any[]): boolean; + listenerCount(eventName: string | symbol, listener?: (...args: any[]) => void): number; + /* Declared as zero-parameter closures so the RESULT is statically + * consumable (.length, indexing, identity): the lowering only admits + * these calls for events whose argument tuple is empty — a listener + * pulled from the array is then safely callable too. Events with + * arguments fence (their element type would lie). */ + listeners(eventName: string): Array<() => void>; + rawListeners(eventName: string): Array<() => void>; + eventNames(): string[]; + setMaxListeners(n: number): this; + getMaxListeners(): number; + } + import internal = require("node:events"); + namespace EventEmitter { + // The class under its own name, so `import { EventEmitter }` resolves + // (the @types/node namespace-alias pattern). + export { internal as EventEmitter }; + // The event's arguments, as checked-dynamic values. + export function once( + emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, + eventName: string, + options?: { signal?: AbortSignal }, + ): Promise; + /* The emitter's own listenerCount/getMaxListeners/setMaxListeners/ + * listeners, over EventEmitter instances. */ + export function listenerCount(emitter: EventEmitter, eventName: string | symbol): number; + export function getMaxListeners(emitter: EventEmitter): number; + export function setMaxListeners(n?: number, ...eventTargets: EventEmitter[]): void; + export function getEventListeners(emitter: EventEmitter, name: string): Array<() => void>; + } + export = EventEmitter; +} + +declare module "node:events" { + import events = require("events"); + export = events; +} diff --git a/packages/compiler/ambient/node-fallback/fs-promises.d.ts b/packages/compiler/ambient/node-fallback/fs-promises.d.ts new file mode 100644 index 000000000..6f3f2c460 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/fs-promises.d.ts @@ -0,0 +1,225 @@ +/// + +/* fs/promises — the SAME synchronous operations behind already-settled + * promises: success fulfills, failure REJECTS with the would-be thrown + * error (catchable at the await, like Node). DOCUMENTED DIVERGENCE + * (SEMANTICS.md): the syscall blocks the event loop, so I/O never + * interleaves with timers or other fibers — observable only in concurrent + * code. readFile is utf8-only, like readFileSync. */ +declare module "fs/promises" { + export interface FileReadResult { + bytesRead: number; + buffer: T; + } + export interface FileWriteResult { + bytesWritten: number; + buffer: T; + } + export interface FileReadvResult { + bytesRead: number; + buffers: T; + } + export interface FileWritevResult { + bytesWritten: number; + buffers: T; + } + export interface FileReadOptions { + buffer?: T; + offset?: number | null; + length?: number | null; + position?: number | null; + } + /* The streams over a FileHandle (createReadStream, createWriteStream, + * readLines): fs streams that hold the handle until they close; closing + * the stream closes the handle unless autoClose is false. */ + export interface CreateReadStreamOptions { + encoding?: BufferEncoding | null; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + end?: number; + highWaterMark?: number; + } + export interface CreateWriteStreamOptions { + encoding?: BufferEncoding | null; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + highWaterMark?: number; + flush?: boolean; + } + export interface FileHandle extends AsyncDisposable { + readonly fd: number; + close(): Promise; + createReadStream(options?: CreateReadStreamOptions): import("node:fs").ReadStream; + createWriteStream(options?: CreateWriteStreamOptions): import("node:fs").WriteStream; + readLines(options?: CreateReadStreamOptions): import("node:readline").Interface; + sync(): Promise; + datasync(): Promise; + truncate(len?: number): Promise; + chmod(mode: number): Promise; + utimes(atime: number | string | Date, mtime: number | string | Date): Promise; + readv( + buffers: T, + position?: number | null, + ): Promise>; + writev( + buffers: T, + position?: number | null, + ): Promise>; + read( + options?: FileReadOptions | null, + ): Promise>; + read( + buffer: T, + options: Omit, "buffer">, + ): Promise>; + read( + buffer: T, + offset?: number | null, + length?: number | null, + position?: number | null, + ): Promise>; + write( + buffer: T, + offset?: number | null, + length?: number | null, + position?: number | null, + ): Promise>; + write( + buffer: T, + options: Omit, "buffer">, + ): Promise>; + write( + data: string, + position?: number | null, + encoding?: "utf8" | "utf-8" | null, + ): Promise>; + readFile(options?: null): Promise; + readFile(encoding: "utf8" | "utf-8"): Promise; + writeFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; + appendFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; + stat(): Promise; + [Symbol.asyncDispose](): Promise; + } + export function open(path: string | URL, flags?: string, mode?: number): Promise; + export function link( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + ): Promise; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type?: string | null, + ): Promise; + export function cp( + source: string | URL, + destination: string | URL, + options?: import("node:fs").CopyOptions, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + ): Promise; + export function utimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): Promise; + export function lutimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): Promise; + export function readFile(path: string | URL, encoding: "utf8" | "utf-8"): Promise; + export function readFile( + path: string | URL, + options: BufferEncoding | { encoding: BufferEncoding }, + ): Promise; + export function readFile(path: string | URL): Promise; + export function writeFile( + path: string | URL, + data: string, + options?: { + mode?: number; + encoding?: "utf8" | "utf-8"; + flag?: string; + [option: string]: unknown; + }, + ): Promise; + export function writeFile( + path: string | URL, + data: string, + encoding: "utf8" | "utf-8", + ): Promise; + export function mkdir( + path: string | URL, + options?: { recursive?: boolean; mode?: number }, + ): Promise; + export function readdir(path: string | URL): Promise; + export function readdir( + path: string | URL, + options: { + encoding?: "utf8" | "utf-8"; + withFileTypes: true; + recursive?: boolean; + [option: string]: unknown; + }, + ): Promise; + /* The async iterator form of fs.globSync: iterate it with for await. */ + export function glob( + pattern: string | readonly string[], + options?: import("node:fs").GlobOptions, + ): AsyncIterableIterator; + export function glob( + pattern: string | readonly string[], + options: import("node:fs").GlobOptionsWithFileTypes, + ): AsyncIterableIterator; + export function rm(path: string | URL): Promise; + export function rm( + path: string | URL, + options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, + ): Promise; + export function stat(path: string | URL): Promise; + export function statfs( + path: string | Uint8Array | URL, + options?: import("node:fs").StatFsOptions & { bigint?: false }, + ): Promise; + export function statfs( + path: string | Uint8Array | URL, + options: import("node:fs").StatFsOptions & { bigint: true }, + ): Promise; + export function statfs( + path: string | Uint8Array | URL, + options?: import("node:fs").StatFsOptions, + ): Promise; + export function realpath(path: string | URL): Promise; + export function lstat(path: string | URL): Promise; + export function unlink(path: string | URL): Promise; + export function chmod(path: string | URL, mode: number): Promise; + export function chown(path: string | URL, uid: number, gid: number): Promise; + export function lchown(path: string | URL, uid: number, gid: number): Promise; + export function access(path: string | URL, mode?: number): Promise; + export function truncate(path: string | URL, len?: number): Promise; + export function appendFile(path: string | URL, data: string | Uint8Array): Promise; + export function copyFile(src: string | URL, dest: string | URL): Promise; + export function mkdtemp(prefix: string | URL): Promise; + export function rmdir(path: string | URL): Promise; + export function rename(oldPath: string | URL, newPath: string | URL): Promise; + export function opendir( + path: string | URL, + options?: import("node:fs").OpenDirOptions, + ): Promise; +} + +declare module "node:fs/promises" { + export * from "fs/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/fs.d.ts b/packages/compiler/ambient/node-fallback/fs.d.ts new file mode 100644 index 000000000..8d22ec66c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/fs.d.ts @@ -0,0 +1,577 @@ +/// +/// + +/* The synchronous node:fs surface — utf8-only, no options objects, no + * Buffers. Importing "node:fs" resolves here (preflight allowlists exactly + * the ambient-declared node: modules); every function lowers to a `libCall` + * with a scr_fs_* runtime implementation. Failures throw CATCHABLE string + * values formatted like Node's error messages ("ENOENT: no such file or + * directory, open 'x'"). `rmSync` removes FILES only (like Node without + * `recursive`); empty directories go through `rmdirSync`. */ +declare module "node:fs" { + import { Readable, Writable } from "stream"; + export function readFileSync(path: string | number | URL, encoding: BufferEncoding): string; + export function readFileSync( + path: string | number | URL, + options: { encoding: BufferEncoding }, + ): string; + export function readFileSync(path: string | number | URL): Buffer; + export function readFileSync(path: string | URL, options: unknown): string | Buffer; + /* Error-first callbacks select string or Buffer data from their encoding. + * Unknown options retain that union until the caller validates the value. */ + export function readFile( + path: string, + options: unknown, + callback: (error: Error | null, data: string | Buffer) => void, + ): void; + /* The options form carries the mode (applied at CREATION only, like + * Node — an existing file keeps its permissions) and/or the utf8 + * encoding spelling. */ + export function writeFileSync( + path: string | URL, + data: string, + /* The options-record stance (see http.RequestOptions): flag fences + * by name at the call site; undocumented keys drop like Node. */ + options?: { + mode?: number; + encoding?: "utf8" | "utf-8"; + flag?: string; + [option: string]: unknown; + }, + ): void; + /* The bare-encoding spelling — the options record's encoding key alone. */ + export function writeFileSync(path: string | URL, data: string, encoding: "utf8" | "utf-8"): void; + export function writeFileSync(path: string | URL, data: string | Uint8Array): void; + export function appendFileSync(path: string | URL, data: string | Uint8Array): void; + export function existsSync(path: string | URL): boolean; + export function mkdirSync(path: string | URL): void; + export function mkdirSync( + path: string | URL, + options: { recursive?: boolean; mode?: number }, + ): void; + export function unlinkSync(path: string | URL): void; + export function chmodSync(path: string | URL, mode: number): void; + export function fchmodSync(fd: number, mode: number): void; + export function fsyncSync(fd: number): void; + export function linkSync( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + ): void; + export function symlinkSync( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type?: string | null, + ): void; + /* fs.cpSync / fs.cp: copies a file, symlink or (with recursive) a + * directory tree. filter decides per path; the callback and promise + * forms complete the copy before reporting. */ + export interface CopySyncOptions { + dereference?: boolean; + errorOnExist?: boolean; + filter?: (source: string, destination: string) => boolean; + force?: boolean; + mode?: number; + preserveTimestamps?: boolean; + recursive?: boolean; + verbatimSymlinks?: boolean; + } + export interface CopyOptions { + dereference?: boolean; + errorOnExist?: boolean; + filter?: (source: string, destination: string) => boolean | Promise; + force?: boolean; + mode?: number; + preserveTimestamps?: boolean; + recursive?: boolean; + verbatimSymlinks?: boolean; + } + export function cpSync( + source: string | URL, + destination: string | URL, + options?: CopySyncOptions, + ): void; + export function cp( + source: string | URL, + destination: string | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function cp( + source: string | URL, + destination: string | URL, + options: CopyOptions, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type: string | null | undefined, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function link( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null, target: string) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + callback: (err: NodeJS.ErrnoException | null, target: Buffer) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: BufferEncoding | { encoding?: BufferEncoding | null } | null, + callback: (err: NodeJS.ErrnoException | null, target: string) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + callback: (err: NodeJS.ErrnoException | null, target: string | Buffer) => void, + ): void; + export function readlinkSync( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + ): Buffer; + export function readlinkSync( + path: string | Uint8Array | URL, + options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, + ): string; + export function readlinkSync( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + ): string | Buffer; + export function chownSync(path: string | URL, uid: number, gid: number): void; + export function lchownSync(path: string | URL, uid: number, gid: number): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function truncateSync(path: string | URL, len?: number): void; + /* The 2-argument form only (Node's mode flags have no lowering). The + * destination is created or truncated carrying the SOURCE's mode. */ + export function copyFileSync(src: string | URL, dest: string | URL): void; + export function rename( + oldPath: string, + newPath: string, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function renameSync(oldPath: string | URL, newPath: string | URL): void; + export function rmSync(path: string | URL): void; + /* maxRetries/retryDelay are accepted no-ops (Node retries around + * EBUSY-class races; the synchronous lowering has no re-entrancy that + * needs them). */ + export function rmSync( + path: string | URL, + options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, + ): void; + export function rmdirSync(path: string | URL): void; + export function readdirSync(path: string | URL): 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 + * carries the entry kind; isFile/isDirectory/isSymbolicLink lower as + * reads of it in builtins/process.ts). */ + export interface Dirent { + name: string; + parentPath: string; + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; + } + export function readdirSync( + path: string | URL, + /* The options-record stance (see http.RequestOptions): recursive + * fences by name at the call site; undocumented keys drop like Node. */ + options: { withFileTypes: true; recursive?: boolean; [option: string]: unknown }, + ): Dirent[]; + /* fs.globSync / fs.glob: Node's glob walk over the file system. Patterns + * are minimatch globs (case-insensitive on macOS and Windows); results + * are relative to cwd unless the pattern is absolute. withFileTypes must + * be a literal so the result type is known. */ + export interface GlobOptions { + cwd?: string | URL; + exclude?: readonly string[] | ((fileName: string) => boolean); + followSymlinks?: boolean; + withFileTypes?: false; + } + export interface GlobOptionsWithFileTypes { + cwd?: string | URL; + exclude?: readonly string[]; + followSymlinks?: boolean; + withFileTypes: true; + } + export function globSync(pattern: string | readonly string[], options?: GlobOptions): string[]; + export function globSync( + pattern: string | readonly string[], + options: GlobOptionsWithFileTypes, + ): Dirent[]; + export function glob( + pattern: string | readonly string[], + callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, + ): void; + export function glob( + pattern: string | readonly string[], + options: GlobOptions, + callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, + ): void; + /* fs.Dir: an open directory stream (opendirSync, fs.promises.opendir) + * read one Dirent at a time in OS order; utf8 names, no recursive walk. */ + export interface Dir { + readonly path: string; + readSync(): Dirent | null; + read(): Promise; + closeSync(): void; + close(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; + } + export interface OpenDirOptions { + encoding?: "utf8" | "utf-8"; + bufferSize?: number; + } + export function opendirSync(path: string | URL, options?: OpenDirOptions): Dir; + /* fs.ReadStream / fs.WriteStream: runtime Readable and Writable streams + * over a file (scr_fs_stream.c). The options are checked at runtime + * with Node's rules; flags and mode are checked when the file opens, so + * their errors arrive as 'error' events. Instances also emit 'open' + * (with the fd) and 'ready'. */ + export interface StreamOptions { + flags?: string; + encoding?: BufferEncoding; + fd?: number; + mode?: number | string; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + highWaterMark?: number; + } + export interface ReadStreamOptions extends StreamOptions { + end?: number; + } + export interface WriteStreamOptions extends StreamOptions { + flush?: boolean; + } + export class ReadStream extends Readable { + readonly bytesRead: number; + readonly path: string | Buffer; + readonly pending: boolean; + close(callback?: () => void): void; + } + export class WriteStream extends Writable { + readonly bytesWritten: number; + readonly path: string | Buffer; + readonly pending: boolean; + close(callback?: () => void): void; + } + export function createReadStream( + path: string | Buffer | URL, + options?: BufferEncoding | ReadStreamOptions, + ): ReadStream; + export function createWriteStream( + path: string | Buffer | URL, + options?: BufferEncoding | WriteStreamOptions, + ): WriteStream; + export function accessSync(path: string | URL, mode?: number): void; + export function mkdtempSync(prefix: string | URL): string; + export const constants: { + readonly F_OK: number; + readonly R_OK: number; + readonly W_OK: number; + readonly X_OK: number; + readonly O_RDONLY: number; + readonly O_WRONLY: number; + readonly O_RDWR: number; + readonly O_CREAT: number; + readonly O_EXCL: number; + readonly O_NOFOLLOW: number; + readonly O_NONBLOCK: number; + readonly O_TRUNC: number; + readonly O_APPEND: number; + readonly COPYFILE_EXCL: number; + readonly COPYFILE_FICLONE: number; + readonly COPYFILE_FICLONE_FORCE: number; + readonly UV_FS_SYMLINK_DIR: number; + readonly UV_FS_SYMLINK_JUNCTION: number; + readonly UV_DIRENT_UNKNOWN: number; + readonly UV_DIRENT_FILE: number; + readonly UV_DIRENT_DIR: number; + readonly UV_DIRENT_LINK: number; + readonly UV_DIRENT_FIFO: number; + readonly UV_DIRENT_SOCKET: number; + readonly UV_DIRENT_CHAR: number; + readonly UV_DIRENT_BLOCK: number; + readonly UV_FS_O_FILEMAP: number; + readonly UV_FS_O_TEMPORARY: number; + readonly UV_FS_O_SHORT_LIVED: number; + readonly UV_FS_O_SEQUENTIAL: number; + readonly UV_FS_O_RANDOM: number; + readonly UV_FS_COPYFILE_EXCL: number; + readonly UV_FS_COPYFILE_FICLONE: number; + readonly UV_FS_COPYFILE_FICLONE_FORCE: number; + readonly S_IFMT: number; + readonly S_IFREG: number; + readonly S_IFDIR: number; + readonly S_IFCHR: number; + readonly S_IFBLK: number; + readonly S_IFIFO: number; + readonly S_IFLNK: number; + readonly S_IFSOCK: number; + readonly O_NOCTTY: number; + readonly O_DIRECTORY: number; + readonly O_NOATIME: number; + readonly O_SYNC: number; + readonly O_DSYNC: number; + readonly O_SYMLINK: number; + readonly O_DIRECT: number; + readonly S_IRWXU: number; + readonly S_IRUSR: number; + readonly S_IWUSR: number; + readonly S_IXUSR: number; + readonly S_IRWXG: number; + readonly S_IRGRP: number; + readonly S_IWGRP: number; + readonly S_IXGRP: number; + readonly S_IRWXO: number; + readonly S_IROTH: number; + readonly S_IWOTH: number; + readonly S_IXOTH: number; + }; + /* A stat(2) snapshot (statSync follows symlinks, lstatSync does not — + * Node's split) — immutable; the supported surface is exactly these + * members. dev/ino identify the filesystem entry, blocks is the allocated + * size in 512-byte units, and the time fields are milliseconds with their + * sub-second fractions. */ + export interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + readonly dev: number; + readonly mode: number; + readonly nlink: number; + readonly uid: number; + readonly gid: number; + readonly rdev: number; + readonly blksize: number; + readonly ino: number; + readonly size: number; + readonly blocks: number; + readonly atimeMs: number; + readonly mtimeMs: number; + readonly ctimeMs: number; + readonly birthtimeMs: number; + readonly atime: Date; + readonly mtime: Date; + readonly ctime: Date; + readonly birthtime: Date; + } + /* throwIfNoEntry: false answers undefined for a missing entry (ENOENT or + * ENOTDIR) instead of throwing. BigIntStats (bigint: true) has no + * lowering. */ + export interface StatSyncOptions { + bigint?: boolean; + throwIfNoEntry?: boolean; + } + export function statSync(path: string | URL, options?: undefined): Stats; + export function statSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, + ): Stats | undefined; + export function statSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, + ): Stats; + /* bigint: true type-checks so the lowering can name it as unsupported. */ + export function statSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; + export function statSync(path: string | URL, options: StatSyncOptions): Stats | undefined; + export function lstatSync(path: string | URL, options?: undefined): Stats; + export function lstatSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, + ): Stats | undefined; + export function lstatSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, + ): Stats; + /* bigint: true type-checks so the lowering can name it as unsupported. */ + export function lstatSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; + export function lstatSync(path: string | URL, options: StatSyncOptions): Stats | undefined; + /* The fd pair behind spawn's fd-stdio form: openSync(path, flags) → + * the raw fd. Numeric flags use an inline OR of the O_* constants. */ + export function openSync(path: string | URL, flags: string): number; + export function openSync(path: string | URL, flags: number, mode?: number): number; + export function closeSync(fd: number): void; + export function fdatasyncSync(fd: number): void; + export function ftruncateSync(fd: number, len?: number): void; + export function utimesSync( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function futimesSync( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function lutimesSync( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function utimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function futimes( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function lutimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function readvSync(fd: number, buffers: Uint8Array[], position?: number | null): number; + export function writevSync(fd: number, buffers: Uint8Array[], position?: number | null): number; + /* Read into a caller buffer from the fd's current position when position + * is omitted/null, or from a numeric byte position without advancing the + * fd. Answers the byte count, 0 at EOF. */ + export interface ReadSyncOptions { + offset?: number; + length?: number; + position?: number | null; + } + export function readSync(fd: number, buffer: Uint8Array, options?: ReadSyncOptions): number; + export function readSync( + fd: number, + buffer: Uint8Array, + offset: number, + length: number, + position?: number | null, + ): number; + /* Write a caller-buffer window, or a utf8 string, at the fd's current + * position when position is omitted/null (advancing it), or at a numeric + * byte position without advancing it. Answers the byte count. */ + export interface WriteSyncOptions { + offset?: number; + length?: number; + position?: number | null; + } + export function writeSync(fd: number, buffer: Uint8Array, options: WriteSyncOptions): number; + export function writeSync( + fd: number, + buffer: Uint8Array, + offset?: number, + length?: number, + position?: number | null, + ): number; + export function writeSync( + fd: number, + string: string, + position?: number | null, + encoding?: "utf8" | "utf-8", + ): number; + /* statSync over an open fd — the same Stats snapshot. */ + export function fstatSync(fd: number): Stats; + /* realpath(3) — resolves symlinks, `.`/`..`, throwing Node's fs error + * shapes for missing paths. */ + export function realpathSync(path: string | URL): string; + export namespace realpathSync { + function native(path: string | URL): string; + } + /* statfs(2)/statvfs(3) — the filesystem-capacity snapshot (the fields + * Node's statfsSync reports; bavail × bsize is the free-space probe). */ + export interface StatsFsBase { + type: T; + bsize: T; + blocks: T; + bfree: T; + bavail: T; + files: T; + ffree: T; + } + export interface StatsFs extends StatsFsBase {} + export interface BigIntStatsFs extends StatsFsBase {} + export interface StatFsOptions { + bigint?: boolean; + } + export function statfsSync( + path: string | Uint8Array | URL, + options?: StatFsOptions & { bigint?: false }, + ): StatsFs; + export function statfsSync( + path: string | Uint8Array | URL, + options: StatFsOptions & { bigint: true }, + ): BigIntStatsFs; + export function statfsSync( + path: string | Uint8Array | URL, + options?: StatFsOptions, + ): StatsFs | BigIntStatsFs; + export function statfs( + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: (StatFsOptions & { bigint?: false }) | undefined, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: StatFsOptions & { bigint: true }, + callback: (err: NodeJS.ErrnoException | null, stats: BigIntStatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: StatFsOptions | undefined, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs | BigIntStatsFs) => void, + ): void; + /* fs.watch — the file-watching slice (scr_watch.c, kqueue EVFILT_VNODE): + * "rename"/"change" events on ONE path, watcher.close() to stop. The + * listener takes zero parameters or the eventType string (the filename + * parameter has no lowering); an open watcher keeps the loop alive. A + * path that cannot be opened THROWS Node's fs error synchronously. */ + export interface FSWatcher { + close(): void; + } + /* The options-record stance (see http.RequestOptions): persistent: true, + * recursive: false, and encoding: "utf8" state the lowered behavior and + * are accepted; persistent: false, recursive: true, and signal fence by + * name at the call site; undocumented keys drop like Node. */ + export interface WatchOptions { + persistent?: boolean; + recursive?: boolean; + encoding?: string; + [option: string]: unknown; + } + export function watch(path: string, listener?: (eventType: string) => void): FSWatcher; + export function watch( + path: string, + options: WatchOptions, + listener?: (eventType: string) => void, + ): FSWatcher; + /* fs.promises IS the fs/promises module (Node's rule) — the namespace- + * import form `fs.promises.readFile(...)` lowers through the same + * fs/promises table as `import { readFile } from "node:fs/promises"`. */ + export const promises: typeof import("node:fs/promises"); +} + +/* Every builtin answers to BOTH specifier forms, like in Node: the bare + * module re-exports its node:-prefixed twin (or holds the declarations + * itself — one of the pair forwards). The lowerer recognizes members by + * their import SPECIFIER, so both forms lower identically. */ +declare module "fs" { + export * from "node:fs"; +} diff --git a/packages/compiler/ambient/node-fallback/graceful-fs.d.ts b/packages/compiler/ambient/node-fallback/graceful-fs.d.ts new file mode 100644 index 000000000..e735fa3e6 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/graceful-fs.d.ts @@ -0,0 +1,9 @@ +/// + +/* graceful-fs — served as node:fs (it monkeypatches fs and retries on + * EMFILE/ENFILE; compiled programs call fs directly, without the retry + * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ +declare module "graceful-fs" { + export * from "node:fs"; + export function gracefulify(fsModule: T): T; +} diff --git a/packages/compiler/ambient/node-fallback/http.d.ts b/packages/compiler/ambient/node-fallback/http.d.ts new file mode 100644 index 000000000..5588767eb --- /dev/null +++ b/packages/compiler/ambient/node-fallback/http.d.ts @@ -0,0 +1,326 @@ +/// +/// + +/* node:http — the SERVER and CLIENT slices (scr_http.c layers a + * hand-written HTTP/1.1 parser + serializer on scr_net.c; both link only + * into using binaries). createServer(handler) returns a net.Server — + * listen/close/address().port/'error' are the same lowered surface. The + * request carries url/method (always present on server requests), + * statusCode (a number on client responses, undefined on server + * requests), the underlying socket, and lowercased headers read as + * `string | undefined`; the body streams through on("data"/"end"). The + * response is setHeader/writeHead/write/end/destroy with Node's framing + * (Content-Length for a single end(body), chunked after writeHead/write) + * and the auto Date/Connection headers; writeHead's headers argument is + * an object literal or a Record. request/get dial one + * connection per call (no agent pooling) with Node's exact wire head. */ +declare module "http" { + import { Server as NetServer, Socket } from "net"; + export const METHODS: string[]; + export const STATUS_CODES: { [status: number]: string | undefined }; + export const maxHeaderSize: number; + export interface Server extends NetServer { + timeout: number; + setTimeout(msecs: number, callback?: (socket: Socket) => void): this; + closeAllConnections(): void; + closeIdleConnections(): void; + keepAliveTimeout: number; + keepAliveTimeoutBuffer: number; + headersTimeout: number; + requestTimeout: number; + } + export type RequestListener = (req: IncomingMessage, res: ServerResponse) => void; + /* The Server VALUE — Node's constructor works with and without `new` + * (test/parallel's http.Server(fn) spelling); both route to the + * createServer lowering. */ + export const Server: { + new (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; + new ( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; + ( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + }; + /* The options-record stance (the RequestOptions precedent): every + * documented key typechecks; the lowering's option walk decides per + * key — requireHostHeader: false, joinDuplicateHeaders, and + * keepAliveTimeoutBuffer lower, + * documented-but-unlowered keys fence by name, unknown keys drop. */ + export interface ServerOptions { + requireHostHeader?: boolean; + joinDuplicateHeaders?: boolean; + keepAliveTimeoutBuffer?: number; + maxHeaderSize?: number; + [option: string]: unknown; + } + /* The outgoing-header shape, @types/node's matrix: numbers format via + * String(n), arrays write one line per element (set-cookie's shape). + * Incoming headers stay `string | undefined` — what the parser answers + * (under @types/node the two SLOTS unify so `{ ...req.headers }` + * merges into outgoing literals as a copy; the fallback keeps reads + * simple and spreads target the same string | undefined slot). */ + export type OutgoingHttpHeaders = { [name: string]: number | string | string[] | undefined }; + export interface IncomingMessage { + readonly url: string; + readonly method: string; + readonly httpVersion: string; + readonly complete: boolean; + readonly aborted: boolean; + readonly statusCode: number | undefined; + readonly statusMessage: string | undefined; + readonly socket: Socket; + readonly connection: Socket; + readonly headers: { [name: string]: string | undefined }; + readonly headersDistinct: { [name: string]: string[] | undefined }; + readonly rawHeaders: string[]; + readonly trailers: { [name: string]: string | undefined }; + readonly trailersDistinct: { [name: string]: string[] | undefined }; + readonly rawTrailers: string[]; + resume(): void; + pause(): void; + destroy(): void; + setTimeout(msecs: number, callback?: () => void): this; + /* setEncoding('utf8'): 'data' delivers strings (other real encodings + * throw a "not supported yet" Error at runtime; unknown names throw + * ERR_UNKNOWN_ENCODING). */ + setEncoding(encoding: string): void; + /* The proxy legs: the body streams into a ServerResponse, a + * ClientRequest, or a raw Socket; natural end ends the destination. */ + pipe( + destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket, + ): void; + on(event: "data", listener: (chunk: any) => void): void; + on(event: "end" | "close" | "aborted", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "data", listener: (chunk: any) => void): void; + addListener(event: "end" | "close" | "aborted", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once(event: "end" | "close" | "aborted", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export interface ServerResponse { + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly finished: boolean; + readonly writableFinished: boolean; + readonly writableCorked: number; + readonly req: IncomingMessage; + readonly socket: Socket | null; + readonly connection: Socket | null; + sendDate: boolean; + strictContentLength: boolean; + /* Node's writable head properties: the implicit head reads them. */ + statusCode: number; + statusMessage: string | undefined; + /* The value keeps its type: getHeader answers the number, string or + * array that was set. */ + setHeader(name: string, value: number | string | readonly string[]): void; + appendHeader(name: string, value: string | readonly string[]): void; + getHeader(name: string): number | string | string[] | undefined; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): OutgoingHttpHeaders; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + /* Both overloads answer the response (`return this` chaining); the + * headers argument also takes Node's flat [name, value, ...] array. */ + writeHead(statusCode: number, headers?: OutgoingHttpHeaders | string[]): ServerResponse; + writeHead( + statusCode: number, + statusMessage: string, + headers?: OutgoingHttpHeaders | string[], + ): ServerResponse; + writeContinue(callback?: () => void): void; + writeProcessing(callback?: () => void): void; + writeEarlyHints(hints: Record, callback?: () => void): void; + write(data: string | Uint8Array): void; + flushHeaders(): void; + cork(): void; + uncork(): void; + addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; + setTimeout(msecs: number, callback?: () => void): this; + /* end's callback forms fire once the body went out (the 'finish' + * emit, deferred past the handler's synchronous tail). */ + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + destroy(): void; + on(event: "close" | "finish", listener: () => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "close" | "finish", listener: () => void): void; + once(event: "close" | "finish", listener: () => void): void; + } + /* The CLIENT slice (http.request/http.get): the options-object form + * with hostname/host, port, path, method, timeout, and headers; the + * callback receives the response as an IncomingMessage (statusCode, + * headers, body via on("data"/"end")). */ + export interface RequestOptions { + hostname?: string | null; + host?: string | null; + /* A string port (a URL's .port) is accepted, as by Node. */ + port?: number | string | null; + path?: string | null; + method?: string; + timeout?: number; + headers?: OutgoingHttpHeaders; + /* The caller's own dialer (the proxy's loopback dial): invoked once, + * synchronously; its socket carries the exchange. */ + createConnection?: () => Socket; + /* agent: false is a one-shot dial with Connection: close — exactly + * the compiled client's model; null/undefined are the default; an + * Agent VALUE threads through (getName-keyed maxSockets accounting + * over one-dial-per-request connections). */ + agent?: Agent | boolean | null; + /* Aborting destroys the request with an AbortError. */ + signal?: AbortSignal; + /* HTTP over the Unix socket at this path instead of host:port. */ + socketPath?: string; + /* The options-record stance: Node ignores an options record's + * unknown keys, so every key typechecks here — the option WALK in + * the lowering decides per key (lower, fence by name, or drop + * undocumented keys exactly like Node). */ + [option: string]: unknown; + } + /* The Agent lowers to a checked-dynamic HANDLE: option parsing at the + * literal construction site, getName, destroy, the sockets/requests/ + * freeSockets snapshots, and REAL maxSockets accounting (over-limit + * requests defer their dial and queue). keepAlive: true keeps finished + * connections in freeSockets for the next request to the same name, + * taken in `scheduling` order. */ + export interface AgentOptions { + keepAlive?: boolean; + keepAliveMsecs?: number; + maxSockets?: number; + maxFreeSockets?: number; + maxTotalSockets?: number; + scheduling?: "fifo" | "lifo"; + timeout?: number; + port?: number; + [option: string]: unknown; + } + export class Agent { + constructor(options?: AgentOptions); + destroy(): void; + /* Node's exact key shape: host:port:localAddress[:family][:socketPath]. */ + getName(options?: { + host?: string; + port?: number; + localAddress?: string; + family?: number; + socketPath?: string; + [option: string]: unknown; + }): string; + maxSockets: number; + maxFreeSockets: number; + keepAliveMsecs: number; + readonly keepAlive: boolean; + readonly protocol: string; + /* Settable — a portless request through this agent dials it (Node's + * option merge). */ + defaultPort: number; + readonly totalSocketCount: number; + readonly sockets: { [key: string]: unknown[] }; + readonly freeSockets: { [key: string]: unknown[] }; + readonly requests: { [key: string]: unknown[] }; + } + /* Node's keepAlive global agent for requests without an agent option. */ + export const globalAgent: Agent; + export interface ClientRequest { + readonly destroyed: boolean; + readonly aborted: boolean; + readonly socket: Socket; + readonly connection: Socket; + readonly writableCorked: number; + readonly method: string; + readonly path: string; + readonly host: string; + readonly protocol: string; + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly finished: boolean; + readonly reusedSocket: boolean; + setHeader(name: string, value: string): void; + getHeader(name: string): string | undefined; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): OutgoingHttpHeaders; + flushHeaders(): void; + addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; + cork(): void; + uncork(): void; + setNoDelay(noDelay?: boolean): void; + setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; + setTimeout(timeout: number, callback?: () => void): this; + write(data: string | Uint8Array): void; + end(data?: string | Uint8Array): void; + destroy(error?: Error): void; + abort(): void; + on(event: "response", listener: (res: IncomingMessage) => void): void; + on(event: "socket", listener: (socket: Socket) => void): void; + on( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + on(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "response", listener: (res: IncomingMessage) => void): void; + addListener(event: "socket", listener: (socket: Socket) => void): void; + addListener( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + addListener(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "response", listener: (res: IncomingMessage) => void): void; + once(event: "socket", listener: (socket: Socket) => void): void; + once( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + once(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export function createServer( + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + export function createServer( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + /* One signature with a UNION first parameter rather than Node's two + * overloads: `const requestFn = tls ? https.request : http.request` + * (the portless isProxyRunning shape) unions two function types, and + * resolving a call on that union collapses each constituent to a + * single signature — with an overload SET on both sides the options + * literal ends up checked against URL, in either declaration order. + * A union parameter has nothing to collapse. The lowering dispatches + * on the argument's own type, so it reads the same either way. + * + * `target` is a URL string or URL object (the spelling a from-scratch + * client reaches for first — dialed directly, with a non-http scheme + * an ERR_INVALID_PROTOCOL at runtime rather than a type error), or + * the options record. */ + export function request( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function get( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function validateHeaderName(name: string, label?: string): void; + export function validateHeaderValue(name: string, value: unknown): void; +} + +declare module "node:http" { + export * from "http"; +} diff --git a/packages/compiler/ambient/node-fallback/http2.d.ts b/packages/compiler/ambient/node-fallback/http2.d.ts new file mode 100644 index 000000000..d47be0189 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/http2.d.ts @@ -0,0 +1,816 @@ +/// +/// +/// +/// + +/* node:http2 — the compatibility surface: allowHTTP1 servers negotiate + * h2 or HTTP/1.1 and expose the shared request/response API. HTTP/2 + * requests retain their backing stream; server session failures surface + * through sessionError. */ +declare module "http2" { + import { Socket } from "net"; + export interface Http2Stream { + on(event: "error" | "close", listener: (err: Error) => void): void; + once(event: "error" | "close", listener: (err: Error) => void): void; + addListener(event: "error" | "close", listener: (err: Error) => void): void; + destroy(): void; + } + export interface Http2ServerRequest { + readonly url: string; + readonly method: string; + readonly statusCode: number | undefined; + readonly socket: Socket; + readonly headers: { [name: string]: string | undefined }; + readonly stream: Http2Stream; + /* The h2 compat request's version triple ("2.0" over real h2 streams; + * the allowHTTP1 lowering's HTTP/1.1 connections answer 1.x). */ + readonly httpVersion: string; + readonly httpVersionMajor: number; + readonly httpVersionMinor: number; + readonly aborted: boolean; + readonly complete: boolean; + resume(): void; + destroy(): void; + setEncoding(encoding: string): void; + pipe(destination: Socket | import("http").ClientRequest): void; + on(event: "data", listener: (chunk: Buffer) => void): void; + on(event: "end" | "close" | "aborted", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — and the compat req IS the + * http.IncomingMessage surface, so the alias mirrors here too. */ + addListener(event: "data", listener: (chunk: Buffer) => void): void; + addListener(event: "end" | "close" | "aborted", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "data", listener: (chunk: Buffer) => void): void; + once(event: "end" | "close" | "aborted", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export interface Http2ServerResponse { + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly writableCorked: number; + /* The same lowered surface as http.ServerResponse — the allowHTTP1 + * lowering serves every connection as HTTP/1.1, where the compat + * response IS this parser's response handle. */ + statusCode: number; + statusMessage: string; + setHeader(name: string, value: string | number): void; + getHeader(name: string): string | undefined; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): import("http").OutgoingHttpHeaders; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + writeHead( + statusCode: number, + headers?: import("http").OutgoingHttpHeaders | string[], + ): Http2ServerResponse; + writeHead( + statusCode: number, + statusMessage: string, + headers?: import("http").OutgoingHttpHeaders | string[], + ): Http2ServerResponse; + writeContinue(): void; + writeEarlyHints(hints: Record): void; + write(data: string | Uint8Array): void; + flushHeaders(): void; + cork(): void; + uncork(): void; + addTrailers( + headers: import("http").OutgoingHttpHeaders | ReadonlyArray<[string, string]>, + ): void; + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + destroy(): void; + on(event: "close", listener: () => void): void; + /* addListener IS on (Node aliases them) — and the compat res IS the + * http.ServerResponse surface (assignability into ServerResponse + * unions — IncomingMessage.pipe's destination — needs the alias). */ + addListener(event: "close", listener: () => void): void; + once(event: "close", listener: () => void): void; + } + export interface Http2SecureServer { + listen(port: number, callback?: () => void): void; + close(callback?: () => void): void; + /* The bound AddressInfo (Node answers null before listen — this + * surface answers the record with port 0 there, the serverPort + * stance). */ + address(): import("node:dgram").AddressInfo; + emit(event: "connection", socket: Socket): void; + on(event: "connection", listener: (socket: Socket) => void): void; + on( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "close", listener: () => void): void; + on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + /* The ALPN=h2 server's surface (createSecureServer WITHOUT + * allowHTTP1 — the real h2-over-TLS stack): per-stream and + * per-session listeners, exactly the h2c server's. */ + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on(event: "session", listener: (session: ServerHttp2Session) => void): void; + /* HTTP CONNECT (and, in Node, the h2 extended CONNECT — which the + * allowHTTP1 lowering never sees): the second argument is the raw + * socket on HTTP/1.1 connections; narrow with instanceof. */ + on( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + once(event: "connection", listener: (socket: Socket) => void): void; + once( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "close", listener: () => void): void; + once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once(event: "session", listener: (session: ServerHttp2Session) => void): void; + once( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + addListener(event: "connection", listener: (socket: Socket) => void): void; + addListener( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "close", listener: () => void): void; + addListener( + event: "sessionError", + listener: (err: Error, session: ServerHttp2Session) => void, + ): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; + addListener( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + } + export interface SecureServerOptions { + allowHTTP1?: boolean; + cert?: string | Uint8Array; + key?: string | Uint8Array; + SNICallback?: ( + servername: string, + cb: (err: Error | null, ctx?: import("tls").SecureContext) => void, + ) => void; + /* The options-record stance (see http.RequestOptions): h2 session- + * tuning knobs are accepted as literals and ignored (no h2 session + * ever exists under the allowHTTP1 lowering); other documented keys + * fence by name; undocumented keys drop like Node. */ + [option: string]: unknown; + } + /* The full Node v24 constants table (240 members), literal-typed so + * computed header keys ([HTTP2_HEADER_PATH]: value) stay literal keys. + * Every ACCESS bakes as its literal at lowering; the object itself + * never materializes (bare http2.constants value uses fence). */ + export interface Http2Constants { + readonly NGHTTP2_ERR_FRAME_SIZE_ERROR: -522; + readonly NGHTTP2_SESSION_SERVER: 0; + readonly NGHTTP2_SESSION_CLIENT: 1; + readonly NGHTTP2_STREAM_STATE_IDLE: 1; + readonly NGHTTP2_STREAM_STATE_OPEN: 2; + readonly NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3; + readonly NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4; + readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5; + readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6; + readonly NGHTTP2_STREAM_STATE_CLOSED: 7; + readonly NGHTTP2_FLAG_NONE: 0; + readonly NGHTTP2_FLAG_END_STREAM: 1; + readonly NGHTTP2_FLAG_END_HEADERS: 4; + readonly NGHTTP2_FLAG_ACK: 1; + readonly NGHTTP2_FLAG_PADDED: 8; + readonly NGHTTP2_FLAG_PRIORITY: 32; + readonly DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096; + readonly DEFAULT_SETTINGS_ENABLE_PUSH: 1; + readonly DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295; + readonly DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535; + readonly DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384; + readonly DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535; + readonly DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0; + readonly MAX_MAX_FRAME_SIZE: 16777215; + readonly MIN_MAX_FRAME_SIZE: 16384; + readonly MAX_INITIAL_WINDOW_SIZE: 2147483647; + readonly NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1; + readonly NGHTTP2_SETTINGS_ENABLE_PUSH: 2; + readonly NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3; + readonly NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4; + readonly NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5; + readonly NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6; + readonly NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8; + readonly PADDING_STRATEGY_NONE: 0; + readonly PADDING_STRATEGY_ALIGNED: 1; + readonly PADDING_STRATEGY_MAX: 2; + readonly PADDING_STRATEGY_CALLBACK: 1; + readonly NGHTTP2_NO_ERROR: 0; + readonly NGHTTP2_PROTOCOL_ERROR: 1; + readonly NGHTTP2_INTERNAL_ERROR: 2; + readonly NGHTTP2_FLOW_CONTROL_ERROR: 3; + readonly NGHTTP2_SETTINGS_TIMEOUT: 4; + readonly NGHTTP2_STREAM_CLOSED: 5; + readonly NGHTTP2_FRAME_SIZE_ERROR: 6; + readonly NGHTTP2_REFUSED_STREAM: 7; + readonly NGHTTP2_CANCEL: 8; + readonly NGHTTP2_COMPRESSION_ERROR: 9; + readonly NGHTTP2_CONNECT_ERROR: 10; + readonly NGHTTP2_ENHANCE_YOUR_CALM: 11; + readonly NGHTTP2_INADEQUATE_SECURITY: 12; + readonly NGHTTP2_HTTP_1_1_REQUIRED: 13; + readonly NGHTTP2_DEFAULT_WEIGHT: 16; + readonly HTTP2_HEADER_STATUS: ":status"; + readonly HTTP2_HEADER_METHOD: ":method"; + readonly HTTP2_HEADER_AUTHORITY: ":authority"; + readonly HTTP2_HEADER_SCHEME: ":scheme"; + readonly HTTP2_HEADER_PATH: ":path"; + readonly HTTP2_HEADER_PROTOCOL: ":protocol"; + readonly HTTP2_HEADER_ACCEPT_ENCODING: "accept-encoding"; + readonly HTTP2_HEADER_ACCEPT_LANGUAGE: "accept-language"; + readonly HTTP2_HEADER_ACCEPT_RANGES: "accept-ranges"; + readonly HTTP2_HEADER_ACCEPT: "accept"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: "access-control-allow-credentials"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: "access-control-allow-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: "access-control-allow-methods"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: "access-control-allow-origin"; + readonly HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: "access-control-expose-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: "access-control-request-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: "access-control-request-method"; + readonly HTTP2_HEADER_AGE: "age"; + readonly HTTP2_HEADER_AUTHORIZATION: "authorization"; + readonly HTTP2_HEADER_CACHE_CONTROL: "cache-control"; + readonly HTTP2_HEADER_CONNECTION: "connection"; + readonly HTTP2_HEADER_CONTENT_DISPOSITION: "content-disposition"; + readonly HTTP2_HEADER_CONTENT_ENCODING: "content-encoding"; + readonly HTTP2_HEADER_CONTENT_LENGTH: "content-length"; + readonly HTTP2_HEADER_CONTENT_TYPE: "content-type"; + readonly HTTP2_HEADER_COOKIE: "cookie"; + readonly HTTP2_HEADER_DATE: "date"; + readonly HTTP2_HEADER_ETAG: "etag"; + readonly HTTP2_HEADER_FORWARDED: "forwarded"; + readonly HTTP2_HEADER_HOST: "host"; + readonly HTTP2_HEADER_IF_MODIFIED_SINCE: "if-modified-since"; + readonly HTTP2_HEADER_IF_NONE_MATCH: "if-none-match"; + readonly HTTP2_HEADER_IF_RANGE: "if-range"; + readonly HTTP2_HEADER_LAST_MODIFIED: "last-modified"; + readonly HTTP2_HEADER_LINK: "link"; + readonly HTTP2_HEADER_LOCATION: "location"; + readonly HTTP2_HEADER_RANGE: "range"; + readonly HTTP2_HEADER_REFERER: "referer"; + readonly HTTP2_HEADER_SERVER: "server"; + readonly HTTP2_HEADER_SET_COOKIE: "set-cookie"; + readonly HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: "strict-transport-security"; + readonly HTTP2_HEADER_TRANSFER_ENCODING: "transfer-encoding"; + readonly HTTP2_HEADER_TE: "te"; + readonly HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: "upgrade-insecure-requests"; + readonly HTTP2_HEADER_UPGRADE: "upgrade"; + readonly HTTP2_HEADER_USER_AGENT: "user-agent"; + readonly HTTP2_HEADER_VARY: "vary"; + readonly HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: "x-content-type-options"; + readonly HTTP2_HEADER_X_FRAME_OPTIONS: "x-frame-options"; + readonly HTTP2_HEADER_KEEP_ALIVE: "keep-alive"; + readonly HTTP2_HEADER_PROXY_CONNECTION: "proxy-connection"; + readonly HTTP2_HEADER_X_XSS_PROTECTION: "x-xss-protection"; + readonly HTTP2_HEADER_ALT_SVC: "alt-svc"; + readonly HTTP2_HEADER_CONTENT_SECURITY_POLICY: "content-security-policy"; + readonly HTTP2_HEADER_EARLY_DATA: "early-data"; + readonly HTTP2_HEADER_EXPECT_CT: "expect-ct"; + readonly HTTP2_HEADER_ORIGIN: "origin"; + readonly HTTP2_HEADER_PURPOSE: "purpose"; + readonly HTTP2_HEADER_TIMING_ALLOW_ORIGIN: "timing-allow-origin"; + readonly HTTP2_HEADER_X_FORWARDED_FOR: "x-forwarded-for"; + readonly HTTP2_HEADER_PRIORITY: "priority"; + readonly HTTP2_HEADER_ACCEPT_CHARSET: "accept-charset"; + readonly HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: "access-control-max-age"; + readonly HTTP2_HEADER_ALLOW: "allow"; + readonly HTTP2_HEADER_CONTENT_LANGUAGE: "content-language"; + readonly HTTP2_HEADER_CONTENT_LOCATION: "content-location"; + readonly HTTP2_HEADER_CONTENT_MD5: "content-md5"; + readonly HTTP2_HEADER_CONTENT_RANGE: "content-range"; + readonly HTTP2_HEADER_DNT: "dnt"; + readonly HTTP2_HEADER_EXPECT: "expect"; + readonly HTTP2_HEADER_EXPIRES: "expires"; + readonly HTTP2_HEADER_FROM: "from"; + readonly HTTP2_HEADER_IF_MATCH: "if-match"; + readonly HTTP2_HEADER_IF_UNMODIFIED_SINCE: "if-unmodified-since"; + readonly HTTP2_HEADER_MAX_FORWARDS: "max-forwards"; + readonly HTTP2_HEADER_PREFER: "prefer"; + readonly HTTP2_HEADER_PROXY_AUTHENTICATE: "proxy-authenticate"; + readonly HTTP2_HEADER_PROXY_AUTHORIZATION: "proxy-authorization"; + readonly HTTP2_HEADER_REFRESH: "refresh"; + readonly HTTP2_HEADER_RETRY_AFTER: "retry-after"; + readonly HTTP2_HEADER_TRAILER: "trailer"; + readonly HTTP2_HEADER_TK: "tk"; + readonly HTTP2_HEADER_VIA: "via"; + readonly HTTP2_HEADER_WARNING: "warning"; + readonly HTTP2_HEADER_WWW_AUTHENTICATE: "www-authenticate"; + readonly HTTP2_HEADER_HTTP2_SETTINGS: "http2-settings"; + readonly HTTP2_METHOD_ACL: "ACL"; + readonly HTTP2_METHOD_BASELINE_CONTROL: "BASELINE-CONTROL"; + readonly HTTP2_METHOD_BIND: "BIND"; + readonly HTTP2_METHOD_CHECKIN: "CHECKIN"; + readonly HTTP2_METHOD_CHECKOUT: "CHECKOUT"; + readonly HTTP2_METHOD_CONNECT: "CONNECT"; + readonly HTTP2_METHOD_COPY: "COPY"; + readonly HTTP2_METHOD_DELETE: "DELETE"; + readonly HTTP2_METHOD_GET: "GET"; + readonly HTTP2_METHOD_HEAD: "HEAD"; + readonly HTTP2_METHOD_LABEL: "LABEL"; + readonly HTTP2_METHOD_LINK: "LINK"; + readonly HTTP2_METHOD_LOCK: "LOCK"; + readonly HTTP2_METHOD_MERGE: "MERGE"; + readonly HTTP2_METHOD_MKACTIVITY: "MKACTIVITY"; + readonly HTTP2_METHOD_MKCALENDAR: "MKCALENDAR"; + readonly HTTP2_METHOD_MKCOL: "MKCOL"; + readonly HTTP2_METHOD_MKREDIRECTREF: "MKREDIRECTREF"; + readonly HTTP2_METHOD_MKWORKSPACE: "MKWORKSPACE"; + readonly HTTP2_METHOD_MOVE: "MOVE"; + readonly HTTP2_METHOD_OPTIONS: "OPTIONS"; + readonly HTTP2_METHOD_ORDERPATCH: "ORDERPATCH"; + readonly HTTP2_METHOD_PATCH: "PATCH"; + readonly HTTP2_METHOD_POST: "POST"; + readonly HTTP2_METHOD_PRI: "PRI"; + readonly HTTP2_METHOD_PROPFIND: "PROPFIND"; + readonly HTTP2_METHOD_PROPPATCH: "PROPPATCH"; + readonly HTTP2_METHOD_PUT: "PUT"; + readonly HTTP2_METHOD_REBIND: "REBIND"; + readonly HTTP2_METHOD_REPORT: "REPORT"; + readonly HTTP2_METHOD_SEARCH: "SEARCH"; + readonly HTTP2_METHOD_TRACE: "TRACE"; + readonly HTTP2_METHOD_UNBIND: "UNBIND"; + readonly HTTP2_METHOD_UNCHECKOUT: "UNCHECKOUT"; + readonly HTTP2_METHOD_UNLINK: "UNLINK"; + readonly HTTP2_METHOD_UNLOCK: "UNLOCK"; + readonly HTTP2_METHOD_UPDATE: "UPDATE"; + readonly HTTP2_METHOD_UPDATEREDIRECTREF: "UPDATEREDIRECTREF"; + readonly HTTP2_METHOD_VERSION_CONTROL: "VERSION-CONTROL"; + readonly HTTP_STATUS_CONTINUE: 100; + readonly HTTP_STATUS_SWITCHING_PROTOCOLS: 101; + readonly HTTP_STATUS_PROCESSING: 102; + readonly HTTP_STATUS_EARLY_HINTS: 103; + readonly HTTP_STATUS_OK: 200; + readonly HTTP_STATUS_CREATED: 201; + readonly HTTP_STATUS_ACCEPTED: 202; + readonly HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203; + readonly HTTP_STATUS_NO_CONTENT: 204; + readonly HTTP_STATUS_RESET_CONTENT: 205; + readonly HTTP_STATUS_PARTIAL_CONTENT: 206; + readonly HTTP_STATUS_MULTI_STATUS: 207; + readonly HTTP_STATUS_ALREADY_REPORTED: 208; + readonly HTTP_STATUS_IM_USED: 226; + readonly HTTP_STATUS_MULTIPLE_CHOICES: 300; + readonly HTTP_STATUS_MOVED_PERMANENTLY: 301; + readonly HTTP_STATUS_FOUND: 302; + readonly HTTP_STATUS_SEE_OTHER: 303; + readonly HTTP_STATUS_NOT_MODIFIED: 304; + readonly HTTP_STATUS_USE_PROXY: 305; + readonly HTTP_STATUS_TEMPORARY_REDIRECT: 307; + readonly HTTP_STATUS_PERMANENT_REDIRECT: 308; + readonly HTTP_STATUS_BAD_REQUEST: 400; + readonly HTTP_STATUS_UNAUTHORIZED: 401; + readonly HTTP_STATUS_PAYMENT_REQUIRED: 402; + readonly HTTP_STATUS_FORBIDDEN: 403; + readonly HTTP_STATUS_NOT_FOUND: 404; + readonly HTTP_STATUS_METHOD_NOT_ALLOWED: 405; + readonly HTTP_STATUS_NOT_ACCEPTABLE: 406; + readonly HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407; + readonly HTTP_STATUS_REQUEST_TIMEOUT: 408; + readonly HTTP_STATUS_CONFLICT: 409; + readonly HTTP_STATUS_GONE: 410; + readonly HTTP_STATUS_LENGTH_REQUIRED: 411; + readonly HTTP_STATUS_PRECONDITION_FAILED: 412; + readonly HTTP_STATUS_PAYLOAD_TOO_LARGE: 413; + readonly HTTP_STATUS_URI_TOO_LONG: 414; + readonly HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415; + readonly HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416; + readonly HTTP_STATUS_EXPECTATION_FAILED: 417; + readonly HTTP_STATUS_TEAPOT: 418; + readonly HTTP_STATUS_MISDIRECTED_REQUEST: 421; + readonly HTTP_STATUS_UNPROCESSABLE_ENTITY: 422; + readonly HTTP_STATUS_LOCKED: 423; + readonly HTTP_STATUS_FAILED_DEPENDENCY: 424; + readonly HTTP_STATUS_TOO_EARLY: 425; + readonly HTTP_STATUS_UPGRADE_REQUIRED: 426; + readonly HTTP_STATUS_PRECONDITION_REQUIRED: 428; + readonly HTTP_STATUS_TOO_MANY_REQUESTS: 429; + readonly HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + readonly HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451; + readonly HTTP_STATUS_INTERNAL_SERVER_ERROR: 500; + readonly HTTP_STATUS_NOT_IMPLEMENTED: 501; + readonly HTTP_STATUS_BAD_GATEWAY: 502; + readonly HTTP_STATUS_SERVICE_UNAVAILABLE: 503; + readonly HTTP_STATUS_GATEWAY_TIMEOUT: 504; + readonly HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505; + readonly HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506; + readonly HTTP_STATUS_INSUFFICIENT_STORAGE: 507; + readonly HTTP_STATUS_LOOP_DETECTED: 508; + readonly HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509; + readonly HTTP_STATUS_NOT_EXTENDED: 510; + readonly HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511; + } + export const constants: Http2Constants; + /* The eager handler is the first COMPAT 'request' listener — Node's + * route on either flavor (the allowHTTP1 server's HTTP/1.1 handles, + * the ALPN=h2 server's compat handles over streams). */ + export function createSecureServer( + options: SecureServerOptions, + onRequestHandler?: ( + req: import("http").IncomingMessage, + res: import("http").ServerResponse, + ) => void, + ): Http2SecureServer; + + /* ── the REAL h2c surface (scr_http2.c: frame codec + HPACK over the + * net loop) — createServer/connect and the session/stream handles the + * Node suite's http2 family exercises. Headers cross as the canonical + * header record (the IncomingHttpHeaders index-signature shape); the + * response :status reads back as a number. Members declared here + * without a lowering fence by name at their use sites. */ + + /** The http2 module's own header shapes: pure index-signature records + * (the HEADER-FAMILY canonicalization — string[] arm included). */ + export interface IncomingHttpHeaders { + [name: string]: number | string | string[] | undefined; + } + export interface OutgoingHttpHeaders { + [name: string]: number | string | string[] | undefined; + } + + export interface Http2Session { + close(callback?: () => void): void; + destroy(): void; + readonly closed: boolean; + readonly destroyed: boolean; + readonly encrypted: boolean; + readonly type: number; + readonly alpnProtocol: string; + readonly socket: import("net").Socket; + readonly pendingSettingsAck: boolean; + ref(): void; + unref(): void; + setTimeout(msecs: number, callback?: () => void): void; + ping(callback: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean; + /* The settings surface rides the checked-dynamic machinery (records + * cross as dyn values — the suite's mustCall listeners are dyn). */ + settings(settings?: any, callback?: any): void; + readonly localSettings: any; + readonly remoteSettings: any; + goaway(code?: number, lastStreamID?: number, opaqueData?: Uint8Array): void; + on(event: "close" | "timeout", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + on( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + on(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "ping", listener: (payload: Uint8Array) => void): void; + once(event: "close" | "timeout", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + once( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + once(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "ping", listener: (payload: Uint8Array) => void): void; + addListener(event: "close" | "timeout", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + addListener(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener(event: "ping", listener: (payload: Uint8Array) => void): void; + } + + export interface ClientHttp2Session extends Http2Session { + request( + headers?: OutgoingHttpHeaders, + options?: { + endStream?: boolean; + exclusive?: boolean; + parent?: number; + weight?: number; + waitForTrailers?: boolean; + }, + ): ClientHttp2Stream; + } + + export interface ServerHttp2Session extends Http2Session { + altsvc(alt: string, originOrStream: number | string): void; + origin(...origins: string[]): void; + } + + /** The shared Http2Stream core (both roles). 'data' chunks are Buffers + * (strings once setEncoding('utf8') is in force — the IncomingMessage + * chunk:any stance keeps both working). */ + interface Http2StreamCore { + readonly id: number | undefined; + readonly rstCode: number; + readonly closed: boolean; + readonly destroyed: boolean; + readonly pending: boolean; + readonly aborted: boolean; + readonly session: Http2Session; + readonly sentHeaders: OutgoingHttpHeaders; + readonly state: Record; + write(chunk: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + close(code?: number, callback?: () => void): void; + destroy(error?: Error): void; + setEncoding(encoding: string): this; + resume(): void; + pause(): void; + setTimeout(msecs: number, callback?: () => void): void; + priority(options: Record): void; + sendTrailers(headers: OutgoingHttpHeaders): void; + } + + export interface ServerHttp2Stream extends Http2StreamCore { + on(event: "data", listener: (chunk: any) => void): void; + on( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + addListener(event: "data", listener: (chunk: any) => void): void; + addListener( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener( + event: "trailers", + listener: (trailers: IncomingHttpHeaders, flags: number) => void, + ): void; + respond( + headers?: OutgoingHttpHeaders, + options?: { endStream?: boolean; waitForTrailers?: boolean }, + ): void; + respondWithFile( + path: string, + headers?: OutgoingHttpHeaders, + options?: Record, + ): void; + respondWithFD( + fd: number, + headers?: OutgoingHttpHeaders, + options?: Record, + ): void; + pushStream( + headers: OutgoingHttpHeaders, + callback: ( + err: Error | null, + pushStream: ServerHttp2Stream, + headers: OutgoingHttpHeaders, + ) => void, + ): void; + readonly headersSent: boolean; + readonly pushAllowed: boolean; + additionalHeaders(headers: OutgoingHttpHeaders): void; + } + + export interface ClientHttp2Stream extends Http2StreamCore { + on(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "data", listener: (chunk: any) => void): void; + on( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + addListener( + event: "response", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "push", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "headers", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "data", listener: (chunk: any) => void): void; + addListener( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener( + event: "trailers", + listener: (trailers: IncomingHttpHeaders, flags: number) => void, + ): void; + } + + export interface Http2Server { + listen(port?: number, callback?: () => void): void; + listen(port: number, host: string, callback?: () => void): void; + close(callback?: () => void): void; + address(): import("node:dgram").AddressInfo; + setTimeout(msecs?: number, callback?: () => void): void; + ref(): void; + unref(): void; + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on(event: "session", listener: (session: ServerHttp2Session) => void): void; + on( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "close" | "listening" | "timeout", listener: () => void): void; + on(event: "connection", listener: (socket: import("net").Socket) => void): void; + on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once(event: "session", listener: (session: ServerHttp2Session) => void): void; + once( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "close" | "listening" | "timeout", listener: () => void): void; + once(event: "connection", listener: (socket: import("net").Socket) => void): void; + once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; + addListener( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "close" | "listening" | "timeout", listener: () => void): void; + addListener(event: "connection", listener: (socket: import("net").Socket) => void): void; + addListener( + event: "sessionError", + listener: (err: Error, session: ServerHttp2Session) => void, + ): void; + } + + export interface ServerOptions { + [option: string]: unknown; + } + + export function createServer( + onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): Http2Server; + export function createServer( + options: ServerOptions, + onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): Http2Server; + + export function connect( + authority: string, + listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, + ): ClientHttp2Session; + export function connect( + authority: string, + options: Record, + listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, + ): ClientHttp2Session; + + export function getDefaultSettings(): any; + export function getPackedSettings(settings: Record): Uint8Array; + export const sensitiveHeaders: unique symbol; +} + +declare module "node:http2" { + export * from "http2"; +} diff --git a/packages/compiler/ambient/node-fallback/https.d.ts b/packages/compiler/ambient/node-fallback/https.d.ts new file mode 100644 index 000000000..a3171f401 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/https.d.ts @@ -0,0 +1,59 @@ +/// + +/* node:https — http over the TLS transport (scr_tls.c). + * createServer({ cert, key }, handler) is the http server behind a + * handshake; request/get are the http client with port 443 default and + * the local-CA knobs: `ca` (PEM string/Buffer) replaces the trust + * anchors, rejectUnauthorized: false disables verification (the + * self-signed probe shape). With neither, /etc/ssl/cert.pem stands in + * for Node's bundled roots. */ +declare module "https" { + import { Server } from "http"; + import { Agent, ClientRequest, IncomingMessage, ServerResponse } from "http"; + export { Agent, Server }; + /* Node's keepAlive global agent for requests without an agent option. */ + export const globalAgent: Agent; + export interface ServerOptions { + cert: string | Uint8Array; + key: string | Uint8Array; + /* The options-record stance (see http.RequestOptions). */ + [option: string]: unknown; + } + export interface RequestOptions { + hostname?: string; + host?: string; + port?: number; + path?: string; + method?: string; + timeout?: number; + headers?: { [name: string]: string }; + rejectUnauthorized?: boolean; + ca?: string | Uint8Array; + agent?: Agent | boolean | null; + /* Aborting destroys the request with an AbortError. */ + signal?: AbortSignal; + socketPath?: string; + /* The options-record stance (see http.RequestOptions). */ + [option: string]: unknown; + } + export function createServer( + options: ServerOptions, + requestListener: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + /* One signature with a union first parameter, for the request-fn + * ternary's sake (see the http block). A URL target takes no options, + * which means Node's defaults — the certificate is verified against + * the default trust anchors. */ + export function request( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function get( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; +} + +declare module "node:https" { + export * from "https"; +} diff --git a/packages/compiler/ambient/node-fallback/module.d.ts b/packages/compiler/ambient/node-fallback/module.d.ts new file mode 100644 index 000000000..0ea020456 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/module.d.ts @@ -0,0 +1,10 @@ +declare module "node:module" { + export function createRequire(filename: string | URL): ScriptcRequire; + export const builtinModules: string[]; + export function isBuiltin(moduleName: string): boolean; + export function syncBuiltinESMExports(): void; +} + +declare module "module" { + export * from "node:module"; +} diff --git a/packages/compiler/ambient/node-fallback/net.d.ts b/packages/compiler/ambient/node-fallback/net.d.ts new file mode 100644 index 000000000..744fbb0b9 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/net.d.ts @@ -0,0 +1,368 @@ +/// +/// +/// +/// +/// + +/* node:net — the inbound-networking slice (scr_net.c links only into + * net-using binaries). TCP only, Node's shapes narrowed to what lowers: + * createServer with an optional connection handler; listen(port[, cb]) + * with the real port discoverable as address().port (the composed read + * is the lowered form — bare address() has no lowering); close([cb]) + * firing once connections drain; socket write/end/destroy/pipe and the + * data/end/close/error/connect/drain events (on and once — listeners + * take at most the declared parameter). write returns Node's + * backpressure boolean; end returns void here (Node returns the socket); + * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to + * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, + * and an 'error' with no listener exits 1 like an unhandled EventEmitter + * 'error'. */ +declare module "net" { + export interface Socket { + /* Whether the socket keeps the event loop alive (ref'd by default). */ + ref(): void; + unref(): void; + readonly remoteAddress: string | undefined; + /* The peer's fields are cached at first read, like remoteAddress; the + * local ones are undefined while connecting and after close. */ + readonly remotePort: number | undefined; + readonly remoteFamily: string | undefined; + readonly localAddress: string | undefined; + readonly localPort: number | undefined; + readonly localFamily: string | undefined; + readonly bytesRead: number; + readonly connecting: boolean; + readonly pending: boolean; + readonly readyState: "opening" | "open" | "readOnly" | "writeOnly" | "closed"; + /* true once the fd is gone (destroy() or full close) — Node's flag. */ + readonly destroyed: boolean; + /* the write half is open: no end() yet, no FIN sent, fd alive. */ + readonly writable: boolean; + /* true until the read half is done (peer FIN / destroy). */ + readonly readable: boolean; + /* every byte the write paths accepted (buffered included — Node + * counts those too; plaintext on TLS sockets). */ + readonly bytesWritten: number; + /* write's callback runs once the data is flushed, with null; end's + * once the socket finishes. */ + /* false when the bytes this write left unsent reach the 65536-byte + * high-water mark, as in Node; 'drain' fires when they are all sent. */ + write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + /* the bytes written and not yet sent */ + readonly writableLength: number; + /* a write answered false and 'drain' has not fired yet */ + readonly writableNeedDrain: boolean; + end(callback?: () => void): void; + end(data: string | Uint8Array, callback?: () => void): void; + destroy(): void; + /* Flow control: pause holds reads off (kernel/TCP backpressure is + * the buffer); resume flows — and discards without listeners, so + * 'end' stays reachable. Both chain, Node's shape. */ + pause(): Socket; + resume(): Socket; + /* TCP_NODELAY on the live fd; missing means true. Chains. */ + setNoDelay(noDelay?: boolean): Socket; + /* SO_KEEPALIVE with the initial delay in ms. Chains. */ + setKeepAlive(enable?: boolean, initialDelay?: number): Socket; + /* end() now, destroy once the FIN actually flushed. */ + destroySoon(): void; + /* An RST instead of a FIN; a connecting socket resets once it + * connects. Throws ERR_INVALID_HANDLE_TYPE on a TLS socket. */ + resetAndDestroy(): Socket; + /* The local address, or {} while connecting and once destroyed. */ + address(): AddressInfo | {}; + /* setEncoding('utf8'): 'data' delivers strings — the IncomingMessage + * twin's contract. */ + setEncoding(encoding: string): void; + /* socket→socket, and socket→response (the extended-CONNECT bridge + * leg: raw chunks become response body writes; EOF ends it). */ + pipe( + destination: Socket | import("http").ServerResponse | import("http2").Http2ServerResponse, + ): void; + /* The callback is a once('timeout') listener (removed again by 0). */ + setTimeout(ms: number, callback?: () => void): void; + /* The paused-mode demux surface: once('readable') + read(1) + + * unshift — read answers exactly n buffered bytes or null. */ + read(size?: number): Buffer | null; + unshift(chunk: Uint8Array): void; + /* secureConnect/secure/session are TLS-socket events declared on the + * shared socket surface (the lowering collapses TLSSocket onto this kind): + * the runtime gates on the transport, so a plain socket's + * registration never fires — Node's exact split. 'session' fires + * once with the serialized session (the received-ticket event). */ + on(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + on(event: "data", listener: (chunk: string) => void): void; + on( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + on(event: "close", listener: (hadError: boolean) => void): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + on( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + addListener(event: "data", listener: (chunk: string) => void): void; + addListener( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + addListener(event: "close", listener: (hadError: boolean) => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + addListener( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + once(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + once(event: "data", listener: (chunk: string) => void): void; + once( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + once(event: "close", listener: (hadError: boolean) => void): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + once( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + } + export interface Server { + /* Whether the server keeps the event loop alive (ref'd by default). */ + ref(): void; + unref(): void; + readonly listening: boolean; + /* Connections past this many are closed with a 'drop' event; + * undefined (the default) or null lifts the cap. */ + maxConnections: number | undefined; + /* Node reads it only in cluster mode: a stored value here. */ + dropMaxConnection: boolean | undefined; + /* cb(null, count) on the next tick. */ + getConnections(callback: (error: Error | null, count: number) => void): Server; + /* Node answers the server itself (`return this` chaining). */ + listen(port: number, callback?: () => void): Server; + /* The positional bind address (the options form's host in argument + * spelling). */ + listen(port: number, host: string, callback?: () => void): Server; + /* A Unix-socket server; the socket file is removed when it closes. */ + listen(path: string, callback?: () => void): Server; + /* The explicit-interface bind: host is an IP literal (absent = the + * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort + * requests kernel connection distribution where Node supports it. */ + listen( + options: { port: number; host?: string; ipv6Only?: boolean; reusePort?: boolean }, + callback?: () => void, + ): Server; + close(callback?: () => void): void; + /* The bound AddressInfo (Node answers null before listen — this + * surface answers the record with port 0 there, the serverPort + * stance). */ + address(): import("node:dgram").AddressInfo; + /* The demux route: hand an accepted socket to another server. */ + emit(event: "connection", socket: Socket): boolean; + on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + on( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + on(event: "close" | "listening", listener: () => void): void; + on(event: "drop", listener: (data?: DropArgument) => void): void; + on(event: "timeout", listener: (socket: Socket) => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* The WebSocket handover: fires INSTEAD of 'request' for + * Connection: upgrade requests, with the raw socket + head bytes; + * 'connect' is its twin for CONNECT requests (HTTP tunneling). */ + on( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + /* http.createServer(handler)'s event twin (a no-parser net server + * never fires it, like Node). */ + on( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + addListener( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + addListener(event: "close" | "listening", listener: () => void): void; + addListener(event: "drop", listener: (data?: DropArgument) => void): void; + addListener(event: "timeout", listener: (socket: Socket) => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + addListener( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + once( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + once(event: "close" | "listening", listener: () => void): void; + once(event: "drop", listener: (data?: DropArgument) => void): void; + once(event: "timeout", listener: (socket: Socket) => void): void; + once(event: "error", listener: (err: Error) => void): void; + once( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + once( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + } + /* The 'drop' payload: the turned-away connection's addresses. */ + export interface DropArgument { + localAddress?: string; + localPort?: number; + localFamily?: string; + remoteAddress?: string; + remotePort?: number; + remoteFamily?: string; + } + export type AddressInfo = import("node:dgram").AddressInfo; + export type IPVersion = "ipv4" | "ipv6"; + export interface SocketAddressInitOptions { + address?: string; + family?: IPVersion; + flowlabel?: number; + port?: number; + } + /* A native handle (scr_net.c); the getters read its parsed address. */ + export class SocketAddress { + constructor(options?: SocketAddressInitOptions); + readonly address: string; + readonly family: IPVersion; + readonly port: number; + readonly flowlabel: number; + toJSON(): { address: string; port: number; family: IPVersion; flowlabel: number }; + /* undefined when the input is not "[:]" */ + static parse(input: string): SocketAddress | undefined; + static isSocketAddress(value: unknown): value is SocketAddress; + } + /* Address, range and subnet rules; check() matches IPv4 addresses + * against IPv6 rules through their IPv4-mapped form, as Node does. */ + export class BlockList { + constructor(); + readonly rules: readonly string[]; + readonly size: number; + addAddress(address: string | SocketAddress, type?: IPVersion): void; + addAddresses(addresses: readonly (string | SocketAddress)[], type?: IPVersion): void; + addRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; + addSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; + addCIDR(cidr: string): void; + addCIDRs(cidrs: readonly string[]): void; + removeAddress(address: string | SocketAddress, type?: IPVersion): void; + removeRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; + removeSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; + removeCIDR(cidr: string): void; + clear(): void; + check(address: string | SocketAddress, type?: IPVersion): boolean; + toJSON(): string[]; + fromJSON(data: string | readonly string[]): void; + static isBlockList(value: unknown): value is BlockList; + static readonly PRIVATE_RANGES: readonly string[]; + } + /* Node's IPv4Reg and IPv6Reg tests: 4, 6, or 0 for isIP. */ + export function isIP(input: string): number; + export function isIPv4(input: string): boolean; + export function isIPv6(input: string): boolean; + /* The Socket VALUE — the `x instanceof net.Socket` narrowing target + * (the h2 compat 'connect' listener's test). Constructing bare sockets + * has no lowering; the construct signature exists for instanceof. */ + export const Socket: abstract new () => Socket; + export function createServer(connectionListener?: (socket: Socket) => void): Server; + export function connect(port: number, host?: string, connectListener?: () => void): Socket; + export function createConnection( + port: number, + host?: string, + connectListener?: () => void, + ): Socket; + /* The caller-resolver dial (@types/node's net.LookupFunction): the + * runtime invokes it as Node's autoSelectFamily dial does (options + * { hints, all: true }, so the callback answers an address list) and + * dials the answered addresses in order. */ + export type LookupFunction = ( + hostname: string, + options: import("node:dns").LookupOptions, + callback: ( + err: NodeJS.ErrnoException | null, + address: string | import("node:dns").LookupAddress[], + family?: number, + ) => void, + ) => void; + export function connect( + options: { + port: number; + host?: string; + autoSelectFamily?: boolean; + lookup?: LookupFunction; + }, + connectListener?: () => void, + ): Socket; + export function createConnection( + options: { + port: number; + host?: string; + autoSelectFamily?: boolean; + lookup?: LookupFunction; + }, + connectListener?: () => void, + ): Socket; + /* The happy-eyeballs attempt budget (Node's process-wide knob; the + * autoSelectFamily dial consults it): one runtime int, default 250ms + * like Node's. */ + export function getDefaultAutoSelectFamilyAttemptTimeout(): number; + export function setDefaultAutoSelectFamilyAttemptTimeout(value: number): void; +} + +declare module "node:net" { + export * from "net"; +} diff --git a/packages/compiler/ambient/node-fallback/os.d.ts b/packages/compiler/ambient/node-fallback/os.d.ts new file mode 100644 index 000000000..d58592df5 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/os.d.ts @@ -0,0 +1,288 @@ +/* node:os — the slice a CLI needs: platform() (same implementation as + * process.platform), homedir(), tmpdir() (Node's env cascade), EOL + * (always "\n" — POSIX pinned, like path.sep), and networkInterfaces() + * (getifaddrs(3); the interface shapes mirror @types/node's exactly so + * both type worlds map to the same IR structure). */ +declare module "os" { + export function platform(): string; + export function homedir(): string; + export function tmpdir(): string; + /* uname(2)'s release field — Node's own implementation. */ + export function release(): string; + /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */ + export function type(): string; + /* Total system memory in bytes (sysctl hw.memsize / sysconf). */ + export function totalmem(): number; + export function availableParallelism(): number; + /* The passwd-entry snapshot (uv_os_get_passwd): shell is `string | + * null` to match @types/node (POSIX always answers the string arm); + * homedir is pw_dir — NOT os.homedir()'s $HOME-first cascade. The type + * is interned explicitly in type-mapper.ts (the AddressInfo pattern — this + * concrete form and @types/node's UserInfo both), and the call + * assembles field-by-field in lowerOsUserInfoCall. */ + export interface UserInfo { + username: string; + uid: number; + gid: number; + shell: string | null; + homedir: string; + } + export function userInfo(): UserInfo; + /* The TARGET platform's line terminator (a compile-time constant: + * "\r\n" under a win32 triple, "\n" everywhere else). */ + export const EOL: "\r\n" | "\n"; + export interface NetworkInterfaceBase { + address: string; + netmask: string; + mac: string; + internal: boolean; + cidr: string | null; + scopeid?: number; + } + export interface NetworkInterfaceInfoIPv4 extends NetworkInterfaceBase { + family: "IPv4"; + } + export interface NetworkInterfaceInfoIPv6 extends NetworkInterfaceBase { + family: "IPv6"; + scopeid: number; + } + export type NetworkInterfaceInfo = NetworkInterfaceInfoIPv4 | NetworkInterfaceInfoIPv6; + export function networkInterfaces(): { [name: string]: NetworkInterfaceInfo[] | undefined }; + /* Host facts, libuv's answers: model/speed/times per logical CPU, the + * host name, the 1/5/15-minute load averages, uptime in seconds, free + * memory in bytes, process.arch, uname's machine and version fields, + * the byte order, and a process's scheduling priority (an omitted pid + * is the current process). Shapes mirror @types/node's. */ + export interface CpuInfo { + model: string; + speed: number; + times: { + user: number; + nice: number; + sys: number; + idle: number; + irq: number; + }; + } + export function cpus(): CpuInfo[]; + export function hostname(): string; + export function loadavg(): number[]; + export function uptime(): number; + export function freemem(): number; + export function arch(): string; + export function machine(): string; + export function version(): string; + export function endianness(): "BE" | "LE"; + export function getPriority(pid?: number): number; + export function setPriority(priority: number): void; + export function setPriority(pid: number, priority: number): void; + /* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */ + export const devNull: string; + /* The platform's constants: null-prototype objects with read-only + * members, `signals` frozen (Node's internalBinding('constants').os). */ + export type SignalConstants = { + [ + key in + | "SIGABRT" + | "SIGALRM" + | "SIGBUS" + | "SIGCHLD" + | "SIGCONT" + | "SIGFPE" + | "SIGHUP" + | "SIGILL" + | "SIGINT" + | "SIGIO" + | "SIGIOT" + | "SIGKILL" + | "SIGPIPE" + | "SIGPOLL" + | "SIGPROF" + | "SIGPWR" + | "SIGQUIT" + | "SIGSEGV" + | "SIGSTKFLT" + | "SIGSTOP" + | "SIGSYS" + | "SIGTERM" + | "SIGTRAP" + | "SIGTSTP" + | "SIGTTIN" + | "SIGTTOU" + | "SIGUNUSED" + | "SIGURG" + | "SIGUSR1" + | "SIGUSR2" + | "SIGVTALRM" + | "SIGWINCH" + | "SIGXCPU" + | "SIGXFSZ" + | "SIGBREAK" + | "SIGLOST" + | "SIGINFO" + ]: number; + }; + export namespace constants { + const UV_UDP_REUSEADDR: number; + namespace signals {} + const signals: SignalConstants; + namespace errno { + const E2BIG: number; + const EACCES: number; + const EADDRINUSE: number; + const EADDRNOTAVAIL: number; + const EAFNOSUPPORT: number; + const EAGAIN: number; + const EALREADY: number; + const EBADF: number; + const EBADMSG: number; + const EBUSY: number; + const ECANCELED: number; + const ECHILD: number; + const ECONNABORTED: number; + const ECONNREFUSED: number; + const ECONNRESET: number; + const EDEADLK: number; + const EDESTADDRREQ: number; + const EDOM: number; + const EDQUOT: number; + const EEXIST: number; + const EFAULT: number; + const EFBIG: number; + const EHOSTUNREACH: number; + const EIDRM: number; + const EILSEQ: number; + const EINPROGRESS: number; + const EINTR: number; + const EINVAL: number; + const EIO: number; + const EISCONN: number; + const EISDIR: number; + const ELOOP: number; + const EMFILE: number; + const EMLINK: number; + const EMSGSIZE: number; + const EMULTIHOP: number; + const ENAMETOOLONG: number; + const ENETDOWN: number; + const ENETRESET: number; + const ENETUNREACH: number; + const ENFILE: number; + const ENOBUFS: number; + const ENODATA: number; + const ENODEV: number; + const ENOENT: number; + const ENOEXEC: number; + const ENOLCK: number; + const ENOLINK: number; + const ENOMEM: number; + const ENOMSG: number; + const ENOPROTOOPT: number; + const ENOSPC: number; + const ENOSR: number; + const ENOSTR: number; + const ENOSYS: number; + const ENOTCONN: number; + const ENOTDIR: number; + const ENOTEMPTY: number; + const ENOTSOCK: number; + const ENOTSUP: number; + const ENOTTY: number; + const ENXIO: number; + const EOPNOTSUPP: number; + const EOVERFLOW: number; + const EPERM: number; + const EPIPE: number; + const EPROTO: number; + const EPROTONOSUPPORT: number; + const EPROTOTYPE: number; + const ERANGE: number; + const EROFS: number; + const ESPIPE: number; + const ESRCH: number; + const ESTALE: number; + const ETIME: number; + const ETIMEDOUT: number; + const ETXTBSY: number; + const EWOULDBLOCK: number; + const EXDEV: number; + const WSAEINTR: number; + const WSAEBADF: number; + const WSAEACCES: number; + const WSAEFAULT: number; + const WSAEINVAL: number; + const WSAEMFILE: number; + const WSAEWOULDBLOCK: number; + const WSAEINPROGRESS: number; + const WSAEALREADY: number; + const WSAENOTSOCK: number; + const WSAEDESTADDRREQ: number; + const WSAEMSGSIZE: number; + const WSAEPROTOTYPE: number; + const WSAENOPROTOOPT: number; + const WSAEPROTONOSUPPORT: number; + const WSAESOCKTNOSUPPORT: number; + const WSAEOPNOTSUPP: number; + const WSAEPFNOSUPPORT: number; + const WSAEAFNOSUPPORT: number; + const WSAEADDRINUSE: number; + const WSAEADDRNOTAVAIL: number; + const WSAENETDOWN: number; + const WSAENETUNREACH: number; + const WSAENETRESET: number; + const WSAECONNABORTED: number; + const WSAECONNRESET: number; + const WSAENOBUFS: number; + const WSAEISCONN: number; + const WSAENOTCONN: number; + const WSAESHUTDOWN: number; + const WSAETOOMANYREFS: number; + const WSAETIMEDOUT: number; + const WSAECONNREFUSED: number; + const WSAELOOP: number; + const WSAENAMETOOLONG: number; + const WSAEHOSTDOWN: number; + const WSAEHOSTUNREACH: number; + const WSAENOTEMPTY: number; + const WSAEPROCLIM: number; + const WSAEUSERS: number; + const WSAEDQUOT: number; + const WSAESTALE: number; + const WSAEREMOTE: number; + const WSASYSNOTREADY: number; + const WSAVERNOTSUPPORTED: number; + const WSANOTINITIALISED: number; + const WSAEDISCON: number; + const WSAENOMORE: number; + const WSAECANCELLED: number; + const WSAEINVALIDPROCTABLE: number; + const WSAEINVALIDPROVIDER: number; + const WSAEPROVIDERFAILEDINIT: number; + const WSASYSCALLFAILURE: number; + const WSASERVICE_NOT_FOUND: number; + const WSATYPE_NOT_FOUND: number; + const WSA_E_NO_MORE: number; + const WSA_E_CANCELLED: number; + const WSAEREFUSED: number; + } + namespace dlopen { + const RTLD_LAZY: number; + const RTLD_NOW: number; + const RTLD_GLOBAL: number; + const RTLD_LOCAL: number; + const RTLD_DEEPBIND: number; + } + namespace priority { + const PRIORITY_LOW: number; + const PRIORITY_BELOW_NORMAL: number; + const PRIORITY_NORMAL: number; + const PRIORITY_ABOVE_NORMAL: number; + const PRIORITY_HIGH: number; + const PRIORITY_HIGHEST: number; + } + } +} + +declare module "node:os" { + export * from "os"; +} diff --git a/packages/compiler/ambient/node-fallback/path-posix.d.ts b/packages/compiler/ambient/node-fallback/path-posix.d.ts new file mode 100644 index 000000000..66db6a876 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path-posix.d.ts @@ -0,0 +1,22 @@ +/// + +declare module "path/posix" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + export function matchesGlob(path: string, pattern: string): boolean; + export function parse(path: string): import("path").ParsedPath; + export function format(pathObject: import("path").FormatInputPathObject): string; + export const sep: "/"; + export const delimiter: ":"; +} + +declare module "node:path/posix" { + export * from "path/posix"; +} diff --git a/packages/compiler/ambient/node-fallback/path-win32.d.ts b/packages/compiler/ambient/node-fallback/path-win32.d.ts new file mode 100644 index 000000000..b889a34e0 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path-win32.d.ts @@ -0,0 +1,22 @@ +/// + +declare module "path/win32" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + export function matchesGlob(path: string, pattern: string): boolean; + export function parse(path: string): import("path").ParsedPath; + export function format(pathObject: import("path").FormatInputPathObject): string; + export const sep: "\\"; + export const delimiter: ";"; +} + +declare module "node:path/win32" { + export * from "path/win32"; +} diff --git a/packages/compiler/ambient/node-fallback/path.d.ts b/packages/compiler/ambient/node-fallback/path.d.ts new file mode 100644 index 000000000..a57e40bb7 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path.d.ts @@ -0,0 +1,55 @@ +/// +/// + +/* node:path — the TARGET platform's implementation (Node's rule): posix + * on posix triples, path.win32 under a win32 triple, with the posix and + * win32 namespaces answering their own platform anywhere. Every function + * is a pure string algorithm matching Node's implementations + * exactly; join/resolve are variadic like Node's. */ +declare module "path" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + /* matchesGlob() is Node's glob matcher: brace expansion, extglobs, POSIX + * classes and the ** and dot rules. */ + export function matchesGlob(path: string, pattern: string): boolean; + /* parse() splits a path into its root, directory, base, extension and + * name; format() joins those fields back (Node's `||` defaults). */ + export interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; + } + export interface FormatInputPathObject { + root?: string | undefined; + dir?: string | undefined; + base?: string | undefined; + ext?: string | undefined; + name?: string | undefined; + } + export function parse(path: string): ParsedPath; + export function format(pathObject: FormatInputPathObject): string; + /* The union @types/node declares: the bare module answers the TARGET + * platform's value (a compile-time constant — "\\"/";" under a win32 + * triple, "/"/":" everywhere else). */ + export const sep: "\\" | "/"; + export const delimiter: ";" | ":"; + /* The platform-specific namespaces (real Node submodules too — + * "node:path/posix" resolves). Each carries the FULL surface and + * answers ITS platform's rules on any target, Node's own contract; + * the bare module IS the target platform's implementation. */ + export const posix: typeof import("path/posix"); + export const win32: typeof import("path/win32"); +} + +declare module "node:path" { + export * from "path"; +} diff --git a/packages/compiler/ambient/node-fallback/process.d.ts b/packages/compiler/ambient/node-fallback/process.d.ts new file mode 100644 index 000000000..7782a1b2d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/process.d.ts @@ -0,0 +1,11 @@ +/* Node's process module default export is the global process object. These + * declaration aliases let the ESM spelling typecheck against the same + * deliberately narrow fallback surface when @types/node is absent. */ +declare module "process" { + export = process; +} + +declare module "node:process" { + import process = require("process"); + export = process; +} diff --git a/packages/compiler/ambient/node-fallback/punycode.d.ts b/packages/compiler/ambient/node-fallback/punycode.d.ts new file mode 100644 index 000000000..fc2725c5d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/punycode.d.ts @@ -0,0 +1,18 @@ +/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and + * toUnicode throw its RangeErrors on malformed input; ucs2 converts + * between strings and UTF-16 code point arrays. */ +declare module "punycode" { + export function decode(string: string): string; + export function encode(string: string): string; + export function toUnicode(domain: string): string; + export function toASCII(domain: string): string; + export namespace ucs2 { + function decode(string: string): number[]; + function encode(codePoints: readonly number[]): string; + } + export const version: string; +} + +declare module "node:punycode" { + export * from "punycode"; +} diff --git a/packages/compiler/ambient/node-fallback/querystring.d.ts b/packages/compiler/ambient/node-fallback/querystring.d.ts new file mode 100644 index 000000000..4ee4346bb --- /dev/null +++ b/packages/compiler/ambient/node-fallback/querystring.d.ts @@ -0,0 +1,55 @@ +/* node:querystring — Node's legacy query-string codec (NOT + * URLSearchParams: '+' means space on the parse side and escape encodes + * spaces as %20). parse answers the null-prototype dictionary as a pure + * index-signature record (repeated keys become string[] buckets; + * @types/node's Dict adds an undefined arm the lowering tolerates); + * stringify serializes string/number/boolean values and arrays of those + * (Node's rules: arrays expand to repeated keys, null/undefined and + * anything else serialize as the empty value). The maxKeys option lowers + * (0 removes the cap, Node's rule); the custom encoder/decoder options + * typecheck under the options-record stance and fence by name at the + * call. decode/encode are Node's own aliases of parse/stringify. */ +declare module "querystring" { + export interface ParseOptions { + maxKeys?: number; + decodeURIComponent?: (str: string) => string; + [option: string]: unknown; + } + export interface StringifyOptions { + encodeURIComponent?: (str: string) => string; + [option: string]: unknown; + } + export interface ParsedUrlQuery { + [key: string]: string | string[] | undefined; + } + export interface ParsedUrlQueryInput { + [key: string]: + | string + | number + | boolean + | bigint + | ReadonlyArray + | null + | undefined; + } + export function parse( + str: string, + sep?: string | null, + eq?: string | null, + options?: ParseOptions, + ): ParsedUrlQuery; + export function stringify( + obj?: ParsedUrlQueryInput, + sep?: string | null, + eq?: string | null, + options?: StringifyOptions, + ): string; + export const decode: typeof parse; + export const encode: typeof stringify; + export function escape(str: string): string; + export function unescape(str: string): string; +} + +declare module "node:querystring" { + export * from "querystring"; +} diff --git a/packages/compiler/ambient/node-fallback/stream-consumers.d.ts b/packages/compiler/ambient/node-fallback/stream-consumers.d.ts new file mode 100644 index 000000000..9e220ea17 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-consumers.d.ts @@ -0,0 +1,21 @@ +declare module "stream/consumers" { + /* The promise consumers over a readable: accumulate every chunk and + * settle — text (the utf8 decode), json (the text through JSON.parse: + * `unknown`, validated with a checked cast; malformed input rejects + * with the parse's SyntaxError), buffer (the concatenated bytes; + * string chunks contribute their utf8 bytes, Node's Blob rule). + * Stream errors reject; an early close rejects with + * ERR_STREAM_PREMATURE_CLOSE. arrayBuffer answers an ArrayBuffer over + * the bytes; blob fences per site. */ + function text(stream: unknown): Promise; + function json(stream: unknown): Promise; + function buffer(stream: unknown): Promise; + function arrayBuffer(stream: unknown): Promise; + function blob(stream: unknown): Promise; + export { arrayBuffer, blob, buffer, json, text }; +} + +declare module "node:stream/consumers" { + import streamConsumers = require("stream/consumers"); + export = streamConsumers; +} diff --git a/packages/compiler/ambient/node-fallback/stream-promises.d.ts b/packages/compiler/ambient/node-fallback/stream-promises.d.ts new file mode 100644 index 000000000..bf771d571 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-promises.d.ts @@ -0,0 +1,28 @@ +/// + +declare module "stream/promises" { + /* The promise forms of finished/pipeline: a void promise the stream's + * terminal point settles (rejected with the error, or + * ERR_STREAM_PREMATURE_CLOSE on an early close). Stream arguments are + * the lowered surface — iterables/generators/functions fence per site. + * pipeline's trailing { signal, end } object and finished's options + * (readable, writable, error, cleanup, signal) are supported. A last + * stage that is an async function resolves the promise with its value. */ + function pipeline(...streams: T): import("stream").PipelinePromise; + function finished( + stream: unknown, + options?: { + readable?: boolean; + writable?: boolean; + error?: boolean; + cleanup?: boolean; + signal?: AbortSignal; + } | null, + ): Promise; + export { pipeline, finished }; +} + +declare module "node:stream/promises" { + import streamPromises = require("stream/promises"); + export = streamPromises; +} diff --git a/packages/compiler/ambient/node-fallback/stream-web.d.ts b/packages/compiler/ambient/node-fallback/stream-web.d.ts new file mode 100644 index 000000000..f947e667d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-web.d.ts @@ -0,0 +1,17 @@ +/* node:stream/web — the module exports the web stream globals themselves + * (require("stream/web").ReadableStream === globalThis.ReadableStream). + * ReadableStream is the implemented class; its reader and controller are + * exported as types. */ +declare module "stream/web" { + export const ReadableStream: typeof globalThis.ReadableStream; + export type ReadableStream = globalThis.ReadableStream; + export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; + export type ReadableStreamDefaultController = + globalThis.ReadableStreamDefaultController; + export type UnderlyingSource = globalThis.UnderlyingSource; + export type QueuingStrategy = globalThis.QueuingStrategy; +} + +declare module "node:stream/web" { + export * from "stream/web"; +} diff --git a/packages/compiler/ambient/node-fallback/stream.d.ts b/packages/compiler/ambient/node-fallback/stream.d.ts new file mode 100644 index 000000000..24f290fd9 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream.d.ts @@ -0,0 +1,644 @@ +/// + +/* node:stream — the static stream classes (runtime-provided, the + * EventEmitter-hierarchy precedent). Phase 1 is the OPTIONS-OBJECT + * constructor surface: `new Readable({ read() {} })`, `new Writable({ + * write(chunk, enc, cb) {} })`, Duplex/Transform/PassThrough, push/read, + * flowing vs paused, write/end backpressure + 'drain', pipe/unpipe, and + * destroy — with Node's event orderings. Chunks are BYTES (Buffer), with + * strings converted at push/write like Node, and any value on an + * object-mode side (objectMode, readableObjectMode, writableObjectMode — + * the object-mode constructor overloads come first). The runtime-fired + * events carry per-class FORCED argument tuples ('data' is one chunk, + * 'error' one Error, 'pipe'/'unpipe' one Readable, the rest empty) — the + * overloads below hand tsc the same contract so unannotated listener + * parameters contextually type in .js sources. */ +declare module "stream" { + import { EventEmitter } from "events"; + + interface ReadableOptions { + highWaterMark?: number; + encoding?: string; + objectMode?: boolean; + read?(this: Readable, size: number): void; + destroy?(this: Readable, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Readable, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + defaultEncoding?: string; + signal?: AbortSignal; + } + interface WritableOptions { + highWaterMark?: number; + decodeStrings?: boolean; + defaultEncoding?: string; + objectMode?: boolean; + write?( + this: Writable, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + writev?( + this: Writable, + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + final?(this: Writable, callback: (error?: Error | null) => void): void; + destroy?(this: Writable, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Writable, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + signal?: AbortSignal; + } + interface DuplexOptions { + highWaterMark?: number; + readableHighWaterMark?: number; + writableHighWaterMark?: number; + encoding?: string; + decodeStrings?: boolean; + objectMode?: boolean; + readableObjectMode?: boolean; + writableObjectMode?: boolean; + allowHalfOpen?: boolean; + read?(this: Duplex, size: number): void; + write?( + this: Duplex, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + final?(this: Duplex, callback: (error?: Error | null) => void): void; + destroy?(this: Duplex, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Duplex, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + readable?: boolean; + writable?: boolean; + defaultEncoding?: string; + writev?( + this: Duplex, + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + signal?: AbortSignal; + } + interface TransformOptions { + highWaterMark?: number; + readableHighWaterMark?: number; + writableHighWaterMark?: number; + encoding?: string; + decodeStrings?: boolean; + objectMode?: boolean; + readableObjectMode?: boolean; + writableObjectMode?: boolean; + allowHalfOpen?: boolean; + transform?( + this: Transform, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null, data?: Buffer | string) => void, + ): void; + flush?(this: Transform, callback: (error?: Error | null, data?: Buffer | string) => void): void; + destroy?(this: Transform, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Transform, callback: (error?: Error | null) => void): void; + read?(this: Transform, size: number): void; + write?( + this: Transform, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + final?(this: Transform, callback: (error?: Error | null) => void): void; + writev?(this: Transform, chunks: unknown[], callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + readable?: boolean; + writable?: boolean; + defaultEncoding?: string; + signal?: AbortSignal; + } + + /* Object-mode options (objectMode: true, or a Duplex's or Transform's + * readableObjectMode / writableObjectMode): chunks and completion data + * are any values, and an object-mode write's encoding argument reaches + * write/transform as passed (undefined when absent, null when a + * callback took its place). The constructors take these before the + * byte-mode options, so a byte-mode stream's callbacks keep Buffer + * chunks. */ + type ObjectModeEncoding = string | null | undefined; + type ObjectModeFlag = + | { objectMode: true } + | { readableObjectMode: true } + | { writableObjectMode: true }; + interface ObjectWritableOptions extends Omit { + objectMode: true; + write?( + this: Writable, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } + type ObjectDuplexOptions = Omit & { + write?( + this: Duplex, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } & ObjectModeFlag; + type ObjectTransformOptions = Omit & { + transform?( + this: Transform, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null, data?: any) => void, + ): void; + flush?(this: Transform, callback: (error?: Error | null, data?: any) => void): void; + write?( + this: Transform, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } & ObjectModeFlag; + + /* decodeStrings: false — a string write reaches write/transform as the + * string with its encoding (Buffers keep 'buffer'). */ + interface StringWritableOptions extends Omit { + decodeStrings: false; + write?( + this: Writable, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + } + type StringDuplexOptions = Omit & { + decodeStrings: false; + write?( + this: Duplex, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + }; + type StringTransformOptions = Omit & { + decodeStrings: false; + transform?( + this: Transform, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null, data?: Buffer | string) => void, + ): void; + }; + + /* The operators' options: concurrency and highWaterMark bound map's + * in-flight callbacks and queued results. */ + interface ArrayOptions { + concurrency?: number; + highWaterMark?: number; + signal?: AbortSignal; + } + + class Readable extends EventEmitter { + constructor(opts?: ReadableOptions); + /* The underscore-method surface (@types/node declares these on the + * classes; assigning them post-construction shadows the prototype + * method and the machinery calls through the assignment — lowered + * onto the option-callback slots). */ + _read(size: number): void; + _destroy(error: Error | null, callback: (error?: Error | null) => void): void; + [Symbol.asyncIterator](): AsyncIterableIterator; + iterator(options?: { destroyOnReturn?: boolean }): AsyncIterableIterator; + /* compose(this, stage), destroyed when options.signal aborts. */ + compose(stage: ComposeStage, options?: { signal: AbortSignal }): Duplex; + push(chunk: Buffer | string | null, encoding?: string): boolean; + /* An object-mode stream's values. */ + push(chunk: any, encoding?: string): boolean; + unshift(chunk: Buffer | string, encoding?: string): void; + unshift(chunk: any, encoding?: string): void; + /* A Buffer, a string once encoded, an object-mode value, or null (as + * @types/node types it). */ + read(size?: number): any; + pause(): this; + resume(): this; + isPaused(): boolean; + setEncoding(encoding: string): this; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: NodeJS.WritableStream): this; + destroy(error?: Error): this; + readonly readable: boolean; + readonly readableEnded: boolean; + readonly readableFlowing: boolean | null; + readonly readableLength: number; + readonly readableHighWaterMark: number; + readonly readableObjectMode: boolean; + readonly closed: boolean; + readonly destroyed: boolean; + readonly errored: Error | null; + static from(iterable: unknown, options?: ReadableOptions): Readable; + /* A Readable over a web ReadableStream's reader. */ + static fromWeb( + readableStream: globalThis.ReadableStream, + options?: { + encoding?: string; + highWaterMark?: number; + objectMode?: boolean; + signal?: AbortSignal; + }, + ): Readable; + /* The Readable operators: chunks are strings or Buffers, map and + * flatMap callbacks return strings or Buffers (flatMap: arrays of + * them, or strings), and callbacks that declare the options argument + * are refused. */ + map( + fn: (data: any, options?: Pick) => any, + options?: ArrayOptions, + ): Readable; + filter( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Readable; + forEach( + fn: (data: any, options?: Pick) => void | Promise, + options?: ArrayOptions, + ): Promise; + toArray(options?: Pick): Promise; + some( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + find( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + every( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + flatMap( + fn: (data: any, options?: Pick) => any, + options?: ArrayOptions, + ): Readable; + drop(limit: number, options?: Pick): Readable; + take(limit: number, options?: Pick): Readable; + reduce( + fn: (previous: any, data: any, options?: Pick) => T, + initial?: undefined, + options?: Pick, + ): Promise; + reduce( + fn: (previous: T, data: any, options?: Pick) => T, + initial: T, + options?: Pick, + ): Promise; + on(event: "data", listener: (chunk: any) => void): this; + on(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "data", listener: (chunk: any) => void): this; + off(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + class Writable extends EventEmitter { + constructor(opts: ObjectWritableOptions); + constructor(opts: StringWritableOptions); + constructor(opts?: WritableOptions); + /* The underscore-method surface (see Readable). */ + _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; + _writev( + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + _destroy(error: Error | null, callback: (error?: Error | null) => void): void; + write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; + write( + chunk: Buffer | string, + encoding: string, + callback?: (error?: Error | null) => void, + ): boolean; + /* An object-mode stream's values (the encoding argument reaches + * _write as passed). */ + write(chunk: any, callback?: (error?: Error | null) => void): boolean; + write( + chunk: any, + encoding: ObjectModeEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): this; + end(chunk: Buffer | string, callback?: () => void): this; + end(chunk: Buffer | string, encoding: string, callback?: () => void): this; + end(chunk: any, callback?: () => void): this; + end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; + cork(): void; + uncork(): void; + setDefaultEncoding(encoding: string): this; + destroy(error?: Error): this; + readonly writable: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly writableLength: number; + readonly writableHighWaterMark: number; + readonly writableObjectMode: boolean; + readonly writableNeedDrain: boolean; + readonly writableCorked: number; + readonly closed: boolean; + readonly destroyed: boolean; + readonly errored: Error | null; + on(event: "drain" | "finish" | "close", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "drain" | "finish" | "close", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "drain" | "finish" | "close", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "drain" | "finish" | "close", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "drain" | "finish" | "close", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "drain" | "finish" | "close", listener: () => void): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "drain" | "finish" | "close", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + /* Duplex is Readable + Writable in one object. TS single inheritance + * mirrors @types/node: extends Readable, the writable half declared + * again. The lowering treats both halves as first-class. */ + class Duplex extends Readable { + /* Duplex.from: an async generator function, an iterable, a string or + * a stream (duplexify.js). */ + static from(body: unknown): Duplex; + constructor(opts: ObjectDuplexOptions); + constructor(opts: StringDuplexOptions); + constructor(opts?: DuplexOptions); + /* The underscore-method surface (see Readable). */ + _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; + _writev( + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; + write( + chunk: Buffer | string, + encoding: string, + callback?: (error?: Error | null) => void, + ): boolean; + /* An object-mode stream's values (the encoding argument reaches + * _write as passed). */ + write(chunk: any, callback?: (error?: Error | null) => void): boolean; + write( + chunk: any, + encoding: ObjectModeEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): this; + end(chunk: Buffer | string, callback?: () => void): this; + end(chunk: Buffer | string, encoding: string, callback?: () => void): this; + end(chunk: any, callback?: () => void): this; + end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; + cork(): void; + uncork(): void; + setDefaultEncoding(encoding: string): this; + readonly writable: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly writableLength: number; + readonly writableHighWaterMark: number; + readonly writableObjectMode: boolean; + readonly writableNeedDrain: boolean; + readonly writableCorked: number; + readonly allowHalfOpen: boolean; + on(event: "data", listener: (chunk: any) => void): this; + on( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "data", listener: (chunk: any) => void): this; + off( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + class Transform extends Duplex { + constructor(opts: ObjectTransformOptions); + constructor(opts: StringTransformOptions); + constructor(opts?: TransformOptions); + /* The underscore-method surface (see Readable). */ + _transform( + chunk: any, + encoding: string, + callback: (error?: Error | null, data?: any) => void, + ): void; + _flush(callback: (error?: Error | null, data?: any) => void): void; + } + class PassThrough extends Transform { + constructor(opts: ObjectTransformOptions); + constructor(opts: StringTransformOptions); + constructor(opts?: TransformOptions); + } + + /* addAbortSignal(signal, stream): the stream, destroyed with an + * AbortError (cause: signal.reason) when the signal aborts. */ + function addAbortSignal(signal: AbortSignal, stream: T): T; + /* pipeline(...stages, callback): the last stream, or the object-mode + * PassThrough that a function destination feeds. */ + type PipelineResult = T extends [ + ...unknown[], + infer D, + (...args: any[]) => unknown, + ] + ? D extends (...args: any[]) => unknown + ? PassThrough + : D + : unknown; + function pipeline(...streams: T): PipelineResult; + /* util.promisify's pipeline overload matches only pipeline itself: a + * generic signature alone admits every function. */ + namespace pipeline { + const __promisify__: typeof promises.pipeline; + } + /* A compose stage: a stream, an async generator function over the + * previous stage, or (first) an iterable, async iterable or web + * ReadableStream. */ + type ComposeStage = + | Readable + | Writable + | Iterable + | AsyncIterable + | globalThis.ReadableStream + | ((source: any, options?: { signal: AbortSignal }) => unknown); + /* compose(...streams): a Duplex that writes to the first stage and + * reads from the last, with the stages running as a pipeline. */ + function compose(...streams: ComposeStage[]): Duplex; + /* finished's options: readable/writable choose which ends count, + * error: false ignores 'error' events, signal aborts the wait. */ + interface FinishedOptions { + readable?: boolean; + writable?: boolean; + error?: boolean; + signal?: AbortSignal; + } + function finished(stream: unknown, callback: (err?: Error | null) => void): () => void; + function finished( + stream: unknown, + options: FinishedOptions | null, + callback: (err?: Error | null) => void, + ): () => void; + /* Folds to the TARGET platform's default at compile time (win32 16384, + * else 65536 — Node's own state.js split); literal argument only. */ + function getDefaultHighWaterMark(objectMode: boolean): number; + /* The promise pipeline's result: a last stage that is an async + * function resolves the promise with its value (before the options + * argument, when one follows). */ + type PipelinePromise = T extends [ + ...unknown[], + (...args: any[]) => Promise, + ] + ? Promise

    + : T extends [ + ...unknown[], + (...args: any[]) => Promise, + { signal?: AbortSignal; end?: boolean }, + ] + ? Promise

    + : Promise; + namespace promises { + function pipeline(...streams: T): PipelinePromise; + function finished( + stream: unknown, + options?: FinishedOptions & { cleanup?: boolean }, + ): Promise; + } + class Stream extends EventEmitter {} + + export { + Readable, + Writable, + Duplex, + Transform, + PassThrough, + Stream, + addAbortSignal, + pipeline, + compose, + finished, + getDefaultHighWaterMark, + promises, + }; + export type { + ReadableOptions, + WritableOptions, + DuplexOptions, + TransformOptions, + FinishedOptions, + PipelinePromise, + }; +} + +declare module "node:stream" { + import stream = require("stream"); + export = stream; +} diff --git a/packages/compiler/ambient/node-fallback/string_decoder.d.ts b/packages/compiler/ambient/node-fallback/string_decoder.d.ts new file mode 100644 index 000000000..617a7871c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/string_decoder.d.ts @@ -0,0 +1,20 @@ +/* node:string_decoder — StringDecoder over Node's whole encoding set: + * write() decodes complete sequences and buffers a trailing partial one + * across chunk boundaries (utf8's truncated sequence, base64's mod-3 + * remainder, utf16le's odd byte / trailing lead surrogate; latin1, ascii + * and hex are stateless); end() flushes the buffered partial. `encoding` + * answers the normalized name. Node's exact algorithms (SEMANTICS.md); + * the encoding must be a literal, and end(buffer) fences — write the + * buffer, then end(). */ +declare module "string_decoder" { + export class StringDecoder { + constructor(encoding?: BufferEncoding); + readonly encoding: string; + write(buffer: Uint8Array): string; + end(buffer?: Uint8Array): string; + } +} + +declare module "node:string_decoder" { + export * from "string_decoder"; +} diff --git a/packages/compiler/ambient/node-fallback/test.d.ts b/packages/compiler/ambient/node-fallback/test.d.ts new file mode 100644 index 000000000..2ca1c0fe7 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/test.d.ts @@ -0,0 +1,236 @@ +/// + +/* node:assert — the static assertion surface. The module object IS a + * callable function (`assert(x)` is `assert.ok(x)`), so the declaration + * is the callable-namespace `export =` shape and BOTH `import assert + * from "node:assert"` and `const assert = require("assert")` bind the + * callable; named imports take the members. Failures throw + * AssertionError (name "AssertionError", code "ERR_ASSERTION" — catch and + * read .name/.message/.code). strictEqual/deepStrictEqual compare with + * Object.is over scalars; deepStrictEqual compares composites + * structurally per their static types. The messages here are plain + * strings (Node accepts Errors — that form fences per site), and the + * shallow legacy pair (equal/notEqual) follows Node's == coercions for + * primitive and primitive-union operands, including Node v24's NaN + * exception. deepEqual/notDeepEqual remain declared-but-fenced; + * assert/strict's loose names alias the strict forms and lower. */ +/* node:test — the in-process test runner (prefix-only in Node too: + * require("test") is MODULE_NOT_FOUND). test/it register tests (sync and + * async bodies, skip/todo/only options and method twins), describe/suite + * group them (bodies run at registration, Node's collection phase), + * before/after/beforeEach/afterEach hook the enclosing suite, and the + * TestContext argument carries subtests (t.test), t.skip/t.todo, + * t.diagnostic, and t.assert. mock/run/snapshot are DECLARED so real + * suites typecheck but fence at their use sites (no lowering). */ +declare module "node:test" { + type TestFn = (t: TestContext) => void | Promise; + type SuiteFn = (s: SuiteContext) => void; + type HookFn = (c: TestContext | SuiteContext) => void | Promise; + /* The describe/suite callback's context, and the before/after hook + * context of a suite. */ + interface SuiteContext { + readonly name: string; + readonly fullName: string; + readonly filePath: string | undefined; + readonly attempt: number; + readonly passed: boolean; + diagnostic(message: string): void; + } + interface TestOptions { + skip?: boolean | string; + todo?: boolean | string; + only?: boolean; + concurrency?: number | boolean; + timeout?: number; + plan?: number; + } + interface TestContext { + readonly name: string; + readonly fullName: string; + readonly filePath: string | undefined; + readonly attempt: number; + readonly passed: boolean; + readonly workerId: number | undefined; + readonly tags: readonly string[]; + readonly assert: TestContextAssert; + readonly mock: MockTracker; + test(name?: string, fn?: TestFn): Promise; + test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + test(options?: TestOptions, fn?: TestFn): Promise; + test(fn?: TestFn): Promise; + skip(message?: string): void; + todo(message?: string): void; + diagnostic(message: string): void; + plan(count: number): void; + after(fn: HookFn): void; + before(fn: HookFn): void; + beforeEach(fn: HookFn): void; + afterEach(fn: HookFn): void; + } + interface AssertSnapshotOptions { + readonly serializers?: ReadonlyArray<(value: any) => any> | undefined; + } + interface TestContextAssert { + snapshot(value: any, options?: AssertSnapshotOptions): void; + fileSnapshot(value: any, path: string, options?: AssertSnapshotOptions): void; + ok(value: unknown, message?: string): void; + strictEqual(actual: unknown, expected: unknown, message?: string): void; + notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + equal(actual: unknown, expected: unknown, message?: string): void; + notEqual(actual: unknown, expected: unknown, message?: string): void; + deepEqual(actual: unknown, expected: unknown, message?: string): void; + notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + fail(message?: string): never; + throws(fn: () => unknown, expected?: unknown, message?: string): void; + match(value: string, regExp: RegExp, message?: string): void; + doesNotMatch(value: string, regExp: RegExp, message?: string): void; + } + /* The name, options and function are each optional, as in Node: a test + * without a name takes the function's name, or "". */ + function test(name?: string, fn?: TestFn): Promise; + function test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function test(options?: TestOptions, fn?: TestFn): Promise; + function test(fn?: TestFn): Promise; + namespace test { + function skip(name?: string, fn?: TestFn): Promise; + function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function skip(options?: TestOptions, fn?: TestFn): Promise; + function skip(fn?: TestFn): Promise; + function todo(name?: string, fn?: TestFn): Promise; + function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function todo(options?: TestOptions, fn?: TestFn): Promise; + function todo(fn?: TestFn): Promise; + function only(name?: string, fn?: TestFn): Promise; + function only(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function only(options?: TestOptions, fn?: TestFn): Promise; + function only(fn?: TestFn): Promise; + } + const it: typeof test; + function describe(name?: string, fn?: SuiteFn): void; + function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function describe(options?: TestOptions, fn?: SuiteFn): void; + function describe(fn?: SuiteFn): void; + namespace describe { + function skip(name?: string, fn?: SuiteFn): void; + function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function skip(options?: TestOptions, fn?: SuiteFn): void; + function skip(fn?: SuiteFn): void; + function todo(name?: string, fn?: SuiteFn): void; + function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function todo(options?: TestOptions, fn?: SuiteFn): void; + function todo(fn?: SuiteFn): void; + function only(name?: string, fn?: SuiteFn): void; + function only(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function only(options?: TestOptions, fn?: SuiteFn): void; + function only(fn?: SuiteFn): void; + } + const suite: typeof describe; + function before(fn: HookFn): void; + function after(fn: HookFn): void; + function beforeEach(fn: HookFn): void; + function afterEach(fn: HookFn): void; + /* The mock function surface: mock.fn, mock.method and their t.mock + * twins, the tracker's restoreAll and reset, and the calls log and the + * implementation controls on fn.mock. */ + interface MockFunctionCall< + F extends Function, + ReturnType = F extends (...args: any) => infer T ? T : unknown, + Args = F extends (...args: infer Y) => any ? Y : unknown[], + > { + arguments: Args; + error: unknown | undefined; + result: ReturnType | undefined; + this: unknown; + } + interface MockFunctionContext { + readonly calls: MockFunctionCall[]; + callCount(): number; + mockImplementation(implementation: F): void; + mockImplementationOnce(implementation: F, onCall?: number): void; + resetCalls(): void; + restore(): void; + } + type Mock = F & { mock: MockFunctionContext }; + interface MockFunctionOptions { + times?: number; + } + type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never; + }[keyof T]; + interface MockTracker { + fn undefined>(original?: F): Mock; + fn undefined, Implementation extends Function = F>( + original?: F, + implementation?: Implementation, + ): Mock; + method>( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function ? Mock : never; + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation: Implementation, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + reset(): void; + restoreAll(): void; + readonly timers: MockTimers; + } + interface MockTimersOptions { + apis: ReadonlyArray<"setInterval" | "setTimeout" | "setImmediate" | "Date">; + now?: number | Date | undefined; + } + interface MockTimers { + enable(options?: MockTimersOptions): void; + setTime(milliseconds?: number): void; + reset(): void; + tick(milliseconds?: number): void; + runAll(): void; + [Symbol.dispose](): void; + } + const mock: MockTracker; + /* The context of the test, suite or hook running in the current async + * context; undefined outside one. */ + function getTestContext(): TestContext | SuiteContext | undefined; + /* Declared for typechecking only — every use fences (no lowering). + * `assert` is the module-level custom-assertion registry (v24's + * `const { assert } = require('node:test')`), not TestContext.assert. */ + const assert: { register(name: string, fn: (...args: unknown[]) => unknown): void }; + function run(options?: unknown): unknown; + function snapshot(...args: unknown[]): unknown; + export { + test, + it, + describe, + suite, + before, + after, + beforeEach, + afterEach, + mock, + assert, + getTestContext, + run, + snapshot, + TestContext, + SuiteContext, + TestOptions, + Mock, + MockFunctionContext, + MockFunctionCall, + MockTracker, + MockTimers, + MockTimersOptions, + }; + export default test; +} diff --git a/packages/compiler/ambient/node-fallback/timers-promises.d.ts b/packages/compiler/ambient/node-fallback/timers-promises.d.ts new file mode 100644 index 000000000..619b92f79 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/timers-promises.d.ts @@ -0,0 +1,31 @@ +/* node:timers/promises — the promisified pair the Node test harness leans + * on (`await setTimeout(ms)` as a sleep). setTimeout and setImmediate + * settle with their resolve value (undefined without one) from the shared + * timer heap and the immediate queue; setInterval(delay, value) lowers to a + * typed async iterator. The options object takes `ref`; AbortSignal + * options remain declared surface that fences per site. */ +declare module "node:timers/promises" { + export interface TimerOptions { + ref?: boolean; + signal?: AbortSignal; + } + export function setTimeout( + delay?: number, + value?: T, + options?: TimerOptions, + ): Promise; + export function setImmediate(value?: T, options?: TimerOptions): Promise; + export function setInterval( + delay?: number, + value?: T, + options?: unknown, + ): AsyncGenerator; + export const scheduler: { + wait(delay?: number, options?: TimerOptions): Promise; + yield(): Promise; + }; +} + +declare module "timers/promises" { + export * from "node:timers/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/timers.d.ts b/packages/compiler/ambient/node-fallback/timers.d.ts new file mode 100644 index 000000000..f6a1d3946 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/timers.d.ts @@ -0,0 +1,28 @@ +/* node:timers — the module twins of the timer globals (Node's timers + * module re-exports them: require('timers').setTimeout IS setTimeout). + * Named imports, destructured requires, and namespace calls all lower to + * the same libCalls as the globals; the surface beyond these six + * (enroll/unenroll, promises) fences per member. */ +declare module "node:timers" { + export function setTimeout(callback: () => void, ms?: number): Timeout; + export function setTimeout( + callback: (...args: never[]) => void, + ms?: number, + ...args: unknown[] + ): Timeout; + export function clearTimeout(handle?: Timeout | number | null | undefined): void; + export function setInterval(callback: () => void, ms?: number): Timeout; + export function setInterval( + callback: (...args: never[]) => void, + ms?: number, + ...args: unknown[] + ): Timeout; + export function clearInterval(handle?: Timeout | number | null | undefined): void; + export function setImmediate(callback: () => void): Immediate; + export function setImmediate(callback: (...args: never[]) => void, ...args: unknown[]): Immediate; + export function clearImmediate(handle?: Immediate | null | undefined): void; +} + +declare module "timers" { + export * from "node:timers"; +} diff --git a/packages/compiler/ambient/node-fallback/tls.d.ts b/packages/compiler/ambient/node-fallback/tls.d.ts new file mode 100644 index 000000000..6ddc08b35 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/tls.d.ts @@ -0,0 +1,169 @@ +/// +/// + +/* node:tls — the TLS server slice (scr_tls.c over scr_net.c with the + * vendored mbedTLS; linked only into using binaries). + * createServer({ cert, key }, handler) returns a net.Server; the handler + * is Node's 'secureConnection' — it fires post-handshake with a socket + * that behaves exactly like a net socket (write/end/destroy/pipe and the + * data/end/close/error events are the same lowered surface). cert/key + * are PEM strings or Buffers. Handshake failures tear the socket down + * without emitting an event, Node's default for 'tlsClientError' with no listener. */ +declare module "tls" { + import { Server as NetServer, Socket } from "net"; + /** tls.Server: a net server whose connections handshake first. */ + export interface Server extends NetServer { + /* serve `context` to clients whose SNI name matches `hostname` ('*' + * matches within one label); the newest match wins */ + addContext(hostname: string, context: TlsOptions | SecureContext): void; + /* new connections serve a context made from `options` */ + setSecureContext(options: TlsOptions): void; + } + export interface TlsOptions { + /* Optional in the DECLARATION only (real Node programs build these + * literals with every TLS knob): the lowering still requires both — + * a literal without them fences at the call site. */ + cert?: string | Uint8Array; + key?: string | Uint8Array; + /* The options-record stance (see http.RequestOptions): every key + * typechecks; the option walk fences documented-but-unlowered keys + * by name and drops undocumented ones like Node. */ + [option: string]: unknown; + } + export function createServer( + options: TlsOptions, + secureConnectionListener?: (socket: TLSSocket) => void, + ): Server; + /** TLSSocket — post-handshake it IS a net socket to every lowered + * member (the mapping collapses it onto the socket kind; the + * secureConnect/session events ride the shared socket declarations). + * The TLS extras: authorized/authorizationError answer Node's verify + * split — authorizationError is the verify-failure CODE STRING + * (DEPTH_ZERO_SELF_SIGNED_CERT, ...), or null. */ + export interface TLSSocket extends Socket { + readonly encrypted: true; + authorized: boolean; + authorizationError: string | null; + /* null before the handshake, then the negotiated protocol or false */ + readonly alpnProtocol: string | false | null; + /* "TLSv1.2" or "TLSv1.3"; null before the handshake and after close */ + getProtocol(): string | null; + /* OpenSSL's suite name, the IANA name and the suite's version */ + getCipher(): CipherNameAndProtocol; + /* whether the handshake resumed a cached session */ + isSessionReused(): boolean; + /* the peer's certificate: {} before the handshake or without one, + * null once the socket is destroyed; detailed adds issuerCertificate */ + getPeerCertificate(detailed: true): DetailedPeerCertificate; + getPeerCertificate(detailed?: false): PeerCertificate; + getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate; + /* the certificate this socket serves, the same way */ + getCertificate(): PeerCertificate | object | null; + /* a new X509Certificate on each call, or undefined without one */ + getPeerX509Certificate(): import("crypto").X509Certificate | undefined; + getX509Certificate(): import("crypto").X509Certificate | undefined; + } + /* A certificate as getPeerCertificate() and X509Certificate's + * toLegacyObject() answer it: subject and issuer map attribute short + * names to values, infoAccess its " - " keys to values. */ + export interface PeerCertificate { + subject: { [attribute: string]: string | string[] }; + issuer: { [attribute: string]: string | string[] }; + subjectaltname?: string; + infoAccess?: { [key: string]: string[] }; + ca: boolean; + modulus?: string; + exponent?: string; + pubkey?: Buffer; + bits?: number; + valid_from: string; + valid_to: string; + fingerprint: string; + fingerprint256: string; + fingerprint512: string; + ext_key_usage?: string[]; + serialNumber: string; + raw: Buffer; + asn1Curve?: string; + nistCurve?: string; + } + /* getPeerCertificate(true): each certificate links to its issuer; a + * self-issued root is its own issuerCertificate */ + export interface DetailedPeerCertificate extends PeerCertificate { + issuerCertificate: DetailedPeerCertificate; + } + + /** new tls.TLSSocket(socket, options): the server-mode wrap of an open + * socket (a server connection or an http CONNECT socket). The options + * record reads at runtime; isServer, cert, key, secureContext, + * ALPNProtocols, minVersion and maxVersion are implemented. */ + export interface TLSSocketOptions { + isServer?: boolean; + cert?: string | Uint8Array; + key?: string | Uint8Array; + secureContext?: SecureContext; + ALPNProtocols?: string[] | Uint8Array; + minVersion?: string; + maxVersion?: string; + [option: string]: unknown; + } + export const TLSSocket: new (socket: Socket, options: TLSSocketOptions) => TLSSocket; + export interface CipherNameAndProtocol { + name: string; + standardName: string; + version: string; + } + export const DEFAULT_MIN_VERSION: string; + export const DEFAULT_MAX_VERSION: string; + /** tls.connect options — the RUNTIME options record (members read at + * runtime; port/host/socket/rejectUnauthorized/ca/servername are the + * honestly-implemented set, every other documented member throws the + * runtime fence — the options-record stance's divergence-66 shape). */ + export interface ConnectionOptions { + port?: number; + host?: string; + /* an open socket to run the client handshake over */ + socket?: Socket; + rejectUnauthorized?: boolean; + ca?: string | Uint8Array | ReadonlyArray; + servername?: string; + [option: string]: unknown; + } + export function connect( + options: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + export function connect( + port: number, + options?: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + export function connect( + port: number, + host?: string, + options?: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + /** An opaque parsed cert/key pair — what an SNI callback answers with + * (the value is a runtime handle; the members are runtime-internal). */ + export interface SecureContext {} + /* @types/node's name for createSecureContext's options */ + export type SecureContextOptions = TlsOptions; + export function createSecureContext(options: TlsOptions): SecureContext; + /** The CA-store introspection surface (scr_tls_ca.c): per-type cached + * PEM string arrays. The host bundle stands in for Node's compiled-in + * Mozilla roots ('bundled', and rootCertificates) AND the platform + * store ('system') — the Windows certificate stores or POSIX bundle probe; + * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws + * Node's ERR_INVALID_ARG_VALUE TypeError. */ + export function getCACertificates(type?: string): string[]; + export const rootCertificates: readonly string[]; + /** Replaces the 'default' set (deduped) and the trust anchors the TLS + * client verifies against; a certificate-free non-empty array throws + * Node's ERR_CRYPTO_OPERATION_FAILED Error. */ + export function setDefaultCACertificates(certs: readonly string[]): void; +} + +declare module "node:tls" { + export * from "tls"; +} diff --git a/packages/compiler/ambient/node-fallback/url.d.ts b/packages/compiler/ambient/node-fallback/url.d.ts new file mode 100644 index 000000000..c690c52b1 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/url.d.ts @@ -0,0 +1,95 @@ +/// + +/* node:url — the file-URL bridge pair. fileURLToPath accepts a URL value + * or a URL string and throws Node's TypeErrors (non-file scheme, encoded + * slashes, non-empty host); pathToFileURL resolves the path and percent- + * encodes it into a file: URL. The legacy API: parse() answers a Url + * object, format() serializes a Url-shaped object, a string, or a URL, + * resolve() resolves one URL string against another, the domain pair + * converts a hostname to and from its Punycode form, and urlToHttpOptions + * builds http.request options from a URL. */ +declare module "url" { + export type URL = globalThis.URL; + export type URLSearchParams = globalThis.URLSearchParams; + export const URL: typeof globalThis.URL; + export const URLSearchParams: typeof globalThis.URLSearchParams; + export function fileURLToPath(url: string | URL, options?: { windows?: boolean }): string; + export function fileURLToPathBuffer(url: string | URL, options?: { windows?: boolean }): Buffer; + export function pathToFileURL(path: string, options?: { windows?: boolean }): URL; + export interface UrlObject { + auth?: string | null | undefined; + hash?: string | null | undefined; + host?: string | null | undefined; + hostname?: string | null | undefined; + href?: string | null | undefined; + pathname?: string | null | undefined; + protocol?: string | null | undefined; + search?: string | null | undefined; + slashes?: boolean | null | undefined; + port?: string | number | null | undefined; + query?: string | null | import("querystring").ParsedUrlQueryInput | undefined; + } + export interface Url { + auth: string | null; + hash: string | null; + host: string | null; + hostname: string | null; + href: string; + path: string | null; + pathname: string | null; + protocol: string | null; + search: string | null; + slashes: boolean | null; + port: string | null; + query: string | null | import("querystring").ParsedUrlQuery; + } + export interface UrlWithParsedQuery extends Url { + query: import("querystring").ParsedUrlQuery; + } + export interface UrlWithStringQuery extends Url { + query: string | null; + } + export interface URLFormatOptions { + auth?: boolean | undefined; + fragment?: boolean | undefined; + search?: boolean | undefined; + unicode?: boolean | undefined; + } + export function parse( + urlString: string, + parseQueryString?: false, + slashesDenoteHost?: boolean, + ): UrlWithStringQuery; + export function parse( + urlString: string, + parseQueryString: true, + slashesDenoteHost?: boolean, + ): UrlWithParsedQuery; + export function parse( + urlString: string, + parseQueryString: boolean, + slashesDenoteHost?: boolean, + ): Url; + export function format(urlObject: URL, options?: URLFormatOptions): string; + export function format(urlObject: UrlObject | string): string; + export function resolve(from: string, to: string): string; + export function domainToASCII(domain: string): string; + export function domainToUnicode(domain: string): string; + /** urlToHttpOptions's null-prototype request options. */ + export interface UrlHttpOptions { + protocol: string; + hostname: string; + hash: string; + search: string; + pathname: string; + path: string; + href: string; + port?: number; + auth?: string; + } + export function urlToHttpOptions(url: URL): UrlHttpOptions; +} + +declare module "node:url" { + export * from "url"; +} diff --git a/packages/compiler/ambient/node-fallback/util-types.d.ts b/packages/compiler/ambient/node-fallback/util-types.d.ts new file mode 100644 index 000000000..3af1b6f00 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/util-types.d.ts @@ -0,0 +1,50 @@ +/* node:util/types — native runtime brands. Other forms retain their + * per-member lowering boundaries. */ +declare module "util/types" { + export function isAnyArrayBuffer(value: unknown): boolean; + export function isArrayBuffer(value: unknown): value is ArrayBuffer; + export function isArrayBufferView(value: unknown): value is ArrayBufferView; + export function isDataView(value: unknown): value is DataView; + export function isTypedArray(value: unknown): boolean; + export function isUint8Array(value: unknown): value is Uint8Array; + export function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray; + export function isUint16Array(value: unknown): value is Uint16Array; + export function isUint32Array(value: unknown): value is Uint32Array; + export function isInt8Array(value: unknown): value is Int8Array; + export function isInt16Array(value: unknown): value is Int16Array; + export function isInt32Array(value: unknown): value is Int32Array; + export function isFloat16Array(value: unknown): value is Float16Array; + export function isFloat32Array(value: unknown): value is Float32Array; + export function isFloat64Array(value: unknown): value is Float64Array; + export function isMap(value: unknown): value is Map; + export function isSet(value: unknown): value is Set; + export function isWeakMap(value: unknown): value is WeakMap; + export function isWeakSet(value: unknown): value is WeakSet; + export function isRegExp(value: unknown): value is RegExp; + export function isNativeError(value: unknown): value is Error; + export function isPromise(value: unknown): value is Promise; + export function isProxy(value: unknown): boolean; + export function isAsyncFunction(value: unknown): boolean; + export function isGeneratorFunction(value: unknown): boolean; + export function isGeneratorObject(value: unknown): boolean; + export function isModuleNamespaceObject(value: unknown): boolean; + export function isDate(value: unknown): value is Date; + export function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer; + export function isMapIterator(value: unknown): boolean; + export function isSetIterator(value: unknown): boolean; + export function isKeyObject(value: unknown): boolean; + export function isNumberObject(value: unknown): boolean; + export function isStringObject(value: unknown): boolean; + export function isBooleanObject(value: unknown): boolean; + export function isBigIntObject(value: unknown): boolean; + export function isSymbolObject(value: unknown): boolean; + export function isBoxedPrimitive(value: unknown): boolean; + export function isBigInt64Array(value: unknown): value is BigInt64Array; + export function isBigUint64Array(value: unknown): value is BigUint64Array; + export function isCryptoKey(value: unknown): boolean; + export function isExternal(value: unknown): boolean; +} + +declare module "node:util/types" { + export * from "util/types"; +} diff --git a/packages/compiler/ambient/node-fallback/util.d.ts b/packages/compiler/ambient/node-fallback/util.d.ts new file mode 100644 index 000000000..5ff4c38ad --- /dev/null +++ b/packages/compiler/ambient/node-fallback/util.d.ts @@ -0,0 +1,270 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// + +/* node:util — compile-time projections for child_process.execFile, + * child_process.exec and fs.readFile. The first two retain Node's custom + * { stdout, stderr } result; the last resolves the callback's data + * argument. Other targets and bare + * promisify values fence per site. */ +declare module "util" { + export * as types from "util/types"; + export function getSystemErrorName(err: number): string; + export function getSystemErrorMessage(err: number): string; + export function getSystemErrorMap(): Map; + export function stripVTControlCharacters(str: string): string; + export function toUSVString(string: string): string; + export function isDeepStrictEqual(val1: unknown, val2: unknown, skipPrototype?: boolean): boolean; + export function styleText( + format: string | readonly string[], + text: string, + options?: { validateStream?: boolean; stream?: unknown }, + ): string; + export function parseEnv(content: string): { [key: string]: string | undefined }; + export function inherits(constructor: unknown, superConstructor: unknown): void; + export interface DeprecateOptions { + modifyPrototype?: boolean; + } + export function deprecate( + fn: T, + msg: string, + code?: string, + options?: DeprecateOptions, + ): T; + /* The callback receives null on success and the rejection reason + * otherwise (falsy reasons arrive as ERR_FALSY_VALUE_REJECTION). */ + export function callbackify( + fn: () => Promise, + ): (callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: () => Promise, + ): (callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): ( + arg1: T1, + arg2: T2, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export type DebugLoggerFunction = (msg: string, ...param: unknown[]) => void; + export interface DebugLogger extends DebugLoggerFunction { + enabled: boolean; + } + export function debuglog( + section: string, + callback?: (fn: DebugLoggerFunction) => void, + ): DebugLogger; + export { debuglog as debug }; + export type TextEncoder = globalThis.TextEncoder; + export type TextDecoder = globalThis.TextDecoder; + export var TextEncoder: typeof globalThis.TextEncoder; + export var TextDecoder: typeof globalThis.TextDecoder; + /* stream.pipeline's overload comes first: its __promisify__ member keeps + * every other target out of it. */ + export function promisify( + fn: typeof import("node:stream").pipeline, + ): typeof import("node:stream/promises").pipeline; + export function promisify( + fn: typeof import("node:fs").readFile, + ): (path: string, encoding: "utf8" | "utf-8") => Promise; + export function promisify( + fn: (file: string, args?: string[] | null, options?: object) => unknown, + ): ( + file: string, + args?: string[], + options?: { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + }, + ) => Promise<{ stdout: string; stderr: string }>; + export function promisify( + fn: typeof globalThis.setTimeout, + ): typeof import("node:timers/promises").setTimeout; + export function promisify( + fn: typeof globalThis.setImmediate, + ): typeof import("node:timers/promises").setImmediate; + export function promisify(fn: typeof import("node:child_process").exec): ( + command: string, + options?: { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + }, + ) => Promise<{ stdout: string; stderr: string }>; + /* The zlib callback one-shots (deflate, inflate, deflateRaw, inflateRaw, + * gzip, gunzip, unzip) share one type. */ + export function promisify( + fn: typeof import("node:zlib").gzip, + ): (data: string | Uint8Array, options?: import("node:zlib").ZlibOptions) => Promise; + export function promisify( + fn: typeof import("node:stream").finished, + ): typeof import("node:stream/promises").finished; + /* The crypto callback functions: the promise settles with the callback's + * value. */ + export function promisify( + fn: typeof import("node:crypto").randomBytes, + ): (size: number) => Promise; + export function promisify( + fn: typeof import("node:crypto").pbkdf2, + ): ( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + ) => Promise; + export function promisify( + fn: typeof import("node:crypto").scrypt, + ): ( + password: import("node:crypto").BinaryLike, + salt: import("node:crypto").BinaryLike, + keylen: number, + options?: import("node:crypto").ScryptOptions, + ) => Promise; + export function promisify( + fn: typeof import("node:crypto").hkdf, + ): ( + digest: string, + ikm: import("node:crypto").BinaryLike | import("node:crypto").KeyObject, + salt: import("node:crypto").BinaryLike, + info: import("node:crypto").BinaryLike, + keylen: number, + ) => Promise; + /* A callback-only function (`obj.close.bind(obj)`): before the targets + * whose leading parameters would otherwise capture it. Builtins that take + * arguments before their callback never match these. */ + export function promisify( + fn: (callback: (err: any, result: TResult) => void) => void, + ): () => Promise; + export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; + export function promisify( + fn: typeof import("node:crypto").randomFill, + ): ( + buffer: T, + offset?: number, + size?: number, + ) => Promise; + export function promisify(fn: typeof import("node:crypto").randomInt): { + (max: number): Promise; + (min: number, max: number): Promise; + }; + export function promisify( + fn: typeof import("node:crypto").generateKey, + ): ( + type: "hmac" | "aes", + options: { length: number }, + ) => Promise; + /* dns.lookup's promise settles with { address, family } (the address + * array with options.all), and lookupService's with { hostname, service }. */ + export function promisify(fn: typeof import("node:dns").lookup): { + ( + hostname: string, + options: import("node:dns").LookupAllOptions, + ): Promise; + ( + hostname: string, + options?: number | import("node:dns").LookupOneOptions, + ): Promise; + }; + export function promisify( + fn: typeof import("node:dns").lookupService, + ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; + /* Declared surface without full lowering (the type-level tolerance + * story: guarded/diagnostic-path code must TYPECHECK; a reached use + * without a lowering fences at its site). inspect's options carry the + * documented knobs through an index signature. */ + export function inspect( + object: unknown, + options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + ): string; + export function format(format?: unknown, ...args: unknown[]): string; + export function formatWithOptions( + inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + format?: unknown, + ...args: unknown[] + ): string; + export type ParseArgsOptionsType = "boolean" | "string"; + export interface ParseArgsOptionDescriptor { + type: ParseArgsOptionsType; + multiple?: boolean; + short?: string; + default?: string | boolean | string[] | boolean[]; + } + export interface ParseArgsOptionsConfig { + [longOption: string]: ParseArgsOptionDescriptor; + } + export interface ParseArgsConfig { + args?: readonly string[]; + options?: ParseArgsOptionsConfig; + strict?: boolean; + allowPositionals?: boolean; + allowNegative?: boolean; + tokens?: boolean; + } + export type ParseArgsToken = + | { + kind: "option"; + index: number; + name: string; + rawName: string; + value: string | undefined; + inlineValue: boolean | undefined; + } + | { kind: "positional"; index: number; value: string } + | { kind: "option-terminator"; index: number }; + export interface ParseArgsResult { + values: { [longOption: string]: undefined | string | boolean | Array }; + positionals: string[]; + tokens?: ParseArgsToken[]; + } + export function parseArgs(config?: ParseArgsConfig): ParseArgsResult; + /* util.getCallSites (Node ≥22.9): the captured-frame slice harness + * code reads. No stack bookkeeping exists in a compiled binary — every + * reached call fences per site. */ + export interface CallSite { + readonly functionName: string; + readonly scriptName: string; + readonly lineNumber: number; + readonly column: number; + } + export function getCallSites(frameCount?: number): CallSite[]; +} + +declare module "node:util" { + export * from "util"; +} diff --git a/packages/compiler/ambient/node-fallback/v8.d.ts b/packages/compiler/ambient/node-fallback/v8.d.ts new file mode 100644 index 000000000..16b112b1c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/v8.d.ts @@ -0,0 +1,43 @@ +/* node:v8 — importable for the packages that require it at top level. + * getHeapStatistics answers allocator-derived figures in Node's shape + * (approximate: the runtime heap's total and used bytes, the machine's + * memory as the limit; V8-only figures are 0). The other members observe + * V8 itself and fence at their use with the by-design reason; they are + * declared so programs that type-check under their own tsc reach that + * fence. */ +declare module "v8" { + type DoesZapCodeSpaceFlag = 0 | 1; + export interface HeapInfo { + total_heap_size: number; + total_heap_size_executable: number; + total_physical_size: number; + total_available_size: number; + used_heap_size: number; + heap_size_limit: number; + malloced_memory: number; + peak_malloced_memory: number; + does_zap_garbage: DoesZapCodeSpaceFlag; + number_of_native_contexts: number; + number_of_detached_contexts: number; + total_global_handles_size: number; + used_global_handles_size: number; + external_memory: number; + } + export function getHeapStatistics(): HeapInfo; + export function cachedDataVersionTag(): number; + export function getHeapSpaceStatistics(): unknown[]; + export function getHeapCodeStatistics(): unknown; + export function getHeapSnapshot(options?: unknown): unknown; + export function writeHeapSnapshot(fileName?: string, options?: unknown): string; + export function setFlagsFromString(flags: string): void; + export function setHeapSnapshotNearHeapLimit(limit: number): void; + export function serialize(value: unknown): Buffer; + export function deserialize(buffer: Uint8Array): unknown; + export function takeCoverage(): void; + export function stopCoverage(): void; + export function isStringOneByteRepresentation(content: string): boolean; +} + +declare module "node:v8" { + export * from "v8"; +} diff --git a/packages/compiler/ambient/node-fallback/worker_threads.d.ts b/packages/compiler/ambient/node-fallback/worker_threads.d.ts new file mode 100644 index 000000000..81873f115 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/worker_threads.d.ts @@ -0,0 +1,113 @@ +/// + +/* node:worker_threads — native workers use statically compiled entry points. + * Lowering and runtime diagnostics define the supported options and methods. */ +declare module "worker_threads" { + export const isMainThread: boolean; + export const isInternalThread: boolean; + export const threadId: number; + export interface ResourceLimits { + maxYoungGenerationSizeMb?: number; + maxOldGenerationSizeMb?: number; + codeRangeSizeMb?: number; + stackSizeMb?: number; + } + /* {} in the main thread */ + export const resourceLimits: ResourceLimits; + /* the Worker's name option, trimmed ("WorkerThread" by default); "" in the main thread */ + export const threadName: string | null; + export interface WorkerOptions { + workerData?: unknown; + transferList?: readonly unknown[]; + argv?: readonly unknown[]; + name?: string; + resourceLimits?: ResourceLimits; + stdout?: boolean; + stderr?: boolean; + stdin?: boolean; + [option: string]: unknown; + } + export class Worker { + constructor(filename: string | URL, options?: WorkerOptions); + readonly threadId: number; + readonly resourceLimits?: ResourceLimits; + readonly threadName: string | null; + /* with the stdout/stderr options: the worker's writes to those streams */ + readonly stdout: import("stream").Readable; + readonly stderr: import("stream").Readable; + /* with the stdin option: what the worker reads from process.stdin */ + readonly stdin: import("stream").Writable | null; + /* terminate(), resolving with undefined once the worker exited */ + [Symbol.asyncDispose](): Promise; + on(event: "online", listener: () => void): this; + on(event: "message", listener: (value: any) => void): this; + on(event: "error", listener: (error: any) => void): this; + on(event: "exit", listener: (code: number) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + postMessage(value: unknown, transferList?: readonly unknown[]): void; + ref(): void; + unref(): void; + terminate(): Promise; + removeAllListeners(event?: string): this; + listenerCount(event: string, listener?: (...args: any[]) => void): number; + } + export class MessagePort { + on(event: "message", listener: (value: any) => void): this; + on(event: "close", listener: () => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + postMessage(value: unknown, transferList?: readonly unknown[]): void; + start(): void; + close(callback?: () => void): void; + ref(): void; + unref(): void; + hasRef(): boolean; + removeAllListeners(event?: string): this; + listenerCount(event: string, listener?: (...args: any[]) => void): number; + } + /* Two entangled ports; a port moves to another thread through a + * postMessage or workerData transferList. */ + export class MessageChannel { + readonly port1: MessagePort; + readonly port2: MessagePort; + } + export function receiveMessageOnPort(port: MessagePort): { message: any } | undefined; + /* This thread's environment data; each new Worker starts from a clone. */ + export function setEnvironmentData(key: any, value?: any): void; + export function getEnvironmentData(key: any): any; + /* A marked object cannot be listed in a transfer list. */ + export function markAsUntransferable(object: any): void; + /* structuredClone and postMessage throw DataCloneError for a marked + * object, error or class instance */ + export function markAsUncloneable(object: any): void; + export function isMarkedAsUntransferable(object: any): boolean; + /* Delivers each message to every other open channel with the name, in + * any thread, as a 'message' MessageEvent. */ + export class BroadcastChannel extends EventTarget { + constructor(name: string); + readonly name: string; + onmessage: ((event: MessageEvent) => void) | null; + onmessageerror: ((event: MessageEvent) => void) | null; + postMessage(message: unknown): void; + close(): void; + ref(): this; + unref(): this; + } + export const parentPort: MessagePort | null; + export const workerData: any; +} + +declare module "node:worker_threads" { + export * from "worker_threads"; +} diff --git a/packages/compiler/ambient/node-fallback/zlib.d.ts b/packages/compiler/ambient/node-fallback/zlib.d.ts new file mode 100644 index 000000000..c601596fe --- /dev/null +++ b/packages/compiler/ambient/node-fallback/zlib.d.ts @@ -0,0 +1,422 @@ +/// + +/* node:zlib — the one-shot zlib/raw/gzip codecs lower for string/Buffer + * inputs with Node's default options; libz links only into zlib-using + * binaries. Callback forms use the executable worker pool. Options stay + * declared so reached uses receive a named SC2020 refusal. The Deflate, + * Inflate, Gzip, Gunzip, DeflateRaw, InflateRaw, Unzip, BrotliCompress + * and BrotliDecompress streams are runtime Transform streams; Zstd remains + * outside the lowered surface. */ +declare module "zlib" { + import { Transform } from "stream"; + export interface ZlibOptions { + [option: string]: unknown; + } + export type ZlibCallback = (error: NodeJS.ErrnoException | null, result: Buffer) => void; + export function deflate(data: string | Uint8Array, callback: ZlibCallback): void; + export function deflate( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function deflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function inflate(data: string | Uint8Array, callback: ZlibCallback): void; + export function inflate( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function inflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function deflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; + export function deflateRaw( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function deflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function inflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; + export function inflateRaw( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function inflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function gzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function gzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function gzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function gunzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function gunzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function gunzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function unzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function unzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function unzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function crc32(data: string | Uint8Array, value?: number): number; + /* Brotli over the vendored brotli 1.2.0 (scr_brotli.c): `params` keys + * are the numeric BROTLI_PARAM_* and BROTLI_DECODER_PARAM_* constants. */ + export interface BrotliOptions { + [option: string]: unknown; + } + export function brotliCompress(data: string | Uint8Array, callback: ZlibCallback): void; + export function brotliCompress( + data: string | Uint8Array, + options: BrotliOptions, + callback: ZlibCallback, + ): void; + export function brotliCompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; + export function brotliDecompress(data: string | Uint8Array, callback: ZlibCallback): void; + export function brotliDecompress( + data: string | Uint8Array, + options: BrotliOptions, + callback: ZlibCallback, + ): void; + export function brotliDecompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; + /* The stream classes: runtime Transform streams over the zlib engine + * (scr_zlib_stream.c). createGzip(options) and new Gzip(options) build + * the same stream; options are validated at runtime with Node's rules. + * Brotli streams have no params(), as in Node. */ + class ZlibBase extends Transform { + readonly bytesWritten: number; + flush(kind?: number, callback?: () => void): void; + flush(callback: () => void): void; + close(callback?: () => void): void; + reset(): void; + } + class Zlib extends ZlibBase { + params(level: number, strategy: number, callback?: () => void): void; + } + export class BrotliCompress extends ZlibBase { + constructor(options?: BrotliOptions); + } + export class BrotliDecompress extends ZlibBase { + constructor(options?: BrotliOptions); + } + export function createBrotliCompress(options?: BrotliOptions): BrotliCompress; + export function createBrotliDecompress(options?: BrotliOptions): BrotliDecompress; + export class Deflate extends Zlib { + constructor(options?: ZlibOptions); + } + export class Inflate extends Zlib { + constructor(options?: ZlibOptions); + } + export class Gzip extends Zlib { + constructor(options?: ZlibOptions); + } + export class Gunzip extends Zlib { + constructor(options?: ZlibOptions); + } + export class DeflateRaw extends Zlib { + constructor(options?: ZlibOptions); + } + export class InflateRaw extends Zlib { + constructor(options?: ZlibOptions); + } + export class Unzip extends Zlib { + constructor(options?: ZlibOptions); + } + export function createDeflate(options?: ZlibOptions): Deflate; + export function createInflate(options?: ZlibOptions): Inflate; + export function createGzip(options?: ZlibOptions): Gzip; + export function createGunzip(options?: ZlibOptions): Gunzip; + export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; + export function createInflateRaw(options?: ZlibOptions): InflateRaw; + export function createUnzip(options?: ZlibOptions): Unzip; + /* Node v24 zlib.constants, literal-typed (the CryptoConstants stance): + * every access bakes as its literal; the object never materializes. The + * members outside BROTLI_* are also module properties (zlib.Z_SYNC_FLUSH). */ + export interface ZlibConstants { + readonly Z_NO_FLUSH: 0; + readonly Z_PARTIAL_FLUSH: 1; + readonly Z_SYNC_FLUSH: 2; + readonly Z_FULL_FLUSH: 3; + readonly Z_FINISH: 4; + readonly Z_BLOCK: 5; + readonly Z_OK: 0; + readonly Z_STREAM_END: 1; + readonly Z_NEED_DICT: 2; + readonly Z_ERRNO: -1; + readonly Z_STREAM_ERROR: -2; + readonly Z_DATA_ERROR: -3; + readonly Z_MEM_ERROR: -4; + readonly Z_BUF_ERROR: -5; + readonly Z_VERSION_ERROR: -6; + readonly Z_NO_COMPRESSION: 0; + readonly Z_BEST_SPEED: 1; + readonly Z_BEST_COMPRESSION: 9; + readonly Z_DEFAULT_COMPRESSION: -1; + readonly Z_FILTERED: 1; + readonly Z_HUFFMAN_ONLY: 2; + readonly Z_RLE: 3; + readonly Z_FIXED: 4; + readonly Z_DEFAULT_STRATEGY: 0; + readonly ZLIB_VERNUM: 4897; + readonly DEFLATE: 1; + readonly INFLATE: 2; + readonly GZIP: 3; + readonly GUNZIP: 4; + readonly DEFLATERAW: 5; + readonly INFLATERAW: 6; + readonly UNZIP: 7; + readonly BROTLI_DECODE: 8; + readonly BROTLI_ENCODE: 9; + readonly ZSTD_DECOMPRESS: 11; + readonly ZSTD_COMPRESS: 10; + readonly Z_MIN_WINDOWBITS: 8; + readonly Z_MAX_WINDOWBITS: 15; + readonly Z_DEFAULT_WINDOWBITS: 15; + readonly Z_MIN_CHUNK: 64; + readonly Z_MAX_CHUNK: number; + readonly Z_DEFAULT_CHUNK: 16384; + readonly Z_MIN_MEMLEVEL: 1; + readonly Z_MAX_MEMLEVEL: 9; + readonly Z_DEFAULT_MEMLEVEL: 8; + readonly Z_MIN_LEVEL: -1; + readonly Z_MAX_LEVEL: 9; + readonly Z_DEFAULT_LEVEL: -1; + readonly BROTLI_OPERATION_PROCESS: 0; + readonly BROTLI_OPERATION_FLUSH: 1; + readonly BROTLI_OPERATION_FINISH: 2; + readonly BROTLI_OPERATION_EMIT_METADATA: 3; + readonly BROTLI_PARAM_MODE: 0; + readonly BROTLI_MODE_GENERIC: 0; + readonly BROTLI_MODE_TEXT: 1; + readonly BROTLI_MODE_FONT: 2; + readonly BROTLI_DEFAULT_MODE: 0; + readonly BROTLI_PARAM_QUALITY: 1; + readonly BROTLI_MIN_QUALITY: 0; + readonly BROTLI_MAX_QUALITY: 11; + readonly BROTLI_DEFAULT_QUALITY: 11; + readonly BROTLI_PARAM_LGWIN: 2; + readonly BROTLI_MIN_WINDOW_BITS: 10; + readonly BROTLI_MAX_WINDOW_BITS: 24; + readonly BROTLI_LARGE_MAX_WINDOW_BITS: 30; + readonly BROTLI_DEFAULT_WINDOW: 22; + readonly BROTLI_PARAM_LGBLOCK: 3; + readonly BROTLI_MIN_INPUT_BLOCK_BITS: 16; + readonly BROTLI_MAX_INPUT_BLOCK_BITS: 24; + readonly BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4; + readonly BROTLI_PARAM_SIZE_HINT: 5; + readonly BROTLI_PARAM_LARGE_WINDOW: 6; + readonly BROTLI_PARAM_NPOSTFIX: 7; + readonly BROTLI_PARAM_NDIRECT: 8; + readonly BROTLI_DECODER_RESULT_ERROR: 0; + readonly BROTLI_DECODER_RESULT_SUCCESS: 1; + readonly BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2; + readonly BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3; + readonly BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0; + readonly BROTLI_DECODER_PARAM_LARGE_WINDOW: 1; + readonly BROTLI_DECODER_NO_ERROR: 0; + readonly BROTLI_DECODER_SUCCESS: 1; + readonly BROTLI_DECODER_NEEDS_MORE_INPUT: 2; + readonly BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3; + readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1; + readonly BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2; + readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3; + readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4; + readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5; + readonly BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6; + readonly BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7; + readonly BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8; + readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9; + readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10; + readonly BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11; + readonly BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12; + readonly BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13; + readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14; + readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15; + readonly BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16; + readonly BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19; + readonly BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20; + readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21; + readonly BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22; + readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25; + readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26; + readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27; + readonly BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30; + readonly BROTLI_DECODER_ERROR_UNREACHABLE: -31; + readonly ZSTD_e_continue: 0; + readonly ZSTD_e_flush: 1; + readonly ZSTD_e_end: 2; + readonly ZSTD_fast: 1; + readonly ZSTD_dfast: 2; + readonly ZSTD_greedy: 3; + readonly ZSTD_lazy: 4; + readonly ZSTD_lazy2: 5; + readonly ZSTD_btlazy2: 6; + readonly ZSTD_btopt: 7; + readonly ZSTD_btultra: 8; + readonly ZSTD_btultra2: 9; + readonly ZSTD_c_compressionLevel: 100; + readonly ZSTD_c_windowLog: 101; + readonly ZSTD_c_hashLog: 102; + readonly ZSTD_c_chainLog: 103; + readonly ZSTD_c_searchLog: 104; + readonly ZSTD_c_minMatch: 105; + readonly ZSTD_c_targetLength: 106; + readonly ZSTD_c_strategy: 107; + readonly ZSTD_c_enableLongDistanceMatching: 160; + readonly ZSTD_c_ldmHashLog: 161; + readonly ZSTD_c_ldmMinMatch: 162; + readonly ZSTD_c_ldmBucketSizeLog: 163; + readonly ZSTD_c_ldmHashRateLog: 164; + readonly ZSTD_c_contentSizeFlag: 200; + readonly ZSTD_c_checksumFlag: 201; + readonly ZSTD_c_dictIDFlag: 202; + readonly ZSTD_c_nbWorkers: 400; + readonly ZSTD_c_jobSize: 401; + readonly ZSTD_c_overlapLog: 402; + readonly ZSTD_d_windowLogMax: 100; + readonly ZSTD_CLEVEL_DEFAULT: 3; + readonly ZSTD_error_no_error: 0; + readonly ZSTD_error_GENERIC: 1; + readonly ZSTD_error_prefix_unknown: 10; + readonly ZSTD_error_version_unsupported: 12; + readonly ZSTD_error_frameParameter_unsupported: 14; + readonly ZSTD_error_frameParameter_windowTooLarge: 16; + readonly ZSTD_error_corruption_detected: 20; + readonly ZSTD_error_checksum_wrong: 22; + readonly ZSTD_error_literals_headerWrong: 24; + readonly ZSTD_error_dictionary_corrupted: 30; + readonly ZSTD_error_dictionary_wrong: 32; + readonly ZSTD_error_dictionaryCreation_failed: 34; + readonly ZSTD_error_parameter_unsupported: 40; + readonly ZSTD_error_parameter_combination_unsupported: 41; + readonly ZSTD_error_parameter_outOfBound: 42; + readonly ZSTD_error_tableLog_tooLarge: 44; + readonly ZSTD_error_maxSymbolValue_tooLarge: 46; + readonly ZSTD_error_maxSymbolValue_tooSmall: 48; + readonly ZSTD_error_stabilityCondition_notRespected: 50; + readonly ZSTD_error_stage_wrong: 60; + readonly ZSTD_error_init_missing: 62; + readonly ZSTD_error_memory_allocation: 64; + readonly ZSTD_error_workSpace_tooSmall: 66; + readonly ZSTD_error_dstSize_tooSmall: 70; + readonly ZSTD_error_srcSize_wrong: 72; + readonly ZSTD_error_dstBuffer_null: 74; + readonly ZSTD_error_noForwardProgress_destFull: 80; + readonly ZSTD_error_noForwardProgress_inputEmpty: 82; + } + export const constants: ZlibConstants; + export const Z_NO_FLUSH: 0; + export const Z_PARTIAL_FLUSH: 1; + export const Z_SYNC_FLUSH: 2; + export const Z_FULL_FLUSH: 3; + export const Z_FINISH: 4; + export const Z_BLOCK: 5; + export const Z_OK: 0; + export const Z_STREAM_END: 1; + export const Z_NEED_DICT: 2; + export const Z_ERRNO: -1; + export const Z_STREAM_ERROR: -2; + export const Z_DATA_ERROR: -3; + export const Z_MEM_ERROR: -4; + export const Z_BUF_ERROR: -5; + export const Z_VERSION_ERROR: -6; + export const Z_NO_COMPRESSION: 0; + export const Z_BEST_SPEED: 1; + export const Z_BEST_COMPRESSION: 9; + export const Z_DEFAULT_COMPRESSION: -1; + export const Z_FILTERED: 1; + export const Z_HUFFMAN_ONLY: 2; + export const Z_RLE: 3; + export const Z_FIXED: 4; + export const Z_DEFAULT_STRATEGY: 0; + export const ZLIB_VERNUM: 4897; + export const DEFLATE: 1; + export const INFLATE: 2; + export const GZIP: 3; + export const GUNZIP: 4; + export const DEFLATERAW: 5; + export const INFLATERAW: 6; + export const UNZIP: 7; + export const ZSTD_DECOMPRESS: 11; + export const ZSTD_COMPRESS: 10; + export const Z_MIN_WINDOWBITS: 8; + export const Z_MAX_WINDOWBITS: 15; + export const Z_DEFAULT_WINDOWBITS: 15; + export const Z_MIN_CHUNK: 64; + export const Z_MAX_CHUNK: number; + export const Z_DEFAULT_CHUNK: 16384; + export const Z_MIN_MEMLEVEL: 1; + export const Z_MAX_MEMLEVEL: 9; + export const Z_DEFAULT_MEMLEVEL: 8; + export const Z_MIN_LEVEL: -1; + export const Z_MAX_LEVEL: 9; + export const Z_DEFAULT_LEVEL: -1; + export const ZSTD_e_continue: 0; + export const ZSTD_e_flush: 1; + export const ZSTD_e_end: 2; + export const ZSTD_fast: 1; + export const ZSTD_dfast: 2; + export const ZSTD_greedy: 3; + export const ZSTD_lazy: 4; + export const ZSTD_lazy2: 5; + export const ZSTD_btlazy2: 6; + export const ZSTD_btopt: 7; + export const ZSTD_btultra: 8; + export const ZSTD_btultra2: 9; + export const ZSTD_c_compressionLevel: 100; + export const ZSTD_c_windowLog: 101; + export const ZSTD_c_hashLog: 102; + export const ZSTD_c_chainLog: 103; + export const ZSTD_c_searchLog: 104; + export const ZSTD_c_minMatch: 105; + export const ZSTD_c_targetLength: 106; + export const ZSTD_c_strategy: 107; + export const ZSTD_c_enableLongDistanceMatching: 160; + export const ZSTD_c_ldmHashLog: 161; + export const ZSTD_c_ldmMinMatch: 162; + export const ZSTD_c_ldmBucketSizeLog: 163; + export const ZSTD_c_ldmHashRateLog: 164; + export const ZSTD_c_contentSizeFlag: 200; + export const ZSTD_c_checksumFlag: 201; + export const ZSTD_c_dictIDFlag: 202; + export const ZSTD_c_nbWorkers: 400; + export const ZSTD_c_jobSize: 401; + export const ZSTD_c_overlapLog: 402; + export const ZSTD_d_windowLogMax: 100; + export const ZSTD_CLEVEL_DEFAULT: 3; + export const ZSTD_error_no_error: 0; + export const ZSTD_error_GENERIC: 1; + export const ZSTD_error_prefix_unknown: 10; + export const ZSTD_error_version_unsupported: 12; + export const ZSTD_error_frameParameter_unsupported: 14; + export const ZSTD_error_frameParameter_windowTooLarge: 16; + export const ZSTD_error_corruption_detected: 20; + export const ZSTD_error_checksum_wrong: 22; + export const ZSTD_error_literals_headerWrong: 24; + export const ZSTD_error_dictionary_corrupted: 30; + export const ZSTD_error_dictionary_wrong: 32; + export const ZSTD_error_dictionaryCreation_failed: 34; + export const ZSTD_error_parameter_unsupported: 40; + export const ZSTD_error_parameter_combination_unsupported: 41; + export const ZSTD_error_parameter_outOfBound: 42; + export const ZSTD_error_tableLog_tooLarge: 44; + export const ZSTD_error_maxSymbolValue_tooLarge: 46; + export const ZSTD_error_maxSymbolValue_tooSmall: 48; + export const ZSTD_error_stabilityCondition_notRespected: 50; + export const ZSTD_error_stage_wrong: 60; + export const ZSTD_error_init_missing: 62; + export const ZSTD_error_memory_allocation: 64; + export const ZSTD_error_workSpace_tooSmall: 66; + export const ZSTD_error_dstSize_tooSmall: 70; + export const ZSTD_error_srcSize_wrong: 72; + export const ZSTD_error_dstBuffer_null: 74; + export const ZSTD_error_noForwardProgress_destFull: 80; + export const ZSTD_error_noForwardProgress_inputEmpty: 82; +} + +declare module "node:zlib" { + export * from "zlib"; +} diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 058b262ea..dff6b6b05 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -416,17 +416,6 @@ declare var process: { once(event: "disconnect", listener: () => void): void; }; -/* Node's process module default export is the global process object. These - * declaration aliases let the ESM spelling typecheck against the same - * deliberately narrow fallback surface when @types/node is absent. */ -declare module "process" { - export = process; -} -declare module "node:process" { - import process = require("process"); - export = process; -} - /* ── globals a real CLI's sources reference (all @types/node or dyn-lib * territory; the fallback declares the slice so projects PREFLIGHT and * every reached use lands on the SC2020 fence — or a real lowering where @@ -1292,150 +1281,6 @@ declare function setInterval( ): Timeout; declare function clearInterval(handle?: Timeout | number | null | undefined): void; -/* node:timers — the module twins of the timer globals (Node's timers - * module re-exports them: require('timers').setTimeout IS setTimeout). - * Named imports, destructured requires, and namespace calls all lower to - * the same libCalls as the globals; the surface beyond these six - * (enroll/unenroll, promises) fences per member. */ -declare module "node:timers" { - export function setTimeout(callback: () => void, ms?: number): Timeout; - export function setTimeout( - callback: (...args: never[]) => void, - ms?: number, - ...args: unknown[] - ): Timeout; - export function clearTimeout(handle?: Timeout | number | null | undefined): void; - export function setInterval(callback: () => void, ms?: number): Timeout; - export function setInterval( - callback: (...args: never[]) => void, - ms?: number, - ...args: unknown[] - ): Timeout; - export function clearInterval(handle?: Timeout | number | null | undefined): void; - export function setImmediate(callback: () => void): Immediate; - export function setImmediate(callback: (...args: never[]) => void, ...args: unknown[]): Immediate; - export function clearImmediate(handle?: Immediate | null | undefined): void; -} -declare module "timers" { - export * from "node:timers"; -} - -/* node:timers/promises — the promisified pair the Node test harness leans - * on (`await setTimeout(ms)` as a sleep). setTimeout and setImmediate - * settle with their resolve value (undefined without one) from the shared - * timer heap and the immediate queue; setInterval(delay, value) lowers to a - * typed async iterator. The options object takes `ref`; AbortSignal - * options remain declared surface that fences per site. */ -declare module "node:timers/promises" { - export interface TimerOptions { - ref?: boolean; - signal?: AbortSignal; - } - export function setTimeout( - delay?: number, - value?: T, - options?: TimerOptions, - ): Promise; - export function setImmediate(value?: T, options?: TimerOptions): Promise; - export function setInterval( - delay?: number, - value?: T, - options?: unknown, - ): AsyncGenerator; - export const scheduler: { - wait(delay?: number, options?: TimerOptions): Promise; - yield(): Promise; - }; -} -declare module "timers/promises" { - export * from "node:timers/promises"; -} - -/* node:diagnostics_channel — the in-process pub/sub core: named channels, - * subscribe/unsubscribe (module-level and channel-level), publish with any - * dyn-convertible message. Channel values are f64 handles into the - * runtime's channel registry (the readline.Interface precedent — channels - * are process-lived in Node too, its WeakRef machinery aside). - * The store-binding surface (bindStore/unbindStore/runStores) lowers onto - * the AsyncLocalStorage integration (dc.chan* libCalls). - * - * TracingChannel (tracingChannel) is the five-event-channel collection: - * an f64 handle into the runtime's tracing registry (type-mapper.ts maps the - * name like Channel). subscribe/unsubscribe walk a handlers object; - * traceSync/traceCallback run Node's publish choreography in the runtime - * (context defaults to a fresh {}); tracePromise is declared (the member - * exists in Node) but fences at the call site — the traced function's - * promise cannot cross the checked-dynamic tree boundary yet. */ -declare module "node:diagnostics_channel" { - export type ChannelListener = (message: unknown, name: string) => void; - export interface Channel { - readonly name: string; - readonly hasSubscribers: boolean; - publish(message: unknown): void; - subscribe(onMessage: ChannelListener): void; - unsubscribe(onMessage: ChannelListener): boolean; - bindStore( - store: import("async_hooks").AsyncLocalStorage, - transform?: (context: any) => any, - ): void; - unbindStore(store: import("async_hooks").AsyncLocalStorage): boolean; - runStores(context: unknown, fn: (...args: any[]) => any, thisArg?: any, ...args: any[]): any; - } - export function channel(name: string): Channel; - export function subscribe(name: string, onMessage: ChannelListener): void; - export function unsubscribe(name: string, onMessage: ChannelListener): boolean; - export function hasSubscribers(name: string): boolean; - export interface TracingChannelSubscribers { - start?: (message: any) => void; - end?: (message: any) => void; - asyncStart?: (message: any) => void; - asyncEnd?: (message: any) => void; - error?: (message: any) => void; - } - export interface TracingChannelCollection { - start: Channel; - end: Channel; - asyncStart: Channel; - asyncEnd: Channel; - error: Channel; - } - export interface TracingChannel { - start: Channel; - end: Channel; - asyncStart: Channel; - asyncEnd: Channel; - error: Channel; - readonly hasSubscribers: boolean; - subscribe(subscribers: TracingChannelSubscribers): void; - /** True when every present handler was found and removed (Node's - * all-found conjunction; @types/node under-declares this as void). */ - unsubscribe(subscribers: TracingChannelSubscribers): boolean; - traceSync( - fn: (this: ThisArg, ...args: Args) => Result, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Result; - tracePromise( - fn: (this: ThisArg, ...args: Args) => Promise, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Promise; - traceCallback( - fn: (this: ThisArg, ...args: Args) => Result, - position?: number, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Result; - } - export function tracingChannel(nameOrChannels: string | TracingChannelCollection): TracingChannel; -} -declare module "diagnostics_channel" { - export * from "node:diagnostics_channel"; -} - /* node:perf_hooks — performance.now() over the runtime's process-start- * anchored monotonic clock (Node's timeOrigin for a compiled program), * fractional milliseconds. The .bind(performance) spelling lowers to the @@ -1525,15 +1370,6 @@ declare module "perf_hooks" { * performance.now() either way. */ declare var performance: import("node:perf_hooks").Performance; -declare module "node:module" { - export function createRequire(filename: string | URL): ScriptcRequire; - export const builtinModules: string[]; - export function isBuiltin(moduleName: string): boolean; - export function syncBuiltinESMExports(): void; -} -declare module "module" { - export * from "node:module"; -} /* import.meta: module-loader metadata with no value representation — * direct file-backed fields lower to constants, while the object and * remaining members fence (SC1090) EXCEPT as createRequire's base, where it @@ -1548,1188 +1384,6 @@ interface ImportMeta { resolve(specifier: string, parent?: string | URL): string; } -/* The synchronous node:fs surface — utf8-only, no options objects, no - * Buffers. Importing "node:fs" resolves here (preflight allowlists exactly - * the ambient-declared node: modules); every function lowers to a `libCall` - * with a scr_fs_* runtime implementation. Failures throw CATCHABLE string - * values formatted like Node's error messages ("ENOENT: no such file or - * directory, open 'x'"). `rmSync` removes FILES only (like Node without - * `recursive`); empty directories go through `rmdirSync`. */ -declare module "node:fs" { - import { Readable, Writable } from "stream"; - export function readFileSync(path: string | number | URL, encoding: BufferEncoding): string; - export function readFileSync( - path: string | number | URL, - options: { encoding: BufferEncoding }, - ): string; - export function readFileSync(path: string | number | URL): Buffer; - export function readFileSync(path: string | URL, options: unknown): string | Buffer; - /* Error-first callbacks select string or Buffer data from their encoding. - * Unknown options retain that union until the caller validates the value. */ - export function readFile( - path: string, - options: unknown, - callback: (error: Error | null, data: string | Buffer) => void, - ): void; - /* The options form carries the mode (applied at CREATION only, like - * Node — an existing file keeps its permissions) and/or the utf8 - * encoding spelling. */ - export function writeFileSync( - path: string | URL, - data: string, - /* The options-record stance (see http.RequestOptions): flag fences - * by name at the call site; undocumented keys drop like Node. */ - options?: { - mode?: number; - encoding?: "utf8" | "utf-8"; - flag?: string; - [option: string]: unknown; - }, - ): void; - /* The bare-encoding spelling — the options record's encoding key alone. */ - export function writeFileSync(path: string | URL, data: string, encoding: "utf8" | "utf-8"): void; - export function writeFileSync(path: string | URL, data: string | Uint8Array): void; - export function appendFileSync(path: string | URL, data: string | Uint8Array): void; - export function existsSync(path: string | URL): boolean; - export function mkdirSync(path: string | URL): void; - export function mkdirSync( - path: string | URL, - options: { recursive?: boolean; mode?: number }, - ): void; - export function unlinkSync(path: string | URL): void; - export function chmodSync(path: string | URL, mode: number): void; - export function fchmodSync(fd: number, mode: number): void; - export function fsyncSync(fd: number): void; - export function linkSync( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - ): void; - export function symlinkSync( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type?: string | null, - ): void; - /* fs.cpSync / fs.cp: copies a file, symlink or (with recursive) a - * directory tree. filter decides per path; the callback and promise - * forms complete the copy before reporting. */ - export interface CopySyncOptions { - dereference?: boolean; - errorOnExist?: boolean; - filter?: (source: string, destination: string) => boolean; - force?: boolean; - mode?: number; - preserveTimestamps?: boolean; - recursive?: boolean; - verbatimSymlinks?: boolean; - } - export interface CopyOptions { - dereference?: boolean; - errorOnExist?: boolean; - filter?: (source: string, destination: string) => boolean | Promise; - force?: boolean; - mode?: number; - preserveTimestamps?: boolean; - recursive?: boolean; - verbatimSymlinks?: boolean; - } - export function cpSync( - source: string | URL, - destination: string | URL, - options?: CopySyncOptions, - ): void; - export function cp( - source: string | URL, - destination: string | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function cp( - source: string | URL, - destination: string | URL, - options: CopyOptions, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type: string | null | undefined, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function link( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null, target: string) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - callback: (err: NodeJS.ErrnoException | null, target: Buffer) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: BufferEncoding | { encoding?: BufferEncoding | null } | null, - callback: (err: NodeJS.ErrnoException | null, target: string) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - callback: (err: NodeJS.ErrnoException | null, target: string | Buffer) => void, - ): void; - export function readlinkSync( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - ): Buffer; - export function readlinkSync( - path: string | Uint8Array | URL, - options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, - ): string; - export function readlinkSync( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - ): string | Buffer; - export function chownSync(path: string | URL, uid: number, gid: number): void; - export function lchownSync(path: string | URL, uid: number, gid: number): void; - export function fchownSync(fd: number, uid: number, gid: number): void; - export function truncateSync(path: string | URL, len?: number): void; - /* The 2-argument form only (Node's mode flags have no lowering). The - * destination is created or truncated carrying the SOURCE's mode. */ - export function copyFileSync(src: string | URL, dest: string | URL): void; - export function rename( - oldPath: string, - newPath: string, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function renameSync(oldPath: string | URL, newPath: string | URL): void; - export function rmSync(path: string | URL): void; - /* maxRetries/retryDelay are accepted no-ops (Node retries around - * EBUSY-class races; the synchronous lowering has no re-entrancy that - * needs them). */ - export function rmSync( - path: string | URL, - options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, - ): void; - export function rmdirSync(path: string | URL): void; - export function readdirSync(path: string | URL): 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 - * carries the entry kind; isFile/isDirectory/isSymbolicLink lower as - * reads of it in builtins/process.ts). */ - export interface Dirent { - name: string; - parentPath: string; - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; - } - export function readdirSync( - path: string | URL, - /* The options-record stance (see http.RequestOptions): recursive - * fences by name at the call site; undocumented keys drop like Node. */ - options: { withFileTypes: true; recursive?: boolean; [option: string]: unknown }, - ): Dirent[]; - /* fs.globSync / fs.glob: Node's glob walk over the file system. Patterns - * are minimatch globs (case-insensitive on macOS and Windows); results - * are relative to cwd unless the pattern is absolute. withFileTypes must - * be a literal so the result type is known. */ - export interface GlobOptions { - cwd?: string | URL; - exclude?: readonly string[] | ((fileName: string) => boolean); - followSymlinks?: boolean; - withFileTypes?: false; - } - export interface GlobOptionsWithFileTypes { - cwd?: string | URL; - exclude?: readonly string[]; - followSymlinks?: boolean; - withFileTypes: true; - } - export function globSync(pattern: string | readonly string[], options?: GlobOptions): string[]; - export function globSync( - pattern: string | readonly string[], - options: GlobOptionsWithFileTypes, - ): Dirent[]; - export function glob( - pattern: string | readonly string[], - callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, - ): void; - export function glob( - pattern: string | readonly string[], - options: GlobOptions, - callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, - ): void; - /* fs.Dir: an open directory stream (opendirSync, fs.promises.opendir) - * read one Dirent at a time in OS order; utf8 names, no recursive walk. */ - export interface Dir { - readonly path: string; - readSync(): Dirent | null; - read(): Promise; - closeSync(): void; - close(): Promise; - [Symbol.asyncIterator](): AsyncIterableIterator; - } - export interface OpenDirOptions { - encoding?: "utf8" | "utf-8"; - bufferSize?: number; - } - export function opendirSync(path: string | URL, options?: OpenDirOptions): Dir; - /* fs.ReadStream / fs.WriteStream: runtime Readable and Writable streams - * over a file (scr_fs_stream.c). The options are checked at runtime - * with Node's rules; flags and mode are checked when the file opens, so - * their errors arrive as 'error' events. Instances also emit 'open' - * (with the fd) and 'ready'. */ - export interface StreamOptions { - flags?: string; - encoding?: BufferEncoding; - fd?: number; - mode?: number | string; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - highWaterMark?: number; - } - export interface ReadStreamOptions extends StreamOptions { - end?: number; - } - export interface WriteStreamOptions extends StreamOptions { - flush?: boolean; - } - export class ReadStream extends Readable { - readonly bytesRead: number; - readonly path: string | Buffer; - readonly pending: boolean; - close(callback?: () => void): void; - } - export class WriteStream extends Writable { - readonly bytesWritten: number; - readonly path: string | Buffer; - readonly pending: boolean; - close(callback?: () => void): void; - } - export function createReadStream( - path: string | Buffer | URL, - options?: BufferEncoding | ReadStreamOptions, - ): ReadStream; - export function createWriteStream( - path: string | Buffer | URL, - options?: BufferEncoding | WriteStreamOptions, - ): WriteStream; - export function accessSync(path: string | URL, mode?: number): void; - export function mkdtempSync(prefix: string | URL): string; - export const constants: { - readonly F_OK: number; - readonly R_OK: number; - readonly W_OK: number; - readonly X_OK: number; - readonly O_RDONLY: number; - readonly O_WRONLY: number; - readonly O_RDWR: number; - readonly O_CREAT: number; - readonly O_EXCL: number; - readonly O_NOFOLLOW: number; - readonly O_NONBLOCK: number; - readonly O_TRUNC: number; - readonly O_APPEND: number; - readonly COPYFILE_EXCL: number; - readonly COPYFILE_FICLONE: number; - readonly COPYFILE_FICLONE_FORCE: number; - readonly UV_FS_SYMLINK_DIR: number; - readonly UV_FS_SYMLINK_JUNCTION: number; - readonly UV_DIRENT_UNKNOWN: number; - readonly UV_DIRENT_FILE: number; - readonly UV_DIRENT_DIR: number; - readonly UV_DIRENT_LINK: number; - readonly UV_DIRENT_FIFO: number; - readonly UV_DIRENT_SOCKET: number; - readonly UV_DIRENT_CHAR: number; - readonly UV_DIRENT_BLOCK: number; - readonly UV_FS_O_FILEMAP: number; - readonly UV_FS_O_TEMPORARY: number; - readonly UV_FS_O_SHORT_LIVED: number; - readonly UV_FS_O_SEQUENTIAL: number; - readonly UV_FS_O_RANDOM: number; - readonly UV_FS_COPYFILE_EXCL: number; - readonly UV_FS_COPYFILE_FICLONE: number; - readonly UV_FS_COPYFILE_FICLONE_FORCE: number; - readonly S_IFMT: number; - readonly S_IFREG: number; - readonly S_IFDIR: number; - readonly S_IFCHR: number; - readonly S_IFBLK: number; - readonly S_IFIFO: number; - readonly S_IFLNK: number; - readonly S_IFSOCK: number; - readonly O_NOCTTY: number; - readonly O_DIRECTORY: number; - readonly O_NOATIME: number; - readonly O_SYNC: number; - readonly O_DSYNC: number; - readonly O_SYMLINK: number; - readonly O_DIRECT: number; - readonly S_IRWXU: number; - readonly S_IRUSR: number; - readonly S_IWUSR: number; - readonly S_IXUSR: number; - readonly S_IRWXG: number; - readonly S_IRGRP: number; - readonly S_IWGRP: number; - readonly S_IXGRP: number; - readonly S_IRWXO: number; - readonly S_IROTH: number; - readonly S_IWOTH: number; - readonly S_IXOTH: number; - }; - /* A stat(2) snapshot (statSync follows symlinks, lstatSync does not — - * Node's split) — immutable; the supported surface is exactly these - * members. dev/ino identify the filesystem entry, blocks is the allocated - * size in 512-byte units, and the time fields are milliseconds with their - * sub-second fractions. */ - export interface Stats { - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; - isBlockDevice(): boolean; - isCharacterDevice(): boolean; - isFIFO(): boolean; - isSocket(): boolean; - readonly dev: number; - readonly mode: number; - readonly nlink: number; - readonly uid: number; - readonly gid: number; - readonly rdev: number; - readonly blksize: number; - readonly ino: number; - readonly size: number; - readonly blocks: number; - readonly atimeMs: number; - readonly mtimeMs: number; - readonly ctimeMs: number; - readonly birthtimeMs: number; - readonly atime: Date; - readonly mtime: Date; - readonly ctime: Date; - readonly birthtime: Date; - } - /* throwIfNoEntry: false answers undefined for a missing entry (ENOENT or - * ENOTDIR) instead of throwing. BigIntStats (bigint: true) has no - * lowering. */ - export interface StatSyncOptions { - bigint?: boolean; - throwIfNoEntry?: boolean; - } - export function statSync(path: string | URL, options?: undefined): Stats; - export function statSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, - ): Stats | undefined; - export function statSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, - ): Stats; - /* bigint: true type-checks so the lowering can name it as unsupported. */ - export function statSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; - export function statSync(path: string | URL, options: StatSyncOptions): Stats | undefined; - export function lstatSync(path: string | URL, options?: undefined): Stats; - export function lstatSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, - ): Stats | undefined; - export function lstatSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, - ): Stats; - /* bigint: true type-checks so the lowering can name it as unsupported. */ - export function lstatSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; - export function lstatSync(path: string | URL, options: StatSyncOptions): Stats | undefined; - /* The fd pair behind spawn's fd-stdio form: openSync(path, flags) → - * the raw fd. Numeric flags use an inline OR of the O_* constants. */ - export function openSync(path: string | URL, flags: string): number; - export function openSync(path: string | URL, flags: number, mode?: number): number; - export function closeSync(fd: number): void; - export function fdatasyncSync(fd: number): void; - export function ftruncateSync(fd: number, len?: number): void; - export function utimesSync( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function futimesSync( - fd: number, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function lutimesSync( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function utimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function futimes( - fd: number, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function lutimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function readvSync(fd: number, buffers: Uint8Array[], position?: number | null): number; - export function writevSync(fd: number, buffers: Uint8Array[], position?: number | null): number; - /* Read into a caller buffer from the fd's current position when position - * is omitted/null, or from a numeric byte position without advancing the - * fd. Answers the byte count, 0 at EOF. */ - export interface ReadSyncOptions { - offset?: number; - length?: number; - position?: number | null; - } - export function readSync(fd: number, buffer: Uint8Array, options?: ReadSyncOptions): number; - export function readSync( - fd: number, - buffer: Uint8Array, - offset: number, - length: number, - position?: number | null, - ): number; - /* Write a caller-buffer window, or a utf8 string, at the fd's current - * position when position is omitted/null (advancing it), or at a numeric - * byte position without advancing it. Answers the byte count. */ - export interface WriteSyncOptions { - offset?: number; - length?: number; - position?: number | null; - } - export function writeSync(fd: number, buffer: Uint8Array, options: WriteSyncOptions): number; - export function writeSync( - fd: number, - buffer: Uint8Array, - offset?: number, - length?: number, - position?: number | null, - ): number; - export function writeSync( - fd: number, - string: string, - position?: number | null, - encoding?: "utf8" | "utf-8", - ): number; - /* statSync over an open fd — the same Stats snapshot. */ - export function fstatSync(fd: number): Stats; - /* realpath(3) — resolves symlinks, `.`/`..`, throwing Node's fs error - * shapes for missing paths. */ - export function realpathSync(path: string | URL): string; - export namespace realpathSync { - function native(path: string | URL): string; - } - /* statfs(2)/statvfs(3) — the filesystem-capacity snapshot (the fields - * Node's statfsSync reports; bavail × bsize is the free-space probe). */ - export interface StatsFsBase { - type: T; - bsize: T; - blocks: T; - bfree: T; - bavail: T; - files: T; - ffree: T; - } - export interface StatsFs extends StatsFsBase {} - export interface BigIntStatsFs extends StatsFsBase {} - export interface StatFsOptions { - bigint?: boolean; - } - export function statfsSync( - path: string | Uint8Array | URL, - options?: StatFsOptions & { bigint?: false }, - ): StatsFs; - export function statfsSync( - path: string | Uint8Array | URL, - options: StatFsOptions & { bigint: true }, - ): BigIntStatsFs; - export function statfsSync( - path: string | Uint8Array | URL, - options?: StatFsOptions, - ): StatsFs | BigIntStatsFs; - export function statfs( - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: (StatFsOptions & { bigint?: false }) | undefined, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: StatFsOptions & { bigint: true }, - callback: (err: NodeJS.ErrnoException | null, stats: BigIntStatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: StatFsOptions | undefined, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs | BigIntStatsFs) => void, - ): void; - /* fs.watch — the file-watching slice (scr_watch.c, kqueue EVFILT_VNODE): - * "rename"/"change" events on ONE path, watcher.close() to stop. The - * listener takes zero parameters or the eventType string (the filename - * parameter has no lowering); an open watcher keeps the loop alive. A - * path that cannot be opened THROWS Node's fs error synchronously. */ - export interface FSWatcher { - close(): void; - } - /* The options-record stance (see http.RequestOptions): persistent: true, - * recursive: false, and encoding: "utf8" state the lowered behavior and - * are accepted; persistent: false, recursive: true, and signal fence by - * name at the call site; undocumented keys drop like Node. */ - export interface WatchOptions { - persistent?: boolean; - recursive?: boolean; - encoding?: string; - [option: string]: unknown; - } - export function watch(path: string, listener?: (eventType: string) => void): FSWatcher; - export function watch( - path: string, - options: WatchOptions, - listener?: (eventType: string) => void, - ): FSWatcher; - /* fs.promises IS the fs/promises module (Node's rule) — the namespace- - * import form `fs.promises.readFile(...)` lowers through the same - * fs/promises table as `import { readFile } from "node:fs/promises"`. */ - export const promises: typeof import("node:fs/promises"); -} -/* Every builtin answers to BOTH specifier forms, like in Node: the bare - * module re-exports its node:-prefixed twin (or holds the declarations - * itself — one of the pair forwards). The lowerer recognizes members by - * their import SPECIFIER, so both forms lower identically. */ -declare module "fs" { - export * from "node:fs"; -} -/* graceful-fs — served as node:fs (it monkeypatches fs and retries on - * EMFILE/ENFILE; compiled programs call fs directly, without the retry - * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ -declare module "graceful-fs" { - export * from "node:fs"; - export function gracefulify(fsModule: T): T; -} - -/* fs/promises — the SAME synchronous operations behind already-settled - * promises: success fulfills, failure REJECTS with the would-be thrown - * error (catchable at the await, like Node). DOCUMENTED DIVERGENCE - * (SEMANTICS.md): the syscall blocks the event loop, so I/O never - * interleaves with timers or other fibers — observable only in concurrent - * code. readFile is utf8-only, like readFileSync. */ -declare module "fs/promises" { - export interface FileReadResult { - bytesRead: number; - buffer: T; - } - export interface FileWriteResult { - bytesWritten: number; - buffer: T; - } - export interface FileReadvResult { - bytesRead: number; - buffers: T; - } - export interface FileWritevResult { - bytesWritten: number; - buffers: T; - } - export interface FileReadOptions { - buffer?: T; - offset?: number | null; - length?: number | null; - position?: number | null; - } - /* The streams over a FileHandle (createReadStream, createWriteStream, - * readLines): fs streams that hold the handle until they close; closing - * the stream closes the handle unless autoClose is false. */ - export interface CreateReadStreamOptions { - encoding?: BufferEncoding | null; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - end?: number; - highWaterMark?: number; - } - export interface CreateWriteStreamOptions { - encoding?: BufferEncoding | null; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - highWaterMark?: number; - flush?: boolean; - } - export interface FileHandle extends AsyncDisposable { - readonly fd: number; - close(): Promise; - createReadStream(options?: CreateReadStreamOptions): import("node:fs").ReadStream; - createWriteStream(options?: CreateWriteStreamOptions): import("node:fs").WriteStream; - readLines(options?: CreateReadStreamOptions): import("node:readline").Interface; - sync(): Promise; - datasync(): Promise; - truncate(len?: number): Promise; - chmod(mode: number): Promise; - utimes(atime: number | string | Date, mtime: number | string | Date): Promise; - readv( - buffers: T, - position?: number | null, - ): Promise>; - writev( - buffers: T, - position?: number | null, - ): Promise>; - read( - options?: FileReadOptions | null, - ): Promise>; - read( - buffer: T, - options: Omit, "buffer">, - ): Promise>; - read( - buffer: T, - offset?: number | null, - length?: number | null, - position?: number | null, - ): Promise>; - write( - buffer: T, - offset?: number | null, - length?: number | null, - position?: number | null, - ): Promise>; - write( - buffer: T, - options: Omit, "buffer">, - ): Promise>; - write( - data: string, - position?: number | null, - encoding?: "utf8" | "utf-8" | null, - ): Promise>; - readFile(options?: null): Promise; - readFile(encoding: "utf8" | "utf-8"): Promise; - writeFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; - appendFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; - stat(): Promise; - [Symbol.asyncDispose](): Promise; - } - export function open(path: string | URL, flags?: string, mode?: number): Promise; - export function link( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - ): Promise; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type?: string | null, - ): Promise; - export function cp( - source: string | URL, - destination: string | URL, - options?: import("node:fs").CopyOptions, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - ): Promise; - export function utimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): Promise; - export function lutimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): Promise; - export function readFile(path: string | URL, encoding: "utf8" | "utf-8"): Promise; - export function readFile( - path: string | URL, - options: BufferEncoding | { encoding: BufferEncoding }, - ): Promise; - export function readFile(path: string | URL): Promise; - export function writeFile( - path: string | URL, - data: string, - options?: { - mode?: number; - encoding?: "utf8" | "utf-8"; - flag?: string; - [option: string]: unknown; - }, - ): Promise; - export function writeFile( - path: string | URL, - data: string, - encoding: "utf8" | "utf-8", - ): Promise; - export function mkdir( - path: string | URL, - options?: { recursive?: boolean; mode?: number }, - ): Promise; - export function readdir(path: string | URL): Promise; - export function readdir( - path: string | URL, - options: { - encoding?: "utf8" | "utf-8"; - withFileTypes: true; - recursive?: boolean; - [option: string]: unknown; - }, - ): Promise; - /* The async iterator form of fs.globSync: iterate it with for await. */ - export function glob( - pattern: string | readonly string[], - options?: import("node:fs").GlobOptions, - ): AsyncIterableIterator; - export function glob( - pattern: string | readonly string[], - options: import("node:fs").GlobOptionsWithFileTypes, - ): AsyncIterableIterator; - export function rm(path: string | URL): Promise; - export function rm( - path: string | URL, - options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, - ): Promise; - export function stat(path: string | URL): Promise; - export function statfs( - path: string | Uint8Array | URL, - options?: import("node:fs").StatFsOptions & { bigint?: false }, - ): Promise; - export function statfs( - path: string | Uint8Array | URL, - options: import("node:fs").StatFsOptions & { bigint: true }, - ): Promise; - export function statfs( - path: string | Uint8Array | URL, - options?: import("node:fs").StatFsOptions, - ): Promise; - export function realpath(path: string | URL): Promise; - export function lstat(path: string | URL): Promise; - export function unlink(path: string | URL): Promise; - export function chmod(path: string | URL, mode: number): Promise; - export function chown(path: string | URL, uid: number, gid: number): Promise; - export function lchown(path: string | URL, uid: number, gid: number): Promise; - export function access(path: string | URL, mode?: number): Promise; - export function truncate(path: string | URL, len?: number): Promise; - export function appendFile(path: string | URL, data: string | Uint8Array): Promise; - export function copyFile(src: string | URL, dest: string | URL): Promise; - export function mkdtemp(prefix: string | URL): Promise; - export function rmdir(path: string | URL): Promise; - export function rename(oldPath: string | URL, newPath: string | URL): Promise; - export function opendir( - path: string | URL, - options?: import("node:fs").OpenDirOptions, - ): Promise; -} -declare module "node:fs/promises" { - export * from "fs/promises"; -} - -/* node:path — the TARGET platform's implementation (Node's rule): posix - * on posix triples, path.win32 under a win32 triple, with the posix and - * win32 namespaces answering their own platform anywhere. Every function - * is a pure string algorithm matching Node's implementations - * exactly; join/resolve are variadic like Node's. */ -declare module "path" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - /* matchesGlob() is Node's glob matcher: brace expansion, extglobs, POSIX - * classes and the ** and dot rules. */ - export function matchesGlob(path: string, pattern: string): boolean; - /* parse() splits a path into its root, directory, base, extension and - * name; format() joins those fields back (Node's `||` defaults). */ - export interface ParsedPath { - root: string; - dir: string; - base: string; - ext: string; - name: string; - } - export interface FormatInputPathObject { - root?: string | undefined; - dir?: string | undefined; - base?: string | undefined; - ext?: string | undefined; - name?: string | undefined; - } - export function parse(path: string): ParsedPath; - export function format(pathObject: FormatInputPathObject): string; - /* The union @types/node declares: the bare module answers the TARGET - * platform's value (a compile-time constant — "\\"/";" under a win32 - * triple, "/"/":" everywhere else). */ - export const sep: "\\" | "/"; - export const delimiter: ";" | ":"; - /* The platform-specific namespaces (real Node submodules too — - * "node:path/posix" resolves). Each carries the FULL surface and - * answers ITS platform's rules on any target, Node's own contract; - * the bare module IS the target platform's implementation. */ - export const posix: typeof import("path/posix"); - export const win32: typeof import("path/win32"); -} -declare module "node:path" { - export * from "path"; -} -declare module "path/posix" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - export function matchesGlob(path: string, pattern: string): boolean; - export function parse(path: string): import("path").ParsedPath; - export function format(pathObject: import("path").FormatInputPathObject): string; - export const sep: "/"; - export const delimiter: ":"; -} -declare module "node:path/posix" { - export * from "path/posix"; -} -declare module "path/win32" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - export function matchesGlob(path: string, pattern: string): boolean; - export function parse(path: string): import("path").ParsedPath; - export function format(pathObject: import("path").FormatInputPathObject): string; - export const sep: "\\"; - export const delimiter: ";"; -} -declare module "node:path/win32" { - export * from "path/win32"; -} - -/* node:os — the slice a CLI needs: platform() (same implementation as - * process.platform), homedir(), tmpdir() (Node's env cascade), EOL - * (always "\n" — POSIX pinned, like path.sep), and networkInterfaces() - * (getifaddrs(3); the interface shapes mirror @types/node's exactly so - * both type worlds map to the same IR structure). */ -declare module "os" { - export function platform(): string; - export function homedir(): string; - export function tmpdir(): string; - /* uname(2)'s release field — Node's own implementation. */ - export function release(): string; - /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */ - export function type(): string; - /* Total system memory in bytes (sysctl hw.memsize / sysconf). */ - export function totalmem(): number; - export function availableParallelism(): number; - /* The passwd-entry snapshot (uv_os_get_passwd): shell is `string | - * null` to match @types/node (POSIX always answers the string arm); - * homedir is pw_dir — NOT os.homedir()'s $HOME-first cascade. The type - * is interned explicitly in type-mapper.ts (the AddressInfo pattern — this - * concrete form and @types/node's UserInfo both), and the call - * assembles field-by-field in lowerOsUserInfoCall. */ - export interface UserInfo { - username: string; - uid: number; - gid: number; - shell: string | null; - homedir: string; - } - export function userInfo(): UserInfo; - /* The TARGET platform's line terminator (a compile-time constant: - * "\r\n" under a win32 triple, "\n" everywhere else). */ - export const EOL: "\r\n" | "\n"; - export interface NetworkInterfaceBase { - address: string; - netmask: string; - mac: string; - internal: boolean; - cidr: string | null; - scopeid?: number; - } - export interface NetworkInterfaceInfoIPv4 extends NetworkInterfaceBase { - family: "IPv4"; - } - export interface NetworkInterfaceInfoIPv6 extends NetworkInterfaceBase { - family: "IPv6"; - scopeid: number; - } - export type NetworkInterfaceInfo = NetworkInterfaceInfoIPv4 | NetworkInterfaceInfoIPv6; - export function networkInterfaces(): { [name: string]: NetworkInterfaceInfo[] | undefined }; - /* Host facts, libuv's answers: model/speed/times per logical CPU, the - * host name, the 1/5/15-minute load averages, uptime in seconds, free - * memory in bytes, process.arch, uname's machine and version fields, - * the byte order, and a process's scheduling priority (an omitted pid - * is the current process). Shapes mirror @types/node's. */ - export interface CpuInfo { - model: string; - speed: number; - times: { - user: number; - nice: number; - sys: number; - idle: number; - irq: number; - }; - } - export function cpus(): CpuInfo[]; - export function hostname(): string; - export function loadavg(): number[]; - export function uptime(): number; - export function freemem(): number; - export function arch(): string; - export function machine(): string; - export function version(): string; - export function endianness(): "BE" | "LE"; - export function getPriority(pid?: number): number; - export function setPriority(priority: number): void; - export function setPriority(pid: number, priority: number): void; - /* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */ - export const devNull: string; - /* The platform's constants: null-prototype objects with read-only - * members, `signals` frozen (Node's internalBinding('constants').os). */ - export type SignalConstants = { - [ - key in - | "SIGABRT" - | "SIGALRM" - | "SIGBUS" - | "SIGCHLD" - | "SIGCONT" - | "SIGFPE" - | "SIGHUP" - | "SIGILL" - | "SIGINT" - | "SIGIO" - | "SIGIOT" - | "SIGKILL" - | "SIGPIPE" - | "SIGPOLL" - | "SIGPROF" - | "SIGPWR" - | "SIGQUIT" - | "SIGSEGV" - | "SIGSTKFLT" - | "SIGSTOP" - | "SIGSYS" - | "SIGTERM" - | "SIGTRAP" - | "SIGTSTP" - | "SIGTTIN" - | "SIGTTOU" - | "SIGUNUSED" - | "SIGURG" - | "SIGUSR1" - | "SIGUSR2" - | "SIGVTALRM" - | "SIGWINCH" - | "SIGXCPU" - | "SIGXFSZ" - | "SIGBREAK" - | "SIGLOST" - | "SIGINFO" - ]: number; - }; - export namespace constants { - const UV_UDP_REUSEADDR: number; - namespace signals {} - const signals: SignalConstants; - namespace errno { - const E2BIG: number; - const EACCES: number; - const EADDRINUSE: number; - const EADDRNOTAVAIL: number; - const EAFNOSUPPORT: number; - const EAGAIN: number; - const EALREADY: number; - const EBADF: number; - const EBADMSG: number; - const EBUSY: number; - const ECANCELED: number; - const ECHILD: number; - const ECONNABORTED: number; - const ECONNREFUSED: number; - const ECONNRESET: number; - const EDEADLK: number; - const EDESTADDRREQ: number; - const EDOM: number; - const EDQUOT: number; - const EEXIST: number; - const EFAULT: number; - const EFBIG: number; - const EHOSTUNREACH: number; - const EIDRM: number; - const EILSEQ: number; - const EINPROGRESS: number; - const EINTR: number; - const EINVAL: number; - const EIO: number; - const EISCONN: number; - const EISDIR: number; - const ELOOP: number; - const EMFILE: number; - const EMLINK: number; - const EMSGSIZE: number; - const EMULTIHOP: number; - const ENAMETOOLONG: number; - const ENETDOWN: number; - const ENETRESET: number; - const ENETUNREACH: number; - const ENFILE: number; - const ENOBUFS: number; - const ENODATA: number; - const ENODEV: number; - const ENOENT: number; - const ENOEXEC: number; - const ENOLCK: number; - const ENOLINK: number; - const ENOMEM: number; - const ENOMSG: number; - const ENOPROTOOPT: number; - const ENOSPC: number; - const ENOSR: number; - const ENOSTR: number; - const ENOSYS: number; - const ENOTCONN: number; - const ENOTDIR: number; - const ENOTEMPTY: number; - const ENOTSOCK: number; - const ENOTSUP: number; - const ENOTTY: number; - const ENXIO: number; - const EOPNOTSUPP: number; - const EOVERFLOW: number; - const EPERM: number; - const EPIPE: number; - const EPROTO: number; - const EPROTONOSUPPORT: number; - const EPROTOTYPE: number; - const ERANGE: number; - const EROFS: number; - const ESPIPE: number; - const ESRCH: number; - const ESTALE: number; - const ETIME: number; - const ETIMEDOUT: number; - const ETXTBSY: number; - const EWOULDBLOCK: number; - const EXDEV: number; - const WSAEINTR: number; - const WSAEBADF: number; - const WSAEACCES: number; - const WSAEFAULT: number; - const WSAEINVAL: number; - const WSAEMFILE: number; - const WSAEWOULDBLOCK: number; - const WSAEINPROGRESS: number; - const WSAEALREADY: number; - const WSAENOTSOCK: number; - const WSAEDESTADDRREQ: number; - const WSAEMSGSIZE: number; - const WSAEPROTOTYPE: number; - const WSAENOPROTOOPT: number; - const WSAEPROTONOSUPPORT: number; - const WSAESOCKTNOSUPPORT: number; - const WSAEOPNOTSUPP: number; - const WSAEPFNOSUPPORT: number; - const WSAEAFNOSUPPORT: number; - const WSAEADDRINUSE: number; - const WSAEADDRNOTAVAIL: number; - const WSAENETDOWN: number; - const WSAENETUNREACH: number; - const WSAENETRESET: number; - const WSAECONNABORTED: number; - const WSAECONNRESET: number; - const WSAENOBUFS: number; - const WSAEISCONN: number; - const WSAENOTCONN: number; - const WSAESHUTDOWN: number; - const WSAETOOMANYREFS: number; - const WSAETIMEDOUT: number; - const WSAECONNREFUSED: number; - const WSAELOOP: number; - const WSAENAMETOOLONG: number; - const WSAEHOSTDOWN: number; - const WSAEHOSTUNREACH: number; - const WSAENOTEMPTY: number; - const WSAEPROCLIM: number; - const WSAEUSERS: number; - const WSAEDQUOT: number; - const WSAESTALE: number; - const WSAEREMOTE: number; - const WSASYSNOTREADY: number; - const WSAVERNOTSUPPORTED: number; - const WSANOTINITIALISED: number; - const WSAEDISCON: number; - const WSAENOMORE: number; - const WSAECANCELLED: number; - const WSAEINVALIDPROCTABLE: number; - const WSAEINVALIDPROVIDER: number; - const WSAEPROVIDERFAILEDINIT: number; - const WSASYSCALLFAILURE: number; - const WSASERVICE_NOT_FOUND: number; - const WSATYPE_NOT_FOUND: number; - const WSA_E_NO_MORE: number; - const WSA_E_CANCELLED: number; - const WSAEREFUSED: number; - } - namespace dlopen { - const RTLD_LAZY: number; - const RTLD_NOW: number; - const RTLD_GLOBAL: number; - const RTLD_LOCAL: number; - const RTLD_DEEPBIND: number; - } - namespace priority { - const PRIORITY_LOW: number; - const PRIORITY_BELOW_NORMAL: number; - const PRIORITY_NORMAL: number; - const PRIORITY_ABOVE_NORMAL: number; - const PRIORITY_HIGH: number; - const PRIORITY_HIGHEST: number; - } - } -} -declare module "node:os" { - export * from "os"; -} - /* The WHATWG URL class (a Node global; the es2023 lib doesn't declare it), * typed as exactly the supported surface: construction from an input and * optional base (invalid input throws a catchable TypeError, like Node), the @@ -2791,1937 +1445,6 @@ declare var URLSearchParams: { new (init?: string | string[][] | { [key: string]: string } | URLSearchParams): URLSearchParams; }; -/* node:url — the file-URL bridge pair. fileURLToPath accepts a URL value - * or a URL string and throws Node's TypeErrors (non-file scheme, encoded - * slashes, non-empty host); pathToFileURL resolves the path and percent- - * encodes it into a file: URL. The legacy API: parse() answers a Url - * object, format() serializes a Url-shaped object, a string, or a URL, - * resolve() resolves one URL string against another, the domain pair - * converts a hostname to and from its Punycode form, and urlToHttpOptions - * builds http.request options from a URL. */ -declare module "url" { - export type URL = globalThis.URL; - export type URLSearchParams = globalThis.URLSearchParams; - export const URL: typeof globalThis.URL; - export const URLSearchParams: typeof globalThis.URLSearchParams; - export function fileURLToPath(url: string | URL, options?: { windows?: boolean }): string; - export function fileURLToPathBuffer(url: string | URL, options?: { windows?: boolean }): Buffer; - export function pathToFileURL(path: string, options?: { windows?: boolean }): URL; - export interface UrlObject { - auth?: string | null | undefined; - hash?: string | null | undefined; - host?: string | null | undefined; - hostname?: string | null | undefined; - href?: string | null | undefined; - pathname?: string | null | undefined; - protocol?: string | null | undefined; - search?: string | null | undefined; - slashes?: boolean | null | undefined; - port?: string | number | null | undefined; - query?: string | null | import("querystring").ParsedUrlQueryInput | undefined; - } - export interface Url { - auth: string | null; - hash: string | null; - host: string | null; - hostname: string | null; - href: string; - path: string | null; - pathname: string | null; - protocol: string | null; - search: string | null; - slashes: boolean | null; - port: string | null; - query: string | null | import("querystring").ParsedUrlQuery; - } - export interface UrlWithParsedQuery extends Url { - query: import("querystring").ParsedUrlQuery; - } - export interface UrlWithStringQuery extends Url { - query: string | null; - } - export interface URLFormatOptions { - auth?: boolean | undefined; - fragment?: boolean | undefined; - search?: boolean | undefined; - unicode?: boolean | undefined; - } - export function parse( - urlString: string, - parseQueryString?: false, - slashesDenoteHost?: boolean, - ): UrlWithStringQuery; - export function parse( - urlString: string, - parseQueryString: true, - slashesDenoteHost?: boolean, - ): UrlWithParsedQuery; - export function parse( - urlString: string, - parseQueryString: boolean, - slashesDenoteHost?: boolean, - ): Url; - export function format(urlObject: URL, options?: URLFormatOptions): string; - export function format(urlObject: UrlObject | string): string; - export function resolve(from: string, to: string): string; - export function domainToASCII(domain: string): string; - export function domainToUnicode(domain: string): string; - /** urlToHttpOptions's null-prototype request options. */ - export interface UrlHttpOptions { - protocol: string; - hostname: string; - hash: string; - search: string; - pathname: string; - path: string; - href: string; - port?: number; - auth?: string; - } - export function urlToHttpOptions(url: URL): UrlHttpOptions; -} -declare module "node:url" { - export * from "url"; -} - -/* node:crypto: the static hashing/MAC/random/key-derivation utility slice. */ -declare module "crypto" { - import { Transform } from "stream"; - export type KeyObjectType = "secret" | "public" | "private"; - export interface JsonWebKey { - crv?: string; - d?: string; - dp?: string; - dq?: string; - e?: string; - k?: string; - kty?: string; - n?: string; - p?: string; - q?: string; - qi?: string; - x?: string; - y?: string; - [key: string]: unknown; - } - export interface JwkKeyExportOptions { - format: "jwk"; - } - export interface KeyExportOptions { - format?: "buffer" | undefined; - } - export type KeyFormat = "pem" | "der" | "jwk"; - export interface AsymmetricKeyExportOptions { - type: "pkcs1" | "spki" | "pkcs8" | "sec1"; - format: T; - cipher?: string | undefined; - passphrase?: string | Buffer | undefined; - } - export interface AsymmetricKeyDetails { - modulusLength?: number; - publicExponent?: bigint; - namedCurve?: string; - } - /* Secret keys, and RSA and EC private and public keys. */ - export class KeyObject { - private constructor(); - type: KeyObjectType; - symmetricKeySize?: number; - asymmetricKeyType?: string; - asymmetricKeyDetails?: AsymmetricKeyDetails; - export(options: AsymmetricKeyExportOptions<"pem">): string | Buffer; - export(options: AsymmetricKeyExportOptions<"der">): Buffer; - export(options?: KeyExportOptions): Buffer; - export(options: JwkKeyExportOptions): JsonWebKey; - equals(otherKeyObject: KeyObject): boolean; - } - export interface PrivateKeyInput { - key: string | Buffer; - format?: "pem" | "der" | undefined; - type?: "pkcs1" | "pkcs8" | "sec1" | undefined; - encoding?: string | undefined; - passphrase?: string | Buffer | undefined; - } - export interface PublicKeyInput { - key: string | Buffer; - format?: "pem" | "der" | undefined; - type?: "pkcs1" | "spki" | undefined; - encoding?: string | undefined; - } - export interface JsonWebKeyInput { - key: JsonWebKey; - format: "jwk"; - } - export function createPrivateKey( - key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, - ): KeyObject; - export function createPublicKey( - key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, - ): KeyObject; - export interface KeyPairKeyObjectResult { - publicKey: KeyObject; - privateKey: KeyObject; - } - export interface KeyPairSyncResult { - publicKey: T1; - privateKey: T2; - } - export interface RSAKeyPairKeyObjectOptions { - modulusLength: number; - publicExponent?: number | undefined; - } - export interface ECKeyPairKeyObjectOptions { - namedCurve: string; - } - export interface RSAKeyPairOptions { - modulusLength: number; - publicExponent?: number | undefined; - publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs1" | "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export interface ECKeyPairOptions { - namedCurve: string; - publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { - type: "sec1" | "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairKeyObjectOptions, - ): KeyPairKeyObjectResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairKeyObjectOptions, - ): KeyPairKeyObjectResult; - export interface ED25519KeyPairOptions { - publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "ed25519", - options: ED25519KeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ed25519", - options: ED25519KeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; - export interface X25519KeyPairOptions { - publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "x25519", - options: X25519KeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "x25519", - options: X25519KeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync(type: "x25519", options?: {}): KeyPairKeyObjectResult; - export function generateKeyPair( - type: "x25519", - options: {} | undefined, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "ed25519", - options: {} | undefined, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "rsa", - options: RSAKeyPairKeyObjectOptions, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "ec", - options: ECKeyPairKeyObjectOptions, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export interface SigningOptions { - padding?: number | undefined; - saltLength?: number | undefined; - dsaEncoding?: "der" | "ieee-p1363" | undefined; - } - export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} - export interface SignKeyObjectInput extends SigningOptions { - key: KeyObject; - } - export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} - export interface VerifyKeyObjectInput extends SigningOptions { - key: KeyObject; - } - export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject }): Buffer; - export function diffieHellman( - options: { privateKey: KeyObject; publicKey: KeyObject }, - callback: (err: Error | null, secret: Buffer) => void, - ): void; - export function sign( - algorithm: string | null | undefined, - data: ArrayBufferView, - key: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - ): Buffer; - export function verify( - algorithm: string | null | undefined, - data: ArrayBufferView, - key: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: ArrayBufferView, - ): boolean; - /* Sign and Verify accumulate update() data; they are not usable as - * writable streams. */ - export class Sign { - private constructor(); - update(data: string | ArrayBufferView): this; - update(data: string, inputEncoding: Encoding): this; - sign( - privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - ): Buffer; - sign( - privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - outputFormat: BinaryToTextEncoding, - ): string; - } - export class Verify { - private constructor(); - update(data: string | ArrayBufferView): this; - update(data: string, inputEncoding: Encoding): this; - verify( - object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: ArrayBufferView, - ): boolean; - verify( - object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: string, - signatureEncoding?: BinaryToTextEncoding, - ): boolean; - } - export function createSign(algorithm: string): Sign; - export function createVerify(algorithm: string): Verify; - export function createSecretKey(key: ArrayBufferView | ArrayBufferLike): KeyObject; - export function createSecretKey(key: string, encoding: BufferEncoding): KeyObject; - export function generateKeySync(type: "hmac" | "aes", options: { length: number }): KeyObject; - export function generateKey( - type: "hmac" | "aes", - options: { length: number }, - callback: (err: Error | null, key: KeyObject) => void, - ): void; - export interface RandomUUIDOptions { - disableEntropyCache?: boolean | undefined; - } - export function randomUUID(options?: RandomUUIDOptions): string; - export function randomUUIDv7(options?: RandomUUIDOptions): string; - export function randomBytes(size: number): Buffer; - export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; - export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "utf-16le" | "latin1"; - export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; - export type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding; - export interface HashOptions { - outputLength?: number | undefined; - } - /* Hash and Hmac are Transforms: written chunks update the digest, which - * the readable side delivers once the writable side ends. */ - export interface Hash extends Transform { - copy(options?: HashOptions): Hash; - update(data: string | ArrayBufferView): Hash; - update(data: string, inputEncoding: Encoding): Hash; - digest(): Buffer; - digest(encoding: BinaryToTextEncoding): string; - } - export interface Hmac extends Transform { - update(data: string | ArrayBufferView): Hmac; - update(data: string, inputEncoding: Encoding): Hmac; - digest(): Buffer; - digest(encoding: BinaryToTextEncoding): string; - } - export function createHash(algorithm: string, options?: HashOptions): Hash; - export function createHmac( - algorithm: string, - key: BinaryLike | KeyObject, - options?: { encoding?: BufferEncoding | undefined }, - ): Hmac; - export interface OneShotDigestOptions { - outputEncoding?: BinaryToTextEncoding | "buffer" | undefined; - outputLength?: number | undefined; - } - export interface OneShotDigestOptionsWithStringEncoding extends OneShotDigestOptions { - outputEncoding?: BinaryToTextEncoding | undefined; - } - export interface OneShotDigestOptionsWithBufferEncoding extends OneShotDigestOptions { - outputEncoding: "buffer"; - } - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options?: OneShotDigestOptionsWithStringEncoding | BinaryToTextEncoding, - ): string; - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options: OneShotDigestOptionsWithBufferEncoding | "buffer", - ): Buffer; - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options: OneShotDigestOptions | BinaryToTextEncoding | "buffer", - ): string | Buffer; - export function timingSafeEqual( - a: ArrayBufferView | ArrayBufferLike, - b: ArrayBufferView | ArrayBufferLike, - ): boolean; - /* WebCrypto's getRandomValues (also the webcrypto object's): integer - * typed arrays, filled in place and returned. */ - type RandomValuesArray = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | BigInt64Array - | BigUint64Array; - export function getRandomValues(typedArray: T): T; - /* The WebCrypto global's object and its SubtleCrypto. */ - export const webcrypto: Crypto; - export const subtle: SubtleCrypto; - export function randomFillSync( - buffer: T, - offset?: number, - size?: number, - ): T; - export function randomFill( - buffer: T, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomFill( - buffer: T, - offset: number, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomFill( - buffer: T, - offset: number, - size: number, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomInt(max: number): number; - export function randomInt(min: number, max: number): number; - export function randomInt( - max: number, - callback: (err: Error | null, value: number) => void, - ): void; - export function randomInt( - min: number, - max: number, - callback: (err: Error | null, value: number) => void, - ): void; - export function pbkdf2Sync( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - ): Buffer; - export function pbkdf2( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function randomBytes( - size: number, - callback: (error: Error | null, buffer: Buffer) => void, - ): void; - export type BinaryLike = string | ArrayBuffer | ArrayBufferView; - /* Cipheriv/Decipheriv: aes-128/192/256 in cbc, ctr, gcm, ccm, ecb, cfb, - * cfb8, cfb1, and ofb modes and their key wraps, des-ede3 and des-ede, - * chacha20, and chacha20-poly1305. Both are Transforms: written chunks go - * through update(), and the end pushes final(). */ - export type CipherCCMTypes = "aes-128-ccm" | "aes-192-ccm" | "aes-256-ccm"; - export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; - export type CipherChaCha20Poly1305Types = "chacha20-poly1305"; - export type CipherKey = BinaryLike | KeyObject; - export interface CipherCCMOptions { - authTagLength: number; - } - export interface CipherGCMOptions { - authTagLength?: number; - } - export interface CipherChaCha20Poly1305Options { - authTagLength?: number; - } - export interface Cipheriv extends Transform { - update(data: string | ArrayBufferView): Buffer; - update(data: string, inputEncoding: BufferEncoding): Buffer; - update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; - update( - data: string, - inputEncoding: BufferEncoding | undefined, - outputEncoding: BufferEncoding, - ): string; - final(): Buffer; - final(outputEncoding: BufferEncoding): string; - setAutoPadding(autoPadding?: boolean): this; - } - export interface CipherCCM extends Cipheriv { - setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface CipherGCM extends Cipheriv { - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface CipherChaCha20Poly1305 extends Cipheriv { - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface Decipheriv extends Transform { - update(data: ArrayBufferView): Buffer; - update(data: string, inputEncoding: BufferEncoding): Buffer; - update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; - update( - data: string, - inputEncoding: BufferEncoding | undefined, - outputEncoding: BufferEncoding, - ): string; - final(): Buffer; - final(outputEncoding: BufferEncoding): string; - setAutoPadding(autoPadding?: boolean): this; - } - export interface DecipherCCM extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; - } - export interface DecipherGCM extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - } - export interface DecipherChaCha20Poly1305 extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - } - export function createCipheriv( - algorithm: CipherCCMTypes, - key: CipherKey, - iv: BinaryLike, - options: CipherCCMOptions, - ): CipherCCM; - export function createCipheriv( - algorithm: CipherGCMTypes, - key: CipherKey, - iv: BinaryLike, - options?: CipherGCMOptions, - ): CipherGCM; - export function createCipheriv( - algorithm: CipherChaCha20Poly1305Types, - key: CipherKey, - iv: BinaryLike, - options?: CipherChaCha20Poly1305Options, - ): CipherChaCha20Poly1305; - export function createCipheriv( - algorithm: string, - key: CipherKey, - iv: BinaryLike | null, - options?: { authTagLength?: number }, - ): Cipheriv; - export function createDecipheriv( - algorithm: CipherCCMTypes, - key: CipherKey, - iv: BinaryLike, - options: CipherCCMOptions, - ): DecipherCCM; - export function createDecipheriv( - algorithm: CipherGCMTypes, - key: CipherKey, - iv: BinaryLike, - options?: CipherGCMOptions, - ): DecipherGCM; - export function createDecipheriv( - algorithm: CipherChaCha20Poly1305Types, - key: CipherKey, - iv: BinaryLike, - options?: CipherChaCha20Poly1305Options, - ): DecipherChaCha20Poly1305; - export function createDecipheriv( - algorithm: string, - key: CipherKey, - iv: BinaryLike | null, - options?: { authTagLength?: number }, - ): Decipheriv; - export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; - export class ECDH { - private constructor(); - static convertKey( - key: BinaryLike, - curve: string, - inputEncoding?: BinaryToTextEncoding, - outputEncoding?: "latin1" | "hex" | "base64" | "base64url", - format?: ECDHKeyFormat, - ): Buffer | string; - generateKeys(): Buffer; - generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; - computeSecret(otherPublicKey: ArrayBufferView): Buffer; - computeSecret(otherPublicKey: string, inputEncoding: BinaryToTextEncoding): Buffer; - computeSecret(otherPublicKey: ArrayBufferView, outputEncoding: BinaryToTextEncoding): string; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding: BinaryToTextEncoding, - ): string; - getPrivateKey(): Buffer; - getPrivateKey(encoding: BinaryToTextEncoding): string; - getPublicKey(encoding?: null, format?: ECDHKeyFormat): Buffer; - getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; - setPrivateKey(privateKey: ArrayBufferView): void; - setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; - } - export function createECDH(curveName: string): ECDH; - export class DiffieHellman { - private constructor(); - generateKeys(): Buffer; - generateKeys(encoding: BinaryToTextEncoding): string; - computeSecret( - otherPublicKey: ArrayBufferView, - inputEncoding?: null, - outputEncoding?: null, - ): Buffer; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding?: null, - ): Buffer; - computeSecret( - otherPublicKey: ArrayBufferView, - inputEncoding: null, - outputEncoding: BinaryToTextEncoding, - ): string; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding: BinaryToTextEncoding, - ): string; - getPrime(): Buffer; - getPrime(encoding: BinaryToTextEncoding): string; - getGenerator(): Buffer; - getGenerator(encoding: BinaryToTextEncoding): string; - getPublicKey(): Buffer; - getPublicKey(encoding: BinaryToTextEncoding): string; - getPrivateKey(): Buffer; - getPrivateKey(encoding: BinaryToTextEncoding): string; - setPublicKey(publicKey: ArrayBufferView): void; - setPublicKey(publicKey: string, encoding: BufferEncoding): void; - setPrivateKey(privateKey: ArrayBufferView): void; - setPrivateKey(privateKey: string, encoding: BufferEncoding): void; - verifyError: number; - } - export type DiffieHellmanGroup = Omit; - export function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman; - export function createDiffieHellman( - prime: ArrayBuffer | ArrayBufferView, - generator?: number | ArrayBuffer | ArrayBufferView, - ): DiffieHellman; - export function createDiffieHellman( - prime: ArrayBuffer | ArrayBufferView, - generator: string, - generatorEncoding: BinaryToTextEncoding, - ): DiffieHellman; - export function createDiffieHellman( - prime: string, - primeEncoding: BinaryToTextEncoding, - generator?: number | ArrayBuffer | ArrayBufferView, - ): DiffieHellman; - export function createDiffieHellman( - prime: string, - primeEncoding: BinaryToTextEncoding, - generator: string, - generatorEncoding: BinaryToTextEncoding, - ): DiffieHellman; - export function getDiffieHellman(groupName: string): DiffieHellmanGroup; - export function createDiffieHellmanGroup(name: string): DiffieHellmanGroup; - export type LargeNumberLike = ArrayBufferView | SharedArrayBuffer | ArrayBuffer | bigint; - export interface GeneratePrimeOptions { - add?: LargeNumberLike | undefined; - rem?: LargeNumberLike | undefined; - safe?: boolean | undefined; - bigint?: boolean | undefined; - } - export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { - bigint: true; - } - export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { - bigint?: false | undefined; - } - export function generatePrime( - size: number, - callback: (err: Error | null, prime: ArrayBuffer) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptionsBigInt, - callback: (err: Error | null, prime: bigint) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptionsArrayBuffer, - callback: (err: Error | null, prime: ArrayBuffer) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptions, - callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, - ): void; - export function generatePrimeSync(size: number): ArrayBuffer; - export function generatePrimeSync(size: number, options: GeneratePrimeOptionsBigInt): bigint; - export function generatePrimeSync( - size: number, - options: GeneratePrimeOptionsArrayBuffer, - ): ArrayBuffer; - export function generatePrimeSync( - size: number, - options: GeneratePrimeOptions, - ): ArrayBuffer | bigint; - export interface CheckPrimeOptions { - checks?: number | undefined; - } - export function checkPrime( - value: LargeNumberLike, - callback: (err: Error | null, result: boolean) => void, - ): void; - export function checkPrime( - value: LargeNumberLike, - options: CheckPrimeOptions, - callback: (err: Error | null, result: boolean) => void, - ): void; - export function checkPrimeSync(candidate: LargeNumberLike, options?: CheckPrimeOptions): boolean; - export interface ScryptOptions { - N?: number; - r?: number; - p?: number; - cost?: number; - blockSize?: number; - parallelization?: number; - maxmem?: number; - } - export function scryptSync( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - options?: ScryptOptions, - ): Buffer; - export function scrypt( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function scrypt( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - options: ScryptOptions, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function hkdfSync( - digest: string, - ikm: BinaryLike | KeyObject, - salt: BinaryLike, - info: BinaryLike, - keylen: number, - ): ArrayBuffer; - export function hkdf( - digest: string, - ikm: BinaryLike | KeyObject, - salt: BinaryLike, - info: BinaryLike, - keylen: number, - callback: (error: Error | null, derivedKey: ArrayBuffer) => void, - ): void; - /* The lowered X509Certificate surface is the data-record slice: the - * fields below, all computed at construction (raw decodes the input, - * and the others read raw). validFrom/validTo are Node's - * ASN1_TIME_print strings ("Jul 1 00:00:00 2026 GMT"). Unparseable - * input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ - export class X509Certificate { - constructor(buffer: Uint8Array | string); - readonly ca: boolean; - readonly fingerprint: string; - readonly fingerprint256: string; - readonly fingerprint512: string; - readonly subject: string; - readonly subjectAltName: string | undefined; - readonly infoAccess: string | undefined; - readonly issuer: string; - readonly raw: Buffer; - readonly serialNumber: string; - readonly signatureAlgorithm: string | undefined; - readonly signatureAlgorithmOid: string; - readonly validFrom: string; - readonly validFromDate: Date; - readonly validTo: string; - readonly validToDate: Date; - /* the certificate's public key; every read answers the same KeyObject */ - readonly publicKey: KeyObject; - verify(publicKey: KeyObject): boolean; - checkPrivateKey(privateKey: KeyObject): boolean; - checkIssued(otherCert: X509Certificate): boolean; - checkHost(name: string, options?: X509CheckOptions): string | undefined; - checkEmail(email: string, options?: X509CheckOptions): string | undefined; - checkIP(ip: string): string | undefined; - toLegacyObject(): import("tls").PeerCertificate; - } - export interface X509CheckOptions { - subject?: "always" | "default" | "never"; - wildcards?: boolean; - partialWildcards?: boolean; - multiLabelWildcards?: boolean; - singleLabelSubdomains?: boolean; - } - /* The crypto introspection statics: getFips() answers 0 (a compiled - * binary is never a FIPS build), the three name lists bake as fresh - * string[] literals of Node v24 answers, and constants (below) bakes - * per member like http2.constants. */ - export function getFips(): 1 | 0; - export function getCiphers(): string[]; - export type CipherMode = - | "cbc" - | "ccm" - | "cfb" - | "ctr" - | "ecb" - | "gcm" - | "ocb" - | "ofb" - | "stream" - | "wrap" - | "xts"; - export interface CipherInfoOptions { - keyLength?: number | undefined; - ivLength?: number | undefined; - } - export interface CipherInfo { - name: string; - nid: number; - blockSize?: number | undefined; - ivLength?: number | undefined; - keyLength: number; - mode: CipherMode; - } - export function getCipherInfo( - nameOrNid: string | number, - options?: CipherInfoOptions, - ): CipherInfo | undefined; - export function getHashes(): string[]; - export function getCurves(): string[]; - /* Node v24 crypto.constants, literal-typed (the Http2Constants - * stance). Every ACCESS bakes as its literal at lowering; the object - * itself never materializes (bare crypto.constants value uses fence). */ - export interface CryptoConstants { - readonly OPENSSL_VERSION_NUMBER: 810549376; - readonly SSL_OP_ALL: 2147485776; - readonly SSL_OP_ALLOW_NO_DHE_KEX: 1024; - readonly SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144; - readonly SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304; - readonly SSL_OP_CISCO_ANYCONNECT: 32768; - readonly SSL_OP_COOKIE_EXCHANGE: 8192; - readonly SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648; - readonly SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048; - readonly SSL_OP_LEGACY_SERVER_CONNECT: 4; - readonly SSL_OP_NO_COMPRESSION: 131072; - readonly SSL_OP_NO_ENCRYPT_THEN_MAC: 524288; - readonly SSL_OP_NO_QUERY_MTU: 4096; - readonly SSL_OP_NO_RENEGOTIATION: 1073741824; - readonly SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536; - readonly SSL_OP_NO_SSLv2: 0; - readonly SSL_OP_NO_SSLv3: 33554432; - readonly SSL_OP_NO_TICKET: 16384; - readonly SSL_OP_NO_TLSv1: 67108864; - readonly SSL_OP_NO_TLSv1_1: 268435456; - readonly SSL_OP_NO_TLSv1_2: 134217728; - readonly SSL_OP_NO_TLSv1_3: 536870912; - readonly SSL_OP_PRIORITIZE_CHACHA: 2097152; - readonly SSL_OP_TLS_ROLLBACK_BUG: 8388608; - readonly ENGINE_METHOD_RSA: 1; - readonly ENGINE_METHOD_DSA: 2; - readonly ENGINE_METHOD_DH: 4; - readonly ENGINE_METHOD_RAND: 8; - readonly ENGINE_METHOD_EC: 2048; - readonly ENGINE_METHOD_CIPHERS: 64; - readonly ENGINE_METHOD_DIGESTS: 128; - readonly ENGINE_METHOD_PKEY_METHS: 512; - readonly ENGINE_METHOD_PKEY_ASN1_METHS: 1024; - readonly ENGINE_METHOD_ALL: 65535; - readonly ENGINE_METHOD_NONE: 0; - readonly DH_CHECK_P_NOT_SAFE_PRIME: 2; - readonly DH_CHECK_P_NOT_PRIME: 1; - readonly DH_UNABLE_TO_CHECK_GENERATOR: 4; - readonly DH_NOT_SUITABLE_GENERATOR: 8; - readonly RSA_PKCS1_PADDING: 1; - readonly RSA_SSLV23_PADDING: 2; - readonly RSA_NO_PADDING: 3; - readonly RSA_PKCS1_OAEP_PADDING: 4; - readonly RSA_X931_PADDING: 5; - readonly RSA_PKCS1_PSS_PADDING: 6; - readonly RSA_PSS_SALTLEN_DIGEST: -1; - readonly RSA_PSS_SALTLEN_MAX_SIGN: -2; - readonly RSA_PSS_SALTLEN_AUTO: -2; - readonly defaultCoreCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; - readonly TLS1_VERSION: 769; - readonly TLS1_1_VERSION: 770; - readonly TLS1_2_VERSION: 771; - readonly TLS1_3_VERSION: 772; - readonly POINT_CONVERSION_COMPRESSED: 2; - readonly POINT_CONVERSION_UNCOMPRESSED: 4; - readonly POINT_CONVERSION_HYBRID: 6; - readonly defaultCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; - } - export const constants: CryptoConstants; -} -declare module "node:crypto" { - export * from "crypto"; -} - -/* node:child_process — the synchronous slice: spawnSync(command, args?) - * runs the child to completion (posix_spawn + waitpid) with stdout/stderr - * captured as utf8 strings. `status` is the exit code, or null when the - * child died to a signal OR could not be spawned at all (nonexistent - * binary, permission denied) — Node reports spawn failure through the - * result's `error` property, which this surface does not carry, and types - * stdout/stderr null there where scriptc returns "" (SEMANTICS.md). - * No shell, no options object: the command runs directly (PATH-searched, - * like Node). */ -declare module "child_process" { - export interface SpawnSyncReturns { - readonly status: number | null; - readonly stdout: string; - readonly stderr: string; - /* The termination signal's name ("SIGTERM", ...) when a signal - * killed the child (a timeout's killSignal included), null otherwise - * — Node's shape. */ - readonly signal: string | null; - /* Node's spawn-failure carrier: a real Error ("spawnSync - * ENOENT", `code` stamped) when the spawn itself failed — status is - * null and both outputs "" then — or ETIMEDOUT after a timeout kill; - * undefined otherwise. */ - readonly error?: Error; - } - /* scriptc always captures utf8 strings; pass { encoding: "utf8" } - * whenever stdout/stderr is READ so plain Node agrees (without it Node - * hands Buffers). The other supported options: timeout (ms — sends - * killSignal at the deadline and the result carries error: ETIMEDOUT + - * the signal, never a throw), killSignal (a signal NAME literal), - * stdio ("ignore"/"inherit"/"pipe" or a 3-tuple of those — captures - * read "" for non-piped streams), env (replaces the child's - * environment), and windowsHide (a POSIX no-op). */ - export function spawnSync( - command: string, - args?: string[], - options?: { - encoding?: "utf8" | "utf-8"; - timeout?: number; - killSignal?: string; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - env?: { [k: string]: string | undefined }; - windowsHide?: boolean; - }, - ): SpawnSyncReturns; - - /* The asynchronous slice: spawn with stdio "ignore"/"inherit"/fd/"pipe" - * tuples (piped stdin is writable through child.stdin; piped stdout/stderr - * deliver through child.stdout/stderr) and the terminal events. "exit" - * fires once with the exit code, or - * null when the child died to a signal; "error" fires ONLY when the - * child could not be spawned at all (Node's split: a spawn failure - * emits "error" and never "exit"). "close" fires after exit/error and - * every piped stdio handle reaches EOF. Exit/close listeners may take - * Node's code and signal parameters. `on`/`once` return void here (Node - * returns the child; chaining is fenced). The event loop keeps the process - * alive until every spawned child is reaped, like Node — reaping polls at - * loop quiescence (SEMANTICS.md documents the divergence). An "error" - * event with no registered listener prints the error and exits 1, - * exactly the unhandled-'error' EventEmitter behavior. */ - export interface ChildStdin { - readonly writable: boolean; - write(chunk: string | Uint8Array): boolean; - end(): void; - destroy(): void; - on(event: "drain" | "finish", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: string, listener: (...args: any[]) => void): void; - once(event: "drain" | "finish", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: string, listener: (...args: any[]) => void): void; - } - - export interface ChildProcess extends Disposable { - ref(): void; - on(event: "spawn", listener: () => void): this; - once(event: "spawn", listener: () => void): this; - /* The exit listener may also take Node's second parameter — the - * terminating signal's name, null for a normal exit. */ - on(event: "exit", listener: (code: number | null, signal: string | null) => void): this; - on(event: "close", listener: (code: number | null, signal: string | null) => void): this; - on(event: "error", listener: (err: Error) => void): this; - once(event: "exit", listener: (code: number | null, signal: string | null) => void): this; - once(event: "close", listener: (code: number | null, signal: string | null) => void): this; - once(event: "error", listener: (err: Error) => void): this; - on(event: "message", listener: (message: any) => void): this; - on(event: "disconnect", listener: () => void): this; - once(event: "message", listener: (message: any) => void): this; - once(event: "disconnect", listener: () => void): this; - /* The lifecycle members, Node's exact shapes: pid is undefined exactly - * when the spawn failed; exitCode is null while running, the code - * after a normal exit, null for a signal death, and -errno once a - * spawn failure's "error" event has fired; kill resolves signal names - * through Node's table (unknown names throw the Unknown signal - * TypeError), answers true when the signal was sent, false once the - * child was reaped (or never spawned), and sets `killed` on success; - * unref() drops the child from the event loop's keep-alive set (it is - * still reaped while the loop runs for other reasons — SEMANTICS.md - * has the reap story). */ - readonly pid: number | undefined; - readonly exitCode: number | null; - readonly killed: boolean; - kill(signal?: string | number): boolean; - unref(): void; - readonly connected: boolean; - send(message: object, callback?: (error: Error | null) => void): boolean; - disconnect(): void; - [Symbol.dispose](): void; - /* The piped streams — non-null exactly when the matching stdio slot - * was "pipe" (Node's shape). */ - readonly stdin: ChildStdin | null; - readonly stdout: NodeJS.ReadableStream | null; - readonly stderr: NodeJS.ReadableStream | null; - } - export interface SpawnOptions { - /* The 3-tuple form admits number fds in the stdout/stderr slots — - * openSync results dup2'd into the child (the daemon-log idiom - * ["ignore", logFd, logFd]) — and "pipe" there too (child.stdout/ - * child.stderr streams); "pipe" in the stdin slot exposes - * child.stdin. */ - stdio?: "ignore" | "inherit" | "pipe" | ("ignore" | "inherit" | "pipe" | number)[]; - /* detached gives the child its own session and process group - * (POSIX_SPAWN_SETSID); env REPLACES the child environment; cwd - * sets its working directory; windowsHide is a POSIX no-op; shell - * runs the complete command string through /bin/sh or cmd.exe. */ - detached?: boolean; - env?: { [k: string]: string | undefined }; - cwd?: string; - windowsHide?: boolean; - shell?: boolean; - } - export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; - export function spawn(command: string, options?: SpawnOptions): ChildProcess; - - export function fork( - modulePath: string | URL, - args?: string[], - options?: { - cwd?: string; - env?: { [k: string]: string | undefined }; - execArgv?: string[]; - silent?: boolean; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit" | "ipc")[]; - serialization?: "json" | "advanced"; - windowsHide?: boolean; - }, - ): ChildProcess; - - /* The synchronous exec pair: execFileSync runs a file directly (no - * shell), execSync runs a command through /bin/sh -c. Both capture - * stdout as a utf8 string (pass { encoding: "utf8" } so plain Node - * agrees — without it Node returns a Buffer) and THROW on a non-zero - * exit / signal death — the Error's message is Node's "Command failed: - * " (with the captured stderr appended). Supported options: - * encoding ("utf8"), cwd, env (REPLACES the child environment), input - * (fed to stdin), timeout (ms — SIGTERMs the child, then throws - * ETIMEDOUT), stdio ("pipe"/"ignore" or a 3-tuple of those — the - * default captures stderr AND echoes it to the parent, like Node), and - * maxBuffer (accepted, not enforced). Node's status/stdout/stderr error - * properties are not carried (SEMANTICS.md). */ - interface ExecSyncOptions { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - input?: string; - timeout?: number; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - shell?: boolean; - } - /* The named options interface for the string-encoding exec form — - * @types/node's ExecFileSyncOptionsWithStringEncoding, narrowed to the - * honestly-implemented members. VALUES of this type are a real record - * (type-mapper.ts interns the shape), so a typed const or a runner function's - * options parameter flows to execFileSync at runtime — the windows-ca - * command-runner idiom. */ - export interface ExecFileSyncOptionsWithStringEncoding { - encoding: "utf8" | "utf-8"; - cwd?: string; - input?: string; - timeout?: number; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - maxBuffer?: number; - windowsHide?: boolean; - } - export function execFileSync(file: string, args?: string[], options?: ExecSyncOptions): string; - export function execSync(command: string, options?: ExecSyncOptions): string; - export type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void; - /* The callback form starts an asynchronous child and captures utf8 - * stdout/stderr. The no-callback overload remains for util.promisify's - * static target recognition; reached no-callback calls still fence. */ - export function execFile(file: string, callback: ExecFileCallback): ChildProcess; - export function execFile( - file: string, - args: string[] | null, - callback: ExecFileCallback, - ): ChildProcess; - export function execFile( - file: string, - args: string[] | null, - options: ExecSyncOptions, - callback: ExecFileCallback, - ): ChildProcess; - export function execFile( - file: string, - args?: string[] | null, - options?: ExecSyncOptions, - ): unknown; - /* The async shell form. The callback forms lower like execFile's, with - * the command line run by /bin/sh -c, and util.promisify recognizes it - * statically, as it does execFile; reached calls without a callback - * fence at their site. */ - export function exec( - command: string, - options?: ExecSyncOptions | ((error: Error | null, stdout: string, stderr: string) => void), - callback?: (error: Error | null, stdout: string, stderr: string) => void, - ): ChildProcess; -} -declare module "node:child_process" { - export * from "child_process"; -} - -/* node:util — compile-time projections for child_process.execFile, - * child_process.exec and fs.readFile. The first two retain Node's custom - * { stdout, stderr } result; the last resolves the callback's data - * argument. Other targets and bare - * promisify values fence per site. */ -declare module "util" { - export * as types from "util/types"; - export function getSystemErrorName(err: number): string; - export function getSystemErrorMessage(err: number): string; - export function getSystemErrorMap(): Map; - export function stripVTControlCharacters(str: string): string; - export function toUSVString(string: string): string; - export function isDeepStrictEqual(val1: unknown, val2: unknown, skipPrototype?: boolean): boolean; - export function styleText( - format: string | readonly string[], - text: string, - options?: { validateStream?: boolean; stream?: unknown }, - ): string; - export function parseEnv(content: string): { [key: string]: string | undefined }; - export function inherits(constructor: unknown, superConstructor: unknown): void; - export interface DeprecateOptions { - modifyPrototype?: boolean; - } - export function deprecate( - fn: T, - msg: string, - code?: string, - options?: DeprecateOptions, - ): T; - /* The callback receives null on success and the rejection reason - * otherwise (falsy reasons arrive as ERR_FALSY_VALUE_REJECTION). */ - export function callbackify( - fn: () => Promise, - ): (callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: () => Promise, - ): (callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; - export function callbackify( - fn: (arg1: T1) => Promise, - ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1) => Promise, - ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2) => Promise, - ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2) => Promise, - ): ( - arg1: T1, - arg2: T2, - callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, - ) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, - ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, - ): ( - arg1: T1, - arg2: T2, - arg3: T3, - callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, - ) => void; - export type DebugLoggerFunction = (msg: string, ...param: unknown[]) => void; - export interface DebugLogger extends DebugLoggerFunction { - enabled: boolean; - } - export function debuglog( - section: string, - callback?: (fn: DebugLoggerFunction) => void, - ): DebugLogger; - export { debuglog as debug }; - export type TextEncoder = globalThis.TextEncoder; - export type TextDecoder = globalThis.TextDecoder; - export var TextEncoder: typeof globalThis.TextEncoder; - export var TextDecoder: typeof globalThis.TextDecoder; - /* stream.pipeline's overload comes first: its __promisify__ member keeps - * every other target out of it. */ - export function promisify( - fn: typeof import("node:stream").pipeline, - ): typeof import("node:stream/promises").pipeline; - export function promisify( - fn: typeof import("node:fs").readFile, - ): (path: string, encoding: "utf8" | "utf-8") => Promise; - export function promisify( - fn: (file: string, args?: string[] | null, options?: object) => unknown, - ): ( - file: string, - args?: string[], - options?: { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - }, - ) => Promise<{ stdout: string; stderr: string }>; - export function promisify( - fn: typeof globalThis.setTimeout, - ): typeof import("node:timers/promises").setTimeout; - export function promisify( - fn: typeof globalThis.setImmediate, - ): typeof import("node:timers/promises").setImmediate; - export function promisify(fn: typeof import("node:child_process").exec): ( - command: string, - options?: { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - }, - ) => Promise<{ stdout: string; stderr: string }>; - /* The zlib callback one-shots (deflate, inflate, deflateRaw, inflateRaw, - * gzip, gunzip, unzip) share one type. */ - export function promisify( - fn: typeof import("node:zlib").gzip, - ): (data: string | Uint8Array, options?: import("node:zlib").ZlibOptions) => Promise; - export function promisify( - fn: typeof import("node:stream").finished, - ): typeof import("node:stream/promises").finished; - /* The crypto callback functions: the promise settles with the callback's - * value. */ - export function promisify( - fn: typeof import("node:crypto").randomBytes, - ): (size: number) => Promise; - export function promisify( - fn: typeof import("node:crypto").pbkdf2, - ): ( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - ) => Promise; - export function promisify( - fn: typeof import("node:crypto").scrypt, - ): ( - password: import("node:crypto").BinaryLike, - salt: import("node:crypto").BinaryLike, - keylen: number, - options?: import("node:crypto").ScryptOptions, - ) => Promise; - export function promisify( - fn: typeof import("node:crypto").hkdf, - ): ( - digest: string, - ikm: import("node:crypto").BinaryLike | import("node:crypto").KeyObject, - salt: import("node:crypto").BinaryLike, - info: import("node:crypto").BinaryLike, - keylen: number, - ) => Promise; - /* A callback-only function (`obj.close.bind(obj)`): before the targets - * whose leading parameters would otherwise capture it. Builtins that take - * arguments before their callback never match these. */ - export function promisify( - fn: (callback: (err: any, result: TResult) => void) => void, - ): () => Promise; - export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; - export function promisify( - fn: typeof import("node:crypto").randomFill, - ): ( - buffer: T, - offset?: number, - size?: number, - ) => Promise; - export function promisify(fn: typeof import("node:crypto").randomInt): { - (max: number): Promise; - (min: number, max: number): Promise; - }; - export function promisify( - fn: typeof import("node:crypto").generateKey, - ): ( - type: "hmac" | "aes", - options: { length: number }, - ) => Promise; - /* dns.lookup's promise settles with { address, family } (the address - * array with options.all), and lookupService's with { hostname, service }. */ - export function promisify(fn: typeof import("node:dns").lookup): { - ( - hostname: string, - options: import("node:dns").LookupAllOptions, - ): Promise; - ( - hostname: string, - options?: number | import("node:dns").LookupOneOptions, - ): Promise; - }; - export function promisify( - fn: typeof import("node:dns").lookupService, - ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; - /* Declared surface without full lowering (the type-level tolerance - * story: guarded/diagnostic-path code must TYPECHECK; a reached use - * without a lowering fences at its site). inspect's options carry the - * documented knobs through an index signature. */ - export function inspect( - object: unknown, - options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, - ): string; - export function format(format?: unknown, ...args: unknown[]): string; - export function formatWithOptions( - inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, - format?: unknown, - ...args: unknown[] - ): string; - export type ParseArgsOptionsType = "boolean" | "string"; - export interface ParseArgsOptionDescriptor { - type: ParseArgsOptionsType; - multiple?: boolean; - short?: string; - default?: string | boolean | string[] | boolean[]; - } - export interface ParseArgsOptionsConfig { - [longOption: string]: ParseArgsOptionDescriptor; - } - export interface ParseArgsConfig { - args?: readonly string[]; - options?: ParseArgsOptionsConfig; - strict?: boolean; - allowPositionals?: boolean; - allowNegative?: boolean; - tokens?: boolean; - } - export type ParseArgsToken = - | { - kind: "option"; - index: number; - name: string; - rawName: string; - value: string | undefined; - inlineValue: boolean | undefined; - } - | { kind: "positional"; index: number; value: string } - | { kind: "option-terminator"; index: number }; - export interface ParseArgsResult { - values: { [longOption: string]: undefined | string | boolean | Array }; - positionals: string[]; - tokens?: ParseArgsToken[]; - } - export function parseArgs(config?: ParseArgsConfig): ParseArgsResult; - /* util.getCallSites (Node ≥22.9): the captured-frame slice harness - * code reads. No stack bookkeeping exists in a compiled binary — every - * reached call fences per site. */ - export interface CallSite { - readonly functionName: string; - readonly scriptName: string; - readonly lineNumber: number; - readonly column: number; - } - export function getCallSites(frameCount?: number): CallSite[]; -} -declare module "node:util" { - export * from "util"; -} - -/* node:util/types — native runtime brands. Other forms retain their - * per-member lowering boundaries. */ -declare module "util/types" { - export function isAnyArrayBuffer(value: unknown): boolean; - export function isArrayBuffer(value: unknown): value is ArrayBuffer; - export function isArrayBufferView(value: unknown): value is ArrayBufferView; - export function isDataView(value: unknown): value is DataView; - export function isTypedArray(value: unknown): boolean; - export function isUint8Array(value: unknown): value is Uint8Array; - export function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray; - export function isUint16Array(value: unknown): value is Uint16Array; - export function isUint32Array(value: unknown): value is Uint32Array; - export function isInt8Array(value: unknown): value is Int8Array; - export function isInt16Array(value: unknown): value is Int16Array; - export function isInt32Array(value: unknown): value is Int32Array; - export function isFloat16Array(value: unknown): value is Float16Array; - export function isFloat32Array(value: unknown): value is Float32Array; - export function isFloat64Array(value: unknown): value is Float64Array; - export function isMap(value: unknown): value is Map; - export function isSet(value: unknown): value is Set; - export function isWeakMap(value: unknown): value is WeakMap; - export function isWeakSet(value: unknown): value is WeakSet; - export function isRegExp(value: unknown): value is RegExp; - export function isNativeError(value: unknown): value is Error; - export function isPromise(value: unknown): value is Promise; - export function isProxy(value: unknown): boolean; - export function isAsyncFunction(value: unknown): boolean; - export function isGeneratorFunction(value: unknown): boolean; - export function isGeneratorObject(value: unknown): boolean; - export function isModuleNamespaceObject(value: unknown): boolean; - export function isDate(value: unknown): value is Date; - export function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer; - export function isMapIterator(value: unknown): boolean; - export function isSetIterator(value: unknown): boolean; - export function isKeyObject(value: unknown): boolean; - export function isNumberObject(value: unknown): boolean; - export function isStringObject(value: unknown): boolean; - export function isBooleanObject(value: unknown): boolean; - export function isBigIntObject(value: unknown): boolean; - export function isSymbolObject(value: unknown): boolean; - export function isBoxedPrimitive(value: unknown): boolean; - export function isBigInt64Array(value: unknown): value is BigInt64Array; - export function isBigUint64Array(value: unknown): value is BigUint64Array; - export function isCryptoKey(value: unknown): boolean; - export function isExternal(value: unknown): boolean; -} -declare module "node:util/types" { - export * from "util/types"; -} - -/* node:assert — the static assertion surface. The module object IS a - * callable function (`assert(x)` is `assert.ok(x)`), so the declaration - * is the callable-namespace `export =` shape and BOTH `import assert - * from "node:assert"` and `const assert = require("assert")` bind the - * callable; named imports take the members. Failures throw - * AssertionError (name "AssertionError", code "ERR_ASSERTION" — catch and - * read .name/.message/.code). strictEqual/deepStrictEqual compare with - * Object.is over scalars; deepStrictEqual compares composites - * structurally per their static types. The messages here are plain - * strings (Node accepts Errors — that form fences per site), and the - * shallow legacy pair (equal/notEqual) follows Node's == coercions for - * primitive and primitive-union operands, including Node v24's NaN - * exception. deepEqual/notDeepEqual remain declared-but-fenced; - * assert/strict's loose names alias the strict forms and lower. */ -/* node:test — the in-process test runner (prefix-only in Node too: - * require("test") is MODULE_NOT_FOUND). test/it register tests (sync and - * async bodies, skip/todo/only options and method twins), describe/suite - * group them (bodies run at registration, Node's collection phase), - * before/after/beforeEach/afterEach hook the enclosing suite, and the - * TestContext argument carries subtests (t.test), t.skip/t.todo, - * t.diagnostic, and t.assert. mock/run/snapshot are DECLARED so real - * suites typecheck but fence at their use sites (no lowering). */ -declare module "node:test" { - type TestFn = (t: TestContext) => void | Promise; - type SuiteFn = (s: SuiteContext) => void; - type HookFn = (c: TestContext | SuiteContext) => void | Promise; - /* The describe/suite callback's context, and the before/after hook - * context of a suite. */ - interface SuiteContext { - readonly name: string; - readonly fullName: string; - readonly filePath: string | undefined; - readonly attempt: number; - readonly passed: boolean; - diagnostic(message: string): void; - } - interface TestOptions { - skip?: boolean | string; - todo?: boolean | string; - only?: boolean; - concurrency?: number | boolean; - timeout?: number; - plan?: number; - } - interface TestContext { - readonly name: string; - readonly fullName: string; - readonly filePath: string | undefined; - readonly attempt: number; - readonly passed: boolean; - readonly workerId: number | undefined; - readonly tags: readonly string[]; - readonly assert: TestContextAssert; - readonly mock: MockTracker; - test(name?: string, fn?: TestFn): Promise; - test(name?: string, options?: TestOptions, fn?: TestFn): Promise; - test(options?: TestOptions, fn?: TestFn): Promise; - test(fn?: TestFn): Promise; - skip(message?: string): void; - todo(message?: string): void; - diagnostic(message: string): void; - plan(count: number): void; - after(fn: HookFn): void; - before(fn: HookFn): void; - beforeEach(fn: HookFn): void; - afterEach(fn: HookFn): void; - } - interface AssertSnapshotOptions { - readonly serializers?: ReadonlyArray<(value: any) => any> | undefined; - } - interface TestContextAssert { - snapshot(value: any, options?: AssertSnapshotOptions): void; - fileSnapshot(value: any, path: string, options?: AssertSnapshotOptions): void; - ok(value: unknown, message?: string): void; - strictEqual(actual: unknown, expected: unknown, message?: string): void; - notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - equal(actual: unknown, expected: unknown, message?: string): void; - notEqual(actual: unknown, expected: unknown, message?: string): void; - deepEqual(actual: unknown, expected: unknown, message?: string): void; - notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - fail(message?: string): never; - throws(fn: () => unknown, expected?: unknown, message?: string): void; - match(value: string, regExp: RegExp, message?: string): void; - doesNotMatch(value: string, regExp: RegExp, message?: string): void; - } - /* The name, options and function are each optional, as in Node: a test - * without a name takes the function's name, or "". */ - function test(name?: string, fn?: TestFn): Promise; - function test(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function test(options?: TestOptions, fn?: TestFn): Promise; - function test(fn?: TestFn): Promise; - namespace test { - function skip(name?: string, fn?: TestFn): Promise; - function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function skip(options?: TestOptions, fn?: TestFn): Promise; - function skip(fn?: TestFn): Promise; - function todo(name?: string, fn?: TestFn): Promise; - function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function todo(options?: TestOptions, fn?: TestFn): Promise; - function todo(fn?: TestFn): Promise; - function only(name?: string, fn?: TestFn): Promise; - function only(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function only(options?: TestOptions, fn?: TestFn): Promise; - function only(fn?: TestFn): Promise; - } - const it: typeof test; - function describe(name?: string, fn?: SuiteFn): void; - function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function describe(options?: TestOptions, fn?: SuiteFn): void; - function describe(fn?: SuiteFn): void; - namespace describe { - function skip(name?: string, fn?: SuiteFn): void; - function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function skip(options?: TestOptions, fn?: SuiteFn): void; - function skip(fn?: SuiteFn): void; - function todo(name?: string, fn?: SuiteFn): void; - function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function todo(options?: TestOptions, fn?: SuiteFn): void; - function todo(fn?: SuiteFn): void; - function only(name?: string, fn?: SuiteFn): void; - function only(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function only(options?: TestOptions, fn?: SuiteFn): void; - function only(fn?: SuiteFn): void; - } - const suite: typeof describe; - function before(fn: HookFn): void; - function after(fn: HookFn): void; - function beforeEach(fn: HookFn): void; - function afterEach(fn: HookFn): void; - /* The mock function surface: mock.fn, mock.method and their t.mock - * twins, the tracker's restoreAll and reset, and the calls log and the - * implementation controls on fn.mock. */ - interface MockFunctionCall< - F extends Function, - ReturnType = F extends (...args: any) => infer T ? T : unknown, - Args = F extends (...args: infer Y) => any ? Y : unknown[], - > { - arguments: Args; - error: unknown | undefined; - result: ReturnType | undefined; - this: unknown; - } - interface MockFunctionContext { - readonly calls: MockFunctionCall[]; - callCount(): number; - mockImplementation(implementation: F): void; - mockImplementationOnce(implementation: F, onCall?: number): void; - resetCalls(): void; - restore(): void; - } - type Mock = F & { mock: MockFunctionContext }; - interface MockFunctionOptions { - times?: number; - } - type FunctionPropertyNames = { - [K in keyof T]: T[K] extends Function ? K : never; - }[keyof T]; - interface MockTracker { - fn undefined>(original?: F): Mock; - fn undefined, Implementation extends Function = F>( - original?: F, - implementation?: Implementation, - ): Mock; - method>( - object: MockedObject, - methodName: MethodName, - options?: MockFunctionOptions, - ): MockedObject[MethodName] extends Function ? Mock : never; - method< - MockedObject extends object, - MethodName extends FunctionPropertyNames, - Implementation extends Function, - >( - object: MockedObject, - methodName: MethodName, - implementation: Implementation, - options?: MockFunctionOptions, - ): MockedObject[MethodName] extends Function - ? Mock - : never; - reset(): void; - restoreAll(): void; - readonly timers: MockTimers; - } - interface MockTimersOptions { - apis: ReadonlyArray<"setInterval" | "setTimeout" | "setImmediate" | "Date">; - now?: number | Date | undefined; - } - interface MockTimers { - enable(options?: MockTimersOptions): void; - setTime(milliseconds?: number): void; - reset(): void; - tick(milliseconds?: number): void; - runAll(): void; - [Symbol.dispose](): void; - } - const mock: MockTracker; - /* The context of the test, suite or hook running in the current async - * context; undefined outside one. */ - function getTestContext(): TestContext | SuiteContext | undefined; - /* Declared for typechecking only — every use fences (no lowering). - * `assert` is the module-level custom-assertion registry (v24's - * `const { assert } = require('node:test')`), not TestContext.assert. */ - const assert: { register(name: string, fn: (...args: unknown[]) => unknown): void }; - function run(options?: unknown): unknown; - function snapshot(...args: unknown[]): unknown; - export { - test, - it, - describe, - suite, - before, - after, - beforeEach, - afterEach, - mock, - assert, - getTestContext, - run, - snapshot, - TestContext, - SuiteContext, - TestOptions, - Mock, - MockFunctionContext, - MockFunctionCall, - MockTracker, - MockTimers, - MockTimersOptions, - }; - export default test; -} - -declare module "assert" { - function assert(value: unknown, message?: string): void; - namespace assert { - interface AssertionErrorOptions { - message?: string | undefined; - actual?: unknown; - expected?: unknown; - operator?: string | undefined; - stackStartFn?: Function | undefined; - diff?: "simple" | "full" | undefined; - } - class AssertionError extends Error { - constructor(options: AssertionErrorOptions); - actual: unknown; - expected: unknown; - generatedMessage: boolean; - code: "ERR_ASSERTION"; - operator: string; - } - function ok(value: unknown, message?: string): void; - function strictEqual(actual: unknown, expected: unknown, message?: string): void; - function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function equal(actual: unknown, expected: unknown, message?: string): void; - function notEqual(actual: unknown, expected: unknown, message?: string): void; - function deepEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - function fail(message?: string): never; - function throws(fn: () => unknown, expected?: unknown, message?: string): void; - function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; - function rejects( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function doesNotReject( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function match(value: string, regExp: RegExp, message?: string): void; - function doesNotMatch(value: string, regExp: RegExp, message?: string): void; - function ifError(value: unknown): void; - const strict: typeof import("assert/strict"); - interface AssertOptions { - diff?: "simple" | "full" | undefined; - strict?: boolean | undefined; - skipPrototype?: boolean | undefined; - } - type AssertMethodNames = - | "deepEqual" - | "deepStrictEqual" - | "doesNotMatch" - | "doesNotReject" - | "doesNotThrow" - | "equal" - | "fail" - | "ifError" - | "match" - | "notDeepEqual" - | "notDeepStrictEqual" - | "notEqual" - | "notStrictEqual" - | "ok" - | "partialDeepStrictEqual" - | "rejects" - | "strictEqual" - | "throws"; - interface Assert extends Pick {} - interface AssertStrict extends Pick {} - const Assert: { - new (options?: AssertOptions & { strict?: true | undefined }): AssertStrict; - new (options: AssertOptions): Assert; - }; - } - export = assert; -} -declare module "node:assert" { - import assert = require("assert"); - export = assert; -} -/* node:assert/strict — the same surface with the loose names bound to the - * strict comparisons (equal IS strictEqual, ...), exactly Node's aliasing; - * the module object is callable like assert's. */ -declare module "assert/strict" { - function strict(value: unknown, message?: string): void; - namespace strict { - function ok(value: unknown, message?: string): void; - function equal(actual: unknown, expected: unknown, message?: string): void; - function notEqual(actual: unknown, expected: unknown, message?: string): void; - function deepEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - function strictEqual(actual: unknown, expected: unknown, message?: string): void; - function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function fail(message?: string): never; - function throws(fn: () => unknown, expected?: unknown, message?: string): void; - function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; - function rejects( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function doesNotReject( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function match(value: string, regExp: RegExp, message?: string): void; - function doesNotMatch(value: string, regExp: RegExp, message?: string): void; - function ifError(value: unknown): void; - const strict: typeof import("assert/strict"); - type AssertOptions = import("assert").AssertOptions; - type Assert = import("assert").Assert; - type AssertStrict = import("assert").AssertStrict; - const Assert: typeof import("assert").Assert; - } - export = strict; -} -declare module "node:assert/strict" { - import strict = require("assert/strict"); - export = strict; -} - -/* node:string_decoder — StringDecoder over Node's whole encoding set: - * write() decodes complete sequences and buffers a trailing partial one - * across chunk boundaries (utf8's truncated sequence, base64's mod-3 - * remainder, utf16le's odd byte / trailing lead surrogate; latin1, ascii - * and hex are stateless); end() flushes the buffered partial. `encoding` - * answers the normalized name. Node's exact algorithms (SEMANTICS.md); - * the encoding must be a literal, and end(buffer) fences — write the - * buffer, then end(). */ -declare module "string_decoder" { - export class StringDecoder { - constructor(encoding?: BufferEncoding); - readonly encoding: string; - write(buffer: Uint8Array): string; - end(buffer?: Uint8Array): string; - } -} -declare module "node:string_decoder" { - export * from "string_decoder"; -} - -/* node:querystring — Node's legacy query-string codec (NOT - * URLSearchParams: '+' means space on the parse side and escape encodes - * spaces as %20). parse answers the null-prototype dictionary as a pure - * index-signature record (repeated keys become string[] buckets; - * @types/node's Dict adds an undefined arm the lowering tolerates); - * stringify serializes string/number/boolean values and arrays of those - * (Node's rules: arrays expand to repeated keys, null/undefined and - * anything else serialize as the empty value). The maxKeys option lowers - * (0 removes the cap, Node's rule); the custom encoder/decoder options - * typecheck under the options-record stance and fence by name at the - * call. decode/encode are Node's own aliases of parse/stringify. */ -declare module "querystring" { - export interface ParseOptions { - maxKeys?: number; - decodeURIComponent?: (str: string) => string; - [option: string]: unknown; - } - export interface StringifyOptions { - encodeURIComponent?: (str: string) => string; - [option: string]: unknown; - } - export interface ParsedUrlQuery { - [key: string]: string | string[] | undefined; - } - export interface ParsedUrlQueryInput { - [key: string]: - | string - | number - | boolean - | bigint - | ReadonlyArray - | null - | undefined; - } - export function parse( - str: string, - sep?: string | null, - eq?: string | null, - options?: ParseOptions, - ): ParsedUrlQuery; - export function stringify( - obj?: ParsedUrlQueryInput, - sep?: string | null, - eq?: string | null, - options?: StringifyOptions, - ): string; - export const decode: typeof parse; - export const encode: typeof stringify; - export function escape(str: string): string; - export function unescape(str: string): string; -} -declare module "node:querystring" { - export * from "querystring"; -} - -/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and - * toUnicode throw its RangeErrors on malformed input; ucs2 converts - * between strings and UTF-16 code point arrays. */ -declare module "punycode" { - export function decode(string: string): string; - export function encode(string: string): string; - export function toUnicode(domain: string): string; - export function toASCII(domain: string): string; - export namespace ucs2 { - function decode(string: string): number[]; - function encode(codePoints: readonly number[]): string; - } - export const version: string; -} -declare module "node:punycode" { - export * from "punycode"; -} - -/* node:constants (deprecated, DEP0008) — one frozen object: os.constants' - * dlopen, errno, priority and signals groups, fs.constants, and - * crypto.constants. A platform's missing constant reads as undefined. */ -declare module "constants" { - const constants: { readonly [name: string]: number } & { - readonly defaultCoreCipherList: string; - readonly defaultCipherList: string; - }; - export = constants; -} -declare module "node:constants" { - import constants = require("constants"); - export = constants; -} - /* node:readline — the question/close slice: createInterface over exactly * { input: process.stdin, output: process.stdout }, question(query, cb) * (the query writes to stdout, the callback gets the next line's text), @@ -4919,2484 +1642,6 @@ declare module "node:readline/promises" { export * from "readline/promises"; } -/* node:zlib — the one-shot zlib/raw/gzip codecs lower for string/Buffer - * inputs with Node's default options; libz links only into zlib-using - * binaries. Callback forms use the executable worker pool. Options stay - * declared so reached uses receive a named SC2020 refusal. The Deflate, - * Inflate, Gzip, Gunzip, DeflateRaw, InflateRaw, Unzip, BrotliCompress - * and BrotliDecompress streams are runtime Transform streams; Zstd remains - * outside the lowered surface. */ -declare module "zlib" { - import { Transform } from "stream"; - export interface ZlibOptions { - [option: string]: unknown; - } - export type ZlibCallback = (error: NodeJS.ErrnoException | null, result: Buffer) => void; - export function deflate(data: string | Uint8Array, callback: ZlibCallback): void; - export function deflate( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function deflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function inflate(data: string | Uint8Array, callback: ZlibCallback): void; - export function inflate( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function inflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function deflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; - export function deflateRaw( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function deflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function inflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; - export function inflateRaw( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function inflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function gzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function gzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function gzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function gunzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function gunzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function gunzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function unzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function unzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function unzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function crc32(data: string | Uint8Array, value?: number): number; - /* Brotli over the vendored brotli 1.2.0 (scr_brotli.c): `params` keys - * are the numeric BROTLI_PARAM_* and BROTLI_DECODER_PARAM_* constants. */ - export interface BrotliOptions { - [option: string]: unknown; - } - export function brotliCompress(data: string | Uint8Array, callback: ZlibCallback): void; - export function brotliCompress( - data: string | Uint8Array, - options: BrotliOptions, - callback: ZlibCallback, - ): void; - export function brotliCompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; - export function brotliDecompress(data: string | Uint8Array, callback: ZlibCallback): void; - export function brotliDecompress( - data: string | Uint8Array, - options: BrotliOptions, - callback: ZlibCallback, - ): void; - export function brotliDecompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; - /* The stream classes: runtime Transform streams over the zlib engine - * (scr_zlib_stream.c). createGzip(options) and new Gzip(options) build - * the same stream; options are validated at runtime with Node's rules. - * Brotli streams have no params(), as in Node. */ - class ZlibBase extends Transform { - readonly bytesWritten: number; - flush(kind?: number, callback?: () => void): void; - flush(callback: () => void): void; - close(callback?: () => void): void; - reset(): void; - } - class Zlib extends ZlibBase { - params(level: number, strategy: number, callback?: () => void): void; - } - export class BrotliCompress extends ZlibBase { - constructor(options?: BrotliOptions); - } - export class BrotliDecompress extends ZlibBase { - constructor(options?: BrotliOptions); - } - export function createBrotliCompress(options?: BrotliOptions): BrotliCompress; - export function createBrotliDecompress(options?: BrotliOptions): BrotliDecompress; - export class Deflate extends Zlib { - constructor(options?: ZlibOptions); - } - export class Inflate extends Zlib { - constructor(options?: ZlibOptions); - } - export class Gzip extends Zlib { - constructor(options?: ZlibOptions); - } - export class Gunzip extends Zlib { - constructor(options?: ZlibOptions); - } - export class DeflateRaw extends Zlib { - constructor(options?: ZlibOptions); - } - export class InflateRaw extends Zlib { - constructor(options?: ZlibOptions); - } - export class Unzip extends Zlib { - constructor(options?: ZlibOptions); - } - export function createDeflate(options?: ZlibOptions): Deflate; - export function createInflate(options?: ZlibOptions): Inflate; - export function createGzip(options?: ZlibOptions): Gzip; - export function createGunzip(options?: ZlibOptions): Gunzip; - export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; - export function createInflateRaw(options?: ZlibOptions): InflateRaw; - export function createUnzip(options?: ZlibOptions): Unzip; - /* Node v24 zlib.constants, literal-typed (the CryptoConstants stance): - * every access bakes as its literal; the object never materializes. The - * members outside BROTLI_* are also module properties (zlib.Z_SYNC_FLUSH). */ - export interface ZlibConstants { - readonly Z_NO_FLUSH: 0; - readonly Z_PARTIAL_FLUSH: 1; - readonly Z_SYNC_FLUSH: 2; - readonly Z_FULL_FLUSH: 3; - readonly Z_FINISH: 4; - readonly Z_BLOCK: 5; - readonly Z_OK: 0; - readonly Z_STREAM_END: 1; - readonly Z_NEED_DICT: 2; - readonly Z_ERRNO: -1; - readonly Z_STREAM_ERROR: -2; - readonly Z_DATA_ERROR: -3; - readonly Z_MEM_ERROR: -4; - readonly Z_BUF_ERROR: -5; - readonly Z_VERSION_ERROR: -6; - readonly Z_NO_COMPRESSION: 0; - readonly Z_BEST_SPEED: 1; - readonly Z_BEST_COMPRESSION: 9; - readonly Z_DEFAULT_COMPRESSION: -1; - readonly Z_FILTERED: 1; - readonly Z_HUFFMAN_ONLY: 2; - readonly Z_RLE: 3; - readonly Z_FIXED: 4; - readonly Z_DEFAULT_STRATEGY: 0; - readonly ZLIB_VERNUM: 4897; - readonly DEFLATE: 1; - readonly INFLATE: 2; - readonly GZIP: 3; - readonly GUNZIP: 4; - readonly DEFLATERAW: 5; - readonly INFLATERAW: 6; - readonly UNZIP: 7; - readonly BROTLI_DECODE: 8; - readonly BROTLI_ENCODE: 9; - readonly ZSTD_DECOMPRESS: 11; - readonly ZSTD_COMPRESS: 10; - readonly Z_MIN_WINDOWBITS: 8; - readonly Z_MAX_WINDOWBITS: 15; - readonly Z_DEFAULT_WINDOWBITS: 15; - readonly Z_MIN_CHUNK: 64; - readonly Z_MAX_CHUNK: number; - readonly Z_DEFAULT_CHUNK: 16384; - readonly Z_MIN_MEMLEVEL: 1; - readonly Z_MAX_MEMLEVEL: 9; - readonly Z_DEFAULT_MEMLEVEL: 8; - readonly Z_MIN_LEVEL: -1; - readonly Z_MAX_LEVEL: 9; - readonly Z_DEFAULT_LEVEL: -1; - readonly BROTLI_OPERATION_PROCESS: 0; - readonly BROTLI_OPERATION_FLUSH: 1; - readonly BROTLI_OPERATION_FINISH: 2; - readonly BROTLI_OPERATION_EMIT_METADATA: 3; - readonly BROTLI_PARAM_MODE: 0; - readonly BROTLI_MODE_GENERIC: 0; - readonly BROTLI_MODE_TEXT: 1; - readonly BROTLI_MODE_FONT: 2; - readonly BROTLI_DEFAULT_MODE: 0; - readonly BROTLI_PARAM_QUALITY: 1; - readonly BROTLI_MIN_QUALITY: 0; - readonly BROTLI_MAX_QUALITY: 11; - readonly BROTLI_DEFAULT_QUALITY: 11; - readonly BROTLI_PARAM_LGWIN: 2; - readonly BROTLI_MIN_WINDOW_BITS: 10; - readonly BROTLI_MAX_WINDOW_BITS: 24; - readonly BROTLI_LARGE_MAX_WINDOW_BITS: 30; - readonly BROTLI_DEFAULT_WINDOW: 22; - readonly BROTLI_PARAM_LGBLOCK: 3; - readonly BROTLI_MIN_INPUT_BLOCK_BITS: 16; - readonly BROTLI_MAX_INPUT_BLOCK_BITS: 24; - readonly BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4; - readonly BROTLI_PARAM_SIZE_HINT: 5; - readonly BROTLI_PARAM_LARGE_WINDOW: 6; - readonly BROTLI_PARAM_NPOSTFIX: 7; - readonly BROTLI_PARAM_NDIRECT: 8; - readonly BROTLI_DECODER_RESULT_ERROR: 0; - readonly BROTLI_DECODER_RESULT_SUCCESS: 1; - readonly BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2; - readonly BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3; - readonly BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0; - readonly BROTLI_DECODER_PARAM_LARGE_WINDOW: 1; - readonly BROTLI_DECODER_NO_ERROR: 0; - readonly BROTLI_DECODER_SUCCESS: 1; - readonly BROTLI_DECODER_NEEDS_MORE_INPUT: 2; - readonly BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3; - readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1; - readonly BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2; - readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3; - readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4; - readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5; - readonly BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6; - readonly BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7; - readonly BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8; - readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9; - readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10; - readonly BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11; - readonly BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12; - readonly BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13; - readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14; - readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15; - readonly BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16; - readonly BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19; - readonly BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20; - readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21; - readonly BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22; - readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25; - readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26; - readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27; - readonly BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30; - readonly BROTLI_DECODER_ERROR_UNREACHABLE: -31; - readonly ZSTD_e_continue: 0; - readonly ZSTD_e_flush: 1; - readonly ZSTD_e_end: 2; - readonly ZSTD_fast: 1; - readonly ZSTD_dfast: 2; - readonly ZSTD_greedy: 3; - readonly ZSTD_lazy: 4; - readonly ZSTD_lazy2: 5; - readonly ZSTD_btlazy2: 6; - readonly ZSTD_btopt: 7; - readonly ZSTD_btultra: 8; - readonly ZSTD_btultra2: 9; - readonly ZSTD_c_compressionLevel: 100; - readonly ZSTD_c_windowLog: 101; - readonly ZSTD_c_hashLog: 102; - readonly ZSTD_c_chainLog: 103; - readonly ZSTD_c_searchLog: 104; - readonly ZSTD_c_minMatch: 105; - readonly ZSTD_c_targetLength: 106; - readonly ZSTD_c_strategy: 107; - readonly ZSTD_c_enableLongDistanceMatching: 160; - readonly ZSTD_c_ldmHashLog: 161; - readonly ZSTD_c_ldmMinMatch: 162; - readonly ZSTD_c_ldmBucketSizeLog: 163; - readonly ZSTD_c_ldmHashRateLog: 164; - readonly ZSTD_c_contentSizeFlag: 200; - readonly ZSTD_c_checksumFlag: 201; - readonly ZSTD_c_dictIDFlag: 202; - readonly ZSTD_c_nbWorkers: 400; - readonly ZSTD_c_jobSize: 401; - readonly ZSTD_c_overlapLog: 402; - readonly ZSTD_d_windowLogMax: 100; - readonly ZSTD_CLEVEL_DEFAULT: 3; - readonly ZSTD_error_no_error: 0; - readonly ZSTD_error_GENERIC: 1; - readonly ZSTD_error_prefix_unknown: 10; - readonly ZSTD_error_version_unsupported: 12; - readonly ZSTD_error_frameParameter_unsupported: 14; - readonly ZSTD_error_frameParameter_windowTooLarge: 16; - readonly ZSTD_error_corruption_detected: 20; - readonly ZSTD_error_checksum_wrong: 22; - readonly ZSTD_error_literals_headerWrong: 24; - readonly ZSTD_error_dictionary_corrupted: 30; - readonly ZSTD_error_dictionary_wrong: 32; - readonly ZSTD_error_dictionaryCreation_failed: 34; - readonly ZSTD_error_parameter_unsupported: 40; - readonly ZSTD_error_parameter_combination_unsupported: 41; - readonly ZSTD_error_parameter_outOfBound: 42; - readonly ZSTD_error_tableLog_tooLarge: 44; - readonly ZSTD_error_maxSymbolValue_tooLarge: 46; - readonly ZSTD_error_maxSymbolValue_tooSmall: 48; - readonly ZSTD_error_stabilityCondition_notRespected: 50; - readonly ZSTD_error_stage_wrong: 60; - readonly ZSTD_error_init_missing: 62; - readonly ZSTD_error_memory_allocation: 64; - readonly ZSTD_error_workSpace_tooSmall: 66; - readonly ZSTD_error_dstSize_tooSmall: 70; - readonly ZSTD_error_srcSize_wrong: 72; - readonly ZSTD_error_dstBuffer_null: 74; - readonly ZSTD_error_noForwardProgress_destFull: 80; - readonly ZSTD_error_noForwardProgress_inputEmpty: 82; - } - export const constants: ZlibConstants; - export const Z_NO_FLUSH: 0; - export const Z_PARTIAL_FLUSH: 1; - export const Z_SYNC_FLUSH: 2; - export const Z_FULL_FLUSH: 3; - export const Z_FINISH: 4; - export const Z_BLOCK: 5; - export const Z_OK: 0; - export const Z_STREAM_END: 1; - export const Z_NEED_DICT: 2; - export const Z_ERRNO: -1; - export const Z_STREAM_ERROR: -2; - export const Z_DATA_ERROR: -3; - export const Z_MEM_ERROR: -4; - export const Z_BUF_ERROR: -5; - export const Z_VERSION_ERROR: -6; - export const Z_NO_COMPRESSION: 0; - export const Z_BEST_SPEED: 1; - export const Z_BEST_COMPRESSION: 9; - export const Z_DEFAULT_COMPRESSION: -1; - export const Z_FILTERED: 1; - export const Z_HUFFMAN_ONLY: 2; - export const Z_RLE: 3; - export const Z_FIXED: 4; - export const Z_DEFAULT_STRATEGY: 0; - export const ZLIB_VERNUM: 4897; - export const DEFLATE: 1; - export const INFLATE: 2; - export const GZIP: 3; - export const GUNZIP: 4; - export const DEFLATERAW: 5; - export const INFLATERAW: 6; - export const UNZIP: 7; - export const ZSTD_DECOMPRESS: 11; - export const ZSTD_COMPRESS: 10; - export const Z_MIN_WINDOWBITS: 8; - export const Z_MAX_WINDOWBITS: 15; - export const Z_DEFAULT_WINDOWBITS: 15; - export const Z_MIN_CHUNK: 64; - export const Z_MAX_CHUNK: number; - export const Z_DEFAULT_CHUNK: 16384; - export const Z_MIN_MEMLEVEL: 1; - export const Z_MAX_MEMLEVEL: 9; - export const Z_DEFAULT_MEMLEVEL: 8; - export const Z_MIN_LEVEL: -1; - export const Z_MAX_LEVEL: 9; - export const Z_DEFAULT_LEVEL: -1; - export const ZSTD_e_continue: 0; - export const ZSTD_e_flush: 1; - export const ZSTD_e_end: 2; - export const ZSTD_fast: 1; - export const ZSTD_dfast: 2; - export const ZSTD_greedy: 3; - export const ZSTD_lazy: 4; - export const ZSTD_lazy2: 5; - export const ZSTD_btlazy2: 6; - export const ZSTD_btopt: 7; - export const ZSTD_btultra: 8; - export const ZSTD_btultra2: 9; - export const ZSTD_c_compressionLevel: 100; - export const ZSTD_c_windowLog: 101; - export const ZSTD_c_hashLog: 102; - export const ZSTD_c_chainLog: 103; - export const ZSTD_c_searchLog: 104; - export const ZSTD_c_minMatch: 105; - export const ZSTD_c_targetLength: 106; - export const ZSTD_c_strategy: 107; - export const ZSTD_c_enableLongDistanceMatching: 160; - export const ZSTD_c_ldmHashLog: 161; - export const ZSTD_c_ldmMinMatch: 162; - export const ZSTD_c_ldmBucketSizeLog: 163; - export const ZSTD_c_ldmHashRateLog: 164; - export const ZSTD_c_contentSizeFlag: 200; - export const ZSTD_c_checksumFlag: 201; - export const ZSTD_c_dictIDFlag: 202; - export const ZSTD_c_nbWorkers: 400; - export const ZSTD_c_jobSize: 401; - export const ZSTD_c_overlapLog: 402; - export const ZSTD_d_windowLogMax: 100; - export const ZSTD_CLEVEL_DEFAULT: 3; - export const ZSTD_error_no_error: 0; - export const ZSTD_error_GENERIC: 1; - export const ZSTD_error_prefix_unknown: 10; - export const ZSTD_error_version_unsupported: 12; - export const ZSTD_error_frameParameter_unsupported: 14; - export const ZSTD_error_frameParameter_windowTooLarge: 16; - export const ZSTD_error_corruption_detected: 20; - export const ZSTD_error_checksum_wrong: 22; - export const ZSTD_error_literals_headerWrong: 24; - export const ZSTD_error_dictionary_corrupted: 30; - export const ZSTD_error_dictionary_wrong: 32; - export const ZSTD_error_dictionaryCreation_failed: 34; - export const ZSTD_error_parameter_unsupported: 40; - export const ZSTD_error_parameter_combination_unsupported: 41; - export const ZSTD_error_parameter_outOfBound: 42; - export const ZSTD_error_tableLog_tooLarge: 44; - export const ZSTD_error_maxSymbolValue_tooLarge: 46; - export const ZSTD_error_maxSymbolValue_tooSmall: 48; - export const ZSTD_error_stabilityCondition_notRespected: 50; - export const ZSTD_error_stage_wrong: 60; - export const ZSTD_error_init_missing: 62; - export const ZSTD_error_memory_allocation: 64; - export const ZSTD_error_workSpace_tooSmall: 66; - export const ZSTD_error_dstSize_tooSmall: 70; - export const ZSTD_error_srcSize_wrong: 72; - export const ZSTD_error_dstBuffer_null: 74; - export const ZSTD_error_noForwardProgress_destFull: 80; - export const ZSTD_error_noForwardProgress_inputEmpty: 82; -} -declare module "node:zlib" { - export * from "zlib"; -} - -/* node:net — the inbound-networking slice (scr_net.c links only into - * net-using binaries). TCP only, Node's shapes narrowed to what lowers: - * createServer with an optional connection handler; listen(port[, cb]) - * with the real port discoverable as address().port (the composed read - * is the lowered form — bare address() has no lowering); close([cb]) - * firing once connections drain; socket write/end/destroy/pipe and the - * data/end/close/error/connect/drain events (on and once — listeners - * take at most the declared parameter). write returns Node's - * backpressure boolean; end returns void here (Node returns the socket); - * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to - * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, - * and an 'error' with no listener exits 1 like an unhandled EventEmitter - * 'error'. */ -declare module "net" { - export interface Socket { - /* Whether the socket keeps the event loop alive (ref'd by default). */ - ref(): void; - unref(): void; - readonly remoteAddress: string | undefined; - /* The peer's fields are cached at first read, like remoteAddress; the - * local ones are undefined while connecting and after close. */ - readonly remotePort: number | undefined; - readonly remoteFamily: string | undefined; - readonly localAddress: string | undefined; - readonly localPort: number | undefined; - readonly localFamily: string | undefined; - readonly bytesRead: number; - readonly connecting: boolean; - readonly pending: boolean; - readonly readyState: "opening" | "open" | "readOnly" | "writeOnly" | "closed"; - /* true once the fd is gone (destroy() or full close) — Node's flag. */ - readonly destroyed: boolean; - /* the write half is open: no end() yet, no FIN sent, fd alive. */ - readonly writable: boolean; - /* true until the read half is done (peer FIN / destroy). */ - readonly readable: boolean; - /* every byte the write paths accepted (buffered included — Node - * counts those too; plaintext on TLS sockets). */ - readonly bytesWritten: number; - /* write's callback runs once the data is flushed, with null; end's - * once the socket finishes. */ - /* false when the bytes this write left unsent reach the 65536-byte - * high-water mark, as in Node; 'drain' fires when they are all sent. */ - write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; - /* the bytes written and not yet sent */ - readonly writableLength: number; - /* a write answered false and 'drain' has not fired yet */ - readonly writableNeedDrain: boolean; - end(callback?: () => void): void; - end(data: string | Uint8Array, callback?: () => void): void; - destroy(): void; - /* Flow control: pause holds reads off (kernel/TCP backpressure is - * the buffer); resume flows — and discards without listeners, so - * 'end' stays reachable. Both chain, Node's shape. */ - pause(): Socket; - resume(): Socket; - /* TCP_NODELAY on the live fd; missing means true. Chains. */ - setNoDelay(noDelay?: boolean): Socket; - /* SO_KEEPALIVE with the initial delay in ms. Chains. */ - setKeepAlive(enable?: boolean, initialDelay?: number): Socket; - /* end() now, destroy once the FIN actually flushed. */ - destroySoon(): void; - /* An RST instead of a FIN; a connecting socket resets once it - * connects. Throws ERR_INVALID_HANDLE_TYPE on a TLS socket. */ - resetAndDestroy(): Socket; - /* The local address, or {} while connecting and once destroyed. */ - address(): AddressInfo | {}; - /* setEncoding('utf8'): 'data' delivers strings — the IncomingMessage - * twin's contract. */ - setEncoding(encoding: string): void; - /* socket→socket, and socket→response (the extended-CONNECT bridge - * leg: raw chunks become response body writes; EOF ends it). */ - pipe( - destination: Socket | import("http").ServerResponse | import("http2").Http2ServerResponse, - ): void; - /* The callback is a once('timeout') listener (removed again by 0). */ - setTimeout(ms: number, callback?: () => void): void; - /* The paused-mode demux surface: once('readable') + read(1) + - * unshift — read answers exactly n buffered bytes or null. */ - read(size?: number): Buffer | null; - unshift(chunk: Uint8Array): void; - /* secureConnect/secure/session are TLS-socket events declared on the - * shared socket surface (the lowering collapses TLSSocket onto this kind): - * the runtime gates on the transport, so a plain socket's - * registration never fires — Node's exact split. 'session' fires - * once with the serialized session (the received-ticket event). */ - on(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - on(event: "data", listener: (chunk: string) => void): void; - on( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - on(event: "close", listener: (hadError: boolean) => void): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - on( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - addListener(event: "data", listener: (chunk: string) => void): void; - addListener( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - addListener(event: "close", listener: (hadError: boolean) => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - addListener( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - once(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - once(event: "data", listener: (chunk: string) => void): void; - once( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - once(event: "close", listener: (hadError: boolean) => void): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - once( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - } - export interface Server { - /* Whether the server keeps the event loop alive (ref'd by default). */ - ref(): void; - unref(): void; - readonly listening: boolean; - /* Connections past this many are closed with a 'drop' event; - * undefined (the default) or null lifts the cap. */ - maxConnections: number | undefined; - /* Node reads it only in cluster mode: a stored value here. */ - dropMaxConnection: boolean | undefined; - /* cb(null, count) on the next tick. */ - getConnections(callback: (error: Error | null, count: number) => void): Server; - /* Node answers the server itself (`return this` chaining). */ - listen(port: number, callback?: () => void): Server; - /* The positional bind address (the options form's host in argument - * spelling). */ - listen(port: number, host: string, callback?: () => void): Server; - /* A Unix-socket server; the socket file is removed when it closes. */ - listen(path: string, callback?: () => void): Server; - /* The explicit-interface bind: host is an IP literal (absent = the - * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort - * requests kernel connection distribution where Node supports it. */ - listen( - options: { port: number; host?: string; ipv6Only?: boolean; reusePort?: boolean }, - callback?: () => void, - ): Server; - close(callback?: () => void): void; - /* The bound AddressInfo (Node answers null before listen — this - * surface answers the record with port 0 there, the serverPort - * stance). */ - address(): import("node:dgram").AddressInfo; - /* The demux route: hand an accepted socket to another server. */ - emit(event: "connection", socket: Socket): boolean; - on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - on( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - on(event: "close" | "listening", listener: () => void): void; - on(event: "drop", listener: (data?: DropArgument) => void): void; - on(event: "timeout", listener: (socket: Socket) => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* The WebSocket handover: fires INSTEAD of 'request' for - * Connection: upgrade requests, with the raw socket + head bytes; - * 'connect' is its twin for CONNECT requests (HTTP tunneling). */ - on( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - /* http.createServer(handler)'s event twin (a no-parser net server - * never fires it, like Node). */ - on( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - addListener( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - addListener(event: "close" | "listening", listener: () => void): void; - addListener(event: "drop", listener: (data?: DropArgument) => void): void; - addListener(event: "timeout", listener: (socket: Socket) => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - addListener( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - once( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - once(event: "close" | "listening", listener: () => void): void; - once(event: "drop", listener: (data?: DropArgument) => void): void; - once(event: "timeout", listener: (socket: Socket) => void): void; - once(event: "error", listener: (err: Error) => void): void; - once( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - once( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - } - /* The 'drop' payload: the turned-away connection's addresses. */ - export interface DropArgument { - localAddress?: string; - localPort?: number; - localFamily?: string; - remoteAddress?: string; - remotePort?: number; - remoteFamily?: string; - } - export type AddressInfo = import("node:dgram").AddressInfo; - export type IPVersion = "ipv4" | "ipv6"; - export interface SocketAddressInitOptions { - address?: string; - family?: IPVersion; - flowlabel?: number; - port?: number; - } - /* A native handle (scr_net.c); the getters read its parsed address. */ - export class SocketAddress { - constructor(options?: SocketAddressInitOptions); - readonly address: string; - readonly family: IPVersion; - readonly port: number; - readonly flowlabel: number; - toJSON(): { address: string; port: number; family: IPVersion; flowlabel: number }; - /* undefined when the input is not "[:]" */ - static parse(input: string): SocketAddress | undefined; - static isSocketAddress(value: unknown): value is SocketAddress; - } - /* Address, range and subnet rules; check() matches IPv4 addresses - * against IPv6 rules through their IPv4-mapped form, as Node does. */ - export class BlockList { - constructor(); - readonly rules: readonly string[]; - readonly size: number; - addAddress(address: string | SocketAddress, type?: IPVersion): void; - addAddresses(addresses: readonly (string | SocketAddress)[], type?: IPVersion): void; - addRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; - addSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; - addCIDR(cidr: string): void; - addCIDRs(cidrs: readonly string[]): void; - removeAddress(address: string | SocketAddress, type?: IPVersion): void; - removeRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; - removeSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; - removeCIDR(cidr: string): void; - clear(): void; - check(address: string | SocketAddress, type?: IPVersion): boolean; - toJSON(): string[]; - fromJSON(data: string | readonly string[]): void; - static isBlockList(value: unknown): value is BlockList; - static readonly PRIVATE_RANGES: readonly string[]; - } - /* Node's IPv4Reg and IPv6Reg tests: 4, 6, or 0 for isIP. */ - export function isIP(input: string): number; - export function isIPv4(input: string): boolean; - export function isIPv6(input: string): boolean; - /* The Socket VALUE — the `x instanceof net.Socket` narrowing target - * (the h2 compat 'connect' listener's test). Constructing bare sockets - * has no lowering; the construct signature exists for instanceof. */ - export const Socket: abstract new () => Socket; - export function createServer(connectionListener?: (socket: Socket) => void): Server; - export function connect(port: number, host?: string, connectListener?: () => void): Socket; - export function createConnection( - port: number, - host?: string, - connectListener?: () => void, - ): Socket; - /* The caller-resolver dial (@types/node's net.LookupFunction): the - * runtime invokes it as Node's autoSelectFamily dial does (options - * { hints, all: true }, so the callback answers an address list) and - * dials the answered addresses in order. */ - export type LookupFunction = ( - hostname: string, - options: import("node:dns").LookupOptions, - callback: ( - err: NodeJS.ErrnoException | null, - address: string | import("node:dns").LookupAddress[], - family?: number, - ) => void, - ) => void; - export function connect( - options: { - port: number; - host?: string; - autoSelectFamily?: boolean; - lookup?: LookupFunction; - }, - connectListener?: () => void, - ): Socket; - export function createConnection( - options: { - port: number; - host?: string; - autoSelectFamily?: boolean; - lookup?: LookupFunction; - }, - connectListener?: () => void, - ): Socket; - /* The happy-eyeballs attempt budget (Node's process-wide knob; the - * autoSelectFamily dial consults it): one runtime int, default 250ms - * like Node's. */ - export function getDefaultAutoSelectFamilyAttemptTimeout(): number; - export function setDefaultAutoSelectFamilyAttemptTimeout(value: number): void; -} -declare module "node:net" { - export * from "net"; -} - -/* node:http — the SERVER and CLIENT slices (scr_http.c layers a - * hand-written HTTP/1.1 parser + serializer on scr_net.c; both link only - * into using binaries). createServer(handler) returns a net.Server — - * listen/close/address().port/'error' are the same lowered surface. The - * request carries url/method (always present on server requests), - * statusCode (a number on client responses, undefined on server - * requests), the underlying socket, and lowercased headers read as - * `string | undefined`; the body streams through on("data"/"end"). The - * response is setHeader/writeHead/write/end/destroy with Node's framing - * (Content-Length for a single end(body), chunked after writeHead/write) - * and the auto Date/Connection headers; writeHead's headers argument is - * an object literal or a Record. request/get dial one - * connection per call (no agent pooling) with Node's exact wire head. */ -declare module "http" { - import { Server as NetServer, Socket } from "net"; - export const METHODS: string[]; - export const STATUS_CODES: { [status: number]: string | undefined }; - export const maxHeaderSize: number; - export interface Server extends NetServer { - timeout: number; - setTimeout(msecs: number, callback?: (socket: Socket) => void): this; - closeAllConnections(): void; - closeIdleConnections(): void; - keepAliveTimeout: number; - keepAliveTimeoutBuffer: number; - headersTimeout: number; - requestTimeout: number; - } - export type RequestListener = (req: IncomingMessage, res: ServerResponse) => void; - /* The Server VALUE — Node's constructor works with and without `new` - * (test/parallel's http.Server(fn) spelling); both route to the - * createServer lowering. */ - export const Server: { - new (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; - new ( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; - ( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - }; - /* The options-record stance (the RequestOptions precedent): every - * documented key typechecks; the lowering's option walk decides per - * key — requireHostHeader: false, joinDuplicateHeaders, and - * keepAliveTimeoutBuffer lower, - * documented-but-unlowered keys fence by name, unknown keys drop. */ - export interface ServerOptions { - requireHostHeader?: boolean; - joinDuplicateHeaders?: boolean; - keepAliveTimeoutBuffer?: number; - maxHeaderSize?: number; - [option: string]: unknown; - } - /* The outgoing-header shape, @types/node's matrix: numbers format via - * String(n), arrays write one line per element (set-cookie's shape). - * Incoming headers stay `string | undefined` — what the parser answers - * (under @types/node the two SLOTS unify so `{ ...req.headers }` - * merges into outgoing literals as a copy; the fallback keeps reads - * simple and spreads target the same string | undefined slot). */ - export type OutgoingHttpHeaders = { [name: string]: number | string | string[] | undefined }; - export interface IncomingMessage { - readonly url: string; - readonly method: string; - readonly httpVersion: string; - readonly complete: boolean; - readonly aborted: boolean; - readonly statusCode: number | undefined; - readonly statusMessage: string | undefined; - readonly socket: Socket; - readonly connection: Socket; - readonly headers: { [name: string]: string | undefined }; - readonly headersDistinct: { [name: string]: string[] | undefined }; - readonly rawHeaders: string[]; - readonly trailers: { [name: string]: string | undefined }; - readonly trailersDistinct: { [name: string]: string[] | undefined }; - readonly rawTrailers: string[]; - resume(): void; - pause(): void; - destroy(): void; - setTimeout(msecs: number, callback?: () => void): this; - /* setEncoding('utf8'): 'data' delivers strings (other real encodings - * throw a "not supported yet" Error at runtime; unknown names throw - * ERR_UNKNOWN_ENCODING). */ - setEncoding(encoding: string): void; - /* The proxy legs: the body streams into a ServerResponse, a - * ClientRequest, or a raw Socket; natural end ends the destination. */ - pipe( - destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket, - ): void; - on(event: "data", listener: (chunk: any) => void): void; - on(event: "end" | "close" | "aborted", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "data", listener: (chunk: any) => void): void; - addListener(event: "end" | "close" | "aborted", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once(event: "end" | "close" | "aborted", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export interface ServerResponse { - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly finished: boolean; - readonly writableFinished: boolean; - readonly writableCorked: number; - readonly req: IncomingMessage; - readonly socket: Socket | null; - readonly connection: Socket | null; - sendDate: boolean; - strictContentLength: boolean; - /* Node's writable head properties: the implicit head reads them. */ - statusCode: number; - statusMessage: string | undefined; - /* The value keeps its type: getHeader answers the number, string or - * array that was set. */ - setHeader(name: string, value: number | string | readonly string[]): void; - appendHeader(name: string, value: string | readonly string[]): void; - getHeader(name: string): number | string | string[] | undefined; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): OutgoingHttpHeaders; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - /* Both overloads answer the response (`return this` chaining); the - * headers argument also takes Node's flat [name, value, ...] array. */ - writeHead(statusCode: number, headers?: OutgoingHttpHeaders | string[]): ServerResponse; - writeHead( - statusCode: number, - statusMessage: string, - headers?: OutgoingHttpHeaders | string[], - ): ServerResponse; - writeContinue(callback?: () => void): void; - writeProcessing(callback?: () => void): void; - writeEarlyHints(hints: Record, callback?: () => void): void; - write(data: string | Uint8Array): void; - flushHeaders(): void; - cork(): void; - uncork(): void; - addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; - setTimeout(msecs: number, callback?: () => void): this; - /* end's callback forms fire once the body went out (the 'finish' - * emit, deferred past the handler's synchronous tail). */ - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - destroy(): void; - on(event: "close" | "finish", listener: () => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "close" | "finish", listener: () => void): void; - once(event: "close" | "finish", listener: () => void): void; - } - /* The CLIENT slice (http.request/http.get): the options-object form - * with hostname/host, port, path, method, timeout, and headers; the - * callback receives the response as an IncomingMessage (statusCode, - * headers, body via on("data"/"end")). */ - export interface RequestOptions { - hostname?: string | null; - host?: string | null; - /* A string port (a URL's .port) is accepted, as by Node. */ - port?: number | string | null; - path?: string | null; - method?: string; - timeout?: number; - headers?: OutgoingHttpHeaders; - /* The caller's own dialer (the proxy's loopback dial): invoked once, - * synchronously; its socket carries the exchange. */ - createConnection?: () => Socket; - /* agent: false is a one-shot dial with Connection: close — exactly - * the compiled client's model; null/undefined are the default; an - * Agent VALUE threads through (getName-keyed maxSockets accounting - * over one-dial-per-request connections). */ - agent?: Agent | boolean | null; - /* Aborting destroys the request with an AbortError. */ - signal?: AbortSignal; - /* HTTP over the Unix socket at this path instead of host:port. */ - socketPath?: string; - /* The options-record stance: Node ignores an options record's - * unknown keys, so every key typechecks here — the option WALK in - * the lowering decides per key (lower, fence by name, or drop - * undocumented keys exactly like Node). */ - [option: string]: unknown; - } - /* The Agent lowers to a checked-dynamic HANDLE: option parsing at the - * literal construction site, getName, destroy, the sockets/requests/ - * freeSockets snapshots, and REAL maxSockets accounting (over-limit - * requests defer their dial and queue). keepAlive: true keeps finished - * connections in freeSockets for the next request to the same name, - * taken in `scheduling` order. */ - export interface AgentOptions { - keepAlive?: boolean; - keepAliveMsecs?: number; - maxSockets?: number; - maxFreeSockets?: number; - maxTotalSockets?: number; - scheduling?: "fifo" | "lifo"; - timeout?: number; - port?: number; - [option: string]: unknown; - } - export class Agent { - constructor(options?: AgentOptions); - destroy(): void; - /* Node's exact key shape: host:port:localAddress[:family][:socketPath]. */ - getName(options?: { - host?: string; - port?: number; - localAddress?: string; - family?: number; - socketPath?: string; - [option: string]: unknown; - }): string; - maxSockets: number; - maxFreeSockets: number; - keepAliveMsecs: number; - readonly keepAlive: boolean; - readonly protocol: string; - /* Settable — a portless request through this agent dials it (Node's - * option merge). */ - defaultPort: number; - readonly totalSocketCount: number; - readonly sockets: { [key: string]: unknown[] }; - readonly freeSockets: { [key: string]: unknown[] }; - readonly requests: { [key: string]: unknown[] }; - } - /* Node's keepAlive global agent for requests without an agent option. */ - export const globalAgent: Agent; - export interface ClientRequest { - readonly destroyed: boolean; - readonly aborted: boolean; - readonly socket: Socket; - readonly connection: Socket; - readonly writableCorked: number; - readonly method: string; - readonly path: string; - readonly host: string; - readonly protocol: string; - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly finished: boolean; - readonly reusedSocket: boolean; - setHeader(name: string, value: string): void; - getHeader(name: string): string | undefined; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): OutgoingHttpHeaders; - flushHeaders(): void; - addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; - cork(): void; - uncork(): void; - setNoDelay(noDelay?: boolean): void; - setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; - setTimeout(timeout: number, callback?: () => void): this; - write(data: string | Uint8Array): void; - end(data?: string | Uint8Array): void; - destroy(error?: Error): void; - abort(): void; - on(event: "response", listener: (res: IncomingMessage) => void): void; - on(event: "socket", listener: (socket: Socket) => void): void; - on( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - on(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "response", listener: (res: IncomingMessage) => void): void; - addListener(event: "socket", listener: (socket: Socket) => void): void; - addListener( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - addListener(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "response", listener: (res: IncomingMessage) => void): void; - once(event: "socket", listener: (socket: Socket) => void): void; - once( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - once(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export function createServer( - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - export function createServer( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - /* One signature with a UNION first parameter rather than Node's two - * overloads: `const requestFn = tls ? https.request : http.request` - * (the portless isProxyRunning shape) unions two function types, and - * resolving a call on that union collapses each constituent to a - * single signature — with an overload SET on both sides the options - * literal ends up checked against URL, in either declaration order. - * A union parameter has nothing to collapse. The lowering dispatches - * on the argument's own type, so it reads the same either way. - * - * `target` is a URL string or URL object (the spelling a from-scratch - * client reaches for first — dialed directly, with a non-http scheme - * an ERR_INVALID_PROTOCOL at runtime rather than a type error), or - * the options record. */ - export function request( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function get( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function validateHeaderName(name: string, label?: string): void; - export function validateHeaderValue(name: string, value: unknown): void; -} -declare module "node:http" { - export * from "http"; -} - -/* node:tls — the TLS server slice (scr_tls.c over scr_net.c with the - * vendored mbedTLS; linked only into using binaries). - * createServer({ cert, key }, handler) returns a net.Server; the handler - * is Node's 'secureConnection' — it fires post-handshake with a socket - * that behaves exactly like a net socket (write/end/destroy/pipe and the - * data/end/close/error events are the same lowered surface). cert/key - * are PEM strings or Buffers. Handshake failures tear the socket down - * without emitting an event, Node's default for 'tlsClientError' with no listener. */ -declare module "tls" { - import { Server as NetServer, Socket } from "net"; - /** tls.Server: a net server whose connections handshake first. */ - export interface Server extends NetServer { - /* serve `context` to clients whose SNI name matches `hostname` ('*' - * matches within one label); the newest match wins */ - addContext(hostname: string, context: TlsOptions | SecureContext): void; - /* new connections serve a context made from `options` */ - setSecureContext(options: TlsOptions): void; - } - export interface TlsOptions { - /* Optional in the DECLARATION only (real Node programs build these - * literals with every TLS knob): the lowering still requires both — - * a literal without them fences at the call site. */ - cert?: string | Uint8Array; - key?: string | Uint8Array; - /* The options-record stance (see http.RequestOptions): every key - * typechecks; the option walk fences documented-but-unlowered keys - * by name and drops undocumented ones like Node. */ - [option: string]: unknown; - } - export function createServer( - options: TlsOptions, - secureConnectionListener?: (socket: TLSSocket) => void, - ): Server; - /** TLSSocket — post-handshake it IS a net socket to every lowered - * member (the mapping collapses it onto the socket kind; the - * secureConnect/session events ride the shared socket declarations). - * The TLS extras: authorized/authorizationError answer Node's verify - * split — authorizationError is the verify-failure CODE STRING - * (DEPTH_ZERO_SELF_SIGNED_CERT, ...), or null. */ - export interface TLSSocket extends Socket { - readonly encrypted: true; - authorized: boolean; - authorizationError: string | null; - /* null before the handshake, then the negotiated protocol or false */ - readonly alpnProtocol: string | false | null; - /* "TLSv1.2" or "TLSv1.3"; null before the handshake and after close */ - getProtocol(): string | null; - /* OpenSSL's suite name, the IANA name and the suite's version */ - getCipher(): CipherNameAndProtocol; - /* whether the handshake resumed a cached session */ - isSessionReused(): boolean; - /* the peer's certificate: {} before the handshake or without one, - * null once the socket is destroyed; detailed adds issuerCertificate */ - getPeerCertificate(detailed: true): DetailedPeerCertificate; - getPeerCertificate(detailed?: false): PeerCertificate; - getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate; - /* the certificate this socket serves, the same way */ - getCertificate(): PeerCertificate | object | null; - /* a new X509Certificate on each call, or undefined without one */ - getPeerX509Certificate(): import("crypto").X509Certificate | undefined; - getX509Certificate(): import("crypto").X509Certificate | undefined; - } - /* A certificate as getPeerCertificate() and X509Certificate's - * toLegacyObject() answer it: subject and issuer map attribute short - * names to values, infoAccess its " - " keys to values. */ - export interface PeerCertificate { - subject: { [attribute: string]: string | string[] }; - issuer: { [attribute: string]: string | string[] }; - subjectaltname?: string; - infoAccess?: { [key: string]: string[] }; - ca: boolean; - modulus?: string; - exponent?: string; - pubkey?: Buffer; - bits?: number; - valid_from: string; - valid_to: string; - fingerprint: string; - fingerprint256: string; - fingerprint512: string; - ext_key_usage?: string[]; - serialNumber: string; - raw: Buffer; - asn1Curve?: string; - nistCurve?: string; - } - /* getPeerCertificate(true): each certificate links to its issuer; a - * self-issued root is its own issuerCertificate */ - export interface DetailedPeerCertificate extends PeerCertificate { - issuerCertificate: DetailedPeerCertificate; - } - - /** new tls.TLSSocket(socket, options): the server-mode wrap of an open - * socket (a server connection or an http CONNECT socket). The options - * record reads at runtime; isServer, cert, key, secureContext, - * ALPNProtocols, minVersion and maxVersion are implemented. */ - export interface TLSSocketOptions { - isServer?: boolean; - cert?: string | Uint8Array; - key?: string | Uint8Array; - secureContext?: SecureContext; - ALPNProtocols?: string[] | Uint8Array; - minVersion?: string; - maxVersion?: string; - [option: string]: unknown; - } - export const TLSSocket: new (socket: Socket, options: TLSSocketOptions) => TLSSocket; - export interface CipherNameAndProtocol { - name: string; - standardName: string; - version: string; - } - export const DEFAULT_MIN_VERSION: string; - export const DEFAULT_MAX_VERSION: string; - /** tls.connect options — the RUNTIME options record (members read at - * runtime; port/host/socket/rejectUnauthorized/ca/servername are the - * honestly-implemented set, every other documented member throws the - * runtime fence — the options-record stance's divergence-66 shape). */ - export interface ConnectionOptions { - port?: number; - host?: string; - /* an open socket to run the client handshake over */ - socket?: Socket; - rejectUnauthorized?: boolean; - ca?: string | Uint8Array | ReadonlyArray; - servername?: string; - [option: string]: unknown; - } - export function connect( - options: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - export function connect( - port: number, - options?: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - export function connect( - port: number, - host?: string, - options?: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - /** An opaque parsed cert/key pair — what an SNI callback answers with - * (the value is a runtime handle; the members are runtime-internal). */ - export interface SecureContext {} - /* @types/node's name for createSecureContext's options */ - export type SecureContextOptions = TlsOptions; - export function createSecureContext(options: TlsOptions): SecureContext; - /** The CA-store introspection surface (scr_tls_ca.c): per-type cached - * PEM string arrays. The host bundle stands in for Node's compiled-in - * Mozilla roots ('bundled', and rootCertificates) AND the platform - * store ('system') — the Windows certificate stores or POSIX bundle probe; - * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws - * Node's ERR_INVALID_ARG_VALUE TypeError. */ - export function getCACertificates(type?: string): string[]; - export const rootCertificates: readonly string[]; - /** Replaces the 'default' set (deduped) and the trust anchors the TLS - * client verifies against; a certificate-free non-empty array throws - * Node's ERR_CRYPTO_OPERATION_FAILED Error. */ - export function setDefaultCACertificates(certs: readonly string[]): void; -} -declare module "node:tls" { - export * from "tls"; -} - -/* node:https — http over the TLS transport (scr_tls.c). - * createServer({ cert, key }, handler) is the http server behind a - * handshake; request/get are the http client with port 443 default and - * the local-CA knobs: `ca` (PEM string/Buffer) replaces the trust - * anchors, rejectUnauthorized: false disables verification (the - * self-signed probe shape). With neither, /etc/ssl/cert.pem stands in - * for Node's bundled roots. */ -declare module "https" { - import { Server } from "http"; - import { Agent, ClientRequest, IncomingMessage, ServerResponse } from "http"; - export { Agent, Server }; - /* Node's keepAlive global agent for requests without an agent option. */ - export const globalAgent: Agent; - export interface ServerOptions { - cert: string | Uint8Array; - key: string | Uint8Array; - /* The options-record stance (see http.RequestOptions). */ - [option: string]: unknown; - } - export interface RequestOptions { - hostname?: string; - host?: string; - port?: number; - path?: string; - method?: string; - timeout?: number; - headers?: { [name: string]: string }; - rejectUnauthorized?: boolean; - ca?: string | Uint8Array; - agent?: Agent | boolean | null; - /* Aborting destroys the request with an AbortError. */ - signal?: AbortSignal; - socketPath?: string; - /* The options-record stance (see http.RequestOptions). */ - [option: string]: unknown; - } - export function createServer( - options: ServerOptions, - requestListener: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - /* One signature with a union first parameter, for the request-fn - * ternary's sake (see the http block). A URL target takes no options, - * which means Node's defaults — the certificate is verified against - * the default trust anchors. */ - export function request( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function get( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; -} -declare module "node:https" { - export * from "https"; -} - -/* node:http2 — the compatibility surface: allowHTTP1 servers negotiate - * h2 or HTTP/1.1 and expose the shared request/response API. HTTP/2 - * requests retain their backing stream; server session failures surface - * through sessionError. */ -declare module "http2" { - import { Socket } from "net"; - export interface Http2Stream { - on(event: "error" | "close", listener: (err: Error) => void): void; - once(event: "error" | "close", listener: (err: Error) => void): void; - addListener(event: "error" | "close", listener: (err: Error) => void): void; - destroy(): void; - } - export interface Http2ServerRequest { - readonly url: string; - readonly method: string; - readonly statusCode: number | undefined; - readonly socket: Socket; - readonly headers: { [name: string]: string | undefined }; - readonly stream: Http2Stream; - /* The h2 compat request's version triple ("2.0" over real h2 streams; - * the allowHTTP1 lowering's HTTP/1.1 connections answer 1.x). */ - readonly httpVersion: string; - readonly httpVersionMajor: number; - readonly httpVersionMinor: number; - readonly aborted: boolean; - readonly complete: boolean; - resume(): void; - destroy(): void; - setEncoding(encoding: string): void; - pipe(destination: Socket | import("http").ClientRequest): void; - on(event: "data", listener: (chunk: Buffer) => void): void; - on(event: "end" | "close" | "aborted", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — and the compat req IS the - * http.IncomingMessage surface, so the alias mirrors here too. */ - addListener(event: "data", listener: (chunk: Buffer) => void): void; - addListener(event: "end" | "close" | "aborted", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "data", listener: (chunk: Buffer) => void): void; - once(event: "end" | "close" | "aborted", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export interface Http2ServerResponse { - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly writableCorked: number; - /* The same lowered surface as http.ServerResponse — the allowHTTP1 - * lowering serves every connection as HTTP/1.1, where the compat - * response IS this parser's response handle. */ - statusCode: number; - statusMessage: string; - setHeader(name: string, value: string | number): void; - getHeader(name: string): string | undefined; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): import("http").OutgoingHttpHeaders; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - writeHead( - statusCode: number, - headers?: import("http").OutgoingHttpHeaders | string[], - ): Http2ServerResponse; - writeHead( - statusCode: number, - statusMessage: string, - headers?: import("http").OutgoingHttpHeaders | string[], - ): Http2ServerResponse; - writeContinue(): void; - writeEarlyHints(hints: Record): void; - write(data: string | Uint8Array): void; - flushHeaders(): void; - cork(): void; - uncork(): void; - addTrailers( - headers: import("http").OutgoingHttpHeaders | ReadonlyArray<[string, string]>, - ): void; - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - destroy(): void; - on(event: "close", listener: () => void): void; - /* addListener IS on (Node aliases them) — and the compat res IS the - * http.ServerResponse surface (assignability into ServerResponse - * unions — IncomingMessage.pipe's destination — needs the alias). */ - addListener(event: "close", listener: () => void): void; - once(event: "close", listener: () => void): void; - } - export interface Http2SecureServer { - listen(port: number, callback?: () => void): void; - close(callback?: () => void): void; - /* The bound AddressInfo (Node answers null before listen — this - * surface answers the record with port 0 there, the serverPort - * stance). */ - address(): import("node:dgram").AddressInfo; - emit(event: "connection", socket: Socket): void; - on(event: "connection", listener: (socket: Socket) => void): void; - on( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "close", listener: () => void): void; - on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - /* The ALPN=h2 server's surface (createSecureServer WITHOUT - * allowHTTP1 — the real h2-over-TLS stack): per-stream and - * per-session listeners, exactly the h2c server's. */ - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on(event: "session", listener: (session: ServerHttp2Session) => void): void; - /* HTTP CONNECT (and, in Node, the h2 extended CONNECT — which the - * allowHTTP1 lowering never sees): the second argument is the raw - * socket on HTTP/1.1 connections; narrow with instanceof. */ - on( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - once(event: "connection", listener: (socket: Socket) => void): void; - once( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "close", listener: () => void): void; - once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once(event: "session", listener: (session: ServerHttp2Session) => void): void; - once( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - addListener(event: "connection", listener: (socket: Socket) => void): void; - addListener( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "close", listener: () => void): void; - addListener( - event: "sessionError", - listener: (err: Error, session: ServerHttp2Session) => void, - ): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; - addListener( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - } - export interface SecureServerOptions { - allowHTTP1?: boolean; - cert?: string | Uint8Array; - key?: string | Uint8Array; - SNICallback?: ( - servername: string, - cb: (err: Error | null, ctx?: import("tls").SecureContext) => void, - ) => void; - /* The options-record stance (see http.RequestOptions): h2 session- - * tuning knobs are accepted as literals and ignored (no h2 session - * ever exists under the allowHTTP1 lowering); other documented keys - * fence by name; undocumented keys drop like Node. */ - [option: string]: unknown; - } - /* The full Node v24 constants table (240 members), literal-typed so - * computed header keys ([HTTP2_HEADER_PATH]: value) stay literal keys. - * Every ACCESS bakes as its literal at lowering; the object itself - * never materializes (bare http2.constants value uses fence). */ - export interface Http2Constants { - readonly NGHTTP2_ERR_FRAME_SIZE_ERROR: -522; - readonly NGHTTP2_SESSION_SERVER: 0; - readonly NGHTTP2_SESSION_CLIENT: 1; - readonly NGHTTP2_STREAM_STATE_IDLE: 1; - readonly NGHTTP2_STREAM_STATE_OPEN: 2; - readonly NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3; - readonly NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4; - readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5; - readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6; - readonly NGHTTP2_STREAM_STATE_CLOSED: 7; - readonly NGHTTP2_FLAG_NONE: 0; - readonly NGHTTP2_FLAG_END_STREAM: 1; - readonly NGHTTP2_FLAG_END_HEADERS: 4; - readonly NGHTTP2_FLAG_ACK: 1; - readonly NGHTTP2_FLAG_PADDED: 8; - readonly NGHTTP2_FLAG_PRIORITY: 32; - readonly DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096; - readonly DEFAULT_SETTINGS_ENABLE_PUSH: 1; - readonly DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295; - readonly DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535; - readonly DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384; - readonly DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535; - readonly DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0; - readonly MAX_MAX_FRAME_SIZE: 16777215; - readonly MIN_MAX_FRAME_SIZE: 16384; - readonly MAX_INITIAL_WINDOW_SIZE: 2147483647; - readonly NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1; - readonly NGHTTP2_SETTINGS_ENABLE_PUSH: 2; - readonly NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3; - readonly NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4; - readonly NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5; - readonly NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6; - readonly NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8; - readonly PADDING_STRATEGY_NONE: 0; - readonly PADDING_STRATEGY_ALIGNED: 1; - readonly PADDING_STRATEGY_MAX: 2; - readonly PADDING_STRATEGY_CALLBACK: 1; - readonly NGHTTP2_NO_ERROR: 0; - readonly NGHTTP2_PROTOCOL_ERROR: 1; - readonly NGHTTP2_INTERNAL_ERROR: 2; - readonly NGHTTP2_FLOW_CONTROL_ERROR: 3; - readonly NGHTTP2_SETTINGS_TIMEOUT: 4; - readonly NGHTTP2_STREAM_CLOSED: 5; - readonly NGHTTP2_FRAME_SIZE_ERROR: 6; - readonly NGHTTP2_REFUSED_STREAM: 7; - readonly NGHTTP2_CANCEL: 8; - readonly NGHTTP2_COMPRESSION_ERROR: 9; - readonly NGHTTP2_CONNECT_ERROR: 10; - readonly NGHTTP2_ENHANCE_YOUR_CALM: 11; - readonly NGHTTP2_INADEQUATE_SECURITY: 12; - readonly NGHTTP2_HTTP_1_1_REQUIRED: 13; - readonly NGHTTP2_DEFAULT_WEIGHT: 16; - readonly HTTP2_HEADER_STATUS: ":status"; - readonly HTTP2_HEADER_METHOD: ":method"; - readonly HTTP2_HEADER_AUTHORITY: ":authority"; - readonly HTTP2_HEADER_SCHEME: ":scheme"; - readonly HTTP2_HEADER_PATH: ":path"; - readonly HTTP2_HEADER_PROTOCOL: ":protocol"; - readonly HTTP2_HEADER_ACCEPT_ENCODING: "accept-encoding"; - readonly HTTP2_HEADER_ACCEPT_LANGUAGE: "accept-language"; - readonly HTTP2_HEADER_ACCEPT_RANGES: "accept-ranges"; - readonly HTTP2_HEADER_ACCEPT: "accept"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: "access-control-allow-credentials"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: "access-control-allow-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: "access-control-allow-methods"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: "access-control-allow-origin"; - readonly HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: "access-control-expose-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: "access-control-request-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: "access-control-request-method"; - readonly HTTP2_HEADER_AGE: "age"; - readonly HTTP2_HEADER_AUTHORIZATION: "authorization"; - readonly HTTP2_HEADER_CACHE_CONTROL: "cache-control"; - readonly HTTP2_HEADER_CONNECTION: "connection"; - readonly HTTP2_HEADER_CONTENT_DISPOSITION: "content-disposition"; - readonly HTTP2_HEADER_CONTENT_ENCODING: "content-encoding"; - readonly HTTP2_HEADER_CONTENT_LENGTH: "content-length"; - readonly HTTP2_HEADER_CONTENT_TYPE: "content-type"; - readonly HTTP2_HEADER_COOKIE: "cookie"; - readonly HTTP2_HEADER_DATE: "date"; - readonly HTTP2_HEADER_ETAG: "etag"; - readonly HTTP2_HEADER_FORWARDED: "forwarded"; - readonly HTTP2_HEADER_HOST: "host"; - readonly HTTP2_HEADER_IF_MODIFIED_SINCE: "if-modified-since"; - readonly HTTP2_HEADER_IF_NONE_MATCH: "if-none-match"; - readonly HTTP2_HEADER_IF_RANGE: "if-range"; - readonly HTTP2_HEADER_LAST_MODIFIED: "last-modified"; - readonly HTTP2_HEADER_LINK: "link"; - readonly HTTP2_HEADER_LOCATION: "location"; - readonly HTTP2_HEADER_RANGE: "range"; - readonly HTTP2_HEADER_REFERER: "referer"; - readonly HTTP2_HEADER_SERVER: "server"; - readonly HTTP2_HEADER_SET_COOKIE: "set-cookie"; - readonly HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: "strict-transport-security"; - readonly HTTP2_HEADER_TRANSFER_ENCODING: "transfer-encoding"; - readonly HTTP2_HEADER_TE: "te"; - readonly HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: "upgrade-insecure-requests"; - readonly HTTP2_HEADER_UPGRADE: "upgrade"; - readonly HTTP2_HEADER_USER_AGENT: "user-agent"; - readonly HTTP2_HEADER_VARY: "vary"; - readonly HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: "x-content-type-options"; - readonly HTTP2_HEADER_X_FRAME_OPTIONS: "x-frame-options"; - readonly HTTP2_HEADER_KEEP_ALIVE: "keep-alive"; - readonly HTTP2_HEADER_PROXY_CONNECTION: "proxy-connection"; - readonly HTTP2_HEADER_X_XSS_PROTECTION: "x-xss-protection"; - readonly HTTP2_HEADER_ALT_SVC: "alt-svc"; - readonly HTTP2_HEADER_CONTENT_SECURITY_POLICY: "content-security-policy"; - readonly HTTP2_HEADER_EARLY_DATA: "early-data"; - readonly HTTP2_HEADER_EXPECT_CT: "expect-ct"; - readonly HTTP2_HEADER_ORIGIN: "origin"; - readonly HTTP2_HEADER_PURPOSE: "purpose"; - readonly HTTP2_HEADER_TIMING_ALLOW_ORIGIN: "timing-allow-origin"; - readonly HTTP2_HEADER_X_FORWARDED_FOR: "x-forwarded-for"; - readonly HTTP2_HEADER_PRIORITY: "priority"; - readonly HTTP2_HEADER_ACCEPT_CHARSET: "accept-charset"; - readonly HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: "access-control-max-age"; - readonly HTTP2_HEADER_ALLOW: "allow"; - readonly HTTP2_HEADER_CONTENT_LANGUAGE: "content-language"; - readonly HTTP2_HEADER_CONTENT_LOCATION: "content-location"; - readonly HTTP2_HEADER_CONTENT_MD5: "content-md5"; - readonly HTTP2_HEADER_CONTENT_RANGE: "content-range"; - readonly HTTP2_HEADER_DNT: "dnt"; - readonly HTTP2_HEADER_EXPECT: "expect"; - readonly HTTP2_HEADER_EXPIRES: "expires"; - readonly HTTP2_HEADER_FROM: "from"; - readonly HTTP2_HEADER_IF_MATCH: "if-match"; - readonly HTTP2_HEADER_IF_UNMODIFIED_SINCE: "if-unmodified-since"; - readonly HTTP2_HEADER_MAX_FORWARDS: "max-forwards"; - readonly HTTP2_HEADER_PREFER: "prefer"; - readonly HTTP2_HEADER_PROXY_AUTHENTICATE: "proxy-authenticate"; - readonly HTTP2_HEADER_PROXY_AUTHORIZATION: "proxy-authorization"; - readonly HTTP2_HEADER_REFRESH: "refresh"; - readonly HTTP2_HEADER_RETRY_AFTER: "retry-after"; - readonly HTTP2_HEADER_TRAILER: "trailer"; - readonly HTTP2_HEADER_TK: "tk"; - readonly HTTP2_HEADER_VIA: "via"; - readonly HTTP2_HEADER_WARNING: "warning"; - readonly HTTP2_HEADER_WWW_AUTHENTICATE: "www-authenticate"; - readonly HTTP2_HEADER_HTTP2_SETTINGS: "http2-settings"; - readonly HTTP2_METHOD_ACL: "ACL"; - readonly HTTP2_METHOD_BASELINE_CONTROL: "BASELINE-CONTROL"; - readonly HTTP2_METHOD_BIND: "BIND"; - readonly HTTP2_METHOD_CHECKIN: "CHECKIN"; - readonly HTTP2_METHOD_CHECKOUT: "CHECKOUT"; - readonly HTTP2_METHOD_CONNECT: "CONNECT"; - readonly HTTP2_METHOD_COPY: "COPY"; - readonly HTTP2_METHOD_DELETE: "DELETE"; - readonly HTTP2_METHOD_GET: "GET"; - readonly HTTP2_METHOD_HEAD: "HEAD"; - readonly HTTP2_METHOD_LABEL: "LABEL"; - readonly HTTP2_METHOD_LINK: "LINK"; - readonly HTTP2_METHOD_LOCK: "LOCK"; - readonly HTTP2_METHOD_MERGE: "MERGE"; - readonly HTTP2_METHOD_MKACTIVITY: "MKACTIVITY"; - readonly HTTP2_METHOD_MKCALENDAR: "MKCALENDAR"; - readonly HTTP2_METHOD_MKCOL: "MKCOL"; - readonly HTTP2_METHOD_MKREDIRECTREF: "MKREDIRECTREF"; - readonly HTTP2_METHOD_MKWORKSPACE: "MKWORKSPACE"; - readonly HTTP2_METHOD_MOVE: "MOVE"; - readonly HTTP2_METHOD_OPTIONS: "OPTIONS"; - readonly HTTP2_METHOD_ORDERPATCH: "ORDERPATCH"; - readonly HTTP2_METHOD_PATCH: "PATCH"; - readonly HTTP2_METHOD_POST: "POST"; - readonly HTTP2_METHOD_PRI: "PRI"; - readonly HTTP2_METHOD_PROPFIND: "PROPFIND"; - readonly HTTP2_METHOD_PROPPATCH: "PROPPATCH"; - readonly HTTP2_METHOD_PUT: "PUT"; - readonly HTTP2_METHOD_REBIND: "REBIND"; - readonly HTTP2_METHOD_REPORT: "REPORT"; - readonly HTTP2_METHOD_SEARCH: "SEARCH"; - readonly HTTP2_METHOD_TRACE: "TRACE"; - readonly HTTP2_METHOD_UNBIND: "UNBIND"; - readonly HTTP2_METHOD_UNCHECKOUT: "UNCHECKOUT"; - readonly HTTP2_METHOD_UNLINK: "UNLINK"; - readonly HTTP2_METHOD_UNLOCK: "UNLOCK"; - readonly HTTP2_METHOD_UPDATE: "UPDATE"; - readonly HTTP2_METHOD_UPDATEREDIRECTREF: "UPDATEREDIRECTREF"; - readonly HTTP2_METHOD_VERSION_CONTROL: "VERSION-CONTROL"; - readonly HTTP_STATUS_CONTINUE: 100; - readonly HTTP_STATUS_SWITCHING_PROTOCOLS: 101; - readonly HTTP_STATUS_PROCESSING: 102; - readonly HTTP_STATUS_EARLY_HINTS: 103; - readonly HTTP_STATUS_OK: 200; - readonly HTTP_STATUS_CREATED: 201; - readonly HTTP_STATUS_ACCEPTED: 202; - readonly HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203; - readonly HTTP_STATUS_NO_CONTENT: 204; - readonly HTTP_STATUS_RESET_CONTENT: 205; - readonly HTTP_STATUS_PARTIAL_CONTENT: 206; - readonly HTTP_STATUS_MULTI_STATUS: 207; - readonly HTTP_STATUS_ALREADY_REPORTED: 208; - readonly HTTP_STATUS_IM_USED: 226; - readonly HTTP_STATUS_MULTIPLE_CHOICES: 300; - readonly HTTP_STATUS_MOVED_PERMANENTLY: 301; - readonly HTTP_STATUS_FOUND: 302; - readonly HTTP_STATUS_SEE_OTHER: 303; - readonly HTTP_STATUS_NOT_MODIFIED: 304; - readonly HTTP_STATUS_USE_PROXY: 305; - readonly HTTP_STATUS_TEMPORARY_REDIRECT: 307; - readonly HTTP_STATUS_PERMANENT_REDIRECT: 308; - readonly HTTP_STATUS_BAD_REQUEST: 400; - readonly HTTP_STATUS_UNAUTHORIZED: 401; - readonly HTTP_STATUS_PAYMENT_REQUIRED: 402; - readonly HTTP_STATUS_FORBIDDEN: 403; - readonly HTTP_STATUS_NOT_FOUND: 404; - readonly HTTP_STATUS_METHOD_NOT_ALLOWED: 405; - readonly HTTP_STATUS_NOT_ACCEPTABLE: 406; - readonly HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407; - readonly HTTP_STATUS_REQUEST_TIMEOUT: 408; - readonly HTTP_STATUS_CONFLICT: 409; - readonly HTTP_STATUS_GONE: 410; - readonly HTTP_STATUS_LENGTH_REQUIRED: 411; - readonly HTTP_STATUS_PRECONDITION_FAILED: 412; - readonly HTTP_STATUS_PAYLOAD_TOO_LARGE: 413; - readonly HTTP_STATUS_URI_TOO_LONG: 414; - readonly HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415; - readonly HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416; - readonly HTTP_STATUS_EXPECTATION_FAILED: 417; - readonly HTTP_STATUS_TEAPOT: 418; - readonly HTTP_STATUS_MISDIRECTED_REQUEST: 421; - readonly HTTP_STATUS_UNPROCESSABLE_ENTITY: 422; - readonly HTTP_STATUS_LOCKED: 423; - readonly HTTP_STATUS_FAILED_DEPENDENCY: 424; - readonly HTTP_STATUS_TOO_EARLY: 425; - readonly HTTP_STATUS_UPGRADE_REQUIRED: 426; - readonly HTTP_STATUS_PRECONDITION_REQUIRED: 428; - readonly HTTP_STATUS_TOO_MANY_REQUESTS: 429; - readonly HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431; - readonly HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451; - readonly HTTP_STATUS_INTERNAL_SERVER_ERROR: 500; - readonly HTTP_STATUS_NOT_IMPLEMENTED: 501; - readonly HTTP_STATUS_BAD_GATEWAY: 502; - readonly HTTP_STATUS_SERVICE_UNAVAILABLE: 503; - readonly HTTP_STATUS_GATEWAY_TIMEOUT: 504; - readonly HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505; - readonly HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506; - readonly HTTP_STATUS_INSUFFICIENT_STORAGE: 507; - readonly HTTP_STATUS_LOOP_DETECTED: 508; - readonly HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509; - readonly HTTP_STATUS_NOT_EXTENDED: 510; - readonly HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511; - } - export const constants: Http2Constants; - /* The eager handler is the first COMPAT 'request' listener — Node's - * route on either flavor (the allowHTTP1 server's HTTP/1.1 handles, - * the ALPN=h2 server's compat handles over streams). */ - export function createSecureServer( - options: SecureServerOptions, - onRequestHandler?: ( - req: import("http").IncomingMessage, - res: import("http").ServerResponse, - ) => void, - ): Http2SecureServer; - - /* ── the REAL h2c surface (scr_http2.c: frame codec + HPACK over the - * net loop) — createServer/connect and the session/stream handles the - * Node suite's http2 family exercises. Headers cross as the canonical - * header record (the IncomingHttpHeaders index-signature shape); the - * response :status reads back as a number. Members declared here - * without a lowering fence by name at their use sites. */ - - /** The http2 module's own header shapes: pure index-signature records - * (the HEADER-FAMILY canonicalization — string[] arm included). */ - export interface IncomingHttpHeaders { - [name: string]: number | string | string[] | undefined; - } - export interface OutgoingHttpHeaders { - [name: string]: number | string | string[] | undefined; - } - - export interface Http2Session { - close(callback?: () => void): void; - destroy(): void; - readonly closed: boolean; - readonly destroyed: boolean; - readonly encrypted: boolean; - readonly type: number; - readonly alpnProtocol: string; - readonly socket: import("net").Socket; - readonly pendingSettingsAck: boolean; - ref(): void; - unref(): void; - setTimeout(msecs: number, callback?: () => void): void; - ping(callback: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean; - /* The settings surface rides the checked-dynamic machinery (records - * cross as dyn values — the suite's mustCall listeners are dyn). */ - settings(settings?: any, callback?: any): void; - readonly localSettings: any; - readonly remoteSettings: any; - goaway(code?: number, lastStreamID?: number, opaqueData?: Uint8Array): void; - on(event: "close" | "timeout", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - on( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - on(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "ping", listener: (payload: Uint8Array) => void): void; - once(event: "close" | "timeout", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - once( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - once(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "ping", listener: (payload: Uint8Array) => void): void; - addListener(event: "close" | "timeout", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - addListener(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener(event: "ping", listener: (payload: Uint8Array) => void): void; - } - - export interface ClientHttp2Session extends Http2Session { - request( - headers?: OutgoingHttpHeaders, - options?: { - endStream?: boolean; - exclusive?: boolean; - parent?: number; - weight?: number; - waitForTrailers?: boolean; - }, - ): ClientHttp2Stream; - } - - export interface ServerHttp2Session extends Http2Session { - altsvc(alt: string, originOrStream: number | string): void; - origin(...origins: string[]): void; - } - - /** The shared Http2Stream core (both roles). 'data' chunks are Buffers - * (strings once setEncoding('utf8') is in force — the IncomingMessage - * chunk:any stance keeps both working). */ - interface Http2StreamCore { - readonly id: number | undefined; - readonly rstCode: number; - readonly closed: boolean; - readonly destroyed: boolean; - readonly pending: boolean; - readonly aborted: boolean; - readonly session: Http2Session; - readonly sentHeaders: OutgoingHttpHeaders; - readonly state: Record; - write(chunk: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - close(code?: number, callback?: () => void): void; - destroy(error?: Error): void; - setEncoding(encoding: string): this; - resume(): void; - pause(): void; - setTimeout(msecs: number, callback?: () => void): void; - priority(options: Record): void; - sendTrailers(headers: OutgoingHttpHeaders): void; - } - - export interface ServerHttp2Stream extends Http2StreamCore { - on(event: "data", listener: (chunk: any) => void): void; - on( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - addListener(event: "data", listener: (chunk: any) => void): void; - addListener( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener( - event: "trailers", - listener: (trailers: IncomingHttpHeaders, flags: number) => void, - ): void; - respond( - headers?: OutgoingHttpHeaders, - options?: { endStream?: boolean; waitForTrailers?: boolean }, - ): void; - respondWithFile( - path: string, - headers?: OutgoingHttpHeaders, - options?: Record, - ): void; - respondWithFD( - fd: number, - headers?: OutgoingHttpHeaders, - options?: Record, - ): void; - pushStream( - headers: OutgoingHttpHeaders, - callback: ( - err: Error | null, - pushStream: ServerHttp2Stream, - headers: OutgoingHttpHeaders, - ) => void, - ): void; - readonly headersSent: boolean; - readonly pushAllowed: boolean; - additionalHeaders(headers: OutgoingHttpHeaders): void; - } - - export interface ClientHttp2Stream extends Http2StreamCore { - on(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "data", listener: (chunk: any) => void): void; - on( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - addListener( - event: "response", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "push", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "headers", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "data", listener: (chunk: any) => void): void; - addListener( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener( - event: "trailers", - listener: (trailers: IncomingHttpHeaders, flags: number) => void, - ): void; - } - - export interface Http2Server { - listen(port?: number, callback?: () => void): void; - listen(port: number, host: string, callback?: () => void): void; - close(callback?: () => void): void; - address(): import("node:dgram").AddressInfo; - setTimeout(msecs?: number, callback?: () => void): void; - ref(): void; - unref(): void; - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on(event: "session", listener: (session: ServerHttp2Session) => void): void; - on( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "close" | "listening" | "timeout", listener: () => void): void; - on(event: "connection", listener: (socket: import("net").Socket) => void): void; - on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once(event: "session", listener: (session: ServerHttp2Session) => void): void; - once( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "close" | "listening" | "timeout", listener: () => void): void; - once(event: "connection", listener: (socket: import("net").Socket) => void): void; - once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; - addListener( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "close" | "listening" | "timeout", listener: () => void): void; - addListener(event: "connection", listener: (socket: import("net").Socket) => void): void; - addListener( - event: "sessionError", - listener: (err: Error, session: ServerHttp2Session) => void, - ): void; - } - - export interface ServerOptions { - [option: string]: unknown; - } - - export function createServer( - onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): Http2Server; - export function createServer( - options: ServerOptions, - onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): Http2Server; - - export function connect( - authority: string, - listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, - ): ClientHttp2Session; - export function connect( - authority: string, - options: Record, - listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, - ): ClientHttp2Session; - - export function getDefaultSettings(): any; - export function getPackedSettings(settings: Record): Uint8Array; - export const sensitiveHeaders: unique symbol; -} -declare module "node:http2" { - export * from "http2"; -} - -/* node:dgram — UDP sockets over the event loop (scr_dgram.c, linked only - * into using binaries). createSocket takes "udp4" or the - * { type: "udp4", reuseAddr? } literal; bind/connect bind NOW and defer - * 'listening'/'connect' to the next loop turn; send takes one string or - * Buffer datagram with an explicit port + address (the connected-send - * and callback forms have no lowering); address() returns the real - * {address, family, port} record and THROWS "Not running" before bind, - * like Node. The socket options throw Node's errors (EBADF before bind); - * add/dropMembership before bind is refused at run time (SC2020). 'error' - * with no listener exits 1 (the unhandled EventEmitter 'error' behavior). */ -declare module "dgram" { - export interface AddressInfo { - address: string; - family: string; - port: number; - } - export interface RemoteInfo { - address: string; - family: string; - port: number; - size: number; - } - export interface Socket { - bind(port: number, address?: string, callback?: () => void): void; - connect(port: number, address?: string, callback?: () => void): void; - send(msg: string | Uint8Array, port: number, address: string): void; - address(): AddressInfo; - close(callback?: () => void): void; - disconnect(): void; - unref(): void; - ref(): void; - setBroadcast(flag: boolean): void; - setTTL(ttl: number): number; - setMulticastTTL(ttl: number): number; - setMulticastLoopback(flag: boolean): boolean; - setMulticastInterface(multicastInterface: string): void; - addMembership(multicastAddress: string, multicastInterface?: string): void; - dropMembership(multicastAddress: string, multicastInterface?: string): void; - getRecvBufferSize(): number; - getSendBufferSize(): number; - setRecvBufferSize(size: number): void; - setSendBufferSize(size: number): void; - getSendQueueSize(): number; - getSendQueueCount(): number; - on(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; - on(event: "listening" | "close" | "connect", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - once(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; - once(event: "listening" | "close" | "connect", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export function createSocket(type: "udp4"): Socket; - export function createSocket(options: { type: "udp4"; reuseAddr?: boolean }): Socket; -} -declare module "node:dgram" { - export * from "dgram"; -} - -/* node:dns — the lookup slice (scr_dgram.c hosts it; getaddrinfo runs AT - * CALL TIME and the callback defers to the next loop turn — SEMANTICS.md - * documents the blocking divergence). Only the { family: 4 } options - * form lowers; failures deliver Node's error shape ("getaddrinfo - * ENOTFOUND ") as the callback's Error argument, with "" for - * the address where Node passes undefined. */ -declare module "dns" { - export interface LookupAddress { - address: string; - family: number; - } - export interface LookupOptions { - family?: number | "IPv4" | "IPv6"; - hints?: number; - all?: boolean; - order?: "ipv4first" | "ipv6first" | "verbatim"; - verbatim?: boolean; - } - export interface LookupOneOptions extends LookupOptions { - all?: false; - } - export interface LookupAllOptions extends LookupOptions { - all: true; - } - /* The callback comes with the other lookup deliveries (literal - * addresses on the next tick); the options follow lib/dns.js. */ - export function lookup( - hostname: string, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - family: number, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - options: LookupOneOptions, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - options: LookupAllOptions, - callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void, - ): void; - /* getnameinfo with NI_NAMEREQD: the host's name and the port's service - * name (the number when it has none). */ - export function lookupService( - address: string, - port: number, - callback: (err: NodeJS.ErrnoException | null, hostname: string, service: string) => void, - ): void; - /* The nameservers /etc/resolv.conf lists. */ - export function getServers(): string[]; - export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; - export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; - /* The promise forms (node:dns/promises). */ - export const promises: typeof import("dns/promises"); -} -declare module "node:dns" { - export * from "dns"; -} -/* node:dns/promises — lookup and lookupService as promises over the same - * resolution as the callback forms: argument errors throw synchronously, - * failures reject with the callbacks' errors. */ -declare module "dns/promises" { - import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; - export function lookup(hostname: string, family: number): Promise; - export function lookup(hostname: string, options: LookupOneOptions): Promise; - export function lookup(hostname: string, options: LookupAllOptions): Promise; - export function lookup( - hostname: string, - options: LookupOptions, - ): Promise; - export function lookup(hostname: string): Promise; - export function lookupService( - address: string, - port: number, - ): Promise<{ hostname: string; service: string }>; - export function getServers(): string[]; - export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; - export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; -} -declare module "node:dns/promises" { - export * from "dns/promises"; -} - -/* node:v8 — importable for the packages that require it at top level. - * getHeapStatistics answers allocator-derived figures in Node's shape - * (approximate: the runtime heap's total and used bytes, the machine's - * memory as the limit; V8-only figures are 0). The other members observe - * V8 itself and fence at their use with the by-design reason; they are - * declared so programs that type-check under their own tsc reach that - * fence. */ -declare module "v8" { - type DoesZapCodeSpaceFlag = 0 | 1; - export interface HeapInfo { - total_heap_size: number; - total_heap_size_executable: number; - total_physical_size: number; - total_available_size: number; - used_heap_size: number; - heap_size_limit: number; - malloced_memory: number; - peak_malloced_memory: number; - does_zap_garbage: DoesZapCodeSpaceFlag; - number_of_native_contexts: number; - number_of_detached_contexts: number; - total_global_handles_size: number; - used_global_handles_size: number; - external_memory: number; - } - export function getHeapStatistics(): HeapInfo; - export function cachedDataVersionTag(): number; - export function getHeapSpaceStatistics(): unknown[]; - export function getHeapCodeStatistics(): unknown; - export function getHeapSnapshot(options?: unknown): unknown; - export function writeHeapSnapshot(fileName?: string, options?: unknown): string; - export function setFlagsFromString(flags: string): void; - export function setHeapSnapshotNearHeapLimit(limit: number): void; - export function serialize(value: unknown): Buffer; - export function deserialize(buffer: Uint8Array): unknown; - export function takeCoverage(): void; - export function stopCoverage(): void; - export function isStringOneByteRepresentation(content: string): boolean; -} -declare module "node:v8" { - export * from "v8"; -} - -/* node:worker_threads — native workers use statically compiled entry points. - * Lowering and runtime diagnostics define the supported options and methods. */ -declare module "worker_threads" { - export const isMainThread: boolean; - export const isInternalThread: boolean; - export const threadId: number; - export interface ResourceLimits { - maxYoungGenerationSizeMb?: number; - maxOldGenerationSizeMb?: number; - codeRangeSizeMb?: number; - stackSizeMb?: number; - } - /* {} in the main thread */ - export const resourceLimits: ResourceLimits; - /* the Worker's name option, trimmed ("WorkerThread" by default); "" in the main thread */ - export const threadName: string | null; - export interface WorkerOptions { - workerData?: unknown; - transferList?: readonly unknown[]; - argv?: readonly unknown[]; - name?: string; - resourceLimits?: ResourceLimits; - stdout?: boolean; - stderr?: boolean; - stdin?: boolean; - [option: string]: unknown; - } - export class Worker { - constructor(filename: string | URL, options?: WorkerOptions); - readonly threadId: number; - readonly resourceLimits?: ResourceLimits; - readonly threadName: string | null; - /* with the stdout/stderr options: the worker's writes to those streams */ - readonly stdout: import("stream").Readable; - readonly stderr: import("stream").Readable; - /* with the stdin option: what the worker reads from process.stdin */ - readonly stdin: import("stream").Writable | null; - /* terminate(), resolving with undefined once the worker exited */ - [Symbol.asyncDispose](): Promise; - on(event: "online", listener: () => void): this; - on(event: "message", listener: (value: any) => void): this; - on(event: "error", listener: (error: any) => void): this; - on(event: "exit", listener: (code: number) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - postMessage(value: unknown, transferList?: readonly unknown[]): void; - ref(): void; - unref(): void; - terminate(): Promise; - removeAllListeners(event?: string): this; - listenerCount(event: string, listener?: (...args: any[]) => void): number; - } - export class MessagePort { - on(event: "message", listener: (value: any) => void): this; - on(event: "close", listener: () => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - postMessage(value: unknown, transferList?: readonly unknown[]): void; - start(): void; - close(callback?: () => void): void; - ref(): void; - unref(): void; - hasRef(): boolean; - removeAllListeners(event?: string): this; - listenerCount(event: string, listener?: (...args: any[]) => void): number; - } - /* Two entangled ports; a port moves to another thread through a - * postMessage or workerData transferList. */ - export class MessageChannel { - readonly port1: MessagePort; - readonly port2: MessagePort; - } - export function receiveMessageOnPort(port: MessagePort): { message: any } | undefined; - /* This thread's environment data; each new Worker starts from a clone. */ - export function setEnvironmentData(key: any, value?: any): void; - export function getEnvironmentData(key: any): any; - /* A marked object cannot be listed in a transfer list. */ - export function markAsUntransferable(object: any): void; - /* structuredClone and postMessage throw DataCloneError for a marked - * object, error or class instance */ - export function markAsUncloneable(object: any): void; - export function isMarkedAsUntransferable(object: any): boolean; - /* Delivers each message to every other open channel with the name, in - * any thread, as a 'message' MessageEvent. */ - export class BroadcastChannel extends EventTarget { - constructor(name: string); - readonly name: string; - onmessage: ((event: MessageEvent) => void) | null; - onmessageerror: ((event: MessageEvent) => void) | null; - postMessage(message: unknown): void; - close(): void; - ref(): this; - unref(): this; - } - export const parentPort: MessagePort | null; - export const workerData: any; -} -declare module "node:worker_threads" { - export * from "worker_threads"; -} - -/* node:buffer — the module spelling of the Buffer/atob/btoa globals. - * Buffer re-exports the GLOBAL declaration, so an imported binding - * resolves to the same symbol the lowerings already answer for (and - * third-party .d.ts files that `import { Buffer } from "node:buffer"` — - * file-entry-cache in a typed-JS source graph — typecheck). */ -declare module "buffer" { - export function isAscii(input: ArrayBuffer | ArrayBufferView): boolean; - export function isUtf8(input: ArrayBuffer | ArrayBufferView): boolean; - export function transcode(source: Uint8Array, fromEncoding: string, toEncoding: string): Buffer; - export function atob(data: string): string; - export function btoa(data: string): string; - export const kMaxLength: number; - export const kStringMaxLength: number; - export const Buffer: BufferConstructor; - export type Blob = globalThis.Blob; - export type File = globalThis.File; - export const Blob: typeof globalThis.Blob; - export const File: typeof globalThis.File; -} -declare module "node:buffer" { - export * from "buffer"; -} - -/* node:cluster — the primary-probe slice. A compiled binary never runs - * as a cluster worker (cluster forks the node binary itself — the - * mechanism is na for compiled programs), so isPrimary lowers to `true` - * and isWorker to `false`; everything else is undeclared. */ -declare module "cluster" { - const cluster: { - readonly isPrimary: boolean; - readonly isMaster: boolean; - readonly isWorker: boolean; - }; - export = cluster; -} -declare module "node:cluster" { - import cluster = require("cluster"); - export = cluster; -} - /* node:tty — the fd probe (the same isatty(3) behind process.*.isTTY) * and the supported structural view of a process output stream. */ declare module "tty" { @@ -7418,808 +1663,6 @@ declare module "node:tty" { export * from "tty"; } -/* node:async_hooks — declared surface without lowering: async-hook - * instrumentation is guarded behind env flags in harness code; a reached - * use fences at its site. */ -declare module "async_hooks" { - /* AsyncLocalStorage: fiber-carried context snapshots — run/getStore/ - * exit/enterWith/disable lower onto the runtime's active-slot machinery - * (als.* libCalls); values cross as dyn values. bind and snapshot bind - * functions to the active context; the options carry the store's - * default value and name. */ - export interface AsyncLocalStorageOptions { - defaultValue?: any; - name?: string | undefined; - } - export class AsyncLocalStorage { - constructor(options?: AsyncLocalStorageOptions); - static bind any>(fn: Func): Func; - static snapshot(): (fn: (...args: TArgs) => R, ...args: TArgs) => R; - readonly name: string; - run(store: T, callback: (...args: any[]) => R, ...args: any[]): R; - exit(callback: (...args: any[]) => R, ...args: any[]): R; - getStore(): T | undefined; - enterWith(store: T): void; - disable(): void; - } - export interface AsyncHook { - enable(): AsyncHook; - disable(): AsyncHook; - } - export function createHook(callbacks: { - init?: (asyncId: number, type: string, triggerAsyncId: number, resource: unknown) => void; - before?: (asyncId: number) => void; - after?: (asyncId: number) => void; - destroy?: (asyncId: number) => void; - }): AsyncHook; - export function executionAsyncId(): number; - export function triggerAsyncId(): number; -} -declare module "node:async_hooks" { - export * from "async_hooks"; -} -/* node:events — the EventEmitter class (the module object IS the class, - * like Node's `module.exports = EventEmitter`). Listener/emit signatures - * mirror @types/node's untyped `...args: any[]` surface; the compiler's - * lowering unifies each event name's argument tuple program-wide and - * checks annotated listener parameters against it (unannotated non-empty - * parameter lists have no static types and fence at the registration). */ -declare module "events" { - class EventEmitter { - constructor(); - static defaultMaxListeners: number; - /* Listeners registered under this symbol observe 'error' emits first - * (an emit with no 'error' listener still throws). */ - static readonly errorMonitor: unique symbol; - static addAbortListener(signal: AbortSignal, listener: (event: Event) => void): Disposable; - on(eventName: string | symbol, listener: (...args: any[]) => void): this; - addListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - once(eventName: string | symbol, listener: (...args: any[]) => void): this; - prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - off(eventName: string | symbol, listener: (...args: any[]) => void): this; - removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - removeAllListeners(eventName?: string | symbol): this; - emit(eventName: string | symbol, ...args: any[]): boolean; - listenerCount(eventName: string | symbol, listener?: (...args: any[]) => void): number; - /* Declared as zero-parameter closures so the RESULT is statically - * consumable (.length, indexing, identity): the lowering only admits - * these calls for events whose argument tuple is empty — a listener - * pulled from the array is then safely callable too. Events with - * arguments fence (their element type would lie). */ - listeners(eventName: string): Array<() => void>; - rawListeners(eventName: string): Array<() => void>; - eventNames(): string[]; - setMaxListeners(n: number): this; - getMaxListeners(): number; - } - import internal = require("node:events"); - namespace EventEmitter { - // The class under its own name, so `import { EventEmitter }` resolves - // (the @types/node namespace-alias pattern). - export { internal as EventEmitter }; - // The event's arguments, as checked-dynamic values. - export function once( - emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, - eventName: string, - options?: { signal?: AbortSignal }, - ): Promise; - /* The emitter's own listenerCount/getMaxListeners/setMaxListeners/ - * listeners, over EventEmitter instances. */ - export function listenerCount(emitter: EventEmitter, eventName: string | symbol): number; - export function getMaxListeners(emitter: EventEmitter): number; - export function setMaxListeners(n?: number, ...eventTargets: EventEmitter[]): void; - export function getEventListeners(emitter: EventEmitter, name: string): Array<() => void>; - } - export = EventEmitter; -} -declare module "node:events" { - import events = require("events"); - export = events; -} - -/* node:stream — the static stream classes (runtime-provided, the - * EventEmitter-hierarchy precedent). Phase 1 is the OPTIONS-OBJECT - * constructor surface: `new Readable({ read() {} })`, `new Writable({ - * write(chunk, enc, cb) {} })`, Duplex/Transform/PassThrough, push/read, - * flowing vs paused, write/end backpressure + 'drain', pipe/unpipe, and - * destroy — with Node's event orderings. Chunks are BYTES (Buffer), with - * strings converted at push/write like Node, and any value on an - * object-mode side (objectMode, readableObjectMode, writableObjectMode — - * the object-mode constructor overloads come first). The runtime-fired - * events carry per-class FORCED argument tuples ('data' is one chunk, - * 'error' one Error, 'pipe'/'unpipe' one Readable, the rest empty) — the - * overloads below hand tsc the same contract so unannotated listener - * parameters contextually type in .js sources. */ -declare module "stream" { - import { EventEmitter } from "events"; - - interface ReadableOptions { - highWaterMark?: number; - encoding?: string; - objectMode?: boolean; - read?(this: Readable, size: number): void; - destroy?(this: Readable, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Readable, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - defaultEncoding?: string; - signal?: AbortSignal; - } - interface WritableOptions { - highWaterMark?: number; - decodeStrings?: boolean; - defaultEncoding?: string; - objectMode?: boolean; - write?( - this: Writable, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - writev?( - this: Writable, - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - final?(this: Writable, callback: (error?: Error | null) => void): void; - destroy?(this: Writable, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Writable, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - signal?: AbortSignal; - } - interface DuplexOptions { - highWaterMark?: number; - readableHighWaterMark?: number; - writableHighWaterMark?: number; - encoding?: string; - decodeStrings?: boolean; - objectMode?: boolean; - readableObjectMode?: boolean; - writableObjectMode?: boolean; - allowHalfOpen?: boolean; - read?(this: Duplex, size: number): void; - write?( - this: Duplex, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - final?(this: Duplex, callback: (error?: Error | null) => void): void; - destroy?(this: Duplex, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Duplex, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - readable?: boolean; - writable?: boolean; - defaultEncoding?: string; - writev?( - this: Duplex, - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - signal?: AbortSignal; - } - interface TransformOptions { - highWaterMark?: number; - readableHighWaterMark?: number; - writableHighWaterMark?: number; - encoding?: string; - decodeStrings?: boolean; - objectMode?: boolean; - readableObjectMode?: boolean; - writableObjectMode?: boolean; - allowHalfOpen?: boolean; - transform?( - this: Transform, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null, data?: Buffer | string) => void, - ): void; - flush?(this: Transform, callback: (error?: Error | null, data?: Buffer | string) => void): void; - destroy?(this: Transform, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Transform, callback: (error?: Error | null) => void): void; - read?(this: Transform, size: number): void; - write?( - this: Transform, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - final?(this: Transform, callback: (error?: Error | null) => void): void; - writev?(this: Transform, chunks: unknown[], callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - readable?: boolean; - writable?: boolean; - defaultEncoding?: string; - signal?: AbortSignal; - } - - /* Object-mode options (objectMode: true, or a Duplex's or Transform's - * readableObjectMode / writableObjectMode): chunks and completion data - * are any values, and an object-mode write's encoding argument reaches - * write/transform as passed (undefined when absent, null when a - * callback took its place). The constructors take these before the - * byte-mode options, so a byte-mode stream's callbacks keep Buffer - * chunks. */ - type ObjectModeEncoding = string | null | undefined; - type ObjectModeFlag = - | { objectMode: true } - | { readableObjectMode: true } - | { writableObjectMode: true }; - interface ObjectWritableOptions extends Omit { - objectMode: true; - write?( - this: Writable, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } - type ObjectDuplexOptions = Omit & { - write?( - this: Duplex, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } & ObjectModeFlag; - type ObjectTransformOptions = Omit & { - transform?( - this: Transform, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null, data?: any) => void, - ): void; - flush?(this: Transform, callback: (error?: Error | null, data?: any) => void): void; - write?( - this: Transform, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } & ObjectModeFlag; - - /* decodeStrings: false — a string write reaches write/transform as the - * string with its encoding (Buffers keep 'buffer'). */ - interface StringWritableOptions extends Omit { - decodeStrings: false; - write?( - this: Writable, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - } - type StringDuplexOptions = Omit & { - decodeStrings: false; - write?( - this: Duplex, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - }; - type StringTransformOptions = Omit & { - decodeStrings: false; - transform?( - this: Transform, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null, data?: Buffer | string) => void, - ): void; - }; - - /* The operators' options: concurrency and highWaterMark bound map's - * in-flight callbacks and queued results. */ - interface ArrayOptions { - concurrency?: number; - highWaterMark?: number; - signal?: AbortSignal; - } - - class Readable extends EventEmitter { - constructor(opts?: ReadableOptions); - /* The underscore-method surface (@types/node declares these on the - * classes; assigning them post-construction shadows the prototype - * method and the machinery calls through the assignment — lowered - * onto the option-callback slots). */ - _read(size: number): void; - _destroy(error: Error | null, callback: (error?: Error | null) => void): void; - [Symbol.asyncIterator](): AsyncIterableIterator; - iterator(options?: { destroyOnReturn?: boolean }): AsyncIterableIterator; - /* compose(this, stage), destroyed when options.signal aborts. */ - compose(stage: ComposeStage, options?: { signal: AbortSignal }): Duplex; - push(chunk: Buffer | string | null, encoding?: string): boolean; - /* An object-mode stream's values. */ - push(chunk: any, encoding?: string): boolean; - unshift(chunk: Buffer | string, encoding?: string): void; - unshift(chunk: any, encoding?: string): void; - /* A Buffer, a string once encoded, an object-mode value, or null (as - * @types/node types it). */ - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - setEncoding(encoding: string): this; - pipe(destination: T, options?: { end?: boolean }): T; - unpipe(destination?: NodeJS.WritableStream): this; - destroy(error?: Error): this; - readonly readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableLength: number; - readonly readableHighWaterMark: number; - readonly readableObjectMode: boolean; - readonly closed: boolean; - readonly destroyed: boolean; - readonly errored: Error | null; - static from(iterable: unknown, options?: ReadableOptions): Readable; - /* A Readable over a web ReadableStream's reader. */ - static fromWeb( - readableStream: globalThis.ReadableStream, - options?: { - encoding?: string; - highWaterMark?: number; - objectMode?: boolean; - signal?: AbortSignal; - }, - ): Readable; - /* The Readable operators: chunks are strings or Buffers, map and - * flatMap callbacks return strings or Buffers (flatMap: arrays of - * them, or strings), and callbacks that declare the options argument - * are refused. */ - map( - fn: (data: any, options?: Pick) => any, - options?: ArrayOptions, - ): Readable; - filter( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Readable; - forEach( - fn: (data: any, options?: Pick) => void | Promise, - options?: ArrayOptions, - ): Promise; - toArray(options?: Pick): Promise; - some( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - find( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - every( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - flatMap( - fn: (data: any, options?: Pick) => any, - options?: ArrayOptions, - ): Readable; - drop(limit: number, options?: Pick): Readable; - take(limit: number, options?: Pick): Readable; - reduce( - fn: (previous: any, data: any, options?: Pick) => T, - initial?: undefined, - options?: Pick, - ): Promise; - reduce( - fn: (previous: T, data: any, options?: Pick) => T, - initial: T, - options?: Pick, - ): Promise; - on(event: "data", listener: (chunk: any) => void): this; - on(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "data", listener: (chunk: any) => void): this; - once(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "data", listener: (chunk: any) => void): this; - addListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "data", listener: (chunk: any) => void): this; - prependListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "data", listener: (chunk: any) => void): this; - prependOnceListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "data", listener: (chunk: any) => void): this; - off(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "data", listener: (chunk: any) => void): this; - removeListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - class Writable extends EventEmitter { - constructor(opts: ObjectWritableOptions); - constructor(opts: StringWritableOptions); - constructor(opts?: WritableOptions); - /* The underscore-method surface (see Readable). */ - _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; - _writev( - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - _final(callback: (error?: Error | null) => void): void; - _destroy(error: Error | null, callback: (error?: Error | null) => void): void; - write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; - write( - chunk: Buffer | string, - encoding: string, - callback?: (error?: Error | null) => void, - ): boolean; - /* An object-mode stream's values (the encoding argument reaches - * _write as passed). */ - write(chunk: any, callback?: (error?: Error | null) => void): boolean; - write( - chunk: any, - encoding: ObjectModeEncoding, - callback?: (error?: Error | null) => void, - ): boolean; - end(callback?: () => void): this; - end(chunk: Buffer | string, callback?: () => void): this; - end(chunk: Buffer | string, encoding: string, callback?: () => void): this; - end(chunk: any, callback?: () => void): this; - end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; - cork(): void; - uncork(): void; - setDefaultEncoding(encoding: string): this; - destroy(error?: Error): this; - readonly writable: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly writableLength: number; - readonly writableHighWaterMark: number; - readonly writableObjectMode: boolean; - readonly writableNeedDrain: boolean; - readonly writableCorked: number; - readonly closed: boolean; - readonly destroyed: boolean; - readonly errored: Error | null; - on(event: "drain" | "finish" | "close", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "drain" | "finish" | "close", listener: () => void): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "drain" | "finish" | "close", listener: () => void): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "drain" | "finish" | "close", listener: () => void): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "drain" | "finish" | "close", listener: () => void): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "drain" | "finish" | "close", listener: () => void): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "drain" | "finish" | "close", listener: () => void): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - /* Duplex is Readable + Writable in one object. TS single inheritance - * mirrors @types/node: extends Readable, the writable half declared - * again. The lowering treats both halves as first-class. */ - class Duplex extends Readable { - /* Duplex.from: an async generator function, an iterable, a string or - * a stream (duplexify.js). */ - static from(body: unknown): Duplex; - constructor(opts: ObjectDuplexOptions); - constructor(opts: StringDuplexOptions); - constructor(opts?: DuplexOptions); - /* The underscore-method surface (see Readable). */ - _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; - _writev( - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - _final(callback: (error?: Error | null) => void): void; - write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; - write( - chunk: Buffer | string, - encoding: string, - callback?: (error?: Error | null) => void, - ): boolean; - /* An object-mode stream's values (the encoding argument reaches - * _write as passed). */ - write(chunk: any, callback?: (error?: Error | null) => void): boolean; - write( - chunk: any, - encoding: ObjectModeEncoding, - callback?: (error?: Error | null) => void, - ): boolean; - end(callback?: () => void): this; - end(chunk: Buffer | string, callback?: () => void): this; - end(chunk: Buffer | string, encoding: string, callback?: () => void): this; - end(chunk: any, callback?: () => void): this; - end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; - cork(): void; - uncork(): void; - setDefaultEncoding(encoding: string): this; - readonly writable: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly writableLength: number; - readonly writableHighWaterMark: number; - readonly writableObjectMode: boolean; - readonly writableNeedDrain: boolean; - readonly writableCorked: number; - readonly allowHalfOpen: boolean; - on(event: "data", listener: (chunk: any) => void): this; - on( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "data", listener: (chunk: any) => void): this; - once( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "data", listener: (chunk: any) => void): this; - addListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "data", listener: (chunk: any) => void): this; - prependListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "data", listener: (chunk: any) => void): this; - prependOnceListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "data", listener: (chunk: any) => void): this; - off( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "data", listener: (chunk: any) => void): this; - removeListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - class Transform extends Duplex { - constructor(opts: ObjectTransformOptions); - constructor(opts: StringTransformOptions); - constructor(opts?: TransformOptions); - /* The underscore-method surface (see Readable). */ - _transform( - chunk: any, - encoding: string, - callback: (error?: Error | null, data?: any) => void, - ): void; - _flush(callback: (error?: Error | null, data?: any) => void): void; - } - class PassThrough extends Transform { - constructor(opts: ObjectTransformOptions); - constructor(opts: StringTransformOptions); - constructor(opts?: TransformOptions); - } - - /* addAbortSignal(signal, stream): the stream, destroyed with an - * AbortError (cause: signal.reason) when the signal aborts. */ - function addAbortSignal(signal: AbortSignal, stream: T): T; - /* pipeline(...stages, callback): the last stream, or the object-mode - * PassThrough that a function destination feeds. */ - type PipelineResult = T extends [ - ...unknown[], - infer D, - (...args: any[]) => unknown, - ] - ? D extends (...args: any[]) => unknown - ? PassThrough - : D - : unknown; - function pipeline(...streams: T): PipelineResult; - /* util.promisify's pipeline overload matches only pipeline itself: a - * generic signature alone admits every function. */ - namespace pipeline { - const __promisify__: typeof promises.pipeline; - } - /* A compose stage: a stream, an async generator function over the - * previous stage, or (first) an iterable, async iterable or web - * ReadableStream. */ - type ComposeStage = - | Readable - | Writable - | Iterable - | AsyncIterable - | globalThis.ReadableStream - | ((source: any, options?: { signal: AbortSignal }) => unknown); - /* compose(...streams): a Duplex that writes to the first stage and - * reads from the last, with the stages running as a pipeline. */ - function compose(...streams: ComposeStage[]): Duplex; - /* finished's options: readable/writable choose which ends count, - * error: false ignores 'error' events, signal aborts the wait. */ - interface FinishedOptions { - readable?: boolean; - writable?: boolean; - error?: boolean; - signal?: AbortSignal; - } - function finished(stream: unknown, callback: (err?: Error | null) => void): () => void; - function finished( - stream: unknown, - options: FinishedOptions | null, - callback: (err?: Error | null) => void, - ): () => void; - /* Folds to the TARGET platform's default at compile time (win32 16384, - * else 65536 — Node's own state.js split); literal argument only. */ - function getDefaultHighWaterMark(objectMode: boolean): number; - /* The promise pipeline's result: a last stage that is an async - * function resolves the promise with its value (before the options - * argument, when one follows). */ - type PipelinePromise = T extends [ - ...unknown[], - (...args: any[]) => Promise, - ] - ? Promise

    - : T extends [ - ...unknown[], - (...args: any[]) => Promise, - { signal?: AbortSignal; end?: boolean }, - ] - ? Promise

    - : Promise; - namespace promises { - function pipeline(...streams: T): PipelinePromise; - function finished( - stream: unknown, - options?: FinishedOptions & { cleanup?: boolean }, - ): Promise; - } - class Stream extends EventEmitter {} - - export { - Readable, - Writable, - Duplex, - Transform, - PassThrough, - Stream, - addAbortSignal, - pipeline, - compose, - finished, - getDefaultHighWaterMark, - promises, - }; - export type { - ReadableOptions, - WritableOptions, - DuplexOptions, - TransformOptions, - FinishedOptions, - PipelinePromise, - }; -} -declare module "node:stream" { - import stream = require("stream"); - export = stream; -} -declare module "stream/promises" { - /* The promise forms of finished/pipeline: a void promise the stream's - * terminal point settles (rejected with the error, or - * ERR_STREAM_PREMATURE_CLOSE on an early close). Stream arguments are - * the lowered surface — iterables/generators/functions fence per site. - * pipeline's trailing { signal, end } object and finished's options - * (readable, writable, error, cleanup, signal) are supported. A last - * stage that is an async function resolves the promise with its value. */ - function pipeline(...streams: T): import("stream").PipelinePromise; - function finished( - stream: unknown, - options?: { - readable?: boolean; - writable?: boolean; - error?: boolean; - cleanup?: boolean; - signal?: AbortSignal; - } | null, - ): Promise; - export { pipeline, finished }; -} -declare module "node:stream/promises" { - import streamPromises = require("stream/promises"); - export = streamPromises; -} -declare module "stream/consumers" { - /* The promise consumers over a readable: accumulate every chunk and - * settle — text (the utf8 decode), json (the text through JSON.parse: - * `unknown`, validated with a checked cast; malformed input rejects - * with the parse's SyntaxError), buffer (the concatenated bytes; - * string chunks contribute their utf8 bytes, Node's Blob rule). - * Stream errors reject; an early close rejects with - * ERR_STREAM_PREMATURE_CLOSE. arrayBuffer answers an ArrayBuffer over - * the bytes; blob fences per site. */ - function text(stream: unknown): Promise; - function json(stream: unknown): Promise; - function buffer(stream: unknown): Promise; - function arrayBuffer(stream: unknown): Promise; - function blob(stream: unknown): Promise; - export { arrayBuffer, blob, buffer, json, text }; -} -declare module "node:stream/consumers" { - import streamConsumers = require("stream/consumers"); - export = streamConsumers; -} -/* node:stream/web — the module exports the web stream globals themselves - * (require("stream/web").ReadableStream === globalThis.ReadableStream). - * ReadableStream is the implemented class; its reader and controller are - * exported as types. */ -declare module "stream/web" { - export const ReadableStream: typeof globalThis.ReadableStream; - export type ReadableStream = globalThis.ReadableStream; - export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; - export type ReadableStreamDefaultController = - globalThis.ReadableStreamDefaultController; - export type UnderlyingSource = globalThis.UnderlyingSource; - export type QueuingStrategy = globalThis.QueuingStrategy; -} -declare module "node:stream/web" { - export * from "stream/web"; -} interface ErrorConstructor { stackTraceLimit: number; } diff --git a/packages/compiler/src/frontend/dts-paths.ts b/packages/compiler/src/frontend/dts-paths.ts index 009d1c333..638e58641 100644 --- a/packages/compiler/src/frontend/dts-paths.ts +++ b/packages/compiler/src/frontend/dts-paths.ts @@ -20,7 +20,9 @@ function declarationPath(name: string): string { ? require.resolve("@scriptc/compiler/scriptc.d.ts") : name === "scriptc-overrides.d.ts" ? require.resolve("@scriptc/compiler/scriptc-overrides.d.ts") - : require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts") + : name === "scriptc-node-fallback.d.ts" + ? require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts") + : join(dirname(require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts")), name) : join(installedDeclarationRoot, name), ); } @@ -56,6 +58,9 @@ export function compilerOwnedFileName(file: string): string | null { for (const declaration of [ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]) { if (path === declaration) return `scriptc:${path.slice(path.lastIndexOf("/") + 1)}`; } + const modules = fallbackModuleRoot() + "/"; + if (path.startsWith(modules)) + return `scriptc:${FALLBACK_MODULE_DIRECTORY}/${path.slice(modules.length)}`; return null; } @@ -75,14 +80,138 @@ export function overridesDtsPath(): string { return declarationPath("scriptc-overrides.d.ts"); } -/** Path of the shipped FALLBACK declarations (console, process, node:fs) — - * part of the program only when the target project has no @types/node. - * With @types/node, the project's real Node types stand in and this file - * stands down (its declaration forms would collide). */ +/** Path of the shipped FALLBACK declarations (the console and process + * globals and the modules they name) — part of the program only when the + * target project has no @types/node. With @types/node, the project's real + * Node types stand in and this file stands down (its declaration forms + * would collide). */ export function fallbackDtsPath(): string { return declarationPath("scriptc-node-fallback.d.ts"); } +const FALLBACK_MODULE_DIRECTORY = "node-fallback"; + +/** The fallback declarations of the other Node modules, one file per module + * under node-fallback/, by specifier (both spellings). Each file references + * the files of the modules its declarations name. */ +const FALLBACK_MODULE_FILES: Readonly> = { + "assert": "assert.d.ts", + "assert/strict": "assert-strict.d.ts", + "async_hooks": "async_hooks.d.ts", + "buffer": "buffer.d.ts", + "child_process": "child_process.d.ts", + "cluster": "cluster.d.ts", + "constants": "constants.d.ts", + "crypto": "crypto.d.ts", + "dgram": "dgram.d.ts", + "diagnostics_channel": "diagnostics_channel.d.ts", + "dns": "dns.d.ts", + "dns/promises": "dns-promises.d.ts", + "events": "events.d.ts", + "fs": "fs.d.ts", + "fs/promises": "fs-promises.d.ts", + "graceful-fs": "graceful-fs.d.ts", + "http": "http.d.ts", + "http2": "http2.d.ts", + "https": "https.d.ts", + "module": "module.d.ts", + "net": "net.d.ts", + "node:assert": "assert.d.ts", + "node:assert/strict": "assert-strict.d.ts", + "node:async_hooks": "async_hooks.d.ts", + "node:buffer": "buffer.d.ts", + "node:child_process": "child_process.d.ts", + "node:cluster": "cluster.d.ts", + "node:constants": "constants.d.ts", + "node:crypto": "crypto.d.ts", + "node:dgram": "dgram.d.ts", + "node:diagnostics_channel": "diagnostics_channel.d.ts", + "node:dns": "dns.d.ts", + "node:dns/promises": "dns-promises.d.ts", + "node:events": "events.d.ts", + "node:fs": "fs.d.ts", + "node:fs/promises": "fs-promises.d.ts", + "node:http": "http.d.ts", + "node:http2": "http2.d.ts", + "node:https": "https.d.ts", + "node:module": "module.d.ts", + "node:net": "net.d.ts", + "node:os": "os.d.ts", + "node:path": "path.d.ts", + "node:path/posix": "path-posix.d.ts", + "node:path/win32": "path-win32.d.ts", + "node:process": "process.d.ts", + "node:punycode": "punycode.d.ts", + "node:querystring": "querystring.d.ts", + "node:stream": "stream.d.ts", + "node:stream/consumers": "stream-consumers.d.ts", + "node:stream/promises": "stream-promises.d.ts", + "node:stream/web": "stream-web.d.ts", + "node:string_decoder": "string_decoder.d.ts", + "node:test": "test.d.ts", + "node:timers": "timers.d.ts", + "node:timers/promises": "timers-promises.d.ts", + "node:tls": "tls.d.ts", + "node:url": "url.d.ts", + "node:util": "util.d.ts", + "node:util/types": "util-types.d.ts", + "node:v8": "v8.d.ts", + "node:worker_threads": "worker_threads.d.ts", + "node:zlib": "zlib.d.ts", + "os": "os.d.ts", + "path": "path.d.ts", + "path/posix": "path-posix.d.ts", + "path/win32": "path-win32.d.ts", + "process": "process.d.ts", + "punycode": "punycode.d.ts", + "querystring": "querystring.d.ts", + "stream": "stream.d.ts", + "stream/consumers": "stream-consumers.d.ts", + "stream/promises": "stream-promises.d.ts", + "stream/web": "stream-web.d.ts", + "string_decoder": "string_decoder.d.ts", + "timers": "timers.d.ts", + "timers/promises": "timers-promises.d.ts", + "tls": "tls.d.ts", + "url": "url.d.ts", + "util": "util.d.ts", + "util/types": "util-types.d.ts", + "v8": "v8.d.ts", + "worker_threads": "worker_threads.d.ts", + "zlib": "zlib.d.ts", +}; + +function fallbackModuleRoot(): string { + return declarationPath(FALLBACK_MODULE_DIRECTORY); +} + +/** The fallback declaration file of the Node module `specifier`, or null. */ +export function fallbackModulePath(specifier: string): string | null { + return Object.hasOwn(FALLBACK_MODULE_FILES, specifier) + ? declarationPath(`${FALLBACK_MODULE_DIRECTORY}/${FALLBACK_MODULE_FILES[specifier]}`) + : null; +} + +const QUOTED_SPECIFIER = /["'`]((?:node:)?[a-z][a-z0-9_]*(?:[-/][a-z0-9_]+)?)["'`]/g; + +/** The fallback declaration files of the Node modules `text` names in a + * string literal: an over-approximation of the modules its imports, requires, + * import types and JSDoc types can name. */ +export function fallbackModulesNamedIn(text: string): string[] { + const files = new Set(); + for (const match of text.matchAll(QUOTED_SPECIFIER)) { + const file = fallbackModulePath(match[1]!); + if (file !== null) files.add(file); + } + return [...files]; +} + +/** True for the fallback declarations: the core file and the module files. */ +export function isFallbackDtsPath(file: string): boolean { + const path = tsgoPath(file); + return path === fallbackDtsPath() || path.startsWith(fallbackModuleRoot() + "/"); +} + /** True for files belonging to the adopted Node type surface: the * @types/node package itself and undici-types (its dependency — the * web-platform globals: fetch/Response/AbortSignal/ReadableStream/...). diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index ffe01ee47..23982976a 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test, vi } from "vitest"; import { forkTargetPaths } from "./fork-target.js"; -import { fallbackDtsPath } from "./dts-paths.js"; +import { fallbackDtsPath, fallbackModulePath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; @@ -35,12 +35,15 @@ test("fork discovery batches only candidate bindings and preserves aliases and s `, ); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, declarations], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, declarations, fallbackModulePath("node:child_process")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); const checker = program.getTypeChecker(); const batch = vi.spyOn(checker, "prefetchSymbolNodesExact"); const whole = vi.spyOn(checker.raw, "getTypeOfSymbol"); @@ -73,12 +76,15 @@ function discover(files: Record): { targets: string[]; queried: for (const [name, text] of Object.entries(files)) writeFileSync(join(directory, name), text); const entry = join(directory, "main.ts"); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, fallbackDtsPath()], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, fallbackDtsPath(), fallbackModulePath("node:child_process")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); const batch = vi.spyOn(program.getTypeChecker(), "prefetchSymbolNodesExact"); try { const targets = forkTargetPaths(program, [program.getSourceFile(entry)!]).map((path) => diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 93fc7c244..398e1b0aa 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -3,7 +3,7 @@ import { fileURLToPath, pathToFileURL } from "node:url"; import * as ts from "./ts7/adapter.js"; import { ambientDtsPath, - fallbackDtsPath, + isFallbackDtsPath, isNodeTypesPath, overridesDtsPath, tsgoPath, @@ -22,7 +22,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo const source = ts.getSourceFileOfNode(declaration); return ( source.isDeclarationFile && - (tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName)) + (isFallbackDtsPath(source.fileName) || isNodeTypesPath(source.fileName)) ); }) ); @@ -333,7 +333,7 @@ const SPELLED_CHILD_PROCESS = new RegExp( function namesChildProcess(program: ts.Program): boolean { const known = program.analysis.namesChildProcess; if (known !== null) return known; - const own = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + const own = new Set([ambientDtsPath(), overridesDtsPath()]); // Implementation files are already fetched; any other file is read as // the host serves it, without fetching its syntax tree. const texts = new Map(); @@ -343,7 +343,7 @@ function namesChildProcess(program: ts.Program): boolean { let names = false; for (const fileName of program.getSourceFileNames()) { const path = tsgoPath(fileName); - if (isNodeTypesPath(fileName) || own.has(path)) continue; + if (isNodeTypesPath(fileName) || isFallbackDtsPath(fileName) || own.has(path)) continue; const text = texts.get(path) ?? program.getSourceText(fileName) ?? ""; if ( text.includes("child_process") || diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e9e351429..106ffb9a7 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -274,12 +274,12 @@ import { cjsExportsAssignedInFunctions, cjsReexportedModules, cjsNamespaceExportNames, - fallbackDtsPath, isCheckedJsSourceFile, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isNodeEsmFile, + isFallbackDtsPath, isNodeTypesPath, locOf, orderedImportsOf, @@ -3325,7 +3325,6 @@ export class Lowerer { readonly typeMemo = new Map(); readonly ambient = ambientDtsPath(); readonly overridesAmbient = overridesDtsPath(); - readonly fallbackAmbient = fallbackDtsPath(); /** The one mapType context: registries + hooks, assembled in the * constructor (typeParamResolver reads the CURRENT instantiation bindings * through `this`, so the same ctx serves generic bodies too). */ @@ -13439,7 +13438,7 @@ export class Lowerer { readonly isStdlibFile = (sf: ts.SourceFile): boolean => sf.fileName === this.ambient || sf.fileName === this.overridesAmbient || - sf.fileName === this.fallbackAmbient || + isFallbackDtsPath(sf.fileName) || this.program.isSourceFileDefaultLibrary(sf) || (sf.isDeclarationFile && isNodeTypesPath(sf.fileName)); diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index b93e3f217..f64733fc0 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -104,6 +104,8 @@ import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js"; import { ambientDtsPath, fallbackDtsPath, + fallbackModulesNamedIn, + isFallbackDtsPath, isNodeTypesPath, overridesDtsPath, tsgoPath, @@ -613,6 +615,27 @@ function externalTypeFileClosure7( return new Map([...ownersByFile].map(([file, owners]) => [file, [...owners]] as const)); } +/** The fallback declaration files of the Node modules the program's files + * name (fallbackModulesNamedIn) that the program does not hold yet. Each + * file is read as tsgo reads it; the TypeScript libraries and scriptc's own + * declarations name no module that needs a file. */ +function fallbackModuleRoots7(program: ts.Program, host: ts.Ts7Host): string[] { + const names = program.getSourceFileNames(); + const held = new Set(names.filter(isFallbackDtsPath).map((name) => tsgoPath(name))); + const ambient = ambientDtsPath(); + const overrides = overridesDtsPath(); + const roots = new Set(); + for (const name of names) { + const path = tsgoPath(name); + if (held.has(path) || path === ambient || path === overrides) continue; + if (/\/lib\.[^/]*\.d\.ts$/.test(path) && program.getSourceFileMetadata(name)?.isDefaultLibrary) + continue; + for (const file of fallbackModulesNamedIn(host.sourceText(name) ?? "")) + if (!held.has(file)) roots.add(file); + } + return [...roots]; +} + /** Program roots hidden behind canonical createRequire bindings. tsgo * discovers ordinary import/require edges itself, but a source binding * returned by createRequire is intentionally just a function to the type @@ -889,12 +912,23 @@ function loadProgram7( } options = { ...options, paths }; } - const coreRoots = [entryPath, ambientDtsPath(), nodeTypes ?? fallbackDtsPath()]; + // Without @types/node the fallback declarations stand in: the core file, + // and a Node module's file once a program file names the module + // (fallbackModuleRoots7). The modules the entry names join the first + // program. + const coreRoots = [ + entryPath, + ambientDtsPath(), + ...(nodeTypes === null + ? [fallbackDtsPath(), ...fallbackModulesNamedIn(host.sourceText(entryPath) ?? "")] + : [nodeTypes]), + ]; const programRoots = [...coreRoots]; let program = host.createProgram([...programRoots, overridesDtsPath()], options); try { for (let pass = 0; pass < 32; pass++) { const candidates = [ + ...(nodeTypes === null ? fallbackModuleRoots7(program, host) : []), ...entryPackageProgramRoots7(program, entryPath, rootNpmStaticEdges), ...createRequireProgramRoots7(program), ...forkTargetPaths(program, program.getImplementationSourceFiles()), @@ -5014,7 +5048,7 @@ function laterJobCallee7(checker: ts.TypeChecker, callee: ts.Expression): boolea // package declarations describe user code. return decls.every((d) => { const file = ts.getSourceFileOfNode(d).fileName; - return isNodeTypesPath(file) || file === fallbackDtsPath(); + return isNodeTypesPath(file) || isFallbackDtsPath(file); }); } @@ -7097,7 +7131,13 @@ const nodeBuiltinNames: ReadonlySet = new Set(builtinModules); * established (the phase-2 port kept every spelling); the world-neutral * helpers re-export from their focused modules. */ -export { ambientDtsPath, fallbackDtsPath, isNodeTypesPath, overridesDtsPath } from "./dts-paths.js"; +export { + ambientDtsPath, + fallbackDtsPath, + isFallbackDtsPath, + isNodeTypesPath, + overridesDtsPath, +} from "./dts-paths.js"; export { builtinDefaultImportModule, diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..0b2c3f6b7 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -47,7 +47,7 @@ import { } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; import { typePatchedText } from "../type-patches/layer.js"; -import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; +import { ambientDtsPath, isFallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; import type { Ts7DiagnosticData } from "./session-schema.generated.js"; @@ -425,12 +425,13 @@ export class Ts7Program { reusedLibDiagnostics(): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { this.snapshot.ensureActive(); const program = this.project.program; - const declarations = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + const declarations = new Set([ambientDtsPath(), overridesDtsPath()]); return reusedLibDiagnostics( program, this.getSourceFileNames(), (name) => declarations.has(tsgoPath(name)) || + isFallbackDtsPath(name) || program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true, (name) => this.getSourceFile(name), ); diff --git a/packages/compiler/src/frontend/worker-target.test.ts b/packages/compiler/src/frontend/worker-target.test.ts index 7125bc309..e68fc798a 100644 --- a/packages/compiler/src/frontend/worker-target.test.ts +++ b/packages/compiler/src/frontend/worker-target.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { workerTargetPaths } from "./worker-target.js"; -import { fallbackDtsPath } from "./dts-paths.js"; +import { fallbackDtsPath, fallbackModulePath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; @@ -17,12 +17,15 @@ function discover(source: string): string[] { 'export { Worker as Task } from "node:worker_threads";', ); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, fallbackDtsPath()], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, fallbackDtsPath(), fallbackModulePath("node:worker_threads")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); try { return workerTargetPaths(program, [program.getSourceFile(entry)!]).map((file) => file.replace(directory + "/", ""), diff --git a/packages/compiler/test/node-fallback-modules.test.ts b/packages/compiler/test/node-fallback-modules.test.ts new file mode 100644 index 000000000..60a2e0bc9 --- /dev/null +++ b/packages/compiler/test/node-fallback-modules.test.ts @@ -0,0 +1,43 @@ +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { basename, dirname, join } from "node:path"; +import { expect, test } from "vitest"; +import { + fallbackDtsPath, + fallbackModulePath, + fallbackModulesNamedIn, +} from "../src/frontend/dts-paths.js"; + +const moduleDirectory = join(dirname(fallbackDtsPath()), "node-fallback"); +const declaredModules = (text: string): string[] => + [...text.matchAll(/^declare module "([^"]+)" \{/gm)].map((m) => m[1]!); + +test("every fallback module file declares the specifiers that map to it, and only those", () => { + const files = readdirSync(moduleDirectory).filter((name) => name.endsWith(".d.ts")); + expect(files.length).toBeGreaterThan(0); + for (const file of files) { + const text = readFileSync(join(moduleDirectory, file), "utf8"); + const declared = declaredModules(text); + expect(declared.length, file).toBeGreaterThan(0); + for (const specifier of declared) expect(basename(fallbackModulePath(specifier)!)).toBe(file); + // Every reference names another module file. + for (const [, reference] of text.matchAll(/^\/\/\/ $/gm)) + expect(existsSync(join(moduleDirectory, reference!)), `${file} -> ${reference}`).toBe(true); + } +}); + +test("the core fallback file declares no module that has a file of its own", () => { + for (const specifier of declaredModules(readFileSync(fallbackDtsPath(), "utf8"))) + expect(fallbackModulePath(specifier), specifier).toBeNull(); +}); + +test("a module named in a string literal selects its file, under either spelling", () => { + const files = fallbackModulesNamedIn( + `import { readFileSync } from "node:fs";\nconst os = require('os');\ntype T = import(\`node:stream/web\`);\nconst label = "not-a-module";`, + ).map((file) => basename(file)); + expect(files.sort()).toEqual(["fs.d.ts", "os.d.ts", "stream-web.d.ts"]); + expect(fallbackModulesNamedIn('console.log("hi")')).toEqual([]); + // Module names with digits. + expect( + fallbackModulesNamedIn(`require("http2"); import("node:v8");`).map((file) => basename(file)), + ).toEqual(["http2.d.ts", "v8.d.ts"]); +}); diff --git a/packages/compiler/test/node-pin.test.ts b/packages/compiler/test/node-pin.test.ts index 4d4c4a7e8..f13c5558e 100644 --- a/packages/compiler/test/node-pin.test.ts +++ b/packages/compiler/test/node-pin.test.ts @@ -33,7 +33,7 @@ test("the Node pins agree", () => { }); test("the crypto.constants declaration matches the baked table", () => { - const dts = read("packages/compiler/ambient/scriptc-node-fallback.d.ts"); + const dts = read("packages/compiler/ambient/node-fallback/crypto.d.ts"); const start = dts.indexOf("export interface CryptoConstants {"); const body = dts.slice(start, dts.indexOf("\n }\n", start)); const declared = Object.fromEntries( @@ -46,7 +46,7 @@ test("the crypto.constants declaration matches the baked table", () => { }); test("the zlib.constants declaration matches the baked table", () => { - const dts = read("packages/compiler/ambient/scriptc-node-fallback.d.ts"); + const dts = read("packages/compiler/ambient/node-fallback/zlib.d.ts"); const start = dts.indexOf("export interface ZlibConstants {"); const body = dts.slice(start, dts.indexOf("\n }\n", start)); const declared = Object.fromEntries( diff --git a/packages/compiler/test/ts7/bench.test.ts b/packages/compiler/test/ts7/bench.test.ts index 13e34bca1..c8de220c9 100644 --- a/packages/compiler/test/ts7/bench.test.ts +++ b/packages/compiler/test/ts7/bench.test.ts @@ -28,6 +28,8 @@ import type { } from "../../src/frontend/ts7/semantic-types.js"; import { CheckerFacade } from "../../src/frontend/ts7/checker.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../../src/frontend/program.js"; +import { fallbackModulesNamedIn } from "../../src/frontend/dts-paths.js"; +import { readFileSync } from "node:fs"; import { ad, options7 } from "./harness.js"; const repoRoot = fileURLToPath(new URL("../../../..", import.meta.url)); @@ -230,7 +232,13 @@ for (const workload of WORKLOADS) { { retry: 2 }, () => { const program = createProgram( - [workload.entry, ambientDtsPath(), fallbackDtsPath(), overridesDtsPath()], + [ + workload.entry, + ambientDtsPath(), + fallbackDtsPath(), + ...fallbackModulesNamedIn(readFileSync(workload.entry, "utf8")), + overridesDtsPath(), + ], options7(), host, ); diff --git a/packages/compiler/test/ts7/harness.ts b/packages/compiler/test/ts7/harness.ts index 484add55d..073de1421 100644 --- a/packages/compiler/test/ts7/harness.ts +++ b/packages/compiler/test/ts7/harness.ts @@ -18,6 +18,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import ts5 from "typescript5"; import { ambientDtsPath, fallbackDtsPath } from "../../src/frontend/program.js"; +import { fallbackModulesNamedIn } from "../../src/frontend/dts-paths.js"; import * as ad from "../../src/frontend/ts7/adapter.js"; export { ad, ts5 }; @@ -78,7 +79,8 @@ export function buildTwoWorlds(sources: Record, host?: ad.Ts7Hos writeFileSync(path, text); files.push(path); } - const roots = [...files, ambientDtsPath(), fallbackDtsPath()]; + const modules = Object.values(sources).flatMap((text) => fallbackModulesNamedIn(text)); + const roots = [...files, ambientDtsPath(), fallbackDtsPath(), ...new Set(modules)]; const p5 = ts5.createProgram(roots, options5()); const p7 = createProgram(roots, options7(), host); return { diff --git a/scripts/native-cli-assets.mts b/scripts/native-cli-assets.mts index bdecc1da2..58d16fb69 100644 --- a/scripts/native-cli-assets.mts +++ b/scripts/native-cli-assets.mts @@ -74,6 +74,11 @@ export function stageNativeCliAssets(options: { for (const name of ["scriptc.d.ts", "scriptc-overrides.d.ts", "scriptc-node-fallback.d.ts"]) { cpSync(join(repository, "packages/compiler/ambient", name), join(declarations, name)); } + cpSync( + join(repository, "packages/compiler/ambient/node-fallback"), + join(declarations, "node-fallback"), + { recursive: true }, + ); // Package shims (packageShimRoot): the built-in npm package implementations. cpSync(join(repository, "packages/compiler/shims"), join(declarations, "shims"), { recursive: true, From 11de08b93116e4758c4d66d114fd859d4ca2f606 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:46:27 -0500 Subject: [PATCH 275/395] Skip worker cancellation polls in short loops inside another loop In a worker program, a loop inside another loop of the same function needs less than its own budget, because the enclosing loop polls at least once every 64 of its iterations: - A loop whose counters' proven ranges bound its runs, and those of the loops in its body, to 64 x 64 emits no poll. This covers loops with constant bounds, such as mandelbrot's lane and iteration loops. The bound uses the counted and two-counter loop ranges, or the range of a counter that the update alone steps by a constant where the condition reads it. Loops whose counted form needs a run-time guard don't count. - An innermost counted loop is strip-mined like a counted loop over typed arrays, whatever its body. A run of at most 64 iterations never polls, and the budget's countdown leaves the body. A run of at most 16384 iterations is one block and polls once as it leaves. A longer run polls between blocks of 64, but not after its last block. Between two polls there are then at most 64 iterations of the enclosing loop, and in each of them at most 16384 unpolled iterations of each strip-mined inner loop and 64 x 64 runs of each bounded nest. A loop that no other loop of its function encloses polls as before. New corpus program: native-worker-terminate-bounded-loops.ts. --- .../src/backend/llvm/emit-statements.ts | 142 +++++++++++++++++- packages/compiler/src/backend/loop-polls.ts | 4 +- packages/compiler/src/ir/integer-loops.ts | 22 +++ .../test/counted-loop-emission.test.ts | 91 +++++++++++ ...ive-worker-terminate-bounded-loops.ts.json | 6 + .../native-worker-terminate-bounded-loops.ts | 91 +++++++++++ ...ive-worker-terminate-bounded-loops.ts.json | 1 + 7 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json create mode 100644 tests/corpus/native-worker-terminate-bounded-loops.ts create mode 100644 tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index a43cf3534..e0db764e8 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -50,6 +50,7 @@ import { matchIntegerBytesForLoop, matchIntegerCountedForLoop, matchIntegerPairedForLoop, + steppedLoopCounter, } from "../../ir/integer-loops.js"; import { emitByteWindowGuard, matchByteWindow } from "./byte-windows.js"; import { @@ -1218,7 +1219,24 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { // has no exit besides its own bound, which LLVM can vectorize. A typed // array's length is fixed, so each block can take it as its limit when // the loop does not assign the array's binding. + // + // Inside another loop of this function, which polls at least once + // every WORKER_POLL_BLOCK of its own iterations, a loop needs less: + // - a loop whose counters' ranges bound its runs, and those of the + // loops in its body, to WORKER_POLL_BLOCK^2 needs no poll; + // - an innermost counted loop is strip-mined, typed arrays or not, + // and does not poll after its last block. A run of at most + // WORKER_POLL_BLOCK iterations then never polls, and the countdown + // leaves its body. A run of at most WORKER_UNPOLLED_RUN iterations + // is one block, polled once as it leaves. + const enclosed = + emitter.mod.workers === true && + emitter.jumpTargets.some((target) => target.kind === "loop"); + const bounded = + enclosed && boundedLoopRuns(emitter, s) <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK; + const innermost = enclosed && !containsLoop(s.body); const stripMined = + !bounded && capturedLets.length === 0 && integerSlot !== null && ((countedLoop !== null && @@ -1226,16 +1244,21 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || (integerLoop !== null && integerArrayLoop === null && integerLoop.stableReceiver)) && emitter.workerLoopPolls(s) && - loopAccessesTypedArrays(s); - const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); + (loopAccessesTypedArrays(s) || (countedLoop !== null && innermost)); + const workerBudget = stripMined || bounded ? null : emitter.workerLoopBudget(s); // One slot for every copy of the loop: a speculative copy resumes an // iteration in another copy's body, which then reads this bound. let blockBound: string | null = null; + let runSlot: string | null = null; if (stripMined) { blockBound = B.slot(); B.entryAllocas.push( `${blockBound} = alloca ${countedLoop ? "i64" : emitter.sizeType} ; cancellation poll block bound`, ); + if (innermost) { + runSlot = B.slot(); + B.entryAllocas.push(`${runSlot} = alloca i1 ; cancellation poll after the run`); + } } freshenBindings(); emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { @@ -1271,7 +1294,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { return len; }, }; - emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit); + emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit, runSlot); return; } const lc = B.newLabel("loop.c"); @@ -2628,6 +2651,51 @@ function emitInt32KindLoop( /** Iterations between cancellation polls of a strip-mined counted loop. */ const WORKER_POLL_BLOCK = 64; +/** The longest run of a strip-mined loop inside another loop that polls + * only as it leaves. */ +const WORKER_UNPOLLED_RUN = 16384; + +/** At most how many times a `for` loop and the loops in its body run their + * bodies per entry, from the counters' proven ranges: its iterations times + * one plus the nested loops' runs. Infinity when a count has no bound, or + * depends on a run-time guard (the guard's slow copy steps a double). */ +function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { + if (loop.kind !== "for") return Infinity; + const counted = matchIntegerCountedForLoop( + loop, + emitter.numericLocals, + emitter.integerRanges, + LOOPS.of(emitter).unaliasedArrays, + ); + let trips = Infinity; + if (counted && !counted.guarded) + trips = Math.floor((counted.range.max - counted.range.min) / Math.abs(counted.step)) + 1; + else if (!counted) { + // Each iteration closes the gap between the two counters by at least two. + const paired = matchIntegerPairedForLoop(loop, emitter.numericLocals, emitter.integerRanges); + if (paired && !paired.guarded) + trips = Math.floor((paired.range.max - paired.range.min) / 2) + 1; + } + if (trips === Infinity) { + // A counter with a proven range where the condition reads it, such as + // one compared with a module constant. + const stepped = steppedLoopCounter(loop, emitter.currentLocals); + const range = stepped && emitter.integerRanges.get(stepped.ref); + if (stepped && range) trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; + } + if (!(trips <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK)) return Infinity; + let nested = 0; + const visit = (s: IrStmt): boolean => { + if (s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf") { + nested += boundedLoopRuns(emitter, s); + return nested !== Infinity; + } + return everyStmtChild(s, () => true, visit); + }; + if (!loop.body.every(visit)) return Infinity; + 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. */ @@ -2645,13 +2713,20 @@ interface StripMinedCounter { * tests the counter against the loop's limit, each block runs the body up * to the nearer of the limit and WORKER_POLL_BLOCK steps on, and the poll * follows the block. It polls as often as the budget it replaces, and the - * inner loop's only exit besides `break` and `return` is its bound. */ + * inner loop's only exit besides `break` and `return` is its bound. + * + * With `runSlot` (for a loop that another loop of the function encloses), a + * block that ends at the limit leaves the loop without polling, and a run + * of at most WORKER_UNPOLLED_RUN iterations is one block, polled once as + * it leaves when it took more than WORKER_POLL_BLOCK iterations. `runSlot` + * holds whether the current block is such a run. */ function emitStripMinedCountedLoop( emitter: LlEmitter, s: Extract, counter: StripMinedCounter, boundSlot: string, exit: string, + runSlot: string | null, ): void { const B = emitter.B; const { slot, type, signed, step } = counter; @@ -2684,7 +2759,27 @@ function emitStripMinedCountedLoop( `${stepped} = call ${type} @${saturating}(${type} ${start}, ${type} ${step * WORKER_POLL_BLOCK})`, ); B.line(`${bound} = call ${type} @${nearer}(${type} ${stepped}, ${type} ${limit})`); - B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + if (runSlot === null) B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + else { + // The steps left to the limit: positive, as the header tested. + const left = B.tmp(), + whole = B.tmp(), + long = B.tmp(), + polled = B.tmp(), + end = B.tmp(); + const stride = Math.abs(step); + B.line( + ascending + ? `${left} = sub ${wrap} ${type} ${limit}, ${start}` + : `${left} = sub ${wrap} ${type} ${start}, ${limit}`, + ); + B.line(`${whole} = icmp ule ${type} ${left}, ${stride * WORKER_UNPOLLED_RUN}`); + B.line(`${long} = icmp ugt ${type} ${left}, ${stride * WORKER_POLL_BLOCK}`); + B.line(`${polled} = and i1 ${whole}, ${long}`); + B.line(`${end} = select i1 ${whole}, ${type} ${limit}, ${type} ${bound}`); + B.line(`store ${type} ${end}, ptr ${boundSlot}`); + B.line(`store i1 ${polled}, ptr ${runSlot}`); + } B.br(bc); B.startBlock(bc); const index = B.tmp(); @@ -2693,7 +2788,8 @@ function emitStripMinedCountedLoop( B.line(`${index} = load ${type}, ptr ${slot}`); B.line(`${end} = load ${type}, ptr ${boundSlot}`); B.line(`${inBlock} = icmp ${compare} ${type} ${index}, ${end}`); - B.condBr(inBlock, lb, poll); + const after = runSlot === null ? poll : B.newLabel("loop.end"); + B.condBr(inBlock, lb, after); B.startBlock(lb); emitter.jumpTargets.push({ kind: "loop", @@ -2716,11 +2812,45 @@ function emitStripMinedCountedLoop( B.line(`store ${type} ${next}, ptr ${stepSlot}`); } B.br(bc); + if (runSlot !== null) { + // A speculative copy may enter the body without passing the header, so + // the limit is read again here. + B.startBlock(after); + const at = B.tmp(); + const more = B.tmp(); + const run = B.tmp(); + const done = B.newLabel("loop.done"); + const last = B.newLabel("loop.poll.run"); + B.line(`${at} = load ${type}, ptr ${slot}`); + B.line(`${more} = icmp ${compare} ${type} ${at}, ${counter.limit()}`); + B.condBr(more, poll, done); + B.startBlock(done); + B.line(`${run} = load i1, ptr ${runSlot}`); + B.condBr(run, last, exit); + B.startBlock(last); + emitter.emitWorkerPoll(); + B.br(exit); + } B.startBlock(poll); emitter.emitWorkerPoll(); B.br(lc); } +/** Whether `body` holds a loop outside its closures. */ +function containsLoop(body: IrStmt[]): boolean { + const inStmt = (s: IrStmt): boolean => + s.kind === "while" || + s.kind === "doWhile" || + s.kind === "for" || + s.kind === "forOf" || + !everyStmtChild( + s, + () => true, + (child) => !inStmt(child), + ); + return body.some(inStmt); +} + function enterLoopHeader(emitter: LlEmitter, header: string): void { const dispatch = emitter.speculationHeader; emitter.speculationHeader = null; diff --git a/packages/compiler/src/backend/loop-polls.ts b/packages/compiler/src/backend/loop-polls.ts index c582a4974..866711fb7 100644 --- a/packages/compiler/src/backend/loop-polls.ts +++ b/packages/compiler/src/backend/loop-polls.ts @@ -110,8 +110,8 @@ export function loopTestsEveryIteration(loop: Loop, tested: (call: IrExpr) => bo /** True when the body of `loop`, outside its nested loops and closures, * reads or writes a typed-array element: the loops whose cancellation poll * the emitter moves between blocks of iterations, so that LLVM can - * vectorize them. Other loops keep the budget in their body, which costs - * less in a loop of a few iterations. */ + * vectorize them. Outside another loop, other loops keep the budget in + * their body, which costs less in a loop of a few iterations. */ export function loopAccessesTypedArrays(loop: Loop): boolean { const inExpr = (e: IrExpr): boolean => { if (e.kind === "bytesIntrinsic" && e.method === "get") return true; diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index ea866a134..427f6879c 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -618,6 +618,28 @@ function writesLocal(body: IrStmt[], localId: string): boolean { }); } +/** The counter of a `for` loop that its condition reads and its update + * steps by a constant, when nothing else in the loop writes it: each test + * of the condition then sees a different value. The local must not be + * boxed (a closure could write it). */ +export function steppedLoopCounter( + stmt: IrStmt & { kind: "for" }, + locals: ReadonlyMap, +): { ref: IrExpr & { kind: "varRef" }; step: number } | null { + const cond = stmt.cond; + if (cond?.kind !== "bin") return null; + for (const side of [cond.left, cond.right]) { + if (side.kind !== "varRef") continue; + const local = locals.get(side.localId); + if (local === undefined || local.boxed) continue; + const step = constantStep(stmt.update, side.localId); + if (step === null || writesLocal(stmt.body, side.localId)) continue; + if (writesLocal([{ kind: "exprStmt", expr: cond, loc: cond.loc }], side.localId)) continue; + return { ref: side, step }; + } + return null; +} + function isUnitIncrement(update: IrStmt | null, localId: string): boolean { // The frontend normally lowers `i++` to `i = i + 1` before backend // emission. Accept the incDec form too so this analysis remains valid for diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index f617c6ab4..794edd1af 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -136,3 +136,94 @@ test("worker programs poll counted loops over typed arrays between blocks of ite await rm(dir, { recursive: true, force: true }); } }); + +test("worker programs poll no loop whose proven ranges bound its runs inside another loop", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-bounded-loop-polls-")); + try { + const entry = join(dir, "bounded.ts"); + await writeFile( + entry, + [ + 'import { isMainThread, Worker } from "node:worker_threads";', + "const LIMIT = 50;", + "export function rows(cr: Float64Array, n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let i = 0; i < 8; i += 2) {", + " const c = cr[i]!;", + " for (let j = 0; j < LIMIT; j++) {", + " t = (t * 31 + j + c) % 1000003;", + " if (t === 7) break;", + " }", + " }", + " return t;", + "}", + "export function open(cr: Float64Array, n: number, k: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let i = 0; i < 8; i += 2) {", + " const c = cr[i]!;", + " for (let j = 0; j < k; j++) {", + " t = (t * 31 + j + c) % 1000003;", + " if (t === 7) break;", + " }", + " }", + " return t;", + "}", + "export function wide(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++) for (let j = 0; j < 5000; j++) t = (t * 31 + j) % 1000003;", + " return t;", + "}", + "export function single(): number {", + " let t = 0;", + " for (let j = 0; j < 50; j++) t = (t * 31 + j) % 1000003;", + " return t;", + "}", + "const cr = new Float64Array(8).fill(0.5);", + 'const k = Number(process.argv[2] ?? "50");', + "console.log(rows(cr, 3), open(cr, 3, k), wide(2), single());", + "if (!isMainThread) new Worker(new URL(import.meta.url));", + ].join("\n"), + ); + const outPath = join(dir, "bounded.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 body = (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"); + const polls = (text: string): number => text.match(/@scr_exc_flag/g)?.length ?? 0; + // The i and j loops of `rows` run at most 4 × (1 + 50) bodies: only the + // r loop polls, and the i loop over the typed array is not strip-mined. + const rows = body("rows"); + expect(rows).not.toMatch(/^loop\.poll\d+:$/m); + expect(polls(rows)).toBeGreaterThan(0); + // With a limit known only at run time the inner loops keep their polls. + // The innermost one is strip-mined, and a block that ends at the limit + // leaves it without polling: only a block that ends short of it polls. + const open = body("open"); + expect(polls(open)).toBeGreaterThan(polls(rows)); + expect(open).toMatch(/^loop\.poll\d+:$/m); + const ends = [...open.matchAll(/^loop\.end\d+:$[\s\S]*?^\S/gm)].map((match) => match[0]); + expect(ends.length).toBeGreaterThan(0); + for (const end of ends) expect(end).toMatch(/br i1 %\w+, label %loop\.poll\d+, label %/); + // A run of up to 16384 iterations is one block, polled as it leaves when + // it took more than 64. + expect(open).toMatch(/^loop\.poll\.run\d+:$/m); + // A nest over the bound, or a loop no other loop encloses, polls. + expect(polls(body("wide"))).toBeGreaterThan(0); + expect(body("wide")).toMatch(/^loop\.poll\d+:$/m); + expect(polls(body("single"))).toBeGreaterThan(0); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json new file mode 100644 index 000000000..2e52a519c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-terminate-bounded-loops.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-terminate-bounded-loops.ts b/tests/corpus/native-worker-terminate-bounded-loops.ts new file mode 100644 index 000000000..d244e021d --- /dev/null +++ b/tests/corpus/native-worker-terminate-bounded-loops.ts @@ -0,0 +1,91 @@ +// worker.terminate() stops a worker whose loop runs short inner loops: +// counted loops of a few iterations, a two-counter loop, an inner loop +// whose length grows past 64 iterations, an inner loop too long to finish, +// and a nest of loops with constant bounds. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +function counted(values: number[]): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < values.length; i++) acc = (acc + values[i]! * i) % 1000003; + for (let i = values.length - 1; i >= 0; i -= 2) acc = (acc * 7 + values[i]!) % 1000003; + if (round === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +function paired(values: number[]): number { + let flips = 0; + for (let round = 0; ; round++) { + for (let i = 0, j = values.length - 1; i < j; i++, j--) { + const t = values[i]!; + values[i] = values[j]!; + values[j] = t; + flips++; + } + if (round === 200) parentPort!.postMessage("ready"); + } + return flips; +} + +function growing(): number { + let acc = 0; + for (let n = 1; ; n++) { + const limit = n % 200; + for (let i = 0; i < limit; i++) acc = (acc + i) % 1000003; + if (n === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +function long(limit: number): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < limit; i++) { + acc = (acc * 7 + i) % 1000003; + if (i === 100000) parentPort!.postMessage("ready"); + } + } + return acc; +} + +const ROUNDS = 50; + +function constant(row: Float64Array): number { + let acc = 0; + for (let y = 0; ; y++) { + for (let i = 0; i < 8; i += 2) { + const c = row[i]!; + for (let j = 0; j < ROUNDS; j++) { + acc = (acc * 3 + j + c) % 1000003; + if (acc === -1) break; + } + } + if (y === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +if (isMainThread) { + for (const kind of ["counted", "paired", "growing", "long", "constant"]) { + await new Promise((resolve) => { + const worker = new Worker(new URL(import.meta.url), { workerData: kind }); + worker.once("message", () => { + void worker.terminate().then((code: number) => console.log(kind, "terminate", code)); + }); + worker.once("exit", (code: number) => { + console.log(kind, "exit", code); + resolve(); + }); + }); + } +} else { + const values = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]; + let result = 0; + if (workerData === "counted") result = counted(values); + else if (workerData === "paired") result = paired(values); + else if (workerData === "growing") result = growing(); + else if (workerData === "long") result = long(1e15); + else result = constant(new Float64Array(8).fill(0.25)); + console.log("unexpected result", result); +} diff --git a/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json b/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json new file mode 100644 index 000000000..bb744e4ed --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json @@ -0,0 +1 @@ +{ "plain": 4034, "san": 5956 } From 829f35bf851bd691078b1fa3ed8226e56c7543a0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:55:57 -0500 Subject: [PATCH 276/395] Pass sanitizeAttributes where the serialized-IR test emits directly The test emits each corpus program from its serialized IR with its own emitter options and compares the text with compile()'s. compile() now marks functions sanitize_address only in sanitized builds, so the test passes the same setting as objectAudit. --- .../compiler/src/backend/llvm/serialized-ir-emission.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts index 6d2457c57..1d35caa05 100644 --- a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts +++ b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts @@ -44,6 +44,7 @@ test("sort-heavy programs emit equal LLVM from memory and from serialized IR", a // compile() emits Linux textual LLVM with the unwinding ABI. unwindAbi: process.platform === "linux", objectAudit: sanitize, + sanitizeAttributes: sanitize, ...inlineRcOption(options.optimization, sanitize), }); const text = typeof emitted === "string" ? emitted : emitted.join(""); From d1f0113e49ce58cb2ea00c38b6521041c4d2a3bd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:58:14 -0500 Subject: [PATCH 277/395] Check only the files whose diagnostics the tsc gate keeps The tsc gate (preflight's errorsOf, and the original program of a JSX or declaration-projection build) asked for every file's semantic diagnostics, then dropped all of those in an opted-in package's files, in unselected package JavaScript and in package shims. With --npm-static that is nearly the whole check: on three.js's nodes fixture the 82 package files took 1.17 s and reported 13,022 diagnostics, the program's own files 0.1 s. getPreEmitDiagnostics takes the files whose diagnostics its caller keeps. When some file is left out, the server checks only the kept files (getDiagnosticsOfCheckedFiles), and their semantic diagnostics come in the full check's order. When a stored answer for the supplied declaration files applies to the program, it stands in for checking those files, as in the full check; the storage rule is unchanged. A global diagnostic can come from checking any file, so when the partial check reports one, the full check answers as before. At load time, before workspace packages are known, the gate leaves out only package and shim files. The kept diagnostics are unchanged. --- packages/compiler/src/frontend/program.ts | 21 +++++- .../src/frontend/ts7/lib-diagnostics.test.ts | 40 ++++++++++ .../src/frontend/ts7/lib-diagnostics.ts | 37 +++++++++- .../src/frontend/ts7/program-host.test.ts | 55 +++++++++++++- .../compiler/src/frontend/ts7/program-host.ts | 74 ++++++++++++++++--- 5 files changed, 208 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index b93e3f217..284b2ce06 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -665,6 +665,14 @@ function createRequireProgramRoots7(program: ts.Program): string[] { return [...roots].sort(); } +/** Whether the tsc gate can keep a file's diagnostics when the program + * loads: it drops all of an opted-in package's files and of the package + * shims (keptErrors in preflight also drops unselected package JavaScript, + * which is not known yet here, so those files are still checked). */ +function tscGateFileKept(fileName: string): boolean { + return npmStaticPackageOfPath(fileName) === null && !isPackageShimFile(fileName); +} + /** Classify the explicitly selected installed package's own sources. */ export function entryPackageFilePredicate(entryPath: string): (file: string) => boolean { const entryFile = tsgoPath(entryPath); @@ -934,7 +942,7 @@ function loadProgram7( const original = program; // The project world must read the original text too, so it is built // before the rewritten files are served, and only when it is needed. - const diagnostics = ts.getPreEmitDiagnostics(original); + const diagnostics = ts.getPreEmitDiagnostics(original, tscGateFileKept); const errors = diagnostics.some((d) => d.category === ts.DiagnosticCategory.Error); tscWorld = { program: original, @@ -982,7 +990,7 @@ function loadProgram7( } else { tscWorld = { program: original, - diagnostics: ts.getPreEmitDiagnostics(original), + diagnostics: ts.getPreEmitDiagnostics(original, tscGateFileKept), projectWorld: null, declWorldFallback: true, }; @@ -5438,7 +5446,14 @@ function preflight7(load: LoadResult): { const sf = p.getSourceFile(d.fileName); return sf !== undefined && isCheckedJsSourceFile(sf, projectCheckJs); }; - const errorsOf = (p: ts.Program): ts.Diagnostic[] => keptErrors(p, ts.getPreEmitDiagnostics(p)); + // keptErrors drops every diagnostic of these files: the gate does not ask + // the checker for them. + const diagnosticsKept = (fileName: string): boolean => + npmStaticPackageOfPath(fileName) === null && + !unselectedPackageJsFile(fileName) && + !isPackageShimFile(fileName); + const errorsOf = (p: ts.Program): ts.Diagnostic[] => + keptErrors(p, ts.getPreEmitDiagnostics(p, diagnosticsKept)); const keptErrors = (p: ts.Program, all: readonly ts.Diagnostic[]): ts.Diagnostic[] => { // First pass: every comment-side 2300's (file, name) — the partners // the second pass forgives. diff --git a/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts b/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts index e87db5f1a..83abaeddc 100644 --- a/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts +++ b/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { afterAll, afterEach, expect, test } from "vitest"; import type { SourceFile } from "./ast-types.js"; import { + reusedKeptLibDiagnostics, reusedLibDiagnostics, setLibDiagnosticsStore, type LibDiagnosticsStore, @@ -118,6 +119,45 @@ test("a stored answer stands in for checking the supplied files, merged by file expect(full?.semantic).toEqual(reused?.semantic); }); +test("with kept files, a stored answer needs checking only the kept other files", () => { + const store = memoryStore(); + setLibDiagnosticsStore(store, "checker"); + const pkg = join(dir, "node_modules", "pkg", "index.d.ts"); + const semantic = new Map([ + [lib, [diagnostic(lib, 1)]], + [fallback, []], + [main, [diagnostic(main, 5)]], + [pkg, [diagnostic(pkg, 3)]], + ]); + const withPackage = files({ + [main]: "export const n: number = 'x';\n", + [pkg]: "export declare const p: number;\n", + }); + const all = [lib, fallback, main, pkg]; + const keep = new Set([lib, fallback, main]); + // Without a stored answer nothing is checked or stored. + const cold = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(cold, all, supplied, withPackage, keep)).toBeNull(); + expect(cold.asked).toEqual([]); + expect(store.entries.size).toBe(0); + reusedLibDiagnostics(sessionProgram({ semantic }), all, supplied, withPackage); + expect(store.entries.size).toBe(1); + const warm = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(warm, all, supplied, withPackage, keep)).toEqual({ + semantic: [diagnostic(lib, 1), diagnostic(main, 5)], + global: [], + }); + expect(warm.asked).toEqual([`checked ${main}`]); + // A left-out file that can declare something global still prevents reuse. + const augmenting = files({ + [main]: "export const n: number = 'x';\n", + [pkg]: "export {};\ndeclare global {\n var NaN: number;\n}\n", + }); + const blocked = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(blocked, all, supplied, augmenting, keep)).toBeNull(); + expect(blocked.asked).toEqual([]); +}); + test("files merge in code point order, which differs from UTF-16 order", () => { const store = memoryStore(); setLibDiagnosticsStore(store, "checker"); diff --git a/packages/compiler/src/frontend/ts7/lib-diagnostics.ts b/packages/compiler/src/frontend/ts7/lib-diagnostics.ts index 30c6e81f0..c3e563fef 100644 --- a/packages/compiler/src/frontend/ts7/lib-diagnostics.ts +++ b/packages/compiler/src/frontend/ts7/lib-diagnostics.ts @@ -64,6 +64,31 @@ export function reusedLibDiagnostics( fileNames: readonly string[], supplied: (fileName: string) => boolean, sourceFile: (fileName: string) => SourceFile | undefined, +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return reuse(program, fileNames, supplied, sourceFile, () => true, false); +} + +/** reusedLibDiagnostics for a caller that keeps only the diagnostics of the + * `kept` files: a stored answer then needs checking only the kept other + * files, and without a stored answer the result is null (nothing is checked + * or stored). */ +export function reusedKeptLibDiagnostics( + program: Ts7SessionProgram, + fileNames: readonly string[], + supplied: (fileName: string) => boolean, + sourceFile: (fileName: string) => SourceFile | undefined, + kept: ReadonlySet, +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return reuse(program, fileNames, supplied, sourceFile, (name) => kept.has(name), true); +} + +function reuse( + program: Ts7SessionProgram, + fileNames: readonly string[], + supplied: (fileName: string) => boolean, + sourceFile: (fileName: string) => SourceFile | undefined, + keep: (fileName: string) => boolean, + storedOnly: boolean, ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { if (configured === null || process.env["SCRIPTC_LIB_DIAGNOSTICS"] === "0") return null; const { store, checker } = configured; @@ -104,12 +129,16 @@ export function reusedLibDiagnostics( if (globals !== null) writeEntry(store, key, globals, Object.fromEntries(stored.files)); } if (stored !== null && (scripts.length === 0 || (globals !== null && !collides(globals)))) { - const checked = program.checkedFileDiagnosticData(own); + const checkedNames = storedOnly ? own.filter(keep) : own; + const checked = program.checkedFileDiagnosticData(checkedNames); if (checked === null) return null; - const byFile = new Map(stored.files); - own.forEach((name, index) => byFile.set(name, checked.files[index] ?? [])); + const byFile = new Map( + storedOnly ? [...stored.files].filter(([name]) => keep(name)) : stored.files, + ); + checkedNames.forEach((name, index) => byFile.set(name, checked.files[index] ?? [])); return { semantic: inFileOrder(byFile), global: checked.global }; } + if (storedOnly) return null; // The full check, as before. Its answers for the supplied files are stored // when it reports no global diagnostics and the program's scripts declare // none of the supplied files' globals (so nothing of the program reached @@ -339,7 +368,7 @@ function readEntry( /** Each file's diagnostics (already in the checker's order) in the order the * checker sorts files: by file name, compared by code point. */ -function inFileOrder(byFile: ReadonlyMap): Ts7DiagnosticData[] { +export function inFileOrder(byFile: ReadonlyMap): Ts7DiagnosticData[] { const names = [...byFile.keys()].sort(compareCodePoints); const out: Ts7DiagnosticData[] = []; for (const name of names) for (const d of byFile.get(name)!) out.push(d); diff --git a/packages/compiler/src/frontend/ts7/program-host.test.ts b/packages/compiler/src/frontend/ts7/program-host.test.ts index 80c4d2bcf..2ab6a0293 100644 --- a/packages/compiler/src/frontend/ts7/program-host.test.ts +++ b/packages/compiler/src/frontend/ts7/program-host.test.ts @@ -3,7 +3,12 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test, vi } from "vitest"; import { Ts7Api } from "./rpc-api.js"; -import { Ts7Host, type Ts7ApiOptions, type Ts7CompilerOptions } from "./program-host.js"; +import { + getPreEmitDiagnostics, + Ts7Host, + type Ts7ApiOptions, + type Ts7CompilerOptions, +} from "./program-host.js"; import { ModuleDetectionKind, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js"; import { tsgoPath } from "./session-path.js"; @@ -70,6 +75,54 @@ test("implementation discovery leaves declaration trees lazy without skipping th expect(() => program.getSourceFileNames()).toThrow("disposed"); }); +test("pre-emit diagnostics for kept files check only those, unless a global diagnostic appears", () => { + const { directory, entry } = fixture(); + const other = join(directory, "other.ts"); + const later = join(directory, "later.ts"); + writeFileSync( + entry, + 'import "./other.js";\nimport "./later.js";\nexport const n: number = "x";\n', + ); + writeFileSync(other, "export const s: string = 1;\n"); + writeFileSync(later, "export const b: boolean = 0;\n"); + const { host } = makeHost(directory); + const program = host.createProgram([entry], options); + try { + const full = getPreEmitDiagnostics(program); + const keep = (name: string): boolean => name !== tsgoPath(other); + const checked = vi.spyOn(program.project.program, "checkedFileDiagnosticData"); + const kept = getPreEmitDiagnostics(program, keep); + // The full answer without the left-out file's diagnostics, in the same order. + expect(full.some((diagnostic) => diagnostic.fileName === tsgoPath(other))).toBe(true); + expect(kept.map((diagnostic) => [diagnostic.fileName, diagnostic.code])).toEqual( + full + .filter((diagnostic) => diagnostic.fileName === undefined || keep(diagnostic.fileName)) + .map((diagnostic) => [diagnostic.fileName, diagnostic.code]), + ); + expect(kept).toHaveLength(2); + expect(checked).toHaveBeenCalledTimes(1); + expect(checked.mock.calls[0]![0]).not.toContain(tsgoPath(other)); + // Keeping every file is the full check. + expect(getPreEmitDiagnostics(program, () => true)).toEqual(full); + expect(checked).toHaveBeenCalledTimes(1); + // A global diagnostic from the partial check, or a server without it: the full check. + for (const answer of [ + { + files: [], + global: [{ fileName: undefined, pos: 0, end: 0, code: 2318, category: 1, text: "global" }], + }, + null, + ]) { + checked.mockReturnValueOnce( + answer as ReturnType, + ); + expect(getPreEmitDiagnostics(program, keep)).toEqual(full); + } + } finally { + program.dispose(); + } +}); + test("the factory receives one connection with the host's filesystem and timing", () => { const { directory, entry } = fixture(); const factory = vi.fn((connection: Ts7ApiOptions) => new Ts7Api(connection)); diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..bd6921afc 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -48,7 +48,7 @@ import { import { packageShimDeclarationText } from "../package-shims.js"; import { typePatchedText } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; -import { reusedLibDiagnostics } from "./lib-diagnostics.js"; +import { inFileOrder, reusedKeptLibDiagnostics, reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; import type { Ts7DiagnosticData } from "./session-schema.generated.js"; @@ -423,17 +423,31 @@ export class Ts7Program { /** The semantic and global diagnostics with the stored diagnostics of the * declaration files scriptc supplies (lib-diagnostics.ts), or null. */ reusedLibDiagnostics(): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return this.libDiagnostics(null); + } + + /** reusedLibDiagnostics for the `kept` files only: null without a stored + * answer. */ + reusedKeptLibDiagnostics( + kept: ReadonlySet, + ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return this.libDiagnostics(kept); + } + + private libDiagnostics( + kept: ReadonlySet | null, + ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { this.snapshot.ensureActive(); const program = this.project.program; const declarations = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); - return reusedLibDiagnostics( - program, - this.getSourceFileNames(), - (name) => - declarations.has(tsgoPath(name)) || - program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true, - (name) => this.getSourceFile(name), - ); + const names = this.getSourceFileNames(); + const supplied = (name: string): boolean => + declarations.has(tsgoPath(name)) || + program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true; + const sourceFile = (name: string): SourceFile | undefined => this.getSourceFile(name); + return kept === null + ? reusedLibDiagnostics(program, names, supplied, sourceFile) + : reusedKeptLibDiagnostics(program, names, supplied, sourceFile, kept); } isSourceFileDefaultLibrary(file: SourceFile): boolean { @@ -464,10 +478,35 @@ export class Ts7Program { /** 5.9.3's getPreEmitDiagnostics, composed from 7.0.2's split surface (the * package dropped the aggregate). Order mirrors 5.9.3: config, options/ - * program, global, syntactic, semantic. */ -export function getPreEmitDiagnostics(program: Ts7Program): readonly Diagnostic[] { + * program, global, syntactic, semantic. + * + * With `keep`, a caller that discards every diagnostic of the other files + * asks for semantic diagnostics of the kept files only: the server checks + * only those (getDiagnosticsOfCheckedFiles), or only those not answered by + * stored diagnostics of the supplied declaration files, and their + * diagnostics come in the full check's order. A global diagnostic can come + * from checking any file, so when that partial check reports one, the full + * check answers. */ +export function getPreEmitDiagnostics( + program: Ts7Program, + keep?: (fileName: string) => boolean, +): readonly Diagnostic[] { const config = program.getConfigFileParsingDiagnostics(); const options = program.getProgramDiagnostics(); + if (keep !== undefined) { + const kept = program.getSourceFileNames().filter((name) => keep(name)); + if (kept.length < program.getSourceFileNames().length) { + const partial = + program.reusedKeptLibDiagnostics(new Set(kept)) ?? keptFileDiagnostics(program, kept); + if (partial !== null && partial.global.length === 0) + return [ + ...config, + ...options, + ...program.getSyntacticDiagnostics(), + ...partial.semantic.map(ts7Diagnostic), + ]; + } + } const reused = program.reusedLibDiagnostics(); if (reused !== null) return [ @@ -486,6 +525,19 @@ export function getPreEmitDiagnostics(program: Ts7Program): readonly Diagnostic[ ]; } +/** The semantic diagnostics of the kept files, in the full check's order, + * and the global diagnostics of checking them; null when the server cannot + * check only some files. */ +function keptFileDiagnostics( + program: Ts7Program, + kept: readonly string[], +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + const checked = program.project.program.checkedFileDiagnosticData(kept); + if (checked === null) return null; + const byFile = new Map(kept.map((name, index) => [name, checked.files[index] ?? []])); + return { semantic: inFileOrder(byFile), global: checked.global }; +} + /** ts.findConfigFile: nearest configName walking up from searchPath. */ export function findConfigFile( searchPath: string, From a96fbcfb641ec841f1ff5e98f5b8cd855782734e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:14:48 -0500 Subject: [PATCH 278/395] Refuse static member reads of MIMEType and MIMEParams in JavaScript JavaScript sources hold node:util's MIMEType and MIMEParams as identity tokens, so a read such as MIMEType.prototype or MIMEType.name answered undefined where Node returns the class's member. Those reads now report SC2020, as the TypeScript spelling does at compile time; in JavaScript the refusal throws when the read runs. The node24-mime-errors fixture reads MIMEType.prototype to take the getters' descriptors, so its expected refusal is now SC2020. --- .../compiler/src/frontend/lowering/builtins/mime.ts | 11 +++++++++++ .../frontend/lowering/expressions/property-access.ts | 2 ++ tests/harness/npm.test.ts | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/mime.ts b/packages/compiler/src/frontend/lowering/builtins/mime.ts index 27f95226c..4c36d6cc5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/mime.ts +++ b/packages/compiler/src/frontend/lowering/builtins/mime.ts @@ -134,6 +134,17 @@ export function lowerMimeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr }; } +/** Reports SC2020 when `access` reads a static member of MIMEType or + * MIMEParams (`MIMEType.prototype`, `MIMEType.name`); null otherwise. + * JavaScript sources hold either class as an identity token, which has no + * members, so the read would answer undefined. MIMEType.parse calls lower + * in lowerMimeCall and never reach the member read. */ +export function fenceMimeStaticRead(lowerer: Lowerer, access: ts.PropertyAccessExpression): null { + const cls = mimeClassOf(lowerer, access.expression); + if (cls !== null) lowerer.noLowering(`util.${cls}.${access.name.text}`, access); + return null; +} + /** `v instanceof MIMEType` / `v instanceof MIMEParams`. */ export function lowerMimeInstanceof(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr | null { const cls = mimeClassOf(lowerer, expr.right); diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index df62da2c8..cfd7ae297 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -63,6 +63,7 @@ import { } from "../../../ir/ir.js"; import { lowerNavigatorMember } from "../builtins/navigator.js"; import { lowerX509PublicKey } from "../builtins/x509.js"; +import { fenceMimeStaticRead } from "../builtins/mime.js"; import { NODE_COMPAT_VERSION } from "../builtins/process.js"; import { inlineNamespaceRequireOf, @@ -942,6 +943,7 @@ export function lowerPropertyAccessExpr( // prototype that no lowering above claimed keeps the SC2020 fence // rather than reading undefined through the field and dynamic reads. fenceBuiltinConstructorStaticRead(lowerer, expr) ?? + fenceMimeStaticRead(lowerer, expr) ?? lowerer.lowerFieldRead(expr); // A union-typed field read narrows like an identifier when the // checker has narrowed this use to one arm. diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index d51a2e242..8e133f6e2 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -74,7 +74,7 @@ const KNOWN: Record = { "node24-file-url-conversion": { build: "SC1090" }, "node24-fs-descriptors": { run: "SC2020" }, "node24-mime": { run: "SC2020" }, - "node24-mime-errors": { run: "SC1090" }, + "node24-mime-errors": { run: "SC2020" }, "node24-no-error-assertions": { run: "SC2020" }, "node24-parseargs": { run: "SC1090" }, "node24-promise-timers": { run: "SC2020" }, From a9ea71df7ddec01a9e0c9984fe3981f4d4b68cf9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:22:25 -0500 Subject: [PATCH 279/395] Compare unions that hold promises as untyped values A promise converted into a union's promise arm is a new promise, so two optional slots set to one promise each hold their own conversion, and a value narrowed out of such a slot may be one too. Union equality compared those arm payloads by pointer and answered false where Node answers true. Any comparison between a union with a promise arm and a promise or another such union now compares both sides as untyped values, where a converted promise answers with its origin's identity. --- .../frontend/lowering/expressions/binary.ts | 27 ++++++++++--------- .../union-promise-function-arm-widening.ts | 9 +++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 01b48554e..4e46b4d5f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -2388,19 +2388,22 @@ export function lowerStrictEquality( loc, }; } - // A plain promise against a union whose promise arm has a different - // payload (`chains.get(id) === tail` with a `Map>`): - // wrapping would convert the promise into that arm, and the converted - // promise is a new object. Promises are objects to ===, so compare both - // sides as untyped values, where a converted promise answers with its - // origin's identity (the plain promise-vs-promise rule below). - const plainPromise = - left.type.kind === "promise" ? left : right.type.kind === "promise" ? right : null; + // A promise against a union with a promise arm, or two such unions: + // converting a promise into a union's promise arm of another payload + // (`chains.get(id) === tail` with a `Map>`, or + // `a.pending === b.pending` after both were set to one promise) builds a + // new promise, so either side may hold a conversion of the other's + // promise, and a value narrowed out of the union may be one too. + // Promises are objects to ===, so compare both sides as untyped values, + // where a converted promise answers with its origin's identity (the plain + // promise-vs-promise rule below). + const holdsPromise = (type: IrType): boolean => + type.kind === "promise" || + (type.kind === "union" && + (lowerer.unions.get(type.unionId)?.arms ?? []).some((arm) => arm.kind === "promise")); if ( - !bothUnion && - plainPromise !== null && - lowerer.armTag(ut.unionId, plainPromise.type) < 0 && - unionArms.some((arm) => arm.kind === "promise") && + holdsPromise(left.type) && + holdsPromise(right.type) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type) ) { diff --git a/tests/corpus/union-promise-function-arm-widening.ts b/tests/corpus/union-promise-function-arm-widening.ts index 751af6f41..d3b8f618d 100644 --- a/tests/corpus/union-promise-function-arm-widening.ts +++ b/tests/corpus/union-promise-function-arm-widening.ts @@ -58,6 +58,15 @@ async function main(): Promise { console.log(store.pending === other); console.log(await store.pending); + // One promise in two optional slots: each holds its own conversion. + const second: Store = {}; + second.pending = work; + console.log(store.pending === second.pending, store.pending !== second.pending); + const slots: Store[] = [store, second, { pending: other }]; + console.log(slots.map((slot) => slot.pending === slots[0]!.pending).join(",")); + second.lookup = findName; + console.log(store.lookup === second.lookup); + const lookup = store.lookup; if (lookup) console.log(await lookup("abcd"), await lookup("ab")); console.log(store.scale?.(21)); From e3f02c73c9cde475b83fae2db150f2cc5f0af083 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:32:27 -0500 Subject: [PATCH 280/395] Probe hashed number-key maps without normalizing integer keys A number-key Map or Set read whose key has an integer view (sc_map_entry_f64_int) fell back to the same hashed probe as any double key, which tests for NaN and -0 before hashing. The fallback is now sc_map_entry_f64_hashed_int, which takes the integer and converts it to the key word directly: an integer is never NaN, and zero converts to +0. Both hashed probes test the absent-key filter only before scanning a map that keeps no bucket table. The filter keeps two of its 64 bits per key, so it rejects few absent keys once a map has a bucket table, and the probe's first bucket read answers those. The map layout and the runtime lookup are unchanged. Corpus 8331 covers integers the compiler tracks as integers on hashed maps and absent keys on small bucket tables, and matches Node's output. --- .../compiler/src/backend/llvm/emit-module.ts | 254 ++++++++++-------- .../backend/llvm/map-number-lookup.test.ts | 22 +- .../src/backend/llvm/map-number-lookup.ts | 7 +- ...331-map-number-hashed-integer-keys.ts.json | 6 + .../8331-map-number-hashed-integer-keys.ts | 102 +++++++ ...331-map-number-hashed-integer-keys.ts.json | 1 + 6 files changed, 276 insertions(+), 116 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json create mode 100644 tests/corpus/8331-map-number-hashed-integer-keys.ts create mode 100644 tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2c..66e419fb9 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1210,6 +1210,126 @@ export function emitModuleParts(emitter: LlEmitter): string[] { return out; } +/** The out-of-line hashed probe of a number-key map (scr_map_find_f64): + * `keyLines` define the normalized key word %kb from the parameter, and + * `runtimeKey` is the double passed to the runtime lookup. A map with at + * most SCR_MAP_LINEAR_LIMIT entries keeps no bucket table: the absent-key + * filter is tested before its entries are scanned. A bucket table is + * probed without the filter: the filter keeps two of its 64 bits per key, + * so past a few dozen keys it rejects few absent keys, and the probe's + * first bucket read answers those. Compact tables tag each 24-bit entry + * index (+1) with the hash's top byte; wider tables take the runtime + * lookup. */ +function hashedNumberProbe( + name: string, + param: string, + sz: string, + keyLines: string[], + runtimeKey: string, +): string[] { + return [ + `define internal ptr @${name}(ptr %m, ${param}) noinline ${FN_ATTRS} {`, + `entry:`, + ...keyLines, + ` %x1s = lshr i64 %kb, 30`, + ` %x1 = xor i64 %kb, %x1s`, + ` %x2 = mul i64 %x1, -4658895280553007687`, + ` %x3s = lshr i64 %x2, 27`, + ` %x3 = xor i64 %x2, %x3s`, + ` %x4 = mul i64 %x3, -7723592293110705685`, + ` %x5s = lshr i64 %x4, 31`, + ` %x5 = xor i64 %x4, %x5s`, + ` %hz = icmp eq i64 %x5, 0`, + ` %h = select i1 %hz, i64 1, i64 %x5`, + ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, + ...(sz === "i64" + ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] + : [` %nb = load i32, ptr %nbp`]), + ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %ents = load ptr, ptr %eps`, + ` %linear = icmp eq ${sz} %nb, 0`, + ` br i1 %linear, label %filter, label %buckets`, + `filter:`, + ` %f1s = lshr i64 %h, 44`, + ` %f1a = and i64 %f1s, 63`, + ` %f1 = shl i64 1, %f1a`, + ` %f2s = lshr i64 %h, 50`, + ` %f2a = and i64 %f2s, 63`, + ` %f2 = shl i64 1, %f2a`, + ` %fb = or i64 %f1, %f2`, + ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, + ` %filter.bits = load i64, ptr %fp`, + ` %fm = and i64 %filter.bits, %fb`, + ` %fin = icmp eq i64 %fm, %fb`, + ` br i1 %fin, label %scan, label %miss`, + // A number-key entry has no cached hash; a dead entry's key matches no key. + `scan:`, + ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, + ...(sz === "i64" + ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] + : [` %ne = load i32, ptr %nep`]), + ` br label %scan.loop`, + `scan.loop:`, + ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, + ` %sdone = icmp eq ${sz} %si, %ne`, + ` br i1 %sdone, label %miss, label %scan.entry`, + `scan.entry:`, + ` %se = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %si`, + ` %sk = load i64, ptr %se`, + ` %skq = icmp eq i64 %sk, %kb`, + ` br i1 %skq, label %scan.found, label %scan.next`, + `scan.found:`, + ` ret ptr %se`, + `scan.next:`, + ` %si.next = add ${sz} %si, 1`, + ` br label %scan.loop`, + `buckets:`, + ` %wide = icmp ugt ${sz} %nb, 16777216`, + ` br i1 %wide, label %runtime, label %probe`, + `probe:`, + ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, + ` %bk = load ptr, ptr %bp`, + ` %mask = sub ${sz} %nb, 1`, + ` %ht = lshr i64 %h, 32`, + ` %ht32 = trunc i64 %ht to i32`, + ` %tag = and i32 %ht32, -16777216`, + sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, + ` %i0 = and ${sz} %hi, %mask`, + ` br label %probe.loop`, + `probe.loop:`, + ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, + ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, + ` %b = load i32, ptr %bep`, + ` %bempty = icmp eq i32 %b, 0`, + ` br i1 %bempty, label %miss, label %probe.tag`, + `probe.tag:`, + ` %bt = and i32 %b, -16777216`, + ` %btq = icmp eq i32 %bt, %tag`, + ` br i1 %btq, label %probe.entry, label %probe.next`, + `probe.entry:`, + ` %bix = and i32 %b, 16777215`, + ` %bix1 = sub i32 %bix, 1`, + sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, + ` %pe = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %bi`, + ` %pk = load i64, ptr %pe`, + ` %pkq = icmp eq i64 %pk, %kb`, + ` br i1 %pkq, label %probe.found, label %probe.next`, + `probe.found:`, + ` ret ptr %pe`, + `probe.next:`, + ` %pi.inc = add ${sz} %pi, 1`, + ` %pi.next = and ${sz} %pi.inc, %mask`, + ` br label %probe.loop`, + `miss:`, + ` ret ptr null`, + `runtime:`, + ` %r = call ptr @scr_map_entry_f64(ptr %m, double ${runtimeKey}) memory(read) nounwind willreturn`, + ` ret ptr %r`, + `}`, + ``, + ]; +} + /** The shared abort helpers (emitted only when referenced): the OOM * abort of untraced shape allocation and the invalid-union-tag abort — * both print the runtime ABI’s exact message on fd 2 and abort. */ @@ -1349,118 +1469,34 @@ export function helperDefs(emitter: LlEmitter): string[] { ` %dense.any = icmp ne i64 %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, - ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, + ` %hashed = call ptr @sc_map_entry_f64_hashed_int(ptr %m, i64 %ki)`, ` ret ptr %hashed`, `}`, ``, - // Hashing (scr_map_find_f64): the SameValueZero key word, its - // scr_key_mix hash (zero remapped to one) and the absent-key filter. - `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, - `entry:`, - ` %nan = fcmp uno double %k, %k`, - ` %zero = fcmp oeq double %k, 0.0`, - ` %kz = select i1 %zero, double 0.0, double %k`, - ` %kzb = bitcast double %kz to i64`, - ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, - ` %x1s = lshr i64 %kb, 30`, - ` %x1 = xor i64 %kb, %x1s`, - ` %x2 = mul i64 %x1, -4658895280553007687`, - ` %x3s = lshr i64 %x2, 27`, - ` %x3 = xor i64 %x2, %x3s`, - ` %x4 = mul i64 %x3, -7723592293110705685`, - ` %x5s = lshr i64 %x4, 31`, - ` %x5 = xor i64 %x4, %x5s`, - ` %hz = icmp eq i64 %x5, 0`, - ` %h = select i1 %hz, i64 1, i64 %x5`, - ` %f1s = lshr i64 %h, 44`, - ` %f1a = and i64 %f1s, 63`, - ` %f1 = shl i64 1, %f1a`, - ` %f2s = lshr i64 %h, 50`, - ` %f2a = and i64 %f2s, 63`, - ` %f2 = shl i64 1, %f2a`, - ` %fb = or i64 %f1, %f2`, - ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, - ` %filter = load i64, ptr %fp`, - ` %fm = and i64 %filter, %fb`, - ` %fin = icmp eq i64 %fm, %fb`, - ` br i1 %fin, label %table, label %miss`, - `table:`, - ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, - ...(sz === "i64" - ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] - : [` %nb = load i32, ptr %nbp`]), - ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, - ` %ents = load ptr, ptr %eps`, - ` %linear = icmp eq ${sz} %nb, 0`, - ` br i1 %linear, label %scan, label %buckets`, - // Small maps keep no bucket table: scan the entries' keys. A number-key - // entry has no cached hash; a dead entry's key matches no key. - `scan:`, - ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, - ...(sz === "i64" - ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] - : [` %ne = load i32, ptr %nep`]), - ` br label %scan.loop`, - `scan.loop:`, - ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, - ` %sdone = icmp eq ${sz} %si, %ne`, - ` br i1 %sdone, label %miss, label %scan.entry`, - `scan.entry:`, - ` %se = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %si`, - ` %sk = load i64, ptr %se`, - ` %skq = icmp eq i64 %sk, %kb`, - ` br i1 %skq, label %scan.found, label %scan.next`, - `scan.found:`, - ` ret ptr %se`, - `scan.next:`, - ` %si.next = add ${sz} %si, 1`, - ` br label %scan.loop`, - // Compact tables tag each 24-bit entry index (+1) with the hash's - // top byte; wider tables take the runtime lookup. - `buckets:`, - ` %wide = icmp ugt ${sz} %nb, 16777216`, - ` br i1 %wide, label %runtime, label %probe`, - `probe:`, - ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, - ` %bk = load ptr, ptr %bp`, - ` %mask = sub ${sz} %nb, 1`, - ` %ht = lshr i64 %h, 32`, - ` %ht32 = trunc i64 %ht to i32`, - ` %tag = and i32 %ht32, -16777216`, - sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, - ` %i0 = and ${sz} %hi, %mask`, - ` br label %probe.loop`, - `probe.loop:`, - ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, - ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, - ` %b = load i32, ptr %bep`, - ` %bempty = icmp eq i32 %b, 0`, - ` br i1 %bempty, label %miss, label %probe.tag`, - `probe.tag:`, - ` %bt = and i32 %b, -16777216`, - ` %btq = icmp eq i32 %bt, %tag`, - ` br i1 %btq, label %probe.entry, label %probe.next`, - `probe.entry:`, - ` %bix = and i32 %b, 16777215`, - ` %bix1 = sub i32 %bix, 1`, - sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, - ` %pe = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %bi`, - ` %pk = load i64, ptr %pe`, - ` %pkq = icmp eq i64 %pk, %kb`, - ` br i1 %pkq, label %probe.found, label %probe.next`, - `probe.found:`, - ` ret ptr %pe`, - `probe.next:`, - ` %pi.inc = add ${sz} %pi, 1`, - ` %pi.next = and ${sz} %pi.inc, %mask`, - ` br label %probe.loop`, - `miss:`, - ` ret ptr null`, - `runtime:`, - ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, - ` ret ptr %r`, - `}`, - ``, + // Hashing (scr_map_find_f64): the SameValueZero key word and its + // scr_key_mix hash. sc_map_entry_f64_int's probe converts the integer + // view to the key word, which is never NaN or -0, and so skips the + // normalization. + ...hashedNumberProbe( + "sc_map_entry_f64_hashed", + "double %k", + sz, + [ + ` %nan = fcmp uno double %k, %k`, + ` %zero = fcmp oeq double %k, 0.0`, + ` %kz = select i1 %zero, double 0.0, double %k`, + ` %kzb = bitcast double %kz to i64`, + ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, + ], + "%k", + ), + ...hashedNumberProbe( + "sc_map_entry_f64_hashed_int", + "i64 %ki", + sz, + [` %kf = sitofp i64 %ki to double`, ` %kb = bitcast double %kf to i64`], + "%kf", + ), ); } // Map/Set iteration steps read the entries array inline: an index is diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts index 7457db42d..f7bd87af1 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts @@ -168,8 +168,10 @@ test("hashed number-key lookups probe inline and call the runtime only for wide expect(hashed).toContain("select i1 %nan, i64 9221120237041090560, i64 %kzb"); expect(hashed).toContain("mul i64 %x1, -4658895280553007687"); expect(hashed).toContain("mul i64 %x3, -7723592293110705685"); - // Filter (field 10), small-map scan of the entry count (field 8), and - // compact buckets (fields 13 and 14) tagged with the hash's top byte. + // Filter (field 10) before the small-map scan of the entry count (field + // 8) only, and compact buckets (fields 13 and 14) tagged with the hash's + // top byte. + expect(hashed).toContain("br i1 %linear, label %filter, label %buckets"); expect(hashed).toContain("getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10"); expect(hashed).toContain(`load i32, ptr %nep`); expect(hashed).toContain(`load i32, ptr %nbp`); @@ -192,11 +194,23 @@ test("a key with an integer view tests the direct index on the integer", () => { integral.module, )![0]; // An unsigned compare also rejects negative keys; the hashed probe takes - // the double. + // the integer. expect(helper).toContain("icmp ult i64 %ki, %nd"); expect(helper).toContain(`getelementptr inbounds i32, ptr %dense, ${sz} %i`); - expect(helper).toContain("call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)"); + expect(helper).toContain("call ptr @sc_map_entry_f64_hashed_int(ptr %m, i64 %ki)"); expect(helper).not.toContain("fptosi"); + // Its key word is the integer's double, which needs no NaN or -0 + // normalization; the runtime lookup gets that double. + const hashed = + /^define internal ptr @sc_map_entry_f64_hashed_int\(ptr %m, i64 %ki\) noinline [^]*?^}/m.exec( + integral.module, + )![0]; + expect(hashed).toContain("sitofp i64 %ki to double"); + expect(hashed).not.toContain("fcmp"); + expect(hashed).toContain("mul i64 %x1, -4658895280553007687"); + expect(hashed).toContain( + "call ptr @scr_map_entry_f64(ptr %m, double %kf) memory(read) nounwind willreturn", + ); // A fractional key and a key without a proof keep the double lookup. for (const key of [{ kind: "numLit", value: 0.5, type: F64, loc } as IrExpr, ref("k", F64)]) { const { work } = emitted(lookupFixture(key), bits); diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.ts b/packages/compiler/src/backend/llvm/map-number-lookup.ts index afc8ec606..17a1563c3 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.ts @@ -4,9 +4,10 @@ * direct index in the runtime (scr_map.c). Reads go through the internal * sc_map_entry_f64 helper (emitter.ts helperDefs), which LLVM inlines: it * resolves directly indexed maps without a call and other maps through - * the internal sc_map_entry_f64_hashed probe, which hashes the key as the - * runtime does and defers to scr_map_entry_f64 only for wide bucket - * tables. Both only read memory. The generated code then reads the entry's + * the internal sc_map_entry_f64_hashed probe (sc_map_entry_f64_hashed_int + * for a key with an integer view), which hashes the key as the runtime + * does and defers to scr_map_entry_f64 only for wide bucket tables. Both + * only read memory. The generated code then reads the entry's * value slot itself and retains a reference value with the value type's own * retain, which is the adapter the collection was constructed with. * String-keyed reads use the same entry contract through the internal diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json new file mode 100644 index 000000000..86822b641 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8331-map-number-hashed-integer-keys.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8331-map-number-hashed-integer-keys.ts b/tests/corpus/8331-map-number-hashed-integer-keys.ts new file mode 100644 index 000000000..8af7293e9 --- /dev/null +++ b/tests/corpus/8331-map-number-hashed-integer-keys.ts @@ -0,0 +1,102 @@ +// Number-keyed Map and Set lookups whose keys are integers the compiler +// tracks as integers (loop counters, literals, integer arithmetic) on +// hashed storage, and lookups of absent keys on small bucket tables. + +// Keys spread past the direct index's range, so the maps hash them. +const spread = new Map(); +for (let i = 0; i < 500; i++) spread.set(i * 104729 - 26000000, i); +let found = 0, + absent = 0, + total = 0; +for (let i = -10; i < 520; i++) { + const v = spread.get(i * 104729 - 26000000); + if (v === undefined) absent++; + else { + found++; + total += v; + } +} +console.log("spread", spread.size, found, absent, total); +console.log("spread literals", spread.get(-26000000), spread.get(-25895271), spread.get(0), spread.has(-1)); + +// Zero stored through -0 and through an integer; read back through both. +const zeros = new Map(); +for (let i = 1; i <= 20; i++) zeros.set(i * 1000003, "k" + i); +zeros.set(-0, "minus zero"); +let z = 0; +console.log("zeros", zeros.size, zeros.get(z), zeros.get(0), zeros.get(-0), zeros.get(z * -1)); +zeros.set(z, "zero"); +console.log("zeros overwrite", zeros.size, zeros.get(-0), [...zeros.keys()].includes(0)); + +// Large and negative integer keys. +const wide = new Map(); +for (let i = 0; i < 64; i++) wide.set(2 ** 40 + i * 3, i); +for (let i = 0; i < 64; i++) wide.set(-(2 ** 33) - i, -i); +let wideUp = 0, + wideDown = 0, + wideMissing = 0; +for (let i = 0; i < 200; i++) { + const a = wide.get(2 ** 40 + i); + const b = wide.get(-(2 ** 33) - i); + if (a === undefined) wideMissing++; + else wideUp += a; + if (b === undefined) wideMissing++; + else wideDown += b; +} +console.log("wide", wide.size, wideUp, wideDown, wideMissing, wide.get(1099511627776), wide.get(-8589934592)); + +// Absent keys on bucket tables just past the linear scan, and as they grow. +for (const size of [9, 12, 16, 33, 64, 100, 257, 400]) { + const m = new Map(); + for (let i = 0; i < size; i++) m.set(i * 7919 + 1000000, i); + let hits = 0, + misses = 0; + for (let i = 0; i < size * 4; i++) { + if (m.has(i * 7919 + 1000000)) hits++; + else misses++; + if (m.get(i * 7919 + 1000001) !== undefined) hits += 1000; + } + for (let i = 0; i < size; i += 2) m.delete(i * 7919 + 1000000); + let after = 0; + for (let i = 0; i < size; i++) if (m.has(i * 7919 + 1000000)) after++; + console.log("size", size, hits, misses, m.size, after, m.get(1000000), m.get(1007919)); +} + +// Small maps that scan their entries, with integer keys. +const tiny = new Map(); +for (let i = 0; i < 8; i++) tiny.set(i * 5000011, "t" + i); +let tinyHits = 0; +for (let i = 0; i < 40; i++) if (tiny.has(i * 5000011)) tinyHits++; +console.log("tiny", tiny.size, tinyHits, tiny.get(0), tiny.get(35000077), tiny.has(5000012)); + +// Sets with integer keys. +const seen = new Set(); +let dupes = 0; +for (let i = 0; i < 3000; i++) { + const k = ((i * 2654435761) % 1000003) - 500000; + if (seen.has(k)) dupes++; + else seen.add(k); +} +let present = 0; +for (let i = -500000; i < 500003; i += 997) if (seen.has(i)) present++; +console.log("set", seen.size, dupes, present, seen.has(-500000), seen.has(500002)); + +// A counting loop like a k-mer histogram: get, then update the found entry. +class Count { + n = 0; +} +const counts = new Map(); +let key = 0; +for (let i = 0; i < 20000; i++) { + key = ((key * 4) % 4194304) + (i % 7 === 0 ? 3 : i % 3); + const c = counts.get(key); + if (c === undefined) { + const fresh = new Count(); + fresh.n = 1; + counts.set(key, fresh); + } else c.n++; +} +let most = 0, + mostKey = -1; +for (const [k, c] of counts) if (c.n > most) [most, mostKey] = [c.n, k]; +console.log("counts", counts.size, most, mostKey); diff --git a/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json b/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json new file mode 100644 index 000000000..961fe0dad --- /dev/null +++ b/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 3441, "san": 1752 } From 3a900ed9b6d8b43cdb7b764228dcd134df7f76d7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:38:27 -0500 Subject: [PATCH 281/395] Keep the poll in nested loops whose counter can stop changing at 2^53 The range analysis caps a counter that steps by 1 at 2^53, where `j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2; j < 2 ** 53 + 10; j++)` has a range of three values at its condition and was taken for a loop of three runs. It never ends, and without its poll worker.terminate() could not stop it. A loop's runs now count as bounded only when a step from every value in its counter's range gives a different value. --- .../src/backend/llvm/emit-statements.ts | 15 ++++++++++++--- .../test/counted-loop-emission.test.ts | 19 ++++++++++++++++++- .../native-worker-terminate-bounded-loops.ts | 15 +++++++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index e0db764e8..cc0af8ebb 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2655,6 +2655,14 @@ const WORKER_POLL_BLOCK = 64; * only as it leaves. */ const WORKER_UNPOLLED_RUN = 16384; +/** Whether a step of `step` from every value in `range` gives a different + * value. Beyond 2^53 a double can stop changing (`j + 1 === j` from 2^53), + * so a counter there can run without end inside a range of a few values. */ +function exactSteps(range: { min: number; max: number }, step: number): boolean { + const size = Math.abs(step); + return range.min >= size - 2 ** 53 && range.max <= 2 ** 53 - size; +} + /** At most how many times a `for` loop and the loops in its body run their * bodies per entry, from the counters' proven ranges: its iterations times * one plus the nested loops' runs. Infinity when a count has no bound, or @@ -2668,12 +2676,12 @@ function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { LOOPS.of(emitter).unaliasedArrays, ); let trips = Infinity; - if (counted && !counted.guarded) + if (counted && !counted.guarded && exactSteps(counted.range, counted.step)) trips = Math.floor((counted.range.max - counted.range.min) / Math.abs(counted.step)) + 1; else if (!counted) { // Each iteration closes the gap between the two counters by at least two. const paired = matchIntegerPairedForLoop(loop, emitter.numericLocals, emitter.integerRanges); - if (paired && !paired.guarded) + if (paired && !paired.guarded && exactSteps(paired.range, 1)) trips = Math.floor((paired.range.max - paired.range.min) / 2) + 1; } if (trips === Infinity) { @@ -2681,7 +2689,8 @@ function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { // one compared with a module constant. const stepped = steppedLoopCounter(loop, emitter.currentLocals); const range = stepped && emitter.integerRanges.get(stepped.ref); - if (stepped && range) trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; + if (stepped && range && exactSteps(range, stepped.step)) + trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; } if (!(trips <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK)) return Infinity; let nested = 0; diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 794edd1af..d7ebc97fa 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -175,6 +175,18 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano " for (let r = 0; r < n; r++) for (let j = 0; j < 5000; j++) t = (t * 31 + j) % 1000003;", " return t;", "}", + "export function exact(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let j = 9007199254740900; j < 9007199254740950; j++) t = (t + (j % 7)) % 1000003;", + " return t;", + "}", + "export function stalled(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let j = 9007199254740990; j < 9007199254741002; j++) t = (t + (j % 7)) % 1000003;", + " return t;", + "}", "export function single(): number {", " let t = 0;", " for (let j = 0; j < 50; j++) t = (t * 31 + j) % 1000003;", @@ -182,7 +194,7 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano "}", "const cr = new Float64Array(8).fill(0.5);", 'const k = Number(process.argv[2] ?? "50");', - "console.log(rows(cr, 3), open(cr, 3, k), wide(2), single());", + "console.log(rows(cr, 3), open(cr, 3, k), wide(2), exact(2), stalled(0), single());", "if (!isMainThread) new Worker(new URL(import.meta.url));", ].join("\n"), ); @@ -219,6 +231,11 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano // A run of up to 16384 iterations is one block, polled as it leaves when // it took more than 64. expect(open).toMatch(/^loop\.poll\.run\d+:$/m); + // Steps of 1 below 2^53 are exact, so 50 runs near it are bounded. From + // 2^53, `j + 1 === j`: a counter ranging up to it can run without end + // and keeps its poll. + expect(polls(body("exact"))).toBe(polls(body("single"))); + expect(polls(body("stalled"))).toBeGreaterThan(polls(body("exact"))); // A nest over the bound, or a loop no other loop encloses, polls. expect(polls(body("wide"))).toBeGreaterThan(0); expect(body("wide")).toMatch(/^loop\.poll\d+:$/m); diff --git a/tests/corpus/native-worker-terminate-bounded-loops.ts b/tests/corpus/native-worker-terminate-bounded-loops.ts index d244e021d..ec1e4c736 100644 --- a/tests/corpus/native-worker-terminate-bounded-loops.ts +++ b/tests/corpus/native-worker-terminate-bounded-loops.ts @@ -1,7 +1,8 @@ // worker.terminate() stops a worker whose loop runs short inner loops: // counted loops of a few iterations, a two-counter loop, an inner loop // whose length grows past 64 iterations, an inner loop too long to finish, -// and a nest of loops with constant bounds. +// a nest of loops with constant bounds, and an inner loop whose counter +// stops changing at 2 ** 53 (`j + 1 === j` there), so it never ends. import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; function counted(values: number[]): number { @@ -66,8 +67,17 @@ function constant(row: Float64Array): number { return acc; } +function stalled(): number { + let acc = 0; + for (let round = 0; ; round++) { + if (round === 0) parentPort!.postMessage("ready"); + for (let j = 9007199254740990; j < 9007199254741002; j++) acc = (acc + (j % 7)) % 1000003; + } + return acc; +} + if (isMainThread) { - for (const kind of ["counted", "paired", "growing", "long", "constant"]) { + for (const kind of ["counted", "paired", "growing", "long", "constant", "stalled"]) { await new Promise((resolve) => { const worker = new Worker(new URL(import.meta.url), { workerData: kind }); worker.once("message", () => { @@ -86,6 +96,7 @@ if (isMainThread) { else if (workerData === "paired") result = paired(values); else if (workerData === "growing") result = growing(); else if (workerData === "long") result = long(1e15); + else if (workerData === "stalled") result = stalled(); else result = constant(new Float64Array(8).fill(0.25)); console.log("unexpected result", result); } From 3c057b501f277a46fc6d798eddae936ff13685bc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:44:46 -0500 Subject: [PATCH 282/395] 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 799337e0a..ef0b764ac 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 42bfe75ad..42c253568 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 24d330cc0..f4393c5f7 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 af00ebc2c..24c32d2e9 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 5569599ee..0b0bf11e0 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 000000000..00cf233db --- /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 8ec7b5ffd..2703e008b 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 ffd56a1bf..ace268af7 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 000000000..8619c5b34 --- /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 4d3f00ff5..a26c50209 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 000000000..d401ac62b --- /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 5a3d6d79037bf839e50f70adc828e7c6625b658f Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 14:53:54 -0700 Subject: [PATCH 283/395] Call function tables without boxing or reference counting Values typed `F | undefined` for a function type F are now plain closure pointers (nullable-pointer unions), so a keyed read of a `Record` or a Map.get no longer allocates a union box. Keyed record reads, string-keyed Map.get and checked extractions lend the stored closure as a receiver or callee when ReferenceWrites proves the call cannot write the locations it was read from, and optional-chain receivers lend under the same proof. Owned record overflow reads use the inline entry lookup. Indirect closure calls skip the ambient receiver bracket for closures whose bodies never observe it (new ScrClosure.function_kind bit SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver copy it from their original), and a callValue that forwards `dyn.this` needs no bracket at all. Co-Authored-By: Claude Opus 5.5 --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/handler-tables.json | 6 + .../runtime/workloads/handler-tables.ts | 108 ++++++++++++++++ .../src/backend/llvm/checked-narrows.ts | 9 +- packages/compiler/src/backend/llvm/common.ts | 4 + .../backend/llvm/constant-callbacks.test.ts | 41 +++++++ packages/compiler/src/backend/llvm/emitter.ts | 61 ++++++++-- .../src/backend/llvm/expr-callbacks.ts | 20 ++- .../compiler/src/backend/llvm/expr-calls.ts | 115 ++++++++++++++++-- .../compiler/src/backend/llvm/expr-control.ts | 33 +++-- .../compiler/src/backend/llvm/expr-dynamic.ts | 6 +- .../compiler/src/backend/llvm/expr-records.ts | 107 ++++++++++++---- .../compiler/src/backend/llvm/lib-http2.ts | 5 +- .../compiler/src/backend/llvm/lib-network.ts | 2 +- .../src/backend/llvm/map-read-lifetimes.ts | 64 ++++++++++ .../src/backend/llvm/nullable-unions.test.ts | 21 +++- .../src/backend/llvm/nullable-unions.ts | 22 +++- .../compiler/src/backend/llvm/receivers.ts | 40 +++++- .../src/backend/llvm/reference-writes.test.ts | 43 +++++++ .../src/backend/llvm/reference-writes.ts | 13 ++ .../src/backend/llvm/stack-callbacks.ts | 6 +- packages/compiler/src/ir/receiver-readers.ts | 92 ++++++++++---- .../8230-function-record-dispatch.ts.json | 6 + .../corpus/8231-function-table-shapes.ts.json | 6 + .../8232-optional-function-values.ts.json | 6 + packages/runtime/src/scr_closure.h | 9 +- packages/runtime/src/scr_json.c | 9 +- tests/corpus/8230-function-record-dispatch.ts | 107 ++++++++++++++++ tests/corpus/8231-function-table-shapes.ts | 108 ++++++++++++++++ tests/corpus/8232-optional-function-values.ts | 75 ++++++++++++ .../8230-function-record-dispatch.ts.json | 1 + .../8231-function-table-shapes.ts.json | 1 + .../8232-optional-function-values.ts.json | 1 + 33 files changed, 1042 insertions(+), 106 deletions(-) create mode 100644 benchmarks/runtime/workloads/handler-tables.json create mode 100644 benchmarks/runtime/workloads/handler-tables.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json create mode 100644 tests/corpus/8230-function-record-dispatch.ts create mode 100644 tests/corpus/8231-function-table-shapes.ts create mode 100644 tests/corpus/8232-optional-function-values.ts create mode 100644 tests/harness/corpus-costs/8230-function-record-dispatch.ts.json create mode 100644 tests/harness/corpus-costs/8231-function-table-shapes.ts.json create mode 100644 tests/harness/corpus-costs/8232-optional-function-values.ts.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index af3d3d239..4c6cf45ed 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -16,6 +16,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `dynamic-methods` | cpu | untyped JavaScript calling builtin methods on values it does not type: string `slice`/`indexOf`/`toUpperCase`/`padStart`, array `push`/`pop`/`includes`, `Map` and `Set`, and an object's own methods | | `dynamic-properties` | cpu | untyped JavaScript reading and writing properties of values it does not type: records built by object literals in two key orders, nested records, and instances of a class and its subclass | | `functional-pipeline` | cpu | `filter`/`map`/`reduce`/`flatMap` chains, closures, small records | +| `handler-tables` | cpu | dispatch tables of functions: `Record` read as `t[k]!(…)`, `t[k]?.(…)` and through a checked local, a `Map`, an array of handlers, and an object literal of methods; some handlers declare fewer parameters than the table type | | `intern-table`, `intern-table-map`, `intern-table-1`, `intern-table-map-1` | cpu | worker threads interning the same type-like objects under string keys and records under number keys, then hits and misses: one shared `InternTable` per kind of key, or a module-level `Map` per thread (the `-map` entries), with 8 threads or 1 (the `-1` entries); under Node.js every thread has its own tables | | `json-records` | cpu | `JSON.stringify`, typed `JSON.parse`, Map aggregation | | `json-unions` | cpu | `JSON.parse` into discriminated unions: an event log with eight kinds and nested expression trees with nine, evaluated after the cast | diff --git a/benchmarks/runtime/workloads/handler-tables.json b/benchmarks/runtime/workloads/handler-tables.json new file mode 100644 index 000000000..1408ae91e --- /dev/null +++ b/benchmarks/runtime/workloads/handler-tables.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/handler-tables.ts", + "args": [ + "4" + ] +} diff --git a/benchmarks/runtime/workloads/handler-tables.ts b/benchmarks/runtime/workloads/handler-tables.ts new file mode 100644 index 000000000..b0a61fcfe --- /dev/null +++ b/benchmarks/runtime/workloads/handler-tables.ts @@ -0,0 +1,108 @@ +// Dispatch tables of functions: a `Record` of opcode handlers +// called as `ops[name]!(…)`, `ops[name]?.(…)` and through a checked local, +// a `Map` of the same handlers, an array of handlers indexed by +// opcode number, and an object literal of methods. Some handlers declare +// fewer parameters than the table's function type. +type Op = (acc: number, arg: number) => number; + +const ops: Record = { + add: (acc, arg) => acc + arg, + sub: (acc, arg) => acc - arg, + mul: (acc, arg) => (acc * arg) % 1000003, + neg: (acc) => -acc, + half: (acc) => Math.floor(acc / 2), + mix: (acc, arg) => (acc ^ arg) & 0xffff, +}; + +const byName = new Map(Object.entries(ops)); +const byCode: Op[] = Object.values(ops); +const names = Object.keys(ops); + +const methods = { + add(acc: number, arg: number): number { + return acc + arg; + }, + sub(acc: number, arg: number): number { + return acc - arg; + }, + mul(acc: number, arg: number): number { + return (acc * arg) % 1000003; + }, +}; + +interface Insn { + name: string; + code: number; + arg: number; +} + +function program(length: number): Insn[] { + const out: Insn[] = []; + let seed = 12345; + for (let i = 0; i < length; i++) { + seed = (seed * 1103515245 + 12345) % 2147483648; + const code = seed % (names.length + 1); + // One in seven instructions names an opcode no table holds. + out.push({ name: code < names.length ? names[code]! : "nop", code, arg: (seed >> 8) % 97 }); + } + return out; +} + +function runAsserted(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) if (insn.name !== "nop") acc = ops[insn.name]!(acc, insn.arg); + return acc; +} + +function runOptional(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) acc = ops[insn.name]?.(acc, insn.arg) ?? acc + 1; + return acc; +} + +function runChecked(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + const handler = ops[insn.name]; + if (handler) acc = handler(acc, insn.arg); + } + return acc; +} + +function runMap(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + const handler = byName.get(insn.name); + if (handler !== undefined) acc = handler(acc, insn.arg); + } + return acc; +} + +function runArray(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) if (insn.code < byCode.length) acc = byCode[insn.code]!(acc, insn.arg); + return acc; +} + +function runMethods(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + if (insn.code === 0) acc = methods.add(acc, insn.arg); + else if (insn.code === 1) acc = methods.sub(acc, insn.arg); + else if (insn.code === 2) acc = methods["mul"](acc, insn.arg); + } + return acc; +} + +const rounds = Number(process.argv[2] ?? "4"); +const insns = program(200_000); +const totals = [0, 0, 0, 0, 0, 0]; +for (let round = 0; round < rounds * 10; round++) { + totals[0] = (totals[0]! + runAsserted(insns)) % 1e9; + totals[1] = (totals[1]! + runOptional(insns)) % 1e9; + totals[2] = (totals[2]! + runChecked(insns)) % 1e9; + totals[3] = (totals[3]! + runMap(insns)) % 1e9; + totals[4] = (totals[4]! + runArray(insns)) % 1e9; + totals[5] = (totals[5]! + runMethods(insns)) % 1e9; +} +console.log(totals.join(" ")); diff --git a/packages/compiler/src/backend/llvm/checked-narrows.ts b/packages/compiler/src/backend/llvm/checked-narrows.ts index c1514df1e..5fbaf6d08 100644 --- a/packages/compiler/src/backend/llvm/checked-narrows.ts +++ b/packages/compiler/src/backend/llvm/checked-narrows.ts @@ -101,6 +101,13 @@ export class CheckedNarrows { } } +/** A call to a checked extraction whose helper borrows its argument, so + * emitCheckedNarrowReceiver can lend the payload instead of calling it. */ +export function borrowsCheckedNarrow(host: LlvmEmitterContext, e: IrExpr): boolean { + if (e.kind !== "call" || host.checkedNarrows.get(e) === null) return false; + return host.callLifetimes.borrowed.get(e.callee)?.has(0) === true && host.mayThrow.has(e.callee); +} + /** A checked extraction consumed at once by a field or tag load. The * successful arm borrows the payload from the union, which itself borrows * from its source: a dense array read keeps the element in the array slot, @@ -113,7 +120,7 @@ export function emitCheckedNarrowReceiver( narrow: CheckedNarrowCall, ): LlValue | null { const callee = narrow.call.callee; - if (!host.callLifetimes.borrowed.get(callee)?.has(0) || !host.mayThrow.has(callee)) return null; + if (!borrowsCheckedNarrow(host, narrow.call)) return null; const B = host.B; const read = host.optionalArrayReads.get(narrow.value); const union = read diff --git a/packages/compiler/src/backend/llvm/common.ts b/packages/compiler/src/backend/llvm/common.ts index cca1b603b..f1bdeb557 100644 --- a/packages/compiler/src/backend/llvm/common.ts +++ b/packages/compiler/src/backend/llvm/common.ts @@ -277,6 +277,10 @@ export function emitFieldAbsentTest(B: BlockBuilder, value: string, undefinedTag return absent; } +/** ScrClosure.function_kind's SCR_FN_RECEIVER_FREE bit (scr_closure.h): + * the body never observes the ambient receiver. */ +export const CLOSURE_RECEIVER_FREE = 16; + /** The right-hand side of a JS function-identity comparison: two closures * are one function when their identity roots match (a signature adapter * stands for the function it adapts). Both operands must be closures. */ diff --git a/packages/compiler/src/backend/llvm/constant-callbacks.test.ts b/packages/compiler/src/backend/llvm/constant-callbacks.test.ts index e8ca5571f..8bdab0408 100644 --- a/packages/compiler/src/backend/llvm/constant-callbacks.test.ts +++ b/packages/compiler/src/backend/llvm/constant-callbacks.test.ts @@ -299,6 +299,47 @@ test("receiver readers propagate backwards through direct calls and recursion", ).toBe(false); }); +test("signature adapters that only forward the receiver ignore it with their original", () => { + const forward = (receiver: IrExpr): IrStmt => ({ + kind: "return", + value: { + kind: "callValue", + callee: ref("f.0"), + receiver, + args: [{ kind: "varRef", localId: "a.0", type: F64, loc }], + type: BOOL, + loc, + }, + loc, + }); + const ambient: IrExpr = { kind: "libCall", fn: "dyn.this", args: [], type: { kind: "dyn" }, loc }; + const adapter: IrFunction = { + name: "%fn.adapt.x.impl", + params: [{ localId: "a.0", name: "a", type: F64 }], + captures: [{ localId: "f.0", name: "f", type: callback }], + locals: [ + { id: "f.0", name: "f", type: callback, mutable: false, boxed: true }, + { id: "a.0", name: "a", type: F64, mutable: false }, + ], + returnType: BOOL, + body: [forward(ambient)], + loc, + }; + const readers = (): AmbientReceiverReaders => + new AmbientReceiverReaders(new Map([[adapter.name, adapter]])); + // The adapter itself reads the receiver, only to forward it. + expect(readers().ignores(adapter)).toBe(false); + expect(readers().forwardsOnly(adapter)).toBe(true); + // Any other read of the receiver, or forwarding something else, does not. + adapter.body = [thisRead, forward(ambient)]; + expect(readers().forwardsOnly(adapter)).toBe(false); + adapter.body = [forward(ref("a.0", F64))]; + expect(readers().forwardsOnly(adapter)).toBe(false); + adapter.body = [forward(ambient)]; + adapter.captures = []; + expect(readers().forwardsOnly(adapter)).toBe(false); +}); + test("virtual calls admit every same-named method", () => { const call: IrStmt = { kind: "exprStmt", diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 1a2c8bef8..4c15ae61d 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -183,14 +183,14 @@ import { unionBoxBits, } from "./inline-unions.js"; import { OptionalArrayReads, type LocalArrayRead } from "./local-array-reads.js"; -import { CheckedNarrows } from "./checked-narrows.js"; +import { borrowsCheckedNarrow, CheckedNarrows } from "./checked-narrows.js"; import { emitDenseArraySet, emitDenseSlotUnlink, SCR_ELEM_I32, type ArrayAccess, } from "./dense-array-access.js"; -import { matchMapRead, type MapReadLifetimes } from "./map-read-lifetimes.js"; +import { matchMapRead, pointerMapGet, type MapReadLifetimes } from "./map-read-lifetimes.js"; import { type MapEntryFusion } from "./map-entry-fusion.js"; import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js"; import { emitBorrowedInputs } from "./borrowed-inputs.js"; @@ -213,7 +213,13 @@ import { } from "./string-slices.js"; import { LazyCaptures } from "./lazy-captures.js"; import { StackCaptures } from "./stack-captures.js"; -import { emitFieldAbsentTest, f64Lit, SCR_ARR_FIELD, SCR_ARR_FROZEN } from "./common.js"; +import { + CLOSURE_RECEIVER_FREE, + emitFieldAbsentTest, + f64Lit, + SCR_ARR_FIELD, + SCR_ARR_FROZEN, +} from "./common.js"; import { ffiExtendsNarrowIntegers } from "../targets.js"; import { emitLiteralExpr, @@ -285,7 +291,12 @@ import { emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js"; -import { emitRegexIntrinsic, emitRecordKeyGet, keyedRecordReadInto } from "./expr-records.js"; +import { + emitRecordKeyGet, + emitRegexIntrinsic, + keyedRecordReadInto, + pointerRecordKeyGet, +} from "./expr-records.js"; import { dynPromiseAdapter, streamTypedRefCommitAdapter, @@ -2540,7 +2551,7 @@ export class LlEmitter { ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, - `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, + `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${this.closureFunctionKind(fn)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, ``, ); if (lending) @@ -5715,7 +5726,10 @@ export class LlEmitter { unwrittenRead(e: IrExpr, writes: Writes): boolean { const rw = this.referenceWrites; if (rw === null) return false; - const keys = rw.readKeys(e, (call) => this.borrowedReturnKeys(call.callee)); + // A checked extraction reads nothing besides the union it narrows. + const keys = rw.readKeys(e, (call) => + borrowsCheckedNarrow(this, call) ? [] : this.borrowedReturnKeys(call.callee), + ); return keys !== null && !rw.conflicts(keys, writes); } @@ -5992,7 +6006,21 @@ export class LlEmitter { } case "fieldGet": case "recordGet": + case "recordKeyGet": return this.borrowsWithoutOwning(e) && this.unownedWalk(e.obj); + case "mapIntrinsic": { + // A Map binding's own pointer (maps are not plainReference types). + if (!this.borrowsWithoutOwning(e) || e.receiver.kind !== "varRef") return false; + const binding = this.binding(e.receiver.localId); + return ( + binding.kind === "global" || + (binding.kind === "local" && + binding.local !== undefined && + !binding.local.boxed && + !binding.local.tdz && + !this.localUnionStorage.has(e.receiver.localId)) + ); + } case "unionNarrow": case "downcast": case "upcast": @@ -7244,6 +7272,11 @@ export class LlEmitter { return this.nullableFieldGet(e) === null; case "recordGet": return true; + // A keyed read whose hits are all the result's own pointer. + case "recordKeyGet": + return pointerRecordKeyGet(this, e); + case "mapIntrinsic": + return pointerMapGet(this, e); // emitReadReceiver lends a required element of a borrowed array when // computing the index removes no edge. case "arrayGet": @@ -7389,8 +7422,20 @@ export class LlEmitter { return wrapEmitterListener(this, target, adapterFn); } - unwrapNullableClosure(u: string, funcTag: number): string { - return unwrapNullableClosure(this, u, funcTag); + /** ScrClosure.function_kind for a closure over `fn`: generator 1, async + * 2, own prototype 4, and SCR_FN_RECEIVER_FREE (16) when its body never + * observes the ambient receiver, so indirect callers skip the bracket. */ + closureFunctionKind(fn: IrFunction): number { + return ( + (fn.generator ? 1 : 0) + + (fn.async ? 2 : 0) + + (fn.ownsPrototype ? 4 : 0) + + (this.receiverReaders.ignores(fn) ? CLOSURE_RECEIVER_FREE : 0) + ); + } + + unwrapNullableClosure(u: string, unionId: string, funcTag: number): string { + return unwrapNullableClosure(this, u, unionId, funcTag); } closeBindThunkFor(cbUnion: IrType, retServer: boolean): string { diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index 398aebb21..ad827c4bf 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -974,10 +974,20 @@ export function wrapEmitterListener( export function unwrapNullableClosure( host: LlvmEmitterContext, u: string, + unionId: string, funcTag: number, ): string { const B = host.B; host.declare(`declare ptr @scr_closure_retain_v(ptr)`); + const nullable = host.nullableUnions.get(unionId); + if (nullable !== null) { + // The union is the closure pointer itself (NULL for the unit arm). + if (nullable.repr !== "single" || nullable.nullTag >= 0 || nullable.refTag !== funcTag) + throw new InternalCompilerError("llvm emitter bug: nullable callback union shape"); + const r = B.tmp(); + B.line(`${r} = call ptr ${retainSym(host.shapeHost, { kind: "union", unionId })}(ptr ${u})`); + return r; + } const slot = B.slot(); B.entryAllocas.push(`${slot} = alloca ptr`); B.line(`store ptr null, ptr ${slot}`); @@ -1026,9 +1036,9 @@ export function closeBindThunkFor( "llvm emitter bug: bound-close callback union lacks its func arm", ); const oneParam = funcArm.params.length === 1; + const cbNullable = host.nullableUnions.get(cbUnion.unionId); host.declare(`declare ptr @scr_box_get_ref(ptr)`); host.declare(`declare void @scr_closure_release(ptr)`); - host.declare(`declare void @scr_union_release(ptr)`); host.declare(`declare ptr @scr_closure_retain_v(ptr)`); host.declare(`declare void @scr_net_server_close_direct(ptr, ptr)`); let trampoline: string | null = null; @@ -1072,13 +1082,11 @@ export function closeBindThunkFor( ` %capsp = getelementptr inbounds %ScrClosure, ptr %self, i64 1`, ` %bx = load ptr, ptr %capsp`, ` %srv = call ptr @scr_box_get_ref(ptr %bx) ; +1`, - ` %tagp = getelementptr inbounds %ScrUnion, ptr %cbu, i64 0, i32 1`, - ` %tag = load i32, ptr %tagp`, + ...unionTagLines(cbNullable, "%cbu", "%tag"), ` %isfn = icmp eq i32 %tag, ${funcTag}`, ` br i1 %isfn, label %fn, label %go`, `fn:`, - ` %pp = getelementptr inbounds %ScrUnion, ptr %cbu, i64 0, i32 5`, - ` %pv = load ptr, ptr %pp`, + ...unionPeekLines(cbNullable, "%cbu", "%pv"), ]; if (oneParam) { d.push( @@ -1097,7 +1105,7 @@ export function closeBindThunkFor( ` br label %go`, `go:`, ` %regv = load ptr, ptr %regslot`, - ` call void @scr_union_release(ptr %cbu) ; the callee owns its +1 param`, + ` call void ${releaseSym(host.shapeHost, cbUnion)}(ptr %cbu) ; the callee owns its +1 param`, ` call void @scr_net_server_close_direct(ptr %srv, ptr %regv) ; reg moves`, ); if (retServer) { diff --git a/packages/compiler/src/backend/llvm/expr-calls.ts b/packages/compiler/src/backend/llvm/expr-calls.ts index d411d9172..3ecf33a79 100644 --- a/packages/compiler/src/backend/llvm/expr-calls.ts +++ b/packages/compiler/src/backend/llvm/expr-calls.ts @@ -42,7 +42,13 @@ import { } from "./classes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; -import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl } from "./common.js"; +import { + CLOSURE_RECEIVER_FREE, + f64Lit, + ffiNativeTypeLl, + ffiNativeParamLl, + ffiNativeReturnLl, +} from "./common.js"; import { canStackUnion, emitStackUnion } from "./stack-unions.js"; import { emitStackRecordKeyGet, scalarRecordKeyGet } from "./expr-records.js"; import { emitCallArrayRead } from "./local-array-reads.js"; @@ -761,8 +767,7 @@ export function emitCallExpr( `${c} = call ptr @scr_closure_new(ptr @${host.callTarget(e.fnName)}, ${host.sizeType} ${e.captures.length})`, ); const out = host.own({ name: c, type: e.type }); - const functionKind = - (target.generator ? 1 : 0) + (target.async ? 2 : 0) + (target.ownsPrototype ? 4 : 0); + const functionKind = host.closureFunctionKind(target); if (functionKind) { const kindPtr = B.tmp(); B.line(`${kindPtr} = getelementptr inbounds %ScrClosure, ptr ${c}, i64 0, i32 4`); @@ -800,6 +805,25 @@ export function emitCallExpr( : 0; host.declare(`declare void @scr_closure_adopt_identity(ptr, ptr, i32)`); B.line(`call void @scr_closure_adopt_identity(ptr ${c}, ptr ${original}, i32 ${length})`); + if (host.receiverReaders.forwardsOnly(target)) { + // The adapter forwards the ambient receiver to its original, + // so it ignores the receiver exactly when the original does. + const fromPtr = B.tmp(), + from = B.tmp(), + bit = B.tmp(), + toPtr = B.tmp(), + to = B.tmp(), + kind = B.tmp(); + B.line( + `${fromPtr} = getelementptr inbounds %ScrClosure, ptr ${original}, i64 0, i32 4`, + ); + B.line(`${from} = load i32, ptr ${fromPtr}`); + B.line(`${bit} = and i32 ${from}, ${CLOSURE_RECEIVER_FREE}`); + B.line(`${toPtr} = getelementptr inbounds %ScrClosure, ptr ${c}, i64 0, i32 4`); + B.line(`${to} = load i32, ptr ${toPtr}`); + B.line(`${kind} = or i32 ${to}, ${bit}`); + B.line(`store i32 ${kind}, ptr ${toPtr}`); + } } }); return out; @@ -813,7 +837,18 @@ export function emitCallExpr( const ft = e.callee.type; if (e.args.length !== ft.params.length) throw new LlvmUnsupportedError("callValue:arity", e.loc); - const callee = host.stackCallbacks.emit(host, e.callee) ?? emitBorrowedInput(host, e.callee); + // A receiver that reads the ambient `this` binds what the callee + // would see without a binding: the call forwards it unchanged and + // needs no receiver bracket (signature adapters forward this way). + const forwardsReceiver = + e.receiver?.kind === "libCall" && + e.receiver.fn === "dyn.this" && + e.receiver.args.length === 0; + const callee = + host.stackCallbacks.emit(host, e.callee) ?? + (lendsCallee(host, e, forwardsReceiver) + ? host.emitReadReceiver(e.callee) + : emitBorrowedInput(host, e.callee)); // A class instance bound as the callee's `this` (`h.fn(x)`) is boxed // only if the callee reads it: the receiver window records the // instance (owned by this frame for the whole call) with its typed-ref @@ -825,7 +860,10 @@ export function emitCallExpr( !host.shapeHost.isErrorClass(e.receiver.value.type.className) ? host.emitExpr(e.receiver.value) : null; - const receiver = e.receiver === undefined || lazyReceiver ? null : host.emitExpr(e.receiver); + const receiver = + e.receiver === undefined || lazyReceiver || forwardsReceiver + ? null + : host.emitExpr(e.receiver); const pushReceiver = (): void => { if (lazyReceiver) { const t = lazyReceiver.type; @@ -865,7 +903,7 @@ export function emitCallExpr( ? borrowableInputs(host, e.args, false, host.referenceWrites?.summary(known!.name)) : []; let dynamic = false; - if (!direct && e.receiver === undefined) { + if (!direct && (e.receiver === undefined || forwardsReceiver)) { const references = new Set(); ft.params.forEach((param, index) => { if (isRefCounted(param)) references.add(index); @@ -898,16 +936,51 @@ export function emitCallExpr( lent.has(i) ? { name: host.retainFrame(a.name, a.type), type: a.type } : a, ) : args; - const invoke = (target: string, needsReceiver: boolean, values = args): string => { + // An entry through the closure's own pointer brackets the call only + // when the closure may observe the receiver (SCR_FN_RECEIVER_FREE). + const invoke = ( + target: string, + bracket: boolean, + values = args, + indirect = false, + ): string => { + const needsReceiver = bracket && !forwardsReceiver; + let observes: string | null = null; if (needsReceiver) { host.declare("declare void @scr_dyn_this_pop()"); - pushReceiver(); + if (indirect) { + const kindPtr = B.tmp(), + kind = B.tmp(), + free = B.tmp(); + observes = B.tmp(); + B.line( + `${kindPtr} = getelementptr inbounds %ScrClosure, ptr ${callee.name}, i64 0, i32 4`, + ); + B.line(`${kind} = load i32, ptr ${kindPtr}`); + B.line(`${free} = and i32 ${kind}, ${CLOSURE_RECEIVER_FREE}`); + B.line(`${observes} = icmp eq i32 ${free}, 0`); + const push = B.newLabel("receiver.push"), + pushed = B.newLabel("receiver.call"); + B.condBr(observes, push, pushed); + B.startBlock(push); + pushReceiver(); + B.br(pushed); + B.startBlock(pushed); + } else pushReceiver(); } const result = e.type.kind === "void" ? "" : B.tmp(); B.line( `${result ? `${result} = ` : ""}call ${host.llType(e.type)} ${target}(${argListOf(values)})`, ); - if (needsReceiver) B.line("call void @scr_dyn_this_pop()"); + if (observes !== null) { + const pop = B.newLabel("receiver.pop"), + popped = B.newLabel("receiver.done"); + B.condBr(observes, pop, popped); + B.startBlock(pop); + B.line("call void @scr_dyn_this_pop()"); + B.br(popped); + B.startBlock(popped); + } else if (needsReceiver) B.line("call void @scr_dyn_this_pop()"); return result; }; const lending = @@ -953,11 +1026,11 @@ export function emitCallExpr( B.line(`${has} = icmp ne ptr ${entry}, null`); B.condBr(has, borrowing, owning); B.startBlock(borrowing); - const lentResult = invoke(entry, true); + const lentResult = invoke(entry, true, args, true); const borrowingEnd = B.currentLabel(); B.br(done); B.startBlock(owning); - const ownedResult = invoke(fn, true, ownedArgs()); + const ownedResult = invoke(fn, true, ownedArgs(), true); const owningEnd = B.currentLabel(); B.br(done); B.startBlock(done); @@ -966,7 +1039,7 @@ export function emitCallExpr( B.line( `${t} = phi ${host.llType(e.type)} [ ${lentResult}, %${borrowingEnd} ], [ ${ownedResult}, %${owningEnd} ]`, ); - } else if (!direct) t = invoke(fn, true, ownedArgs()); + } else if (!direct) t = invoke(fn, true, ownedArgs(), true); else { // A global initializer may not have run, or a callback may enter // through a boundary the census cannot prove. Validate the actual @@ -984,7 +1057,7 @@ export function emitCallExpr( const fastEnd = B.currentLabel(); B.br(done); B.startBlock(fallback); - const indirect = invoke(fn, true, ownedArgs()); + const indirect = invoke(fn, true, ownedArgs(), true); const fallbackEnd = B.currentLabel(); B.br(done); B.startBlock(done); @@ -1358,6 +1431,22 @@ function ctorArguments( * walk consumes without owning it (LlEmitter.canBorrowReceiver proved every * reference argument borrowable and every argument edge-preserving). The * result stays reachable from the borrowed arguments until the consumer. */ +/** A closure value read from a location the call cannot write (a keyed + * table entry, a field, a checked extraction of either) is entered without + * a reference of its own: the location keeps the closure alive until the + * call returns. Plain stable bindings already lend (emitBorrowedInput). */ +function lendsCallee( + host: LlvmEmitterContext, + e: ExprOf<"callValue">, + forwardsReceiver: boolean, +): boolean { + const writes = host.referenceWrites; + if (writes === null || host.canBorrowCallArgument(e.callee) || !host.canBorrowReceiver(e.callee)) + return false; + const later = e.receiver === undefined || forwardsReceiver ? e.args : [e.receiver, ...e.args]; + return borrowableInputs(host, [e.callee, ...later], false, writes.ownWrites(e))[0] === true; +} + export function emitBorrowedResultCall(host: LlvmEmitterContext, e: ExprOf<"call">): LlValue { const callee = host.fnByName.get(e.callee); if (!callee || !host.borrowedReturns.has(e.callee)) diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 2f7c31813..9ce5540a6 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -491,10 +491,7 @@ export function emitOptionalChainReceiver( // needs no reference of its own while the body cannot remove an edge // (so nothing can overwrite the projection before the body finishes). const borrowReceiver = - !multiple && - isRefCounted(narrowed) && - host.canBorrowReceiver(e.receiver) && - host.referenceEffects.preserves(e.body); + !multiple && isRefCounted(narrowed) && borrowsChainReceiver(host, e.receiver, e.body); // An optional array read of a nullable-pointer union is the element // pointer itself, read inline and owned by the statement frame until // the body finishes; the bind slot borrows it like an emitExpr value. @@ -554,6 +551,16 @@ export function emitOptionalChainReceiver( }; } +/** An optional chain's receiver can lend its payload to the bind slot when + * the body leaves the receiver's locations alone: it removes no edge at + * all, or it writes none of the locations the receiver reads. */ +function borrowsChainReceiver(host: LlvmEmitterContext, receiver: IrExpr, body: IrExpr): boolean { + if (!host.canBorrowReceiver(receiver)) return false; + if (host.referenceEffects.preserves(body)) return true; + const writes = host.referenceWrites; + return writes !== null && host.unwrittenRead(receiver, writes.expr(body)); +} + /** `a?.b ?? d` narrowed to the chain body's present arm. The body wraps a * value of the result type in a non-unit arm, so the default runs exactly * when the receiver is nullish and the intermediate result box is never @@ -580,16 +587,21 @@ function emitNullishOptionalChain(host: LlvmEmitterContext, e: ExprOf<"nullish"> if (unitTags.length === 0 || present.length !== 1) return null; const B = host.B; const narrowed = receiverDef.arms[present[0]!]!; - const r = host.canStackReceiver(chain.receiver) - ? host.emitReadReceiver(chain.receiver) - : host.emitExpr(chain.receiver); + // A receiver read from a location the body cannot write lends its + // payload to the bind slot for the whole body (`table[k]?.(…) ?? d`). + const borrowed = + isRefCounted(narrowed) && borrowsChainReceiver(host, chain.receiver, chain.body.value); + const r = + borrowed || host.canStackReceiver(chain.receiver) + ? host.emitReadReceiver(chain.receiver) + : host.emitExpr(chain.receiver); const bindTy = host.llType(narrowed); const bind = B.slot(); B.entryAllocas.push(`${bind} = alloca ${bindTy}`); B.line( `store ${bindTy} ${bindTy === "ptr" ? "null" : bindTy === "double" ? f64Lit(0) : "false"}, ptr ${bind}`, ); - host.ownSlot(bind, narrowed); + if (!borrowed) host.ownSlot(bind, narrowed); const isUnit = host.tagInSet(r.name, receiverDef.id, unitTags); const ty = host.llType(e.type); const slot = B.slot(); @@ -602,7 +614,10 @@ function emitNullishOptionalChain(host: LlvmEmitterContext, e: ExprOf<"nullish"> host.emitBranchInto(slot, e.right); B.br(lj); B.startBlock(lb); - B.line(`store ${bindTy} ${host.unionExtract(r.name, receiverDef.id, narrowed)}, ptr ${bind}`); + const payload = borrowed + ? host.unionPeek(r.name, receiverDef.id) + : host.unionExtract(r.name, receiverDef.id, narrowed); + B.line(`store ${bindTy} ${payload}, ptr ${bind}`); host.chainSlots.set(chain.id, { name: bind, type: narrowed, slot: true }); host.emitBranchInto(slot, chain.body.value); host.chainSlots.delete(chain.id); diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 55bbb828b..7f4b7e48a 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -11,7 +11,7 @@ import { bindLiveArrayOps } from "./expr-stream-bridges.js"; /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { emitNullableIsTag, emitNullablePresent } from "./union-repr.js"; -import { isObjectArm } from "./nullable-unions.js"; +import { isPointerIdentityArm } from "./nullable-unions.js"; import { streamTypedRefEligible } from "../../ir/analysis.js"; import { DYN, @@ -908,13 +908,13 @@ export function emitDynamicExpr( if (!def) throw new InternalCompilerError(`llvm emitter bug: equality of unknown union ${e.unionId}`); const nullable = host.nullableUnions.get(def.id); - if (nullable && isObjectArm(nullable)) { + if (nullable && isPointerIdentityArm(nullable)) { // Object identity, and NULL === NULL for the unit arm: one compare. const eq = B.tmp(); B.line(`${eq} = icmp ${e.negated ? "ne" : "eq"} ptr ${l.name}, ${r.name}`); return { name: eq, type: e.type }; } - if (nullable) { + if (nullable && nullable.repr === "single" && nullable.arm.kind === "string") { // String arm: identical pointers are equal (the same string or the // same unit encoding); otherwise equal only when both hold strings // with the same bytes. diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..597cda630 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -9,6 +9,7 @@ import { borrowableInputs, emitBorrowedInputs, emitBorrowedInput } from "./borro import { regexLiteralTemplate } from "./expr-primitives.js"; import { isScalarUnionDef, loadInlineUnion, presentBits } from "./inline-unions.js"; import { preservesRegexInputs } from "./reference-effects.js"; +import { emitNumberMapSlot, emitStringMapEntry } from "./map-number-lookup.js"; /** Widen one tagged read. The source is borrowed unless the overflow map * supplied an owned value. Each selected reference payload is retained @@ -343,8 +344,8 @@ export function emitRecordKeyGet( e: IrExpr & { kind: "recordKeyGet" }, ): LlValue { const B = host.B; - const obj = host.emitExpr(e.obj); - const k = host.emitExpr(e.key); + // The read runs no user code: both inputs lend for its duration. + const [obj, k] = emitBorrowedInputs(host, [e.obj, e.key]) as [LlValue, LlValue]; const ty = host.llType(e.type); const slot = B.slot(); B.entryAllocas.push(`${slot} = alloca ${ty}`); @@ -366,6 +367,80 @@ export function emitRecordKeyGet( return host.own({ name: t, type: e.type }); } +/** A keyed read whose every hit is the result's own pointer: the result + * is a nullable-pointer union (nullable-unions.ts) with an undefined arm, + * and each declared field and the overflow value hold exactly the result + * type or its reference arm in a plain (non-inline) slot. A miss is the + * immortal undefined encoding. Such a read can lend the stored pointer: + * borrowedRecordKeyGet reads it without a reference. */ +export function pointerRecordKeyGet(host: LlvmEmitterContext, e: IrExpr): boolean { + if (e.kind !== "recordKeyGet" || e.missNaN === true || e.type.kind !== "union") return false; + const resultType = e.type; + const nullable = host.nullableUnions.single(resultType.unionId); + if (nullable === null || undefinedArmTag(resultType, host.unionsById) < 0) return false; + const exact = (t: IrType): boolean => typeEquals(t, resultType) || typeEquals(t, nullable.arm); + const shape = host.recordShape(e.shapeId); + if (e.overflowOnly !== true) { + for (const f of shape.fields) + if (host.recordFieldInline(e.shapeId, f.name) || !exact(f.type)) return false; + } + return shape.indexValue === undefined || typeEquals(shape.indexValue, nullable.arm); +} + +/** A pointerRecordKeyGet read without a reference: the declared field or + * overflow entry the key names, or the undefined encoding for a miss. The + * record (and through it the stored value) must stay unchanged and alive + * until the consumer is done with the pointer (canBorrowReceiver). */ +export function emitBorrowedRecordKeyGet( + host: LlvmEmitterContext, + e: IrExpr & { kind: "recordKeyGet" }, +): LlValue { + const B = host.B; + if (e.type.kind !== "union") + throw new InternalCompilerError("llvm emitter bug: borrowed keyed read of a non-union"); + const resultType = e.type; + const obj = host.emitReadReceiver(e.obj); + const key = host.canBorrowReceiver(e.key) ? host.emitReadReceiver(e.key) : host.emitExpr(e.key); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ptr`); + const join = B.newLabel("rkb.j"); + const shape = host.recordShape(e.shapeId); + for (const f of e.overflowOnly === true ? [] : shape.fields) { + const hit = B.tmp(); + B.line( + `${hit} = call zeroext i1 ${host.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, + ); + const lh = B.newLabel("rkb.h"); + const ln = B.newLabel("rkb.n"); + B.condBr(hit, lh, ln); + B.startBlock(lh); + const { ptr, type } = host.recordFieldPtr(obj.name, e.shapeId, f.name); + B.line(`store ptr ${host.loadRecordField(ptr, type, false)}, ptr ${slot}`); + B.br(join); + B.startBlock(ln); + } + if (shape.indexValue) { + const ovf = host.recordOvfPtr(obj.name, e.shapeId); + const entry = emitStringMapEntry(host, ovf, key.name); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + const lh = B.newLabel("rkb.h"); + const ln = B.newLabel("rkb.n"); + B.condBr(found, lh, ln); + B.startBlock(lh); + B.line(`store ptr ${emitNumberMapSlot(host, entry, "ptr")}, ptr ${slot}`); + B.br(join); + B.startBlock(ln); + } + const undefTag = undefinedArmTag(resultType, host.unionsById); + B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); + B.br(join); + B.startBlock(join); + const t = B.tmp(); + B.line(`${t} = load ptr, ptr ${slot}`); + return { name: t, type: e.type }; +} + /** A keyed read whose result is a scalar union (`r[k]` on a * `Record`, at `number | undefined`) and whose every hit * is a number or boolean arm of it, or an inline field of the same union. @@ -660,27 +735,17 @@ export function keyedRecordReadInto( B.br(join); B.startBlock(ln); } else { - host.declare(`declare ptr @scr_map_get_str_ref(ptr, ptr)`); + // The entry lookup filters absent keys inline; a hit retains the + // stored value with its own type's retain (map-number-lookup.ts). A + // nullable value's NULL is a stored unit arm: presence decides. const lh = B.newLabel("rkg.h"); const ln = B.newLabel("rkg.n"); - let raw: string; - if (iv.kind === "union" && host.nullableUnions.has(iv.unionId)) { - // NULL is a stored unit arm of a nullable value: presence decides. - host.declare(`declare zeroext i1 @scr_map_has_str(ptr, ptr)`); - const has = B.tmp(); - B.line(`${has} = call zeroext i1 @scr_map_has_str(ptr ${ovf}, ptr ${keyName})`); - B.condBr(has, lh, ln); - B.startBlock(lh); - raw = B.tmp(); - B.line(`${raw} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${keyName})`); - } else { - raw = B.tmp(); - const isnull = B.tmp(); - B.line(`${raw} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${keyName})`); - B.line(`${isnull} = icmp eq ptr ${raw}, null`); - B.condBr(isnull, ln, lh); - B.startBlock(lh); - } + const entry = emitStringMapEntry(host, ovf, keyName); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + B.condBr(found, lh, ln); + B.startBlock(lh); + const raw = host.retainHeap(emitNumberMapSlot(host, entry, "ptr"), iv); B.line(`store ${ty} ${surface(iv, raw, true)}, ptr ${slot}`); B.br(join); B.startBlock(ln); diff --git a/packages/compiler/src/backend/llvm/lib-http2.ts b/packages/compiler/src/backend/llvm/lib-http2.ts index d750fb7c5..e9f60cdfa 100644 --- a/packages/compiler/src/backend/llvm/lib-http2.ts +++ b/packages/compiler/src/backend/llvm/lib-http2.ts @@ -95,11 +95,12 @@ export function sniCallbackArgs(host: LlvmEmitterContext, cb: LlValue): AbiArgum let type = cb.type, value = cb.name; if (type.kind === "union") { - const arms = host.unionsById.get(type.unionId)?.arms ?? []; + const unionId = type.unionId; + const arms = host.unionsById.get(unionId)?.arms ?? []; const tag = arms.findIndex((arm) => arm.kind === "func"); if (tag < 0) throw new InternalCompilerError("SNI callback union lacks its function arm"); type = arms[tag]!; - value = host.unwrapNullableClosure(value, tag); + value = host.unwrapNullableClosure(value, unionId, tag); } else host.toHeap(cb); if (type.kind !== "func" || type.params[1] === undefined) throw new InternalCompilerError("SNI callback lacks its answer function"); diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index 35eb885cd..63661ab4c 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -165,7 +165,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) const funcTag = def ? def.arms.findIndex((a) => a.kind === "func") : -1; if (funcTag < 0) throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its func arm`); - cb = host.unwrapNullableClosure(args[cbIndex]!.name, funcTag); + cb = host.unwrapNullableClosure(args[cbIndex]!.name, cbT.unionId, funcTag); } } const valueCount = reusePort ? 5 : 4; diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index ae741463d..fbb21dcd2 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -12,13 +12,18 @@ import type { CallLifetimes } from "./call-lifetimes.js"; import type { ReferenceEffects } from "./reference-effects.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { + emitCompactMapSlot, emitNumberMapGetRef, emitNumberMapGetScalar, + emitNumberMapSlot, + emitStringMapEntry, emitStringMapGetScalar, emitStringMapGetRef, compactMapValues, noteNumberMapKey, } from "./map-number-lookup.js"; +import { InternalCompilerError } from "../../errors.js"; +import { undefinedArmTag } from "../../ir/analysis.js"; import { mapKeyAccess, mapKeyParamType } from "./shapes.js"; import { rememberMapEntry } from "./map-entry-fusion.js"; @@ -490,3 +495,62 @@ export function emitStackMapRead(host: LlvmEmitterContext, read: LocalMapRead): B.line(`store i32 ${selected}, ptr ${tag}`); return { value: { name: box, type: read.type }, owner }; } + +/** A string-keyed Map.get whose result is a nullable-pointer union over the + * map's own value type (`Map` read as `F | undefined`): a hit is + * the stored pointer itself and a miss the immortal undefined encoding, so + * the read can lend the stored value (emitBorrowedMapGet). */ +export function pointerMapGet(host: LlvmEmitterContext, e: IrExpr): boolean { + if ( + e.kind !== "mapIntrinsic" || + e.method !== "get" || + e.args.length !== 1 || + e.type.kind !== "union" || + e.receiver.type.kind !== "map" || + e.receiver.type.key.kind !== "string" || + !borrowsMapReadInputs(e) + ) + return false; + const nullable = host.nullableUnions.single(e.type.unionId); + return ( + nullable !== null && + typeEquals(nullable.arm, e.receiver.type.value) && + undefinedArmTag(e.type, host.unionsById) >= 0 + ); +} + +/** A pointerMapGet read without a reference: the map (and through it the + * stored value) must stay unchanged and alive until the consumer is done + * with the pointer (canBorrowReceiver). */ +export function emitBorrowedMapGet( + host: LlvmEmitterContext, + e: IrExpr & { kind: "mapIntrinsic" }, +): LlValue { + const B = host.B; + const mapType = e.receiver.type; + if (e.type.kind !== "union" || mapType.kind !== "map") + throw new InternalCompilerError("llvm emitter bug: borrowed Map.get shape"); + const receiver = host.emitReadReceiver(e.receiver); + const keyExpr = e.args[0]!; + const key = host.canBorrowReceiver(keyExpr) + ? host.emitReadReceiver(keyExpr) + : host.emitExpr(keyExpr); + const compact = compactMapValues(host, mapType.key, mapType.value); + const entry = emitStringMapEntry(host, receiver.name, key.name, compact); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + const hit = B.newLabel("mapb.hit"), + miss = B.newLabel("mapb.miss"), + join = B.newLabel("mapb.join"); + B.condBr(found, hit, miss); + B.startBlock(hit); + const value = compact ? emitCompactMapSlot(host, entry) : emitNumberMapSlot(host, entry, "ptr"); + B.br(join); + B.startBlock(miss); + B.br(join); + B.startBlock(join); + const absent = host.unitInstanceRef(e.type.unionId, undefinedArmTag(e.type, host.unionsById)); + const result = B.tmp(); + B.line(`${result} = phi ptr [ ${value}, %${hit} ], [ ${absent}, %${miss} ]`); + return { name: result, type: e.type }; +} diff --git a/packages/compiler/src/backend/llvm/nullable-unions.test.ts b/packages/compiler/src/backend/llvm/nullable-unions.test.ts index bf0941652..f4e6a6866 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.test.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.test.ts @@ -7,7 +7,7 @@ import { type IrType, type IrUnionDef, } from "../../ir/ir.js"; -import { NullableUnions } from "./nullable-unions.js"; +import { isObjectArm, isPointerIdentityArm, NullableUnions } from "./nullable-unions.js"; const loc = { file: "nullable-unions.ts", start: 0, end: 0 }; const a: IrType = { kind: "record", shapeId: "a" }; @@ -111,3 +111,22 @@ test("library, lifetime-region and debug builds keep record unions boxed", () => expect(new NullableUnions(module(unions, { lifetimeRegions: true })).get("ab")).toBeNull(); expect(new NullableUnions(module(unions)).shapes("ab")).not.toBeNull(); }); + +test("function arms are nullable pointers compared by identity root", () => { + const fn: IrType = { kind: "func", params: [F64], ret: F64 }; + const unions = new NullableUnions( + module([ + { id: "fu", arms: [fn, UNDEFINED_T] }, + { id: "fnu", arms: [NULL_T, fn, UNDEFINED_T] }, + { id: "ff", arms: [fn, F64] }, + ]), + ); + expect(unions.single("fu")).toMatchObject({ refTag: 0, unitTag: 1, nullTag: -1, arm: fn }); + expect(unions.single("fnu")).toMatchObject({ refTag: 1, unitTag: 2, nullTag: 0 }); + expect(unions.get("ff")).toBeNull(); + const single = unions.single("fu")!; + // Always truthy, but `===` compares adapters by the function they adapt. + expect(isObjectArm(single)).toBe(true); + expect(isPointerIdentityArm(single)).toBe(false); + expect(unions.armTypes().get("fu")).toEqual(fn); +}); diff --git a/packages/compiler/src/backend/llvm/nullable-unions.ts b/packages/compiler/src/backend/llvm/nullable-unions.ts index c51f3b5f6..eedb922b3 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.ts @@ -89,21 +89,30 @@ export const NULLABLE_ABSENT = "@sc_nullable_absent"; /** The reference arms whose instances are never NULL. Class and record * entry points all tolerate NULL; strings, Stats snapshots, arrays, maps and - * sets retain through a NULL-skipping wrapper (shapes.ts). Object arms are - * always truthy and compare by identity; string arms are truthy when - * non-empty and compare bytes. */ + * sets retain through a NULL-skipping wrapper (shapes.ts), as do closures + * (function arms). Object arms are always truthy and compare by identity + * (function arms by identity root); string arms are truthy when non-empty + * and compare bytes. */ export type NullableRefArm = | { kind: "object"; className: string } | { kind: "record"; shapeId: string } | { kind: "string" } | { kind: "stats" } - | (IrType & { kind: "array" | "map" | "set" }); + | (IrType & { kind: "array" | "map" | "set" | "func" }); -/** The arm is a JS object: always truthy, compared by identity. */ +/** The arm is a JS object: always truthy. */ export function isObjectArm(nullable: PointerUnion): boolean { return nullable.repr === "shapes" || nullable.arm.kind !== "string"; } +/** The arm compares by pointer identity. Function arms compare identity + * roots instead: a signature adapter is the function it adapts. */ +export function isPointerIdentityArm(nullable: PointerUnion): boolean { + return ( + nullable.repr === "shapes" || (nullable.arm.kind !== "string" && nullable.arm.kind !== "func") + ); +} + /** The representation decision for every union of one module. It is a * pure function of the module, so serialized-IR and in-memory emission * agree. */ @@ -264,7 +273,8 @@ function nullableShape( return { ...base, arm: { kind: "record", shapeId: arm.shapeId } }; if (arm.kind === "string") return { ...base, arm: { kind: "string" } }; if (arm.kind === "stats") return { ...base, arm: { kind: "stats" } }; - if (arm.kind === "array" || arm.kind === "map" || arm.kind === "set") return { ...base, arm }; + if (arm.kind === "array" || arm.kind === "map" || arm.kind === "set" || arm.kind === "func") + return { ...base, arm }; return null; } diff --git a/packages/compiler/src/backend/llvm/receivers.ts b/packages/compiler/src/backend/llvm/receivers.ts index 2fe9dd66b..8524a51fb 100644 --- a/packages/compiler/src/backend/llvm/receivers.ts +++ b/packages/compiler/src/backend/llvm/receivers.ts @@ -11,14 +11,24 @@ import type { LlValue } from "./expr-context.js"; import { guardedValue } from "./borrowed-receivers.js"; import { InternalCompilerError } from "../../errors.js"; import { emitBorrowedArrayRead, emitProjectedArrayRead } from "./local-array-reads.js"; -import { emitStackMapRead, matchMapRead } from "./map-read-lifetimes.js"; +import { + emitBorrowedMapGet, + emitStackMapRead, + matchMapRead, + pointerMapGet, +} from "./map-read-lifetimes.js"; import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js"; -import { emitCheckedNarrowReceiver } from "./checked-narrows.js"; +import { borrowsCheckedNarrow, emitCheckedNarrowReceiver } from "./checked-narrows.js"; import { canStackUnion, emitStackUnion } from "./stack-unions.js"; import { emitDowncastGuard, emitUnionNarrowGuard } from "./narrow-guards.js"; import { emitScalarOptional, isScalarOptionalSource } from "./local-union-storage.js"; import { borrowableInputs } from "./borrowed-inputs.js"; -import { emitStackRecordKeyGet, scalarRecordKeyGet } from "./expr-records.js"; +import { + emitBorrowedRecordKeyGet, + emitStackRecordKeyGet, + pointerRecordKeyGet, + scalarRecordKeyGet, +} from "./expr-records.js"; import { type WalkBorrowHost, analyzeWalks } from "./walk-borrows.js"; /** Read a receiver whose value is consumed immediately by a field or tag @@ -92,6 +102,10 @@ export function emitReadReceiver(emitter: LlEmitter, e: IrExpr): LlValue { return emitBorrowedResultCall(emitter, e); const arrayRead = emitter.optionalArrayReads.get(e); if (arrayRead) return emitProjectedArrayRead(emitter, arrayRead); + if (e.kind === "recordKeyGet" && emitter.canBorrowReceiver(e)) + return emitBorrowedRecordKeyGet(emitter, e); + if (e.kind === "mapIntrinsic" && emitter.canBorrowReceiver(e)) + return emitBorrowedMapGet(emitter, e); const narrow = emitter.checkedNarrows.get(e); const narrowed = narrow ? emitCheckedNarrowReceiver(emitter, narrow) : null; if (narrowed) return narrowed; @@ -294,7 +308,27 @@ export function canBorrowReceiver(emitter: LlEmitter, e: IrExpr): boolean { // snapshot, so neither the callee nor a later argument may replace // what it was read from: a callee that preserves every edge cannot, // and any other one must leave the argument's locations unwritten. + // A keyed read lends the stored pointer while the record keeps it. + case "recordKeyGet": + return ( + pointerRecordKeyGet(emitter, e) && + emitter.canBorrowReceiver(e.obj) && + emitter.referenceEffects.preserves(e.key) + ); + // So does a Map.get while the map keeps it. + case "mapIntrinsic": + return ( + pointerMapGet(emitter, e) && + emitter.canBorrowReceiver(e.receiver) && + emitter.referenceEffects.preserves(e.args[0]!) + ); case "call": { + // A checked extraction lends the payload of the union it narrows + // (emitCheckedNarrowReceiver). + if (borrowsCheckedNarrow(emitter, e)) { + const narrow = emitter.checkedNarrows.get(e)!; + return emitter.canBorrowReceiver(narrow.value); + } if (!emitter.borrowedReturns.has(e.callee)) return false; if (!e.args.every((arg) => emitter.referenceEffects.preserves(arg))) return false; const preserving = emitter.referenceEffects.functions.has(e.callee); diff --git a/packages/compiler/src/backend/llvm/reference-writes.test.ts b/packages/compiler/src/backend/llvm/reference-writes.test.ts index cbbe49d3b..db575babe 100644 --- a/packages/compiler/src/backend/llvm/reference-writes.test.ts +++ b/packages/compiler/src/backend/llvm/reference-writes.test.ts @@ -97,3 +97,46 @@ test("an array that is read back or escapes keeps its element writes", () => { const input = fill("input", [store("input", element("input"))]); expect(writes(input).conflicts(["a"], writes(input).summary("input"))).toBe(true); }); + +test("a keyed record read depends on every field and keyed entry of records", () => { + const handler: IrType = { kind: "func", params: [F64], ret: F64 }; + const table: IrType = { kind: "record", shapeId: "t" }; + const key: IrExpr = { kind: "strLit", value: "k", type: { kind: "string" }, loc }; + const read: IrExpr = { + kind: "recordKeyGet", + obj: ref("table", table), + shapeId: "t", + key, + type: { kind: "union", unionId: "hu" }, + loc, + }; + const keyedWrite: IrStmt = { + kind: "recordKeySet", + obj: ref("table", table), + shapeId: "t", + key, + value: ref("h", handler), + loc, + }; + const fn = (name: string, body: IrStmt[]): IrFunction => ({ + name, + loc, + returnType: F64, + params: [{ localId: "table", name: "table", type: table }], + locals: [ + { id: "table", name: "table", type: table, mutable: false }, + { id: "h", name: "h", type: handler, mutable: false }, + ], + body, + }); + const records = [{ id: "t", fields: [{ name: "add", type: handler }], indexValue: handler }]; + const quiet = fn("quiet", []); + const writer = fn("writer", [keyedWrite]); + const functions = new Map([quiet, writer].map((f) => [f.name, f])); + const rw = new ReferenceWrites(functions, [], () => false, new Set(), { records }); + rw.enter(quiet); + const keys = rw.readKeys(read); + expect(keys).toEqual(expect.arrayContaining(["r:*", "r:add"])); + expect(rw.conflicts(keys!, rw.summary("quiet"))).toBe(false); + expect(rw.conflicts(keys!, rw.summary("writer"))).toBe(true); +}); diff --git a/packages/compiler/src/backend/llvm/reference-writes.ts b/packages/compiler/src/backend/llvm/reference-writes.ts index 89132a715..d64beafc5 100644 --- a/packages/compiler/src/backend/llvm/reference-writes.ts +++ b/packages/compiler/src/backend/llvm/reference-writes.ts @@ -92,6 +92,8 @@ const INERT_LIB_PREFIXES = [ ]; const INERT_LIB_CALLS = new Set([ "dyn.compare", + // Reads the ambient receiver (a fresh reference to the bound value). + "dyn.this", "error.new", "error.nodeThrow", "dyn.typedRefIs", @@ -922,6 +924,17 @@ export class ReferenceWrites { case "arrayGet": out.push(ARRAY_KEY); return this.expr(node.index) === null && walk(node.arr); + case "recordKeyGet": + // Any declared field or overflow entry the key may name. + out.push(ANY_RECORD_KEY); + for (const f of this.records.get(node.shapeId)?.fields ?? []) out.push(`r:${f.name}`); + return this.expr(node.key) === null && walk(node.obj); + case "mapIntrinsic": + // A typed Map.get: any entry of any map. + if (node.method !== "get" || node.args.length !== 1 || !borrowsMapReadInputs(node)) + return false; + out.push(MAP_KEY); + return this.expr(node.args[0]!) === null && walk(node.receiver); case "unionNarrow": case "upcast": case "downcast": diff --git a/packages/compiler/src/backend/llvm/stack-callbacks.ts b/packages/compiler/src/backend/llvm/stack-callbacks.ts index 5c404896a..20de50372 100644 --- a/packages/compiler/src/backend/llvm/stack-callbacks.ts +++ b/packages/compiler/src/backend/llvm/stack-callbacks.ts @@ -32,10 +32,12 @@ export class StackCallbacks { const count = B.tmp(); B.line(`${count} = getelementptr inbounds %ScrClosure, ptr ${closure}, i32 0, i32 2`); B.line(`store ${host.sizeType} ${value.captures.length}, ptr ${count}`); - if (this.functions.get(value.fnName)?.ownsPrototype) { + const target = this.functions.get(value.fnName); + const functionKind = target ? host.closureFunctionKind(target) : 0; + if (functionKind !== 0) { const kind = B.tmp(); B.line(`${kind} = getelementptr inbounds %ScrClosure, ptr ${closure}, i32 0, i32 4`); - B.line(`store i32 4, ptr ${kind}`); + B.line(`store i32 ${functionKind}, ptr ${kind}`); } value.captures.forEach((localId, index) => { const box = host.retainBox(host.captureBox(localId)); diff --git a/packages/compiler/src/ir/receiver-readers.ts b/packages/compiler/src/ir/receiver-readers.ts index f5270aa7d..067c474ca 100644 --- a/packages/compiler/src/ir/receiver-readers.ts +++ b/packages/compiler/src/ir/receiver-readers.ts @@ -1,6 +1,7 @@ import { isBorrowSafeMath } from "./analysis.js"; import type { IrExpr, IrFunction, IrLibFn, IrStmt } from "./ir.js"; import { eachNodeOfKinds, type IrNodeIndex } from "./node-index.js"; +import { everyExprChild, everyStmtChild } from "./traverse.js"; /** Expression kinds that run no user code of their own and never consult * the ambient receiver stack. Calls are admitted here and resolved through @@ -151,6 +152,7 @@ function opaqueOperand(e: IrExpr): boolean { * this set needs no receiver bracket. */ export class AmbientReceiverReaders { private readers: Set | null = null; + private byMethod: Map | null = null; constructor( private readonly functions: ReadonlyMap, @@ -163,8 +165,63 @@ export class AmbientReceiverReaders { return !this.compute().has(fn.name); } - private compute(): Set { - if (this.readers) return this.readers; + /** A signature adapter body that observes the ambient receiver only by + * forwarding it, unchanged, to the original it captures (a callValue on + * that capture whose receiver is the plain `this` read). Such an adapter + * ignores the receiver exactly when its original does. */ + forwardsOnly(fn: IrFunction): boolean { + if (fn.async || fn.generator) return false; + const original = fn.captures?.[0]?.localId; + if (original === undefined) return false; + const readers = this.compute(); + const edge = (name: string): boolean => this.functions.has(name) && !readers.has(name); + const stmt = (node: IrStmt): boolean => + RECEIVER_FREE_STMTS.has(node.kind) && everyStmtChild(node, expr, stmt); + const expr = (node: IrExpr): boolean => { + if (node.kind === "callValue" && node.receiver !== undefined) { + const receiver = node.receiver; + return ( + receiver.kind === "libCall" && + receiver.fn === "dyn.this" && + receiver.args.length === 0 && + node.callee.kind === "varRef" && + node.callee.localId === original && + node.args.every(expr) + ); + } + return this.admits(node, edge) && everyExprChild(node, expr, stmt); + }; + return fn.body.every(stmt); + } + + /** Whether one expression node, apart from its children, leaves the + * ambient receiver alone; `edge` admits a direct callee. */ + private admits(expr: IrExpr, edge: (name: string) => boolean): boolean { + if (!RECEIVER_FREE_EXPRS.has(expr.kind)) return false; + switch (expr.kind) { + case "call": + return edge(expr.callee); + case "new": + return edge(`%${expr.className}.constructor`); + case "virtualCall": { + // Any class's override may run: admit only when every + // same-named method is itself receiver-free. + const targets = this.methods().get(expr.method) ?? []; + return targets.length > 0 && targets.every(edge); + } + case "libCall": + return isBorrowSafeMath(expr.fn) || RECEIVER_FREE_LIB_FNS.has(expr.fn); + case "toString": + return !opaqueOperand(expr.operand); + default: { + const operands = dataIntrinsicOperands(expr); + return operands === null || !operands.some(opaqueOperand); + } + } + } + + private methods(): Map { + if (this.byMethod) return this.byMethod; const byMethod = new Map(); for (const name of this.functions.keys()) { const dot = name.lastIndexOf("."); @@ -174,6 +231,12 @@ export class AmbientReceiverReaders { list.push(name); byMethod.set(method, list); } + this.byMethod = byMethod; + return byMethod; + } + + private compute(): Set { + if (this.readers) return this.readers; const readers = new Set(); const callers = new Map(); const scan = this.nodes?.scanExcept(TRIVIALLY_RECEIVER_FREE); @@ -185,34 +248,11 @@ export class AmbientReceiverReaders { callees.add(name); return true; }; - const expr = (expr: IrExpr): boolean => { - if (!RECEIVER_FREE_EXPRS.has(expr.kind)) return false; - switch (expr.kind) { - case "call": - return edge(expr.callee); - case "new": - return edge(`%${expr.className}.constructor`); - case "virtualCall": { - // Any class's override may run: admit only when every - // same-named method is itself receiver-free. - const targets = byMethod.get(expr.method) ?? []; - return targets.length > 0 && targets.every(edge); - } - case "libCall": - return isBorrowSafeMath(expr.fn) || RECEIVER_FREE_LIB_FNS.has(expr.fn); - case "toString": - return !opaqueOperand(expr.operand); - default: { - const operands = dataIntrinsicOperands(expr); - return operands === null || !operands.some(opaqueOperand); - } - } - }; if (!reads) eachNodeOfKinds(fn, scan, (node, statement) => { const free = statement ? RECEIVER_FREE_STMTS.has((node as IrStmt).kind) - : expr(node as IrExpr); + : this.admits(node as IrExpr, edge); if (!free) reads = true; return free; }); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json new file mode 100644 index 000000000..1670b97b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8230-function-record-dispatch.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json new file mode 100644 index 000000000..8f1963ef5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8231-function-table-shapes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json new file mode 100644 index 000000000..671364bc6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8232-optional-function-values.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_closure.h b/packages/runtime/src/scr_closure.h index 9644722ce..cfca90313 100644 --- a/packages/runtime/src/scr_closure.h +++ b/packages/runtime/src/scr_closure.h @@ -98,7 +98,7 @@ typedef struct ScrClosure { * (like the dyn→closure edge): a cycle through it is merely never * collected. */ ScrBox *props; - uint32_t function_kind; /* low bits: 0 ordinary, 1 generator, 2 async, 3 async generator; bit 2: ordinary own prototype; bit 3: checked callable adapter in caps[0] */ + uint32_t function_kind; /* low bits: 0 ordinary, 1 generator, 2 async, 3 async generator; bit 2: ordinary own prototype; bit 3: checked callable adapter in caps[0]; bit 4: SCR_FN_RECEIVER_FREE */ /* The root's `length` as boxed values report it; meaningful only when * identity is set (the root's own boxes carry their arity). */ uint32_t identity_length; @@ -117,6 +117,13 @@ typedef struct ScrClosure { ScrBox *caps[]; } ScrClosure; +/* function_kind bit 4: the body never observes the ambient receiver (the + * `this` an unbound call binds), so compiled callers skip the receiver + * bracket around an indirect call. Set only by the compiler; the kind + * proper is function_kind & SCR_FN_KIND_MASK. */ +#define SCR_FN_RECEIVER_FREE 16u +#define SCR_FN_KIND_MASK 15u + ScrClosure *scr_closure_new(void *fn, size_t ncaps); /* +1; caller fills caps with +1 box refs */ static inline ScrClosure *scr_closure_retain(ScrClosure *c) { diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..cef3be673 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6880,7 +6880,7 @@ ScrDyn *scr_dyn_class_base_prototype(ScrDyn *constructor) { default: break; } if (constructor->kind != SCR_DYN_FUNC || constructor->v.fn.class_obj || - scr_closure_identity(constructor->v.fn.clo)->function_kind != 4) { + (scr_closure_identity(constructor->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) != 4) { static const char message[] = "Class extends value is not a native ordinary constructor"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; @@ -7028,7 +7028,8 @@ void scr_dyn_name_class_prototype(ScrDyn *prototype, const ScrStr *name) { /* Ordinary native function constructors retain their live prototype table. * A constructor may replace the fresh receiver with another object. */ ScrDyn *scr_dyn_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what) { - if (callee->kind != SCR_DYN_FUNC || callee->v.fn.class_obj || scr_closure_identity(callee->v.fn.clo)->function_kind != 4) + if (callee->kind != SCR_DYN_FUNC || callee->v.fn.class_obj || + (scr_closure_identity(callee->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) != 4) return scr_bytes_construct(callee, args, what); ScrDyn *prototype = scr_dyn_fn_get(callee, "prototype", 9); if (!prototype) return NULL; @@ -9144,7 +9145,9 @@ bool scr_dyn_is_callable(const ScrDyn *value) { int scr_dyn_function_kind(const ScrDyn *value) { while (value->kind == SCR_DYN_PROXY) value = value->v.proxy.target; - return value->kind == SCR_DYN_FUNC ? scr_closure_identity(value->v.fn.clo)->function_kind : 0; + return value->kind == SCR_DYN_FUNC + ? (int)(scr_closure_identity(value->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) + : 0; } void scr_dyn_adopt_identity(ScrClosure *adapter, const ScrDyn *value) { diff --git a/tests/corpus/8230-function-record-dispatch.ts b/tests/corpus/8230-function-record-dispatch.ts new file mode 100644 index 000000000..3acde5bd7 --- /dev/null +++ b/tests/corpus/8230-function-record-dispatch.ts @@ -0,0 +1,107 @@ +// Records of functions used as dispatch tables: keyed reads consumed at once +// (`t[k]!(…)`, `t[k]?.(…)`, a checked local), missing keys, handlers that +// declare fewer parameters than the table's type, handlers that replace, +// delete or re-add table entries (their own included) while they run, and +// key order after such mutation. +type Handler = (x: number, y: number) => number; + +let log: string[] = []; + +function make(tag: string): Handler { + let calls = 0; + return (x, y) => { + calls++; + log.push(tag + ":" + calls); + return x * 10 + y; + }; +} + +const table: Record = { + add: (x, y) => x + y, + neg: (x) => -x, + pow: (x, y) => x ** y, + made: make("made"), +}; + +function keys(): string[] { + return process.argv.length > 99 ? [] : ["add", "neg", "pow", "made", "missing", ""]; +} + +for (const k of keys()) { + const h = table[k] as Handler | undefined; + console.log(JSON.stringify(k), typeof h, h === undefined, h !== undefined && h === table[k]); + console.log(" optional", table[k]?.(3, 4) ?? "none"); + if (h) console.log(" checked", h(2, 5)); + try { + console.log(" asserted", table[k]!(5, 2)); + } catch (e) { + console.log(" asserted threw", (e as Error).name); + } +} + +// A handler that replaces its own entry while it runs: the replaced closure +// keeps running with its captured state. +function selfReplacing(): Handler { + const state = { hits: 0, label: "first" }; + return (x, y) => { + table["swap"] = make("second"); + state.hits++; + log.push(state.label + " still running after replace, hits " + state.hits); + return x + y + state.hits; + }; +} +table["swap"] = selfReplacing(); +console.log("swap 1", table["swap"]!(1, 2)); +console.log("swap 2", table["swap"]?.(1, 2)); +console.log("swap 3", table["swap"]!(1, 2)); + +// A handler that deletes its own entry. +function selfDeleting(name: string): Handler { + const captured = [name, "captured"]; + return (x) => { + delete table[name]; + log.push(captured.join(" ") + " after delete"); + return x; + }; +} +table["gone"] = selfDeleting("gone"); +const goneKey = keys()[0] === "add" ? "gone" : "add"; +console.log("gone", table[goneKey]?.(7, 0) ?? "none", table[goneKey]?.(7, 0) ?? "none"); + +// A checked local keeps its closure after the entry changes. +table["kept"] = make("kept-old"); +const kept = table["kept"] as Handler | undefined; +table["kept"] = make("kept-new"); +delete table["kept"]; +if (kept) console.log("kept", kept(4, 4)); +console.log("kept entry", table["kept"] === undefined); + +// Iteration while handlers add and delete keys: key order follows inserts. +const order: Record = { + b: (x) => x + 1, + a: (x) => { + delete order["c"]; + order["z"] = (v) => v * 2; + return x + 2; + }, + c: (x) => x + 3, + "1": (x) => x + 4, +}; +for (const k in order) console.log("in", k, order[k]?.(1, 0)); +console.log("keys", Object.keys(order).join(",")); +for (const k of Object.keys(order)) { + order[k + k] = (x) => x; + console.log("of", k, order[k]!(10, 0)); +} +console.log("keys", Object.keys(order).join(",")); + +// Reassigning the table binding itself from inside a handler. +let current: Record = { + go: (x) => { + current = { go: (y) => y * 100 }; + const local = [x, x]; + return local[0]! + local[1]!; + }, +}; +console.log("rebind", current["go"]!(3, 0), current["go"]!(3, 0)); +console.log(log.join("\n")); diff --git a/tests/corpus/8231-function-table-shapes.ts b/tests/corpus/8231-function-table-shapes.ts new file mode 100644 index 000000000..108c4cee0 --- /dev/null +++ b/tests/corpus/8231-function-table-shapes.ts @@ -0,0 +1,108 @@ +// Function dispatch through other table shapes: Map, arrays of +// handlers, object literals of methods (with and without `this`), handler +// mutation of the Map during calls, and adapters for handlers that declare +// fewer parameters than the table type. +type Op = (acc: number, arg: number) => number; + +const trace: string[] = []; +const byName = new Map(); +byName.set("add", (acc, arg) => acc + arg); +byName.set("half", (acc) => acc / 2); +byName.set("reset", () => { + byName.delete("reset"); + byName.set("reset", (acc) => acc - 1); + trace.push("reset replaced itself"); + return 0; +}); +byName.set("clear", (acc) => { + const keep = [acc, "before clear"]; + byName.clear(); + trace.push(keep.join(" ") + ", size " + byName.size); + return acc; +}); + +function program(): string[] { + return process.argv.length > 99 ? [] : ["add", "half", "nop", "reset", "reset", "add", "clear", "add"]; +} + +let acc = 8; +for (const name of program()) { + const handler = byName.get(name); + if (handler !== undefined) acc = handler(acc, 3); + else trace.push("no " + name); + console.log(name, acc, byName.get(name)?.(1, 1) ?? "-"); +} + +const byCode: Op[] = [(a, b) => a - b, (a) => a * 3, (a, b) => Math.max(a, b)]; +let total = 1; +for (let i = 0; i < 9; i++) { + total = byCode[i % 4]?.(total, i) ?? total + 100; + if (i % 4 < byCode.length) total = byCode[i % 4]!(total, 1); +} +console.log("array", total); + +const counter = { + base: 5, + bump(by: number): number { + this.base += by; + return this.base; + }, + twice(by: number): number { + return this.bump(by) + this.bump(by); + }, + plain(by: number): number { + return by * 2; + }, +}; +console.log("methods", counter.bump(1), counter.twice(2), counter.plain(3), counter["plain"](4)); + +// Methods that read `this`, called through computed keys and through a +// signature adapter (a method declaring fewer parameters than its type). +interface Account { + balance: number; + deposit: (amount: number, note: string) => number; + withdraw: (amount: number, note: string) => number; + peek: (amount: number, note: string) => number; +} +const account: Account = { + balance: 10, + deposit(amount) { + this.balance += amount; + return this.balance; + }, + withdraw(amount, note) { + this.balance -= amount; + trace.push("withdraw " + note); + return this.balance; + }, + peek: (amount) => amount, +}; +for (const op of ["deposit", "withdraw", "peek", "deposit"] as const) { + console.log(op, account[op](5, "n-" + op), account.balance); +} +const detached = account.peek; +console.log("detached", detached(3, "x")); + +interface Machine { + state: string; + on: Record string>; +} +const machine: Machine = { + state: "idle", + on: { + idle: (m, input) => (input > 0 ? "running" : m.state), + running: (m, input) => { + if (input < 0) { + m.on["stopped"] = () => "idle"; + return "stopped"; + } + return m.state; + }, + }, +}; +for (const input of [0, 1, 2, -1, 5, 3]) { + const next = machine.on[machine.state]?.(machine, input) ?? machine.state; + console.log("machine", machine.state, input, "->", next); + machine.state = next; +} +console.log(trace.join("\n")); diff --git a/tests/corpus/8232-optional-function-values.ts b/tests/corpus/8232-optional-function-values.ts new file mode 100644 index 000000000..5035c5f51 --- /dev/null +++ b/tests/corpus/8232-optional-function-values.ts @@ -0,0 +1,75 @@ +// Values typed `F | undefined` for a function type F: identity and +// equality (signature adapters compare as the function they adapt), typeof, +// truthiness, storage in arrays, records, class fields and Maps, optional +// parameters and returns. +type Unary = (x: number) => number; +type Binary = (x: number, y: number) => number; + +const double: Unary = (x) => x * 2; +const asBinary: Binary = double; +const maybe = (flag: boolean): Unary | undefined => (flag ? double : undefined); + +const a = maybe(true); +const b = maybe(true); +const none = maybe(false); +console.log(a === b, a === none, none === undefined, typeof a, typeof none, !!a, !!none); +console.log(a === double, (asBinary as unknown) === (double as unknown)); + +const slots: (Unary | undefined)[] = [double, undefined, (x) => x + 1]; +console.log(slots.map((f) => (f ? f(5) : "empty")).join(" "), slots.length); +console.log(slots.indexOf(double), slots.includes(undefined), slots.filter((f) => f !== undefined).length); + +interface Hooks { + before?: Unary; + after?: Unary; +} +const hooks: Hooks = { after: (x) => x - 1 }; +console.log(hooks.before?.(1) ?? "no before", hooks.after?.(1) ?? "no after", "before" in hooks); + +class Button { + onClick: Unary | undefined; + clicks = 0; + click(): number { + this.clicks++; + return this.onClick?.(this.clicks) ?? -1; + } +} +const button = new Button(); +console.log(button.click()); +button.onClick = (n) => n * 100; +console.log(button.click(), button.click()); +button.onClick = undefined; +console.log(button.click(), button.onClick === undefined); + +const registry = new Map([ + ["set", double], + ["unset", undefined], +]); +console.log(registry.get("set")?.(4), registry.get("unset")?.(4), registry.has("unset"), registry.get("other") === undefined); + +function run(x: number, transform?: Unary, combine?: Binary): number { + const t = transform ? transform(x) : x; + return combine === undefined ? t : combine(t, x); +} +console.log(run(3), run(3, double), run(3, undefined, (p, q) => p * q), run(3, (x) => -x, asBinary)); + +function pick(name: string): Binary | undefined { + switch (name) { + case "mul": + return (x, y) => x * y; + case "first": + return (x) => x; + default: + return undefined; + } +} +for (const name of ["mul", "first", "none"]) { + const f = pick(name); + console.log(name, f?.(6, 7), f === undefined ? "undefined" : typeof f); +} + +let late: Unary | undefined; +const callLate = (): number => late?.(9) ?? 0; +console.log(callLate()); +late = (x) => x + 0.5; +console.log(callLate()); diff --git a/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json b/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } diff --git a/tests/harness/corpus-costs/8231-function-table-shapes.ts.json b/tests/harness/corpus-costs/8231-function-table-shapes.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8231-function-table-shapes.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } diff --git a/tests/harness/corpus-costs/8232-optional-function-values.ts.json b/tests/harness/corpus-costs/8232-optional-function-values.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8232-optional-function-values.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } From 5f76d41a7181348e2e14d60a2ec3377f825bc3d5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:57:11 -0500 Subject: [PATCH 284/395] Keep U+11A7 after a Hangul LV syllable in NFC and NFKC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libunicode composes an LV syllable with U+11A7 as if it were a trailing consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize() gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel that composes with nothing, so for the composing forms the code points between occurrences of it normalize on their own and it is kept as it is. The guard sits in front of the vendored library (scr_unicode_normalize), for String.prototype.normalize and the NFC step of IDNA mapping. 8009-normalize-hangul-jungseong-o-yae prints the code points of all four forms for U+11A7 after LV and LVT syllables, conjoining jamo, other starters and combining marks. --- ...9-normalize-hangul-jungseong-o-yae.ts.json | 6 ++ packages/runtime/src/scr_regex.c | 55 ++++++++++++++++++- .../8009-normalize-hangul-jungseong-o-yae.ts | 28 ++++++++++ ...9-normalize-hangul-jungseong-o-yae.ts.json | 1 + 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json create mode 100644 tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts create mode 100644 tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json new file mode 100644 index 000000000..872a85396 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index 2a98e92c6..37f94bd65 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -2418,6 +2418,57 @@ static void *scr_normalize_realloc(void *opaque, void *ptr, size_t size) { return realloc(ptr, size); } +/* unicode_normalize, except that U+11A7 never composes. libunicode composes + * an LV syllable with U+11A7 as a trailing consonant (those are U+11A8 to + * U+11C2), which drops it: "\uAC00\u11A7" became "\uAC00". U+11A7 is a + * class-0 vowel that composes with nothing, so for the composing forms the + * code points between occurrences of it normalize on their own and it is + * kept as it is, as in Node. */ +static int scr_unicode_normalize(uint32_t **pdst, const uint32_t *src, int len, + UnicodeNormalizationEnum mode) { + int at = 0; + if (mode == UNICODE_NFC || mode == UNICODE_NFKC) + while (at < len && src[at] != 0x11A7) at++; + else + at = len; + if (at == len) return unicode_normalize(pdst, src, len, mode, NULL, scr_normalize_realloc); + uint32_t *out = NULL; + size_t count = 0, cap = 0; + int start = 0; + for (int i = 0; i <= len; i++) { + if (i < len && src[i] != 0x11A7) continue; + uint32_t *part = NULL; + int n = 0; + if (i > start) { + n = unicode_normalize(&part, src + start, i - start, mode, NULL, scr_normalize_realloc); + if (n < 0) { + free(out); + return -1; + } + } + size_t need = count + (size_t)n + 1; + if (need > cap) { + size_t grown = cap ? cap * 2 : 16; + if (grown < need) grown = need; + uint32_t *bigger = realloc(out, grown * sizeof *out); + if (!bigger) { + free(part); + free(out); + return -1; + } + out = bigger; + cap = grown; + } + if (n > 0) memcpy(out + count, part, (size_t)n * sizeof *out); + count += (size_t)n; + free(part); + if (i < len) out[count++] = 0x11A7; + start = i + 1; + } + *pdst = out; + return (int)count; +} + ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { UnicodeNormalizationEnum mode; if (form->len == 3 && !memcmp(form->data, "NFC", 3)) mode = UNICODE_NFC; @@ -2442,7 +2493,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { offset += step; } uint32_t *output = NULL; - int count = unicode_normalize(&output, input, length, mode, NULL, scr_normalize_realloc); + int count = scr_unicode_normalize(&output, input, length, mode); free(input); if (count < 0) scr_regex_oom(); ScrJsonBuf buffer; @@ -2455,7 +2506,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { uint32_t *scr_nfc_code_points(const uint32_t *input, size_t length, size_t *out_len) { if (length > INT32_MAX) scr_regex_oom(); uint32_t *output = NULL; - int count = unicode_normalize(&output, input, (int)length, UNICODE_NFC, NULL, scr_normalize_realloc); + int count = scr_unicode_normalize(&output, input, (int)length, UNICODE_NFC); if (count < 0) scr_regex_oom(); *out_len = (size_t)count; return output; diff --git a/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts b/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts new file mode 100644 index 000000000..b7cb5418a --- /dev/null +++ b/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts @@ -0,0 +1,28 @@ +// U+11A7 (HANGUL JUNGSEONG O-YAE) is a vowel that composes with nothing: +// after an LV syllable, after a leading consonant, and around trailing +// consonants it stays as it is in every normalization form, as in Node. + +const samples = [ + "가ᆧ", + "가ᆧ", + "가ᆧᆨ", + "각ᆧ", + "ᄀᆧ", + "ᆧ", + "ᆧᆧ", + "x가ᆧy각z", + "café 가ᆧ é", + "ガᆧ가", +]; + +function points(text: string): string { + return Array.from(text, (ch) => ch.codePointAt(0)!.toString(16)).join("."); +} + +for (const sample of samples) { + console.log( + points(sample), + ["NFC", "NFD", "NFKC", "NFKD"].map((form) => points(sample.normalize(form))).join(" "), + ); +} +console.log("가ᆧ".normalize() === "가ᆧ", "가ᆧ".normalize().length); diff --git a/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json b/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json new file mode 100644 index 000000000..6c20e6ad7 --- /dev/null +++ b/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json @@ -0,0 +1 @@ +{ "plain": 2503, "san": 2110 } From e7a088f90420ca6de0d81675e6e95989a60eb215 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:58:11 -0500 Subject: [PATCH 285/395] 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 afae8b2c1..68aedbc33 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 f26c4c89a..ed351e809 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 8619c5b34..ecf4af7d9 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 52abff7c147f5429310136513d17fb72880dd3f8 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 13:33:51 -0700 Subject: [PATCH 286/395] Clone union records whose override is an empty array literal Spreading a union of records and replacing a shared field keeps each value's variant (lowerUnionRecordClone), but only when every override's type lifts into the field. An empty array literal is typed `never[]`, which maps to nothing, so `{ ...entry, content: [] }` over a union fell back to the ordinary literal path. That path cannot build a union of spread records, and the literal was refused (SC2001). An empty array literal now builds an empty array of each arm's own element type. This covers a field that is an array and a field that is a union with exactly one array arm (`string | Part[]`). pi's session-manager repairs messages with missing content this way (`return [{ ...message, content: [] }]`). The refusal rejected the whole SessionManager class. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/object-literals.ts | 19 +++- .../union-clone-empty-array-overrides.ts.json | 6 ++ .../union-clone-empty-array-overrides.ts | 92 +++++++++++++++++++ .../union-clone-empty-array-overrides.ts.json | 1 + 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json create mode 100644 tests/corpus/union-clone-empty-array-overrides.ts create mode 100644 tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 70766949a..ae82133bf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -2513,6 +2513,20 @@ export function lowerObjectLiteral( : { kind: "seqExpr", stmts: spreadStmts, result, type, loc }; } +/** The field type is an array, or a union with exactly one array arm. */ +function singleArrayArm(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "array") return true; + if (type.kind !== "union") return false; + const arms = lowerer.unions.get(type.unionId)?.arms ?? []; + return arms.filter((arm) => arm.kind === "array").length === 1; +} + +function isEmptyArrayLiteral(node: ts.Node): boolean { + let inner = node; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + return ts.isArrayLiteralExpression(inner) && inner.elements.nodes.length === 0; +} + /** A union of plain records can preserve its existing variant while * replacing shared fields. Dispatch before constructing the copy: only * the selected arm evaluates overrides, and recordClone snapshots fields @@ -2561,8 +2575,11 @@ function lowerUnionRecordClone(lowerer: Lowerer, expr: ts.ObjectLiteralExpressio for (let index = 0; index < props.length; index++) { const prop = props[index]!; const node = ts.isPropertyAssignment(prop) ? prop.initializer : prop.name; - const valueType = lowerer.mapTypeOf(lowerer.typeOf(node)); const fieldType = shape.fields.find((field) => field.name === names[index])!.type; + // An empty array literal (`content: []`, typed `never[]`) builds a + // fresh array of each arm's own element type. + if (isEmptyArrayLiteral(node) && singleArrayArm(lowerer, fieldType)) continue; + const valueType = lowerer.mapTypeOf(lowerer.typeOf(node)); const lift = valueType ? lowerer.widthLiftPlan(valueType, fieldType) : null; // The inferred result must retain an override's own fields and // aliases. A structural copy into the source field's narrower shape diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json new file mode 100644 index 000000000..cb11c5098 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-clone-empty-array-overrides.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-clone-empty-array-overrides.ts b/tests/corpus/union-clone-empty-array-overrides.ts new file mode 100644 index 000000000..98ddac59c --- /dev/null +++ b/tests/corpus/union-clone-empty-array-overrides.ts @@ -0,0 +1,92 @@ +// Spreading a union of records and overriding a shared field with an empty +// array literal keeps each value's own variant: the `[]` (typed `never[]`) +// becomes an empty array of the selected arm's element type, also when that +// arm's field is a union with one array arm (`string | Part[]`). + +interface TextPart { + type: "text"; + text: string; +} +interface ImagePart { + type: "image"; + data: string; +} +interface UserEntry { + role: "user"; + content: string | (TextPart | ImagePart)[]; + timestamp: number; +} +interface AssistantEntry { + role: "assistant"; + content: TextPart[]; + model: string; + timestamp: number; +} +interface ToolEntry { + role: "tool"; + toolCallId: string; + content: (TextPart | ImagePart)[]; + isError: boolean; + timestamp: number; +} +interface SystemEntry { + role: "system"; + content: string; + timestamp: number; +} +interface NoteEntry { + role: "note"; + text: string; + timestamp: number; +} +type Entry = UserEntry | AssistantEntry | ToolEntry | SystemEntry | NoteEntry; + +function repair(entry: Entry, clear: boolean): Entry[] { + if (entry.role === "system" && clear) return [{ ...entry, content: "" }]; + if ((entry.role === "user" || entry.role === "assistant" || entry.role === "tool") && clear) { + return [{ ...entry, content: [] }]; + } + return [entry]; +} + +interface Circle { + kind: "circle"; + tags: string[]; + radius: number; +} +interface Square { + kind: "square"; + tags: string[]; + side: number; +} +type Shape = Circle | Square; + +function cleared(shape: Shape): Shape { + return { ...shape, tags: [] }; +} + +const entries: Entry[] = [ + { role: "user", content: [{ type: "text", text: "hello" }], timestamp: 1 }, + { role: "assistant", content: [{ type: "text", text: "hi" }], model: "m", timestamp: 2 }, + { role: "tool", toolCallId: "t1", content: [{ type: "image", data: "AA==" }], isError: false, timestamp: 3 }, + { role: "note", text: "hi", timestamp: 4 }, + { role: "system", content: "be brief", timestamp: 5 }, + { role: "user", content: "kept", timestamp: 6 }, +]; +entries.forEach((entry, index) => { + for (const fixed of repair(entry, index % 2 === 0 || entry.role === "system")) { + console.log(fixed.role, JSON.stringify(fixed), fixed === entry); + } +}); +console.log(JSON.stringify(entries[0])); + +const shapes: Shape[] = [ + { kind: "circle", tags: ["round", "red"], radius: 2 }, + { kind: "square", tags: ["blue"], side: 3 }, +]; +for (const shape of shapes) { + const copy = cleared(shape); + copy.tags.push("fresh"); + const area = copy.kind === "circle" ? copy.radius * copy.radius * 3 : copy.side * copy.side; + console.log(copy.kind, area, copy.tags.join("+"), shape.tags.join("+"), copy !== shape); +} diff --git a/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json b/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 605e312e788131d8a3067227a0abdd1a2fbfb0fe Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:00:29 -0700 Subject: [PATCH 287/395] Let boxable function arms ride a checked-dynamic union A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal) maps to the checked-dynamic value when every other arm is one the checked-dynamic tree holds faithfully. Function arms were excluded, so `Progress | AbortSignal | undefined` was refused (SC2009). The tree boxes a function whose parameters check in and whose result converts out by reference (canBoxFuncIntoDyn), and `typeof x === "function"` narrows it back out to the typed function. Such arms now join the collapse. The corpus program checks calls, identity (`===` against the original), and the other arm's members against Node. pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress | AbortSignal`. The refusal rejected the whole SessionManager class. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 11 +++++ .../corpus/dyn-union-function-arms.ts.json | 6 +++ tests/corpus/dyn-union-function-arms.ts | 48 +++++++++++++++++++ .../dyn-union-function-arms.ts.json | 1 + 4 files changed, 66 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json create mode 100644 tests/corpus/dyn-union-function-arms.ts create mode 100644 tests/harness/corpus-costs/dyn-union-function-arms.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..03d85ba7d 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -10,6 +10,7 @@ import { isDynTypedRefType, BOOL, bytesOf, + canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, CHILD_T, @@ -8341,6 +8342,16 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { // `string | URL | Request`, fetch's own input type under @types/node. case "url": return true; + // A function the checked-dynamic tree can box (each parameter checks + // in, the result converts out) is held by reference there, and + // `typeof x === "function"` narrows it back out: `Progress | AbortSignal` + // beside fetch's handles. + case "func": + return canBoxFuncIntoDyn( + arm, + (id) => ctx.shapes.get(id), + (id) => ctx.unions.get(id), + ); case "record": case "array": // `T | Promise` with T bound to `unknown`: the checked-dynamic value diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json new file mode 100644 index 000000000..80675e5e6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-union-function-arms.ts" + ], + "diags": [] +} diff --git a/tests/corpus/dyn-union-function-arms.ts b/tests/corpus/dyn-union-function-arms.ts new file mode 100644 index 000000000..e85cc2e6b --- /dev/null +++ b/tests/corpus/dyn-union-function-arms.ts @@ -0,0 +1,48 @@ +// A union of a function type with a value the checked-dynamic tree holds +// (AbortSignal, `unknown`) rides that representation: the function is held +// by reference, `typeof x === "function"` narrows it back out, and calls, +// identity and the other arm's members behave as in Node. + +type Progress = (loaded: number, total: number) => void; + +function listAll( + dirOrProgress?: string | Progress, + progressOrSignal?: Progress | AbortSignal, + signal?: AbortSignal, +): string { + const dir = typeof dirOrProgress === "string" ? dirOrProgress : "/default"; + const progress = + typeof dirOrProgress === "function" + ? dirOrProgress + : typeof progressOrSignal === "function" + ? progressOrSignal + : undefined; + const abortSignal = + typeof dirOrProgress === "string" || typeof progressOrSignal === "function" + ? signal + : (progressOrSignal ?? signal); + progress?.(1, 3); + progress?.(3, 3); + return `${dir} aborted=${abortSignal?.aborted ?? "none"}`; +} + +const seen: string[] = []; +const report: Progress = (loaded, total) => seen.push(`${loaded}/${total}`); +const controller = new AbortController(); +console.log(listAll()); +console.log(listAll("/x", report)); +console.log(listAll(report, controller.signal)); +controller.abort(); +console.log(listAll("/y", undefined, controller.signal)); +console.log(listAll(report, controller.signal), seen.join(" ")); + +function same(handler: Progress | AbortSignal | undefined): boolean { + return handler === report; +} +console.log(same(report), same(controller.signal), same(undefined), same((a, b) => void (a + b))); + +function describe(value: unknown | ((n: number) => string)): string { + if (typeof value === "function") return `function -> ${(value as (n: number) => string)(7)}`; + return `value ${JSON.stringify(value)}`; +} +console.log(describe((n: number) => `n=${n}`), describe({ a: 1 }), describe("s")); diff --git a/tests/harness/corpus-costs/dyn-union-function-arms.ts.json b/tests/harness/corpus-costs/dyn-union-function-arms.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-union-function-arms.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From be0e825ac1e2a1f8e2c88ce9bfafbb8d2788635a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:56 -0700 Subject: [PATCH 288/395] Map `any` parameters of interface method and call signatures as checked values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A parameter a TypeScript program types `any` maps to the checked-dynamic slot for function declarations and spelled function types, but not for method or call signatures of interfaces and type literals. So `interface Tool { execute(id: string, params: any): Promise }` was refused (SC2009), along with every value of the interface, whenever no class implementing it gave it a class view. Method and call signatures in program source now take the same rule. pi's ToolDefinition declares `execute(toolCallId: string, params: Static, …)`. Under --node-islands, typebox's types resolve to `any`, so the parameter is `any`. The refusal rejected the AgentSession class through its `_customTools: ToolDefinition[]` field, and CreateAgentSessionRuntimeResult with it. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 12 ++++-- .../method-signature-any-params.ts.json | 6 +++ tests/corpus/method-signature-any-params.ts | 39 +++++++++++++++++++ .../method-signature-any-params.ts.json | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json create mode 100644 tests/corpus/method-signature-any-params.ts create mode 100644 tests/harness/corpus-costs/method-signature-any-params.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..aa3af2b14 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5218,10 +5218,12 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // A parameter a TypeScript program types `any` takes whatever the // caller passes: the checked-dynamic slot, as an `any`-typed binding // already is (dynFallbackType). Declaration files keep their paths. - // Only function declarations, spelled function types and lambda - // parameters whose default is the only source of their type (`(n = - // lookup() ?? 5) => ...` with an `any` lookup): other lambdas and - // methods in object literals keep their existing checked-dynamic story. + // Only function declarations, spelled function types, method and call + // signatures of interfaces and type literals (`execute(params: any): + // Promise`), and lambda parameters whose default is the only source + // of their type (`(n = lookup() ?? 5) => ...` with an `any` lookup): + // other lambdas and methods in object literals keep their existing + // checked-dynamic story. if ( !pt && !rest && @@ -5230,6 +5232,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null decl.parent !== undefined && (ts.isFunctionDeclaration(decl.parent) || ts.isFunctionTypeNode(decl.parent) || + ts.isMethodSignatureDeclaration(decl.parent) || + ts.isCallSignatureDeclaration(decl.parent) || ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && decl.initializer !== undefined && decl.type === undefined && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json new file mode 100644 index 000000000..b3f7ed165 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/method-signature-any-params.ts" + ], + "diags": [] +} diff --git a/tests/corpus/method-signature-any-params.ts b/tests/corpus/method-signature-any-params.ts new file mode 100644 index 000000000..e505ae78c --- /dev/null +++ b/tests/corpus/method-signature-any-params.ts @@ -0,0 +1,39 @@ +// A method or call signature of an interface whose parameter is typed `any` +// (directly, or through an alias that resolves to `any`) takes whatever the +// caller passes, as a function type's `any` parameter already does: the +// interface compiles, and implementations receive the checked-dynamic value. + +type Loose = any; + +interface Tool { + name: string; + execute(id: string, params: any, extra?: Loose): Promise; + describe(value: Loose): string; +} + +interface Formatter { + (value: any): string; +} + +const tools: Tool[] = [ + { + name: "count", + execute: async (id, params) => `${id} has ${Object.keys(params ?? {}).length} keys`, + describe: (value) => `count(${Array.isArray(value) ? "array" : typeof value})`, + }, +]; + +const format: Formatter = (value) => (typeof value === "number" ? value.toFixed(1) : String(value)); + +async function main(): Promise { + const inputs: unknown[] = [{ a: 1, b: [2, 3] }, "text", 42, null, [1, 2]]; + for (const tool of tools) { + for (const input of inputs) { + console.log(tool.name, await tool.execute("call", input), tool.describe(input)); + } + console.log(tool.name, await tool.execute("extra", { k: true }, { more: "yes" })); + } + console.log(format(3), format("x"), format(undefined)); +} + +main().catch((e: unknown) => console.log("failed", e instanceof Error ? e.message : String(e))); diff --git a/tests/harness/corpus-costs/method-signature-any-params.ts.json b/tests/harness/corpus-costs/method-signature-any-params.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/method-signature-any-params.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 3375304945e1b6e548d501b3c9d403f1c5d155fa Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:40:32 -0700 Subject: [PATCH 289/395] Erase lambdas that implement a generic method through their context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lambda with no type parameters of its own, contextually typed by a generic signature (`send: (m) => …` for an interface's `send(m: Message): string`), takes that signature's type parameters: its parameter is `Message`. A generic lambda written with its own type parameters already lowers once, erased, with them bound to `unknown`, but this one tried to map `Message` with `T` unbound and was refused (SC2009). It now lowers the same way, erased, binding the contextual signature's type parameters to `unknown`. Parameters bound in the enclosing scope keep their bindings. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-functions.ts | 42 +++++++++++++++++++ .../generic-member-lambda-impls.ts.json | 6 +++ tests/corpus/generic-member-lambda-impls.ts | 40 ++++++++++++++++++ .../generic-member-lambda-impls.ts.json | 1 + 4 files changed, 89 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json create mode 100644 tests/corpus/generic-member-lambda-impls.ts create mode 100644 tests/harness/corpus-costs/generic-member-lambda-impls.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index bd9b5f55d..7f4c8dfb7 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -167,6 +167,23 @@ function declareFunctionArguments( } } +/** The type parameters a lambda without its own takes from a generic + * contextual signature, unbound in the current scope. */ +function contextualTypeParameterSymbols( + lowerer: Lowerer, + node: ts.ArrowFunction | ts.FunctionExpression, +): ts.Symbol[] { + const signatures = lowerer.checker.getCallSignatures(lowerer.typeOf(node)); + if (signatures.length !== 1) return []; + const symbols: ts.Symbol[] = []; + for (const parameter of signatures[0]!.getTypeParameters()) { + const symbol = parameter.getSymbol(); + if (!symbol || lowerer.typeParamBindings?.has(symbol)) return []; + symbols.push(symbol); + } + return symbols; +} + /** Lifts an arrow function / function expression / nested declaration / * object-literal shorthand method to a module-level function and yields * the `closure` expression creating it. */ @@ -202,6 +219,31 @@ export function lowerLambda( lowerer.typeParamBindings = previous; } } + // A lambda contextually typed by a GENERIC signature (`send: (m) => …` + // for `send(m: Msg): string`) takes that signature's type + // parameters as its own; it lowers once, erased, like a generic lambda. + if ( + (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && + !node.typeParameters?.nodes.length && + !lowerer.erasingLambdas.has(node) + ) { + const contextual = contextualTypeParameterSymbols(lowerer, node); + if (contextual.length > 0) { + const previous = lowerer.typeParamBindings; + const bindings = new Map(previous ?? []); + for (const symbol of contextual) bindings.set(symbol, DYN); + lowerer.typeParamBindings = bindings; + lowerer.erasingLambdas.add(node); + lowerer.erasingDepth++; + try { + return lowerLambda(lowerer, node, checkedReceiver); + } finally { + lowerer.erasingDepth--; + lowerer.erasingLambdas.delete(node); + lowerer.typeParamBindings = previous; + } + } + } const prepared = ts.isArrowFunction(node) || ts.isFunctionExpression(node) ? lowerer.preparedClassFactories.get(node) diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json new file mode 100644 index 000000000..bc87c6ea0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-member-lambda-impls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-member-lambda-impls.ts b/tests/corpus/generic-member-lambda-impls.ts new file mode 100644 index 000000000..fd25bae96 --- /dev/null +++ b/tests/corpus/generic-member-lambda-impls.ts @@ -0,0 +1,40 @@ +// An object literal implements an interface's generic method with a plain +// lambda (`send: (m) => …` for `send(m: Message): string`). The +// lambda takes the contextual signature's type parameters as its own and +// lowers once, erased, like a generic lambda: its parameters hold whatever +// each call passes. + +interface Message { + content: string; + details?: T; +} + +interface Sender { + name: string; + send(message: Message): string; + notify(message: Message): void; + pick(keys: K[], fallback: K): K; +} + +const log: string[] = []; + +function makeSender(name: string): Sender { + return { + name, + send: (message) => `${name} sent ${message.content} (${message.details === undefined ? "no details" : JSON.stringify(message.details)})`, + notify: (message) => { + log.push(`${name} notified ${message.content}`); + }, + pick: (keys, fallback) => keys[0] ?? fallback, + }; +} + +const senders: Sender[] = [makeSender("first"), makeSender("second")]; +for (const sender of senders) { + console.log(sender.send({ content: "hi", details: { n: 3 } })); + console.log(sender.send({ content: "bare" })); + console.log(sender.send({ content: "list", details: [1, 2] })); + sender.notify({ content: "ping", details: true }); + console.log(sender.pick(["a", "b"], "z"), sender.pick([], "z")); +} +console.log(log.join("; ")); diff --git a/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json b/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 171214b2c214e71c6dda67b9401859dbf8b0e967 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:40:32 -0700 Subject: [PATCH 290/395] Discard the result of void generic methods called through class-backed interfaces A generic method called through an interface value that classes implement dispatches by name on the checked-dynamic receiver, and the result converts to the call's instantiated return type. For a void method, that conversion (`unknown` to `void`) was refused (SC1100). The result is now discarded, as JavaScript does. The dispatch helper selected overriding subclasses' erased instances with a ternary, which has no void form (an internal compiler error once the call got that far). A void method now selects them with if statements. Co-Authored-By: Claude Opus 5.5 --- .../lowering/class-dynamic-dispatch.ts | 50 ++++++++++++------- .../src/frontend/lowering/lower-calls.ts | 13 ++++- .../generic-interface-void-calls.ts.json | 6 +++ tests/corpus/generic-interface-void-calls.ts | 45 +++++++++++++++++ .../generic-interface-void-calls.ts.json | 1 + 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json create mode 100644 tests/corpus/generic-interface-void-calls.ts create mode 100644 tests/harness/corpus-costs/generic-interface-void-calls.ts.json diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 2af84f8fb..7992f7fec 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -6793,24 +6793,41 @@ export class ClassDynamicDispatch { type: result, loc, }; + // A void method dispatches its overrides with statements: a ternary + // has no void form. + let voidDispatch: IrStmt[] = [{ kind: "exprStmt", expr: call, loc }]; for (const override of overrides.toReversed()) { const subType: IrType = { kind: "object", className: override.cls.def.name }; + const isOverride: IrExpr = { + kind: "instanceOf", + value: receiver, + className: override.cls.def.name, + type: BOOL, + loc, + }; + const overrideCall: IrExpr = { + kind: "call", + callee: override.callee, + args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args], + type: result, + loc, + }; + if (result.kind === "void") { + voidDispatch = [ + { + kind: "if", + cond: isOverride, + then: [{ kind: "exprStmt", expr: overrideCall, loc }], + else_: voidDispatch, + loc, + }, + ]; + continue; + } call = { kind: "ternary", - cond: { - kind: "instanceOf", - value: receiver, - className: override.cls.def.name, - type: BOOL, - loc, - }, - then: { - kind: "call", - callee: override.callee, - args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args], - type: result, - loc, - }, + cond: isOverride, + then: overrideCall, else_: call, type: result, loc, @@ -6818,10 +6835,7 @@ export class ClassDynamicDispatch { } let body: IrStmt[]; if (result.kind === "void") - body = [ - { kind: "exprStmt", expr: call, loc }, - { kind: "return", value: dynUndefinedExpr(loc), loc }, - ]; + body = [...voidDispatch, { kind: "return", value: dynUndefinedExpr(loc), loc }]; else { const boxed = lowerer.coerceToExpected(call, DYN); if (boxed.type.kind !== "dyn") return fence(); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c8f02562e..e2f7ade90 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10073,7 +10073,18 @@ function lowerObjLitGenericMethodCall( type: DYN, loc, }; - return lowerer.coerceInto(call, invoked, lowerer.irTypeOf(call)); + const resultType = lowerer.irTypeOf(call); + // A void method's runtime result is discarded, as JavaScript does. + if (resultType.kind === "void") { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: invoked, loc }], + result: { kind: "libCall", fn: "console.noop", args: [], type: VOID, loc }, + type: VOID, + loc, + }; + } + return lowerer.coerceInto(call, invoked, resultType); } } if (onInterface) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json new file mode 100644 index 000000000..a5c69b714 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-interface-void-calls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-interface-void-calls.ts b/tests/corpus/generic-interface-void-calls.ts new file mode 100644 index 000000000..d810383f7 --- /dev/null +++ b/tests/corpus/generic-interface-void-calls.ts @@ -0,0 +1,45 @@ +// Calling a void generic method through an interface value that classes +// implement (the receiver is a checked-dynamic object, and the call +// dispatches by name to the class's erased method) discards the result, as +// JavaScript does, instead of converting it to `void`. + +interface Message { + content: string; + details?: T; +} + +interface Channel { + name: string; + publish(message: Message): void; + count(items: T[]): number; +} + +const delivered: string[] = []; + +class LogChannel implements Channel { + name: string; + constructor(name: string) { + this.name = name; + } + publish(message: Message): void { + delivered.push(`${this.name}:${message.content}:${message.details === undefined ? "-" : JSON.stringify(message.details)}`); + } + count(items: T[]): number { + return items.length; + } +} + +class LoudChannel extends LogChannel { + publish(message: Message): void { + super.publish({ content: message.content.toUpperCase(), details: message.details }); + } +} + +const channels: Channel[] = [new LogChannel("quiet"), new LoudChannel("loud")]; +for (const channel of channels) { + channel.publish({ content: "hello", details: [1, 2] }); + channel.publish({ content: "bare" }); + const result = channel.publish({ content: "typed", details: "x" }); + console.log(channel.name, result === undefined, channel.count(["a", "b", "c"])); +} +console.log(delivered.join(" | ")); diff --git a/tests/harness/corpus-costs/generic-interface-void-calls.ts.json b/tests/harness/corpus-costs/generic-interface-void-calls.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/generic-interface-void-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 56e84177467483c007ac218a04bd248c885ccc48 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:07 -0700 Subject: [PATCH 291/395] Name the package declaration and its patch when a declared-type check fails A value that contradicts a type an npm package declares used to throw "expected number at $, got undefined" and nothing else. Such a check now names the declaration and the type patch that admits the value: profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) if that declaration is wrong, patch it in package.json: ... Origins are found for values a package function returns under a written return type, for properties of records typed by a package-declared type (record projections into such a type are keyed by it), and for declaration-boundary casts, whose origin the projection records from the declaration-world checker. Generic declarations get none: their types come from the program's own instantiation. Checked conversions carry dynCheck.origin, an index into IrModule.declOrigins; the site's existing cold exception block calls scr_type_origin_note (new runtime unit behind the typeOrigins feature), which appends the note to the pending TypeError and derives the selector suffix and the admitted kind from the failure. Union extractions get the note per arm at compile time. The hot path is unchanged. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 8 + .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 5 + .../compiler/src/backend/llvm/expr-async.ts | 19 +- .../src/backend/native-link-info-core.ts | 1 + .../compiler/src/backend/native/contracts.ts | 1 + .../compiler/src/backend/native/executable.ts | 1 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/executable/executable-cache.ts | 1 + .../compiler/src/frontend/decl-projection.ts | 61 +++ .../src/frontend/lowering/coercions/state.ts | 9 +- .../frontend/lowering/coercions/structural.ts | 31 +- .../src/frontend/lowering/coercions/unions.ts | 36 +- .../src/frontend/lowering/decl-origin.ts | 392 ++++++++++++++++++ .../lowering/expressions/narrowing.ts | 54 ++- .../compiler/src/frontend/lowering/lowerer.ts | 100 ++++- .../src/frontend/lowering/union-narrow.ts | 17 +- packages/compiler/src/frontend/program.ts | 7 +- packages/compiler/src/index.ts | 1 + .../src/ir/function-references.test.ts | 1 + packages/compiler/src/ir/ir.ts | 10 + packages/compiler/src/ir/runtime-features.ts | 4 + packages/compiler/src/ir/validate.ts | 13 + .../compiler/src/native/sanitizer.test.ts | 1 + .../runtime-pack-matrix.mjs | 1 + packages/runtime/src/scr_type_origin.c | 154 +++++++ tests/fixtures/type-patches/unpatched/main.ts | 13 + .../type-patches/unpatched/package.json | 1 + tests/harness/type-patches.test.ts | 32 +- 29 files changed, 961 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/decl-origin.ts create mode 100644 packages/runtime/src/scr_type_origin.c create mode 100644 tests/fixtures/type-patches/unpatched/main.ts create mode 100644 tests/fixtures/type-patches/unpatched/package.json diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 9b660c76f..58b0a0a1d 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -145,6 +145,14 @@ A JavaScript package can contain operations scriptc can't compile, often in func scriptc trusts declared types: a package's `.d.ts` files, its `@types/` package, and the JSDoc types in its JavaScript. A value that contradicts its declared type throws a `TypeError` instead of flowing on, so a package whose declarations are wrong can stop a program that runs under Node.js. A common case is a member declared `string` or `number` that is sometimes `undefined`. +When the type comes from a package's declarations, the error names the declaration and the patch that would admit the value: + +```text +Uncaught TypeError: expected string at $, got undefined + profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) + if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } +``` + Fix such a declaration in your project with a type patch in `package.json`: ```json:package.json diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 2a6c6959e..b2deb374d 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -31,6 +31,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), ...(features.declAdopt ? { declAdopt: true } : {}), + ...(features.typeOrigins ? { typeOrigins: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index f1f1fa7d6..f3b2f44fe 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -3700,6 +3700,9 @@ export class LlEmitter { passThrough?: { name: string; type: IrType }, poll = false, guard?: string, + /** Runs at the start of the cold unwind block, before the unwind (a + * package-declared check's origin note). */ + onUnwind?: () => void, ): void { const through = passThrough !== undefined && @@ -3724,6 +3727,7 @@ export class LlEmitter { const lk = B.newLabel("exc.k"); B.condBr(p, lu, lk); B.startBlock(lu); + onUnwind?.(); this.emitUnwind(through); B.startBlock(lk); if (skip !== undefined) { @@ -3800,6 +3804,7 @@ export class LlEmitter { B.condBr(cold, lu, lk); } B.startBlock(lu); + onUnwind?.(); this.emitUnwind(through); B.startBlock(lk); if (skip !== undefined) { diff --git a/packages/compiler/src/backend/llvm/expr-async.ts b/packages/compiler/src/backend/llvm/expr-async.ts index ee9ff2983..28a4cf57f 100644 --- a/packages/compiler/src/backend/llvm/expr-async.ts +++ b/packages/compiler/src/backend/llvm/expr-async.ts @@ -372,6 +372,16 @@ export function emitSerializationExpr( // the dummy harmlessly. // `JSON.parse(text) as T` fuses into one schema-directed parse that // falls back to exactly this route whenever the fast path declines. + // A package-declared target: the cold path names the declaration in + // the pending TypeError (scr_type_origin.c). + const origin = e.origin === undefined ? undefined : host.mod.declOrigins?.[e.origin]; + const note = + origin === undefined + ? undefined + : (): void => { + host.declare("declare void @scr_type_origin_note(ptr)"); + B.line(`call void @scr_type_origin_note(ptr ${host.cstr(origin)})`); + }; const parsed = e.value; if (!e.preserveRefs && parsed.kind === "libCall" && parsed.fn === "json.parse") { const fused = host.dyn.jsonParseHelper(e.type); @@ -380,7 +390,7 @@ export function emitSerializationExpr( const r = B.tmp(); B.line(`${r} = call ptr @${fused}(ptr ${text.name})`); const fusedOut = host.own({ name: r, type: e.type }); - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); return fusedOut; } } @@ -394,14 +404,14 @@ export function emitSerializationExpr( } const dynV = emitBorrowedInput(host, e.value); const helper = host.dyn.dynCheckHelper(e.type, e.preserveRefs); - if (isDynTypedRefType(e.type)) return emitClassCheck(host, e.type, dynV.name, helper); + if (isDynTypedRefType(e.type)) return emitClassCheck(host, e.type, dynV.name, helper, note); const ty = host.llType(e.type); const t = B.tmp(); B.line( `${t} = call ${ty === "i1" ? "zeroext i1" : ty} @${helper}(ptr ${dynV.name}, ptr null)`, ); const out = host.own({ name: t, type: e.type }); - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); return out; } default: { @@ -420,6 +430,7 @@ function emitClassCheck( type: IrType & { kind: "object" }, value: string, helper: string, + note?: () => void, ): LlValue { const B = host.B; const slot = B.slot(); @@ -460,7 +471,7 @@ function emitClassCheck( B.line(`${result} = call ptr @${helper}(ptr ${value}, ptr null)`); B.line(`store ptr ${result}, ptr ${slot}`); // A throwing check returns null, so the unwind has nothing to release. - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); B.br(done); B.startBlock(done); const out = B.tmp(); diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index f250a2ac6..de92ca584 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -55,6 +55,7 @@ export interface NativeLinkFeatures { * Absent (false) for programs without them. */ handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: boolean; /** for await over checked-dynamic values: scr_dyn_for_await.c. Absent * (false) for programs without it. */ dynForAwait?: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index e3f97710f..9aeff6138 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -126,6 +126,7 @@ export interface CcOptions { /** Own properties JavaScript adds to children: compiles scr_dyn_handle_props.c. */ handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: boolean; /** for await over checked-dynamic values: compiles scr_dyn_for_await.c. */ dynForAwait?: boolean; /** events.once with options (events.onceSignal): compiles diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 7f989a5e4..2fba9ca4b 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -300,6 +300,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.handleProps === true, "scr_dyn_handle_props.c"), // The declaration-typed boundary's adoption. unit((u) => u.opts.declAdopt === true, "scr_decl_adopt.c"), + unit((u) => u.opts.typeOrigins === true, "scr_type_origin.c"), // for await over checked-dynamic values. unit((u) => u.opts.dynForAwait === true, "scr_dyn_for_await.c"), // for await over checked-dynamic values that may hold native streams. diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 22bba85d2..8457b7295 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -87,6 +87,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { streamDyn: boolean; handleProps: boolean; declAdopt: boolean; + typeOrigins: boolean; dynForAwait: boolean; eventsOnceSignal: boolean; dynBuffer: boolean; @@ -287,6 +288,7 @@ export function effectiveRuntimeFeatures( streamDyn: features.streamDyn === true, handleProps: features.handleProps === true, declAdopt: features.declAdopt === true, + typeOrigins: features.typeOrigins === true, dynForAwait: features.dynForAwait === true, eventsOnceSignal: features.eventsOnceSignal === true, dynBuffer: features.dynBuffer === true, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index f26c4c89a..498ee7e19 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -74,6 +74,7 @@ export interface EarlyExecutableNativeFeatures { streamDyn?: boolean; handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: boolean; dynForAwait?: boolean; eventsOnceSignal?: boolean; dynBuffer?: boolean; diff --git a/packages/compiler/src/frontend/decl-projection.ts b/packages/compiler/src/frontend/decl-projection.ts index a999559c2..eebe1f773 100644 --- a/packages/compiler/src/frontend/decl-projection.ts +++ b/packages/compiler/src/frontend/decl-projection.ts @@ -27,6 +27,59 @@ * scriptc-perf/results/design-decl-boundary/DESIGN.md. */ import * as ts from "./ts7/adapter.js"; +import { declarationSelector, originAt } from "./lowering/decl-origin.js"; + +/** Per user file, the origin (lowering/decl-origin.ts) of each boundary + * cast in source order: the package declaration the cast's type comes + * from, so a failing cast names it. Set by planDeclProjection, reset per + * program load. */ +const boundaryOrigins = new Map(); + +export function resetDeclBoundaryOrigins(): void { + boundaryOrigins.clear(); +} + +/** The origin of the `index`-th boundary cast of a file, or null. */ +export function declBoundaryOrigin(fileName: string, index: number): string | null { + return boundaryOrigins.get(fileName)?.[index] ?? null; +} + +/** The package declaration a projected expression's type comes from, in + * the declaration world. */ +function castOrigin(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): string | null { + let inner = node as ts.Expression; + while ( + ts.isParenthesizedExpression(inner) || + ts.isAwaitExpression(inner) || + ts.isNonNullExpression(inner) + ) + inner = inner.expression; + let decl: ts.Node | undefined; + let suffix = ""; + try { + if (ts.isCallExpression(inner) || ts.isNewExpression(inner)) { + decl = checker.getResolvedSignature(inner)?.declaration; + suffix = "()"; + } else { + const name = ts.isPropertyAccessExpression(inner) + ? inner.name + : ts.isIdentifier(inner) + ? inner + : null; + if (name === null) return null; + let symbol = checker.getSymbolAtLocation(name); + if (symbol !== undefined && symbol.flags & ts.SymbolFlags.Alias) + symbol = checker.getAliasedSymbol(symbol); + decl = symbol?.declarations[0]; + } + if (decl === undefined) return null; + const selector = declarationSelector(decl); + if (selector === null) return null; + return originAt(decl, `${selector}${suffix}`, checker.typeToString(type)); + } catch { + return null; + } +} /** Alias names of projected types start with this prefix; the lowering * recognizes boundary casts and parameters by it. */ @@ -573,6 +626,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const declSf = input.declProgram.getSourceFile(file); if (jsSf === undefined || declSf === undefined || jsSf.text !== declSf.text) continue; const fileEdits: Edit[] = []; + const fileOrigins: (string | null)[] = []; /** Candidate expressions (by js node) with their declared type, named * only for the casts that survive suppression. */ const exprCandidates = new Map(); @@ -946,6 +1000,12 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const candidate = exprCandidates.get(e)!; const alias = candidate === "any" ? "any" : printer.project(candidate); if (alias === null) continue; + // Only casts to a projected alias are boundary casts the lowering + // counts (an `as any` cast is not one), so only they take a slot. + if (candidate !== "any") { + const twin = twins.get(e); + fileOrigins.push(twin === undefined ? null : castOrigin(declChecker, twin, candidate)); + } const start = ts.tokenStartOfNode(e); const stmt = e.parent; let prefix = "("; @@ -980,6 +1040,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | if (fileEdits.length === 0) continue; edits.set(file, fileEdits); + if (fileOrigins.some((o) => o !== null)) boundaryOrigins.set(file, fileOrigins); } if (edits.size === 0) return null; diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index 76e5ac434..9fa815081 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -16,7 +16,14 @@ export class CoercionState { // The extracted arm of each checked (non-defaulting) extraction helper. readonly checkedNarrowArms = new Map< string, - { unionId: string; tag: number; target: IrType; widens?: true; message?: string } + { + unionId: string; + tag: number; + target: IrType; + widens?: true; + message?: string; + origin?: string; + } >(); readonly retags = new Map(); readonly narrows = new Map(); diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 93ae1c231..76c6c9c3b 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -1,3 +1,4 @@ +import { fieldOrigin } from "../decl-origin.js"; import { keyedHelperName } from "../stable-names.js"; import { constructedValue } from "../constructor-returns.js"; import { countedFor, dynUndefinedExpr, varRef } from "../../../ir/build.js"; @@ -394,7 +395,13 @@ export function recordWidthHelper( // orphan one). const plan = lowerer.recordWidthPlan(fromId, toId); if (!plan) return null; - const key = `rec:${fromId}:${toId}`; + // Package code converting into a package-declared record type (an + // object literal returned under `@returns {HelpContext}`): its field + // extractions name the declared fields (decl-origin.ts), so the helper + // is keyed by that type too. Every other projection is shared as before. + const declared = lowerer.coercionRecordOrigin(); + const key = + declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.name}`; const existing = lowerer.valueHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, "%rec.width", key); @@ -402,7 +409,21 @@ export function recordWidthHelper( // (self-referential shapes) resolves to this helper itself. lowerer.valueHelpers.set(key, name); const fromT: IrType = { kind: "record", shapeId: fromId }; - lowerer.liftedFns.push(buildRecordProjection(lowerer, name, fromT, to, plan, loc)); + const outerNode = lowerer.coercionOriginNode; + const outerField = lowerer.coercionFieldOrigin; + // Nested conversions see only their own field's origin. + lowerer.coercionOriginNode = null; + lowerer.coercionFieldOrigin = null; + try { + lowerer.liftedFns.push( + buildRecordProjection(lowerer, name, fromT, to, plan, loc, (field) => + declared === null ? null : fieldOrigin(lowerer.checker, declared, field), + ), + ); + } finally { + lowerer.coercionOriginNode = outerNode; + lowerer.coercionFieldOrigin = outerField; + } return name; } @@ -799,6 +820,7 @@ function buildRecordProjection( target: IrRecordShape, plan: Map, loc: SrcLoc, + originOf: (field: string) => string | null = () => null, ): IrFunction { const parameter = source.kind === "record" ? "r" : "o"; const localId = parameter + ".0"; @@ -855,7 +877,12 @@ function buildRecordProjection( type: conversion.src, loc, }; + // Only the field's own extraction (a plain narrow) carries the + // declared field's origin; nested conversions are shared. + lowerer.coercionFieldOrigin = + conversion.lift.how === "narrow" ? originOf(field.name) : null; const lifted = lowerer.applyWidthLift(conversion.lift, read, field.type, loc); + lowerer.coercionFieldOrigin = null; return { name: field.name, value: diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 6bd72e989..316b1ecad 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -1,3 +1,4 @@ +import { renderOriginNote } from "../decl-origin.js"; import { keyedHelperName } from "../stable-names.js"; import { buildUnionNarrow, unionNarrowFailureMessage } from "../union-narrow.js"; import { isSafeToRepeat } from "../expressions/evaluation-safety.js"; @@ -408,7 +409,24 @@ export function narrowedArmHelper( if (!from || isUnitType(target)) return null; const tag = lowerer.armTag(fromId, target); if (tag < 0) return null; - const key = message === undefined ? `${fromId}:${tag}` : `${fromId}:${tag}:${message}`; + // A slot in package code typed by a package declaration: the TypeError + // names the declaration (decl-origin.ts). Other extractions keep their + // shared helper. + const originNode = + message === undefined && lowerer.coercionOriginUnion === fromId + ? lowerer.coercionOriginNode + : null; + const origin = + message !== undefined + ? null + : (lowerer.coercionFieldOrigin ?? + (originNode === null ? null : lowerer.slotOriginOf(originNode))); + const key = + message !== undefined + ? `${fromId}:${tag}:${message}` + : origin !== null + ? `${fromId}:${tag}:origin:${origin}` + : `${fromId}:${tag}`; const existing = lowerer.coercions.narrows.get(key); if (existing) { lowerer.coercions.checkedNarrows.add(existing); @@ -425,6 +443,8 @@ export function narrowedArmHelper( (type) => lowerer.fmt(type), null, message ?? null, + null, + origin, ); if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); lowerer.coercions.checkedNarrowArms.set(name, { @@ -432,6 +452,7 @@ export function narrowedArmHelper( tag, target: from.arms[tag]!, ...(message === undefined ? {} : { message }), + ...(origin === null ? {} : { origin }), }); lowerer.liftedFns.push(fn); return name; @@ -516,8 +537,19 @@ export function checkedNarrowCall( let failure: IrExpr | null = null; for (let tag = from.arms.length - 1; tag >= 0; tag--) { if (tag === arm.tag) continue; + const failing = from.arms[tag]!; const message = - arm.message ?? unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); + (arm.message ?? unionNarrowFailureMessage(failing, (t) => lowerer.fmt(t))) + + (arm.origin === undefined + ? "" + : renderOriginNote( + arm.origin, + isUnitType(failing) + ? failing.kind === "undefinedT" + ? "undefined" + : "null" + : lowerer.fmt(failing), + )); const thrown = nodeThrowExpr(1, "", message, type, loc); failure = failure === null diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts new file mode 100644 index 000000000..60510ecec --- /dev/null +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -0,0 +1,392 @@ +/* Origins of package-declared types at checked conversions. + * + * scriptc enforces declared types: a value that contradicts the type an + * npm package declares (its .d.ts, @types package or JSDoc) throws a + * TypeError where Node runs on. When the declaration is what is wrong, the + * fix is a type patch (package.json scriptc.typePatches), so the error + * names the declaration and the patch that would admit the value: + * + * TypeError: expected number at $, got undefined + * termctx@1.2.0 declares Terminal.columns() as number (index.js:14) + * if that declaration is wrong, patch it in package.json: "scriptc": + * { "typePatches": { "termctx@^1.2.0": { "declarations": + * { "Terminal.columns()": "| undefined" } } } } + * + * The origin is found for three kinds of slots: a value a package function + * returns under a written return type, a property of an object literal + * typed by a package-declared type, and a declaration-boundary cast (a + * value user code receives from a package; decl-projection.ts records + * those with originAt). Generic declarations get none: their types come + * from how the program instantiates them. Checks in + * code that names no package declaration carry no origin, so programs + * without npm packages lower exactly as before. + * + * The origin string's fields (separated by \x1f) are read by the runtime + * (scr_type_origin.c) for checked conversions, whose path and actual kind + * are only known when they fail, and rendered here for union extractions, + * whose failing arm is known statically. */ + +import type { IrExpr, IrModule } from "../../ir/ir.js"; +import { everyModuleNode } from "../../ir/traverse.js"; +import { trackedReadFile } from "../input-tracker.js"; +import * as ts from "../ts7/adapter.js"; + +export const ORIGIN_SEPARATOR = "\x1f"; + +interface PackageFile { + pkg: string; + version: string; + /** The file relative to the package root. */ + rel: string; +} + +const versions = new Map(); + +function packageFileOf(fileName: string): PackageFile | null { + const path = fileName.split("\\").join("/"); + const marker = "/node_modules/"; + const at = path.lastIndexOf(marker); + if (at < 0) return null; + const parts = path.slice(at + marker.length).split("/"); + const nameParts = parts[0]!.startsWith("@") ? 2 : 1; + if (parts.length <= nameParts) return null; + let pkg = parts.slice(0, nameParts).join("/"); + const root = path.slice(0, at + marker.length) + pkg; + let versionRoot = root; + if (pkg.startsWith("@types/")) { + // A twin describes the runtime package installed beside it. + const bare = pkg.slice("@types/".length); + const i = bare.indexOf("__"); + pkg = i > 0 ? `@${bare.slice(0, i)}/${bare.slice(i + 2)}` : bare; + versionRoot = path.slice(0, at + marker.length) + pkg; + } + let version = versions.get(versionRoot); + if (version === undefined) { + version = ""; + try { + const parsed = JSON.parse(trackedReadFile(`${versionRoot}/package.json`) ?? "{}") as { + version?: unknown; + }; + if (typeof parsed.version === "string") version = parsed.version; + } catch { + version = ""; + } + versions.set(versionRoot, version); + } + return { pkg, version, rel: parts.slice(nameParts).join("/") }; +} + +/** True for a node in an installed package's file. */ +export function inPackageFile(node: ts.Node): boolean { + return packageFileOf(ts.getSourceFileOfNode(node).fileName) !== null; +} + +function lineOf(node: ts.Node): number { + const sf = ts.getSourceFileOfNode(node); + return ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)).line + 1; +} + +function encode(file: PackageFile, selector: string, where: string, declared: string): string { + const range = file.version === "" ? "*" : `^${file.version}`; + return [file.pkg, file.version, range, selector, where, declared.replace(/\s+/g, " ")].join( + ORIGIN_SEPARATOR, + ); +} + +function nameOf(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isPrivateIdentifier(name)) + return name.text; + return null; +} + +/** `module.exports` → "export="; `exports.x` / `module.exports.x` → "x". */ +function exportTarget(left: ts.Node): string | null { + if (!ts.isPropertyAccessExpression(left)) return null; + const obj = left.expression; + const prop = nameOf(left.name); + if (prop === null) return null; + if (ts.isIdentifier(obj) && obj.text === "module" && prop === "exports") return "export="; + if (ts.isIdentifier(obj) && obj.text === "exports") return prop; + if ( + ts.isPropertyAccessExpression(obj) && + ts.isIdentifier(obj.expression) && + obj.expression.text === "module" && + nameOf(obj.name) === "exports" + ) + return prop; + return null; +} + +/** The selector naming a function-like declaration (without `()`), or + * null when it has no name a selector can reach. */ +function functionSelector(fn: ts.Node): string | null { + if (ts.isFunctionDeclaration(fn)) return nameOf(fn.name); + if (ts.isMethodDeclaration(fn) || ts.isGetAccessor(fn)) { + const method = nameOf(fn.name); + const holder = fn.parent; + if (method === null || holder === undefined) return null; + if (ts.isClassDeclaration(holder) || ts.isClassExpression(holder)) { + const cls = nameOf(holder.name); + return cls === null ? null : `${cls}.${method}`; + } + if (ts.isObjectLiteralExpression(holder)) { + const owner = valueSelector(holder); + return owner === null ? null : `${owner}.${method}`; + } + return null; + } + if (ts.isFunctionExpression(fn) || ts.isArrowFunction(fn)) return valueSelector(fn); + return null; +} + +/** The selector of the declaration an expression is the value of. */ +function valueSelector(value: ts.Node): string | null { + let e = value; + while (e.parent !== undefined && ts.isParenthesizedExpression(e.parent)) e = e.parent; + const p = e.parent; + if (p === undefined) return null; + if (ts.isVariableDeclaration(p) && p.initializer === e) return nameOf(p.name); + if ( + ts.isBinaryExpression(p) && + p.right === e && + p.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + return exportTarget(p.left); + if (ts.isPropertyAssignment(p) && p.initializer === e && p.parent !== undefined) { + const name = nameOf(p.name); + const owner = ts.isObjectLiteralExpression(p.parent) ? valueSelector(p.parent) : null; + return name === null || owner === null ? null : `${owner}.${name}`; + } + return null; +} + +/** True when a function-like declaration writes its return type: a type + * annotation, or a JSDoc @returns tag. */ +function writesReturnType(fn: ts.Node): boolean { + if ((fn as ts.FunctionDeclaration).type !== undefined) return true; + const sf = ts.getSourceFileOfNode(fn); + let owner: ts.Node = fn; + // The JSDoc of `exports.f = function`, `const f = () =>` sits on the + // statement. + while ( + owner.parent !== undefined && + !ts.isSourceFile(owner.parent) && + !ts.isBlock(owner.parent) && + !ts.isClassDeclaration(owner.parent) && + !ts.isClassExpression(owner.parent) && + !ts.isObjectLiteralExpression(owner.parent) + ) + owner = owner.parent; + const leading = sf.text.slice(owner.pos, ts.tokenStartOfNode(owner)); + const doc = leading.lastIndexOf("/**"); + return doc >= 0 && /@returns?\s*\{/.test(leading.slice(doc)); +} + +function enclosingFunction(node: ts.Node): ts.Node | null { + for (let p = node.parent; p !== undefined; p = p.parent) { + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isArrowFunction(p) || + ts.isMethodDeclaration(p) || + ts.isGetAccessor(p) + ) + return p; + if (ts.isSourceFile(p) || ts.isClassDeclaration(p)) return null; + } + return null; +} + +function typeText(checker: ts.TypeChecker, type: ts.Type | undefined): string { + if (type === undefined) return ""; + try { + return checker.typeToString(type); + } catch { + return ""; + } +} + +/** The origin of the slot a value expression in a package file flows + * into, or null when no package declaration types that slot. */ +export function slotOrigin(checker: ts.TypeChecker, node: ts.Node): string | null { + let value = node; + while (value.parent !== undefined && ts.isParenthesizedExpression(value.parent)) + value = value.parent; + const parent = value.parent; + if (parent === undefined) return null; + // A property of an object literal typed by a declared type. + if ( + (ts.isPropertyAssignment(parent) && parent.initializer === value) || + ts.isShorthandPropertyAssignment(value) || + (ts.isShorthandPropertyAssignment(parent) && parent.name === value) + ) { + const property = ts.isShorthandPropertyAssignment(value) ? value : parent; + const literal = property.parent; + const name = nameOf((property as ts.PropertyAssignment).name); + if (literal === undefined || !ts.isObjectLiteralExpression(literal) || name === null) + return null; + const contextual = checker.getContextualType(literal); + if (contextual === undefined) return null; + const typeSymbol = contextual.getAliasSymbol() ?? contextual.getSymbol(); + const typeName = typeSymbol === undefined ? "" : ts.symbolName(typeSymbol); + if (!/^[A-Za-z_$][\w$]*$/.test(typeName)) return null; + const symbol = checker.getPropertyOfType(contextual, name); + const decl = symbol?.declarations[0]; + if (symbol === undefined || decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode( + file, + `${typeName}.${name}`, + `${file.rel}:${lineOf(decl)}`, + typeText(checker, checker.getTypeOfSymbol(symbol)), + ); + } + // A value a package function returns under its written return type. + let fn: ts.Node | null = null; + if (ts.isReturnStatement(parent) && parent.expression === value) fn = enclosingFunction(parent); + else if (ts.isArrowFunction(parent) && parent.body === value) fn = parent; + if (fn === null || !writesReturnType(fn)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(fn).fileName); + const selector = functionSelector(fn); + if (file === null || selector === null) return null; + let declared = ""; + try { + const signature = checker.getSignatureFromDeclaration(fn as ts.SignatureDeclaration); + declared = + signature === undefined ? "" : typeText(checker, checker.getReturnTypeOfSignature(signature)); + } catch { + declared = ""; + } + return encode(file, `${selector}()`, `${file.rel}:${lineOf(fn)}`, declared); +} + +/** A package-declared record type a value is converted to (the + * contextual type of a value in package code, such as an object literal + * returned under `@returns {HelpContext}`), or null. */ +export interface RecordOrigin { + type: ts.Type; + name: string; +} + +export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrigin | null { + const contextual = checker.getContextualType(node); + if (contextual === undefined) return null; + const symbol = contextual.getAliasSymbol() ?? contextual.getSymbol(); + const name = symbol === undefined ? "" : ts.symbolName(symbol); + if (!/^[A-Za-z_$][\w$]*$/.test(name)) return null; + const decl = symbol?.declarations[0]; + if ( + decl === undefined || + genericDeclaration(decl) || + packageFileOf(ts.getSourceFileOfNode(decl).fileName) === null + ) + return null; + return { type: contextual, name }; +} + +/** The origin of one field of a package-declared record type. */ +export function fieldOrigin( + checker: ts.TypeChecker, + record: RecordOrigin, + field: string, +): string | null { + const symbol = checker.getPropertyOfType(record.type, field); + const decl = symbol?.declarations[0]; + if (symbol === undefined || decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode( + file, + `${record.name}.${field}`, + `${file.rel}:${lineOf(decl)}`, + typeText(checker, checker.getTypeOfSymbol(symbol)), + ); +} + +/** The origin of a declaration in a package file named by `selector`. */ +export function originAt(decl: ts.Node, selector: string, declared: string): string | null { + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode(file, selector, `${file.rel}:${lineOf(decl)}`, declared); +} + +/** True when a declaration or the class, interface or type alias holding + * it has type parameters: its declared type then comes from how the + * program instantiates it (a lying `z.custom()`), not from the package. */ +function genericDeclaration(decl: ts.Node): boolean { + for (let n: ts.Node | undefined = decl; n !== undefined && !ts.isSourceFile(n); n = n.parent) { + const params = (n as ts.FunctionDeclaration).typeParameters; + if (params !== undefined && params.nodes.length > 0) return true; + } + return false; +} + +/** The selector of a declaration in a declaration file: a function, a + * member of a class, interface or named object type, or null (also for + * generic declarations, whose types the program supplies). */ +export function declarationSelector(decl: ts.Node): string | null { + if (genericDeclaration(decl)) return null; + const own = nameOf((decl as ts.FunctionDeclaration).name); + if (own === null) return null; + if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl)) return own; + let holder = decl.parent; + if (holder !== undefined && ts.isTypeLiteralNode(holder)) holder = holder.parent; + if ( + holder !== undefined && + (ts.isClassDeclaration(holder) || + ts.isInterfaceDeclaration(holder) || + ts.isTypeAliasDeclaration(holder)) + ) { + const owner = nameOf(holder.name); + return owner === null ? null : `${owner}.${own}`; + } + return null; +} + +/** The note a union extraction's TypeError carries for a failing `arm` + * (the same text the runtime renders for checked conversions). */ +export function renderOriginNote(origin: string, arm: string): string { + const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + origin.split(ORIGIN_SEPARATOR); + return ( + `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + + `${declared === "" ? "" : ` as ${declared}`} (${where})` + + `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + ); +} + +/** Keeps only the origins the module's checks still reference (a coercion + * whose check was later replaced leaves its origin behind), renumbered in + * first-use order; drops the table when none is left, so the runtime unit + * is not linked for nothing. */ +export function compactDeclOrigins(mod: IrModule): void { + const table = mod.declOrigins ?? []; + const remap = new Map(); + const used: string[] = []; + // A node the IR shares at several places is renumbered once. + const checks = new Set(); + everyModuleNode(mod, { + expr: (e) => { + if (e.kind === "dynCheck" && e.origin !== undefined) { + // An exact capsule check never throws: nothing to name. + if (e.exact === true) { + delete e.origin; + return true; + } + checks.add(e); + if (!remap.has(e.origin)) { + remap.set(e.origin, used.length); + used.push(table[e.origin]!); + } + } + return true; + }, + stmt: () => true, + type: () => true, + }); + for (const check of checks) check.origin = remap.get(check.origin!)!; + if (used.length === 0) delete mod.declOrigins; + else mod.declOrigins = used; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f0559672..5c1b276f7 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1,4 +1,5 @@ /* Type narrowing of lowered values, conditional and as-expressions, and runtime-optional receiver reads. */ +import { declBoundaryOrigin } from "../../decl-projection.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, dynFallbackType } from "../lowerer.js"; import { @@ -1531,7 +1532,7 @@ export function lowerAsExpression( // The declaration-typed boundary (decl-projection.ts): an identity- // preserving checked conversion of a value package code produced. if (isDeclBoundaryTypeNode(expr.type)) { - const adopted = declBoundaryCast(lowerer, inner, target, locOf(expr)); + const adopted = declBoundaryCast(lowerer, inner, target, locOf(expr), expr); if (adopted) return adopted; } // An all-`unknown`-fields record target (`err as { code?: unknown }` @@ -1639,11 +1640,44 @@ function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): * becomes a live capsule of the value built from it * (scr_dyn_decl_adopt), so identity and mutation visibility hold on both * sides. Scalar targets are ordinary checked casts. */ +const boundaryCastOrder = new WeakMap>(); + +/** The origin the projection recorded for a boundary cast (the package + * declaration its type comes from, decl-projection.ts), found by the + * cast's position among the file's boundary casts in source order. */ +function recordedBoundaryOrigin(cast: ts.Node): string | null { + const sf = ts.getSourceFileOfNode(cast); + let order = boundaryCastOrder.get(sf); + if (order === undefined) { + const found = new Map(); + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) && isDeclBoundaryTypeNode(node.type)) found.set(node, found.size); + ts.forEachChild( + node, + (child) => { + visit(child); + return undefined; + }, + (list) => { + for (const child of list.nodes) visit(child); + return undefined; + }, + ); + }; + visit(sf); + order = found; + boundaryCastOrder.set(sf, order); + } + const index = order.get(cast); + return index === undefined ? null : declBoundaryOrigin(sf.fileName, index); +} + function declBoundaryCast( lowerer: Lowerer, inner: IrExpr, target: IrType, loc: SrcLoc, + cast: ts.Node, ): IrExpr | null { if ( !canDynCheckTo( @@ -1654,8 +1688,23 @@ function declBoundaryCast( !(target.kind === "union" && lowerer.dynConvertible(target)) ) return null; + // The package call or read the value comes from (decl-origin.ts): a + // failing check names it and the patch admitting the value. + let originText: string | null = null; + try { + originText = recordedBoundaryOrigin(cast); + } catch { + originText = null; + } + const originId = originText === null ? undefined : lowerer.declOriginIndex(originText); if (!holdsReference(lowerer, target)) { - return { kind: "dynCheck", value: inner, type: target, loc }; + return { + kind: "dynCheck", + value: inner, + type: target, + loc, + ...(originId === undefined ? {} : { origin: originId }), + }; } const source = lowerer.declareHiddenLocal("%boundarySource", DYN); const value = lowerer.declareHiddenLocal("%boundaryValue", target); @@ -1672,6 +1721,7 @@ function declBoundaryCast( adopt: true, type: target, loc, + ...(originId === undefined ? {} : { origin: originId }), }, loc, }, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index feca34427..5b5ae3471 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,10 @@ +import { + compactDeclOrigins, + inPackageFile, + recordOrigin, + slotOrigin, + type RecordOrigin, +} from "./decl-origin.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js"; @@ -3761,6 +3768,73 @@ export class Lowerer { * (decl-projection.ts): user-file record and array values crossing into * dynamic slots (package parameters) box as live capsules, so a value * that comes back through a boundary cast keeps its identity. */ + /** Origins of package-declared types checked in this program + * (decl-origin.ts), by IrModule.declOrigins index. */ + readonly declOrigins: string[] = []; + private readonly declOriginIds = new Map(); + private readonly packageFileMemo = new Map(); + private readonly slotOriginMemo = new Map(); + /** The value whose slot coercion is being lowered, while that value is + * in a package file: union extractions built for it carry the slot's + * origin in their TypeError. */ + coercionOriginNode: ts.Node | null = null; + /** The union type of that value: only its own extraction is the slot's. */ + coercionOriginUnion: string | null = null; + /** While a record projection into a package-declared type builds, the + * origin of the field being converted. */ + coercionFieldOrigin: string | null = null; + private readonly recordOriginMemo = new Map(); + + /** The package-declared record type the current coercion converts to. */ + coercionRecordOrigin(): RecordOrigin | null { + const node = this.coercionOriginNode; + if (node === null) return null; + let origin = this.recordOriginMemo.get(node); + if (origin === undefined) { + try { + origin = recordOrigin(this.checker, node); + } catch { + origin = null; + } + this.recordOriginMemo.set(node, origin); + } + return origin; + } + + declOriginIndex(origin: string): number { + let id = this.declOriginIds.get(origin); + if (id === undefined) { + id = this.declOrigins.length; + this.declOrigins.push(origin); + this.declOriginIds.set(origin, id); + } + return id; + } + + /** The origin of the package-declared slot a value flows into. */ + slotOriginOf(node: ts.Node): string | null { + let origin = this.slotOriginMemo.get(node); + if (origin === undefined) { + try { + origin = slotOrigin(this.checker, node); + } catch { + origin = null; + } + this.slotOriginMemo.set(node, origin); + } + return origin; + } + + private inPackageFileOf(node: ts.Node): boolean { + const sf = ts.getSourceFileOfNode(node); + let inside = this.packageFileMemo.get(sf); + if (inside === undefined) { + inside = inPackageFile(node); + this.packageFileMemo.set(sf, inside); + } + return inside; + } + private declBoundaryMemo: boolean | null = null; get declBoundary(): boolean { this.declBoundaryMemo ??= this.program.getSourceFileNames().some(isDeclBoundaryFile); @@ -8327,6 +8401,7 @@ export class Lowerer { ...(this.foreignElementKeys.size > 0 ? { foreignElementKeys: [...this.foreignElementKeys].sort() } : {}), + ...(this.declOrigins.length > 0 ? { declOrigins: [...this.declOrigins] } : {}), ...(this.threadSlotKeys.size > 0 ? { threadSlotKeys: [...this.threadSlotKeys].sort() } : {}), @@ -8338,6 +8413,7 @@ export class Lowerer { ...(this.ffiImports.length > 0 ? { ffiImports: [...this.ffiImports] } : {}), }; if (module) sanitizeUnregisteredClassTypes(module, (name) => this.classes.has(name)); + if (module?.declOrigins !== undefined) compactDeclOrigins(module); if (module) checkCycleBindingAccesses(this, module); if (module) bindCompletionCallbacks(module); // Execution tiers from the final bodies (ir/tiers.ts). Assigned with or @@ -10956,8 +11032,30 @@ export class Lowerer { * wrap implicitly (coerceToExpected), then the exact-type fence runs * (SC2002 for record shapes, SC2003 for unions). Every slot-directed * lowering goes through here (via lowerExprExpecting) or calls this - * directly when the expression was already lowered. */ + * directly when the expression was already lowered. In package files, a + * check this coercion creates names the package declaration that types + * the slot (decl-origin.ts). */ coerceInto(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { + if (!this.inPackageFileOf(node)) return this.coerceIntoSlot(node, expr, expected); + const outer = this.coercionOriginNode; + const outerUnion = this.coercionOriginUnion; + this.coercionOriginNode = node; + this.coercionOriginUnion = expr.type.kind === "union" ? expr.type.unionId : null; + let out: IrExpr; + try { + out = this.coerceIntoSlot(node, expr, expected); + } finally { + this.coercionOriginNode = outer; + this.coercionOriginUnion = outerUnion; + } + if (out !== expr && out.kind === "dynCheck" && out.origin === undefined) { + const origin = this.slotOriginOf(node); + if (origin !== null) return { ...out, origin: this.declOriginIndex(origin) }; + } + return out; + } + + private coerceIntoSlot(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { refuseInferredCallbackDrops(this, node, expr.type, expected); // An assertion's scalar check whose value goes straight back into a // checked-dynamic slot (`process.loadEnvFile({} as unknown as string)`) diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 99cca9941..d224d3e9e 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -1,4 +1,5 @@ import { BOOL, STRING, isUnitType, typeEquals } from "../../ir/ir.js"; +import { renderOriginNote } from "./decl-origin.js"; import type { IrExpr, IrFunction, IrStmt, IrType, IrUnionDef, SrcLoc } from "../../ir/ir.js"; /** The TypeError message for a checked extraction that finds `arm`. */ @@ -35,6 +36,9 @@ export function buildUnionNarrow( /** Converts another arm's payload into the target (a record arm that is * a width subtype of a record target), or null to throw. */ convert: ((tag: number, payload: IrExpr) => IrExpr | null) | null = null, + /** The target's package-declared origin (decl-origin.ts): each arm's + * message names the declaration and the patch admitting that arm. */ + origin: string | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -87,7 +91,18 @@ export function buildUnionNarrow( args: [ { kind: "strLit", - value: failureMessage ?? unionNarrowFailureMessage(arm, formatType), + value: + (failureMessage ?? unionNarrowFailureMessage(arm, formatType)) + + (origin === null + ? "" + : renderOriginNote( + origin, + isUnitType(arm) + ? arm.kind === "undefinedT" + ? "undefined" + : "null" + : formatType(arm), + )), type: STRING, loc, }, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 284b2ce06..44e048916 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -139,7 +139,11 @@ import { typeOnlyReachedFiles, } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; -import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; +import { + planDeclProjection, + resetDeclBoundaryOrigins, + type DeclProjection, +} from "./decl-projection.js"; import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; @@ -1077,6 +1081,7 @@ export function loadProgram( // JavaScript, even when named. A registry entry's `sourceVersions` // (chalk 6) compile from their own source. configurePackageShims(entryPath); + resetDeclBoundaryOrigins(); // The project's type patches (type-patches/layer.ts), read by every host // this load creates; nothing at all without `scriptc.typePatches`. configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 11b0f6d13..e2d14ebde 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -648,6 +648,7 @@ async function compileExecutableNative( ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), ...(features.declAdopt ? { declAdopt: true } : {}), + ...(features.typeOrigins ? { typeOrigins: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index e97025e82..4101ae2fd 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -223,6 +223,7 @@ const MODULE_FIELDS: Record>, Role> = { "threadSlotKeys[]": "other", "foreignElementKeys[]": "other", "freezableArrays[]": "other", + "declOrigins[]": "other", }; const loc = { file: "references.ts", start: 0, end: 0 }; diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index c946d799f..f82e71d4a 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1063,6 +1063,11 @@ export interface IrModule { * closures boxed for package code return records and arrays as live * capsules, so a value that comes back keeps its identity. */ declBoundary?: true; + /** Origins of package-declared types checked by `dynCheck.origin` + * (frontend/lowering/decl-origin.ts): fields separated by \x1f — + * package, version, suggested range, selector, where it is declared, + * declared type. */ + declOrigins?: string[]; /** The program constructs LifetimeRegion (@scriptc/experimental): every * ProgramLifetime instance lives in a counted region (the region.* * intrinsics; backend/llvm/regions.ts). */ @@ -4040,12 +4045,17 @@ export type IrExpr = * unshared source is simply consumed. Values must be plain data * (ordinary objects and arrays without accessors, prototypes, symbols * or frozen state), otherwise the cast throws the path TypeError. */ + /** `origin`: the target type comes from an npm package's declaration; + * an index into `IrModule.declOrigins`. A failing check's TypeError then + * names the declaration and the type patch that would admit the value + * (scr_type_origin.c). */ | { kind: "dynCheck"; value: IrExpr; preserveRefs?: true; adopt?: true; exact?: true; + origin?: number; type: IrType; loc: SrcLoc; }; diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 10e0a9e38..832329672 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -953,6 +953,9 @@ const RUNTIME_FEATURE_RULES = { * casts, and closures boxed in a boundary program, which adopt their * arguments. */ declAdopt: rule({ fns: ["dyn.declAdopt"], initial: (mod) => mod.declBoundary === true }), + // Checked conversions into package-declared types name the declaration + // in their TypeError (scr_type_origin.c). + typeOrigins: rule({ initial: (mod) => (mod.declOrigins?.length ?? 0) > 0 }), /** 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 @@ -1237,6 +1240,7 @@ function noRuntimeFeatures(): RuntimeFeatures { streamDyn: false, handleProps: false, declAdopt: false, + typeOrigins: false, dynForAwait: false, dynBuffer: false, builtinValues: false, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 45ebf5c79..b42bca72b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -127,6 +127,12 @@ export function validateModule(mod: IrModule): IrValidationError[] { errors.push({ message: "worker execution mode must be a boolean", loc: moduleLoc }); if (mod.declBoundary !== undefined && mod.declBoundary !== true) errors.push({ message: "declBoundary must be true when present", loc: moduleLoc }); + const declOrigins = mod.declOrigins; + if ( + declOrigins !== undefined && + (!Array.isArray(declOrigins) || declOrigins.some((o) => typeof o !== "string")) + ) + errors.push({ message: "declOrigins must be an array of strings", loc: moduleLoc }); if (mod.untypedIntegrity !== undefined && mod.untypedIntegrity !== true) errors.push({ message: "untypedIntegrity must be true when present", loc: moduleLoc }); if (mod.lifetimeRegions !== undefined && mod.lifetimeRegions !== true) @@ -1001,6 +1007,7 @@ export function validateModule(mod: IrModule): IrValidationError[] { mod.workers === true, mod.lifetimeRegions === true, new Set(mod.threadSlotKeys ?? []), + mod.declOrigins?.length ?? 0, ); } return errors; @@ -1020,6 +1027,7 @@ function validateFunction( workers: boolean, lifetimeRegions: boolean, threadSlotKeys: ReadonlySet, + declOriginCount: number, ): void { const locals = new Map(fn.locals.map((l) => [l.id, l])); const err = (message: string, loc: SrcLoc) => @@ -6154,6 +6162,11 @@ function validateFunction( case "dynCheck": { checkExpr(e.value); expectType(e.value, DYN, "dynCheck operand"); + if ( + e.origin !== undefined && + (!Number.isInteger(e.origin) || e.origin < 0 || e.origin >= declOriginCount) + ) + err(`dynCheck origin ${e.origin} is not an index of declOrigins`, e.loc); // The target drives the emitted validator/builder: non-dyn, // non-void, JSON-representable (closures/class instances can never // be found inside a JSON dyn — the frontend rejects those casts). diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 771e2a426..3786542cc 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -81,6 +81,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( streamDyn: false, handleProps: false, declAdopt: false, + typeOrigins: false, dynForAwait: false, eventsOnceSignal: false, dynBuffer: false, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index b4868d5cb..82a0f8451 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -134,6 +134,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // Own properties JavaScript adds to children. ["scr_dyn_handle_props.c", "handleProps"], ["scr_decl_adopt.c", "declAdopt"], + ["scr_type_origin.c", "typeOrigins"], // for await over checked-dynamic values. ["scr_dyn_for_await.c", "dynForAwait"], ["scr_dyn_for_await_stream.c", all("dynForAwait", "stream", "dynAsync")], diff --git a/packages/runtime/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c new file mode 100644 index 000000000..c52a09164 --- /dev/null +++ b/packages/runtime/src/scr_type_origin.c @@ -0,0 +1,154 @@ +/* Declared-type origins on checked-conversion TypeErrors, linked only by + * programs that check values against types an npm package declares + * (runtime feature typeOrigins). + * + * A checked conversion into a type that comes from a package's + * declarations (its .d.ts, @types package or JSDoc) throws the usual + * `expected at , got ` TypeError when the value does + * not fit. The compiler passes the declaration's origin to the conversion + * site's cold exception path, which calls scr_type_origin_note: the + * pending TypeError's message gains two lines naming the declaration and + * the type patch (package.json scriptc.typePatches) that would make the + * declaration admit the value. The hot path is unchanged, and so is every + * other error. + * + * The origin is one string of fields separated by \x1f: package, version, + * suggested range, the selector of the declared slot, where it is + * declared, and the declared type. */ +#include "scr_runtime.h" + +#include +#include + +typedef struct { + char *data; + size_t len, cap; +} ScrOriginBuf; + +static void scr_origin_put(ScrOriginBuf *b, const char *s, size_t n) { + if (b->len + n + 1 > b->cap) { + size_t cap = b->cap ? b->cap : 256; + while (b->len + n + 1 > cap) cap *= 2; + char *grown = realloc(b->data, cap); + if (!grown) scr_trap("scriptc: out of memory\n"); + b->data = grown; + b->cap = cap; + } + memcpy(b->data + b->len, s, n); + b->len += n; + b->data[b->len] = 0; +} + +static void scr_origin_puts(ScrOriginBuf *b, const char *s) { scr_origin_put(b, s, strlen(s)); } + +/* Field `index` of the origin string (not terminated: [*out, *out+len)). */ +static size_t scr_origin_field(const char *origin, int index, const char **out) { + const char *p = origin; + for (int i = 0; i < index; i++) { + const char *sep = strchr(p, '\x1f'); + if (!sep) { + *out = ""; + return 0; + } + p = sep + 1; + } + const char *end = strchr(p, '\x1f'); + *out = p; + return end ? (size_t)(end - p) : strlen(p); +} + +/* The TypeScript type a `got` kind of the check message suggests adding. */ +static const char *scr_origin_arm(const char *kind, size_t len) { + if (len == 5 && memcmp(kind, "array", 5) == 0) return "unknown[]"; + if (len == 8 && memcmp(kind, "function", 8) == 0) return "Function"; + return NULL; /* the kind itself (undefined, null, string, number, ...) */ +} + +void scr_type_origin_note(const char *origin) { + ScrExcCell *cell = scr_exc_current_cell(); + if (cell->kind != SCR_EXC_OBJ || cell->payload == NULL) return; + ScrError *e = (ScrError *)cell->payload; + if (e->vt != &scr_error_vts[SCR_ERR_TYPE] || e->message == NULL) return; + const char *msg = e->message->data; + size_t mlen = e->message->len; + if (memchr(msg, '\n', mlen) != NULL) return; /* already noted */ + /* `expected at , got `: the path extends the + * declared slot's selector, the kind is what the patch admits. */ + const char *path = NULL, *got = NULL; + size_t path_len = 0, got_len = 0; + if (mlen > 9 && memcmp(msg, "expected ", 9) == 0) { + for (size_t i = mlen; i-- > 7;) { + if (memcmp(msg + i - 6, ", got ", 6) == 0) { + got = msg + i; + got_len = mlen - i; + for (size_t j = i - 6; j-- > 4;) { + if (memcmp(msg + j - 4, " at $", 5) == 0) { + path = msg + j; + path_len = i - 6 - j; + break; + } + } + break; + } + } + } + const char *pkg, *version, *range, *selector, *where, *declared; + size_t pkg_len = scr_origin_field(origin, 0, &pkg); + size_t version_len = scr_origin_field(origin, 1, &version); + size_t range_len = scr_origin_field(origin, 2, &range); + size_t selector_len = scr_origin_field(origin, 3, &selector); + size_t where_len = scr_origin_field(origin, 4, &where); + size_t declared_len = scr_origin_field(origin, 5, &declared); + + ScrOriginBuf b = {0}; + scr_origin_put(&b, msg, mlen); + scr_origin_puts(&b, "\n "); + scr_origin_put(&b, pkg, pkg_len); + if (version_len > 0) { + scr_origin_puts(&b, "@"); + scr_origin_put(&b, version, version_len); + } + scr_origin_puts(&b, " declares "); + scr_origin_put(&b, selector, selector_len); + if (declared_len > 0) { + scr_origin_puts(&b, " as "); + scr_origin_put(&b, declared, declared_len); + } + scr_origin_puts(&b, " ("); + scr_origin_put(&b, where, where_len); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_put(&b, pkg, pkg_len); + scr_origin_puts(&b, "@"); + scr_origin_put(&b, range, range_len); + scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_put(&b, selector, selector_len); + /* $[3].email extends the selector as [].email: elements of an array + * share one declared type. */ + for (size_t i = 1; path != NULL && i < path_len; i++) { + if (path[i] == '[') { + scr_origin_puts(&b, "[]"); + while (i < path_len && path[i] != ']') i++; + } else { + scr_origin_put(&b, path + i, 1); + } + } + scr_origin_puts(&b, "\": \"| "); + if (got != NULL) { + const char *arm = scr_origin_arm(got, got_len); + if (arm != NULL) scr_origin_puts(&b, arm); + else scr_origin_put(&b, got, got_len); + } else { + scr_origin_puts(&b, "undefined"); + } + scr_origin_puts(&b, "\" } } } }"); + + ScrStr *next = scr_str_new(b.data, b.len); + free(b.data); + scr_str_release(e->message); + e->message = next; + if (e->stack != NULL) { + /* The cached `stack` starts with the message. */ + scr_str_release(e->stack); + e->stack = NULL; + } +} diff --git a/tests/fixtures/type-patches/unpatched/main.ts b/tests/fixtures/type-patches/unpatched/main.ts new file mode 100644 index 000000000..89418d2d0 --- /dev/null +++ b/tests/fixtures/type-patches/unpatched/main.ts @@ -0,0 +1,13 @@ +// termctx's lying JSDoc without the project's type patches: each failing +// check's TypeError names the declaration and the patch that admits the +// value (native only; Node never checks). +import { Terminal } from "termctx"; + +const term = new Terminal(false); +for (const read of [() => term.columns(), () => term.outputContext(false).width]) { + try { + console.log(read()); + } catch (e) { + console.log((e as Error).message); + } +} diff --git a/tests/fixtures/type-patches/unpatched/package.json b/tests/fixtures/type-patches/unpatched/package.json new file mode 100644 index 000000000..b2d19d710 --- /dev/null +++ b/tests/fixtures/type-patches/unpatched/package.json @@ -0,0 +1 @@ +{ "name": "type-patches-unpatched", "private": true, "type": "module" } diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index 816418a96..cfd8b4dd6 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -7,7 +7,9 @@ * apply as written warn (SC8030-SC8033). The `.d.ts`, `@types`, replaced * file and supplied entry cases run in npm.test.ts (type-patch-dts, * type-patch-types-twin, type-patch-files, type-patch-types-entry); here - * their unpatched copies show the patches are what makes them compile. And + * their unpatched copies show the patches are what makes them compile, and + * a failing check names the declaration and the patch (scr_type_origin.c, + * frontend/lowering/decl-origin.ts). And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -115,7 +117,11 @@ describe("type patches", () => { if (result.ok) { const native = await run(result.binaryPath, []); expect(native.exitCode).not.toBe(0); - expect(native.stderr).toContain("TypeError: expected string at $, got undefined"); + expect(native.stderr).toBe( + "Uncaught TypeError: expected string at $, got undefined\n" + + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ); } } finally { rmSync(copy, { recursive: true, force: true }); @@ -165,6 +171,28 @@ describe("type patches", () => { }, ); + test( + "a failing check names the package declaration and the patch", + { timeout: 600_000 }, + async () => { + const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); + const native = await run(result.binaryPath, []); + const patch = (selector: string) => + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + expect(native.stdout).toBe( + [ + "expected number at $, got undefined", + " termctx@1.2.0 declares Terminal.columns() as number (index.js:16)", + patch("Terminal.columns()"), + "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", + " termctx@1.2.0 declares OutputContext.width as number (index.js:24)", + patch("OutputContext.width"), + "", + ].join("\n"), + ); + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From 5e843317aff77bf1f029014fc7c4ede8cece26db Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:16 -0700 Subject: [PATCH 292/395] Add scriptc types patch and scriptc types list `scriptc types patch [--range] [--dry-run]` finds the installed package from the working directory, shows every declaration the selector matches (declaration files, JSDoc, @types package) with its type before and after, and adds the patch to the nearest package.json: into an entry whose range covers the installed version, or under ^. Nothing is written when the selector matches nothing. `scriptc types list` shows each patch and what it matches. Both CLI hosts run the same implementation with their own parser. The TypeError note for a wrong declaration now names the command. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cli.mdx | 27 +- docs/content/docs/dependencies.mdx | 13 +- packages/cli/src/main.ts | 2 + packages/compiler/src/cli/command.ts | 34 ++ packages/compiler/src/cli/host.ts | 6 + packages/compiler/src/cli/usage.ts | 9 + .../src/frontend/lowering/decl-origin.ts | 4 +- .../src/frontend/type-patches/command.ts | 382 ++++++++++++++++++ packages/compiler/src/index.ts | 15 + packages/compiler/src/native/cli.ts | 14 + .../test/type-patches-command.test.ts | 120 ++++++ packages/runtime/src/scr_type_origin.c | 20 +- tests/harness/type-patches.test.ts | 4 +- 13 files changed, 632 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/command.ts create mode 100644 packages/compiler/test/type-patches-command.test.ts diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 4c1b7e7c6..7166d3f10 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -2,7 +2,7 @@ title: "CLI Reference" --- -The `scriptc` command has five subcommands: `build`, `run`, `coverage`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. +The `scriptc` command has six subcommands: `build`, `run`, `coverage`, `types`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. ```console $ scriptc --help @@ -21,6 +21,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -88,6 +95,24 @@ $ scriptc run fib.ts Analyzes the program without building it and reports what compiles and what blocks the rest. See [Coverage Reports](/docs/coverage). +## scriptc types + +Writes and lists [type patches](/docs/dependencies#fix-a-packages-types), the project's fixes for wrong declarations of npm packages. + +
    +
    scriptc types patch <package> <selector> <type>
    +
    Finds the package installed for the working directory, prints every declaration the selector matches in its declaration files, its JSDoc and its @types/ package with the type before and after, and adds the patch to the nearest package.json. The patch joins an entry whose range covers the installed version, or gets the range ^<installed version>. Nothing is written when the selector matches nothing. --range <range> chooses the range, and --dry-run only prints.
    +
    scriptc types list
    +
    Prints each type patch in the nearest package.json, whether it applies to the installed version, and what it matches.
    +
    + +```console +$ scriptc types list +commander@^15.0.0 + HelpContext.helpWidth: | undefined + node_modules/commander/lib/command.js:2481 number -> number | undefined +``` + ## scriptc cache warm Verifies and loads the installed precompiled runtime objects and vendored libraries, and prints the cache directory. Pass `runtime`, `tls`, or both to select which to warm. Ordinary builds don't need this step. With `--sanitize`, it builds the instrumented runtime objects using a C toolchain. diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 58b0a0a1d..2288aeb83 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -150,10 +150,19 @@ When the type comes from a package's declarations, the error names the declarati ```text Uncaught TypeError: expected string at $, got undefined profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) - if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } + if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined" ``` -Fix such a declaration in your project with a type patch in `package.json`: +Fix such a declaration in your project with a type patch. `scriptc types patch` shows what a declaration path matches in the installed package and adds the patch to your `package.json`: + +```console +$ scriptc types patch commander "HelpContext.helpWidth" "| undefined" +commander 15.0.0 (node_modules/commander) + lib/command.js:2481 number -> number | undefined +wrote package.json scriptc.typePatches["commander@^15.0.0"].declarations["HelpContext.helpWidth"] +``` + +The patch it writes looks like this: ```json:package.json { diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 8203b959e..5deb4ca29 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -10,6 +10,7 @@ import { mergeProfiles, resolveProvenanceSources, sourceTargetPlatform, + typePatches, warmNativeCaches, } from "@scriptc/compiler"; import { runCli } from "./command.js"; @@ -39,6 +40,7 @@ process.exitCode = await runCli(process.argv.slice(2), { sourceTargetPlatform, warmNativeCaches, mergeProfiles, + typePatches, run: (binary) => new Promise((resolveExit) => { let child; diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 8b2e4c480..1064b18ad 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -1,3 +1,4 @@ +import type { TypePatchCommandRequest } from "../frontend/type-patches/command.js"; import { rmSync, statSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { parseArgs } from "node:util"; @@ -205,6 +206,39 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write(`${outputPath}\n`); return 0; } + if (command === "types") { + const sub = positionals[1]; + const rest = positionals.slice(2); + let request: TypePatchCommandRequest; + if (sub === "patch" && rest.length === 3) { + request = { + action: "patch", + cwd: process.cwd(), + pkg: rest[0]!, + selector: rest[1]!, + type: rest[2]!, + range: values.range ?? null, + dryRun: values["dry-run"], + }; + } else if ( + sub === "list" && + rest.length === 0 && + values.range === undefined && + !values["dry-run"] + ) { + request = { action: "list", cwd: process.cwd() }; + } else { + fail( + `scriptc types takes "patch " (with --range or --dry-run) or "list"\n\n${USAGE}`, + ); + } + const result = await host.typePatches(request); + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + return result.exitCode; + } + if (values.range !== undefined || values["dry-run"]) + fail(`--range and --dry-run are scriptc types patch options\n\n${USAGE}`); if (values.sparse) fail(`--sparse is a merge-profiles option\n\n${USAGE}`); if (command !== "build" && command !== "run" && command !== "coverage") { fail(`unknown command "${command}"\n\n${USAGE}`); diff --git a/packages/compiler/src/cli/host.ts b/packages/compiler/src/cli/host.ts index 7a927ee39..eafe1882b 100644 --- a/packages/compiler/src/cli/host.ts +++ b/packages/compiler/src/cli/host.ts @@ -1,3 +1,7 @@ +import type { + TypePatchCommandRequest, + TypePatchCommandResult, +} from "../frontend/type-patches/command.js"; import type { AnalyzeOptions, AnalyzeResult, @@ -30,5 +34,7 @@ export interface CliHost { /** Merges raw or indexed profiles into one indexed profile; a sparse one * leaves out functions that never ran. */ mergeProfiles: (outputPath: string, inputPaths: string[], sparse: boolean) => Promise; + /** `scriptc types patch|list` (frontend/type-patches/command.ts). */ + typePatches: (request: TypePatchCommandRequest) => Promise; run: (binary: string) => Promise; } diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index f8b6a4869..2d4b101fd 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -13,6 +13,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -157,6 +164,8 @@ export const CLI_OPTIONS = { "profile-generate": { type: "string" }, "profile-use": { type: "string" }, sparse: { type: "boolean", default: false }, + range: { type: "string" }, + "dry-run": { type: "boolean", default: false }, "keep-llvm": { type: "boolean", default: true }, "emit-ir": { type: "boolean", default: false }, sanitize: { type: "boolean", default: false }, diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..a44c54eca 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -348,12 +348,12 @@ export function declarationSelector(decl: ts.Node): string | null { /** The note a union extraction's TypeError carries for a failing `arm` * (the same text the runtime renders for checked conversions). */ export function renderOriginNote(origin: string, arm: string): string { - const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + const [pkg = "", version = "", , selector = "", where = "", declared = ""] = origin.split(ORIGIN_SEPARATOR); return ( `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + `${declared === "" ? "" : ` as ${declared}`} (${where})` + - `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + `\n if that declaration is wrong, patch it: scriptc types patch ${pkg} "${selector}" "| ${arm}"` ); } diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts new file mode 100644 index 000000000..d7fb817ae --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -0,0 +1,382 @@ +/* `scriptc types patch` and `scriptc types list`: write and inspect the + * project's type patches (package.json scriptc.typePatches). + * + * scriptc types patch [--range ] [--dry-run] + * scriptc types list + * + * `patch` finds the installed package from the working directory, shows + * every declaration the selector matches (in the package's declaration + * files, its JSDoc and its @types package) with its type before and after, + * and adds the patch to the nearest package.json under a range covering + * the installed version (`^` unless --range says otherwise, or an + * existing entry's range when one covers it). Nothing is written when the + * selector matches nothing. `list` shows each configured patch, the + * installed copies it applies to and what it matches. */ + +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, join, relative } from "node:path"; +import { parseProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import type { TypePatchParse } from "./layer.js"; +import { patchFile, type PatchMatch } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { isValidRange, satisfies } from "./semver.js"; + +export type TypePatchCommandRequest = + | { + action: "patch"; + cwd: string; + pkg: string; + selector: string; + type: string; + range: string | null; + dryRun: boolean; + } + | { action: "list"; cwd: string }; + +export interface TypePatchCommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface PackageCopy { + root: string; + version: string | null; + /** True for an `@types/` twin. */ + twin: boolean; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +function nearestManifest(cwd: string): string | null { + for (let dir = cwd; ;) { + const manifest = join(dir, "package.json"); + if (existsSync(manifest)) return manifest; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function versionOf(root: string): string | null { + try { + const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { + version?: unknown; + }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +function typesName(pkg: string): string { + return pkg.startsWith("@") ? `@types/${pkg.slice(1).replace("/", "__")}` : `@types/${pkg}`; +} + +/** The copy of `pkg` (and of its @types twin) Node resolution finds from + * `cwd`. A twin is versioned by the runtime package beside it. */ +function installedCopies(cwd: string, pkg: string): PackageCopy[] { + const out: PackageCopy[] = []; + let runtimeVersion: string | null = null; + let twinFound = false; + for (let dir = cwd; ;) { + const modules = join(dir, "node_modules"); + if (runtimeVersion === null && existsSync(join(modules, pkg, "package.json"))) { + const root = join(modules, pkg); + runtimeVersion = versionOf(root); + out.push({ root, version: runtimeVersion, twin: false }); + } + if (!twinFound && existsSync(join(modules, typesName(pkg), "package.json"))) { + twinFound = true; + out.push({ root: join(modules, typesName(pkg)), version: null, twin: true }); + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + for (const copy of out) if (copy.twin) copy.version = runtimeVersion ?? versionOf(copy.root); + return out; +} + +/** The files of a package copy a patch can apply to (nested packages + * excluded), package-relative. */ +function packageFiles(root: string): string[] { + const out: string[] = []; + const walk = (dir: string): void => { + let names: string[]; + try { + names = readdirSync(dir).sort(); + } catch { + return; + } + for (const name of names) { + if (name === "node_modules" || name.startsWith(".")) continue; + const path = join(dir, name); + let isDir = false; + try { + isDir = statSync(path).isDirectory(); + } catch { + continue; + } + if (isDir) walk(path); + else if (PATCHABLE.test(name)) out.push(relative(root, path).split("\\").join("/")); + } + }; + walk(root); + return out; +} + +interface FileMatch extends PatchMatch { + file: string; +} + +/** Every declaration a selector matches in a copy, and the problems where + * it cannot apply. */ +function matchCopy( + copy: PackageCopy, + selector: Selector, + op: TypePatchOp, + parse: TypePatchParse, +): { matches: FileMatch[]; problems: string[] } { + const words = + selector.root === "export=" + ? ["exports", "export"] + : selector.root === "default" + ? ["default"] + : [selector.root]; + const matches: FileMatch[] = []; + const problems: string[] = []; + for (const rel of packageFiles(copy.root)) { + if (selector.file !== null && selector.file !== rel) continue; + const path = join(copy.root, rel); + let text: string; + try { + text = readFileSync(path, "utf8"); + } catch { + continue; + } + if (!words.some((w) => text.includes(w))) continue; + let result; + try { + result = patchFile(rel, text, parse(path, text, /\.[cm]?ts$/.test(rel) ? "ts" : "js"), [ + { id: 0, selector, op }, + ]); + } catch { + continue; + } + for (const m of result.matches) matches.push({ ...m, file: rel }); + for (const p of result.problems) problems.push(`${p.message} (${rel})`); + } + return { matches, problems }; +} + +function shownCopy(copy: PackageCopy, base: string, pkg: string): string { + const name = copy.twin ? typesName(pkg) : pkg; + return `${name} ${copy.version ?? "(no version)"} (${relative(base, copy.root).split("\\").join("/")})`; +} + +function describe(m: FileMatch): string { + if (m.redundant) return ` ${m.file}:${m.line} ${m.before} (already declared so)`; + return ` ${m.file}:${m.line} ${m.before} -> ${m.after}`; +} + +/** The indentation a JSON text uses (two spaces when it cannot tell). */ +function indentOf(text: string): string { + const m = /^[{[]\s*\n([ \t]+)\S/.exec(text); + return m === null ? " " : m[1]!; +} + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** JSON text of `value` with `path` set to the string `leaf` (objects + * created along the way, the leaf replaced if present), keeping key order + * and indenting like JSON.stringify(value, null, indent). The parsed value + * itself is only read. */ +function renderJson( + value: unknown, + indent: string, + at: string, + path: readonly string[], + leaf: string, +): string { + const inner = at + indent; + if (path.length > 0 && !isObject(value)) return renderJson({}, indent, at, path, leaf); + if (isObject(value)) { + const keys = Object.keys(value); + const parts: string[] = []; + const head = path[0]; + for (const k of keys) { + const child = + head === k + ? path.length === 1 + ? JSON.stringify(leaf) + : renderJson(value[k], indent, inner, path.slice(1), leaf) + : renderJson(value[k], indent, inner, [], leaf); + parts.push(`${inner}${JSON.stringify(k)}: ${child}`); + } + if (head !== undefined && !keys.includes(head)) { + const child = + path.length === 1 + ? JSON.stringify(leaf) + : renderJson({}, indent, inner, path.slice(1), leaf); + parts.push(`${inner}${JSON.stringify(head)}: ${child}`); + } + return parts.length === 0 ? "{}" : `{\n${parts.join(",\n")}\n${at}}`; + } + if (Array.isArray(value)) { + const items: unknown[] = value; + if (items.length === 0) return "[]"; + return `[\n${items.map((item) => `${inner}${renderJson(item, indent, inner, [], leaf)}`).join(",\n")}\n${at}]`; + } + return JSON.stringify(value) ?? "null"; +} + +function patchCommand( + req: Extract, + parse: TypePatchParse, +): TypePatchCommandResult { + const fail = (message: string): TypePatchCommandResult => ({ + stdout: "", + stderr: `scriptc: ${message}\n`, + exitCode: 1, + }); + const selector = parseSelector(req.selector); + if ("error" in selector) return fail(`'${req.selector}' is not a selector: it ${selector.error}`); + const op = parseTypeText(req.type); + if ("error" in op) return fail(`the type '${req.type}' ${op.error}`); + if (req.range !== null && !isValidRange(req.range)) + return fail(`'${req.range}' is not a semver range`); + const manifest = nearestManifest(req.cwd); + if (manifest === null) return fail("no package.json found from the working directory"); + const base = dirname(manifest); + const copies = installedCopies(req.cwd, req.pkg); + const runtime = copies.find((c) => !c.twin) ?? copies[0]; + if (runtime === undefined) return fail(`${req.pkg} is not installed here`); + const version = runtime.version; + const lines: string[] = []; + let matched = 0; + let changed = 0; + const problems: string[] = []; + for (const copy of copies) { + const found = matchCopy(copy, selector, op, parse); + problems.push(...found.problems); + if (found.matches.length === 0) continue; + lines.push(shownCopy(copy, base, req.pkg)); + for (const m of found.matches) { + lines.push(describe(m)); + matched++; + if (!m.redundant) changed++; + } + } + if (matched === 0) { + const why = + problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + return fail(`${why}\nnothing was written`); + } + if (changed === 0) { + lines.push("the declarations already say this; nothing was written"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const text = readFileSync(manifest, "utf8"); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); + } + // An existing entry whose range covers the installed version takes the + // patch; otherwise a new one under the range asked for. + const existing = parseProjectConfig(manifest, text).typePatches.find( + (e: TypePatchEntry) => + e.pkg === req.pkg && + (req.range === null + ? version !== null && satisfies(version, e.range) + : e.range === req.range), + ); + const key = + existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; + const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; + if (req.dryRun) { + lines.push(`would write ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const trailing = text.endsWith("\n") ? "\n" : ""; + writeFileSync( + manifest, + renderJson( + parsed, + indentOf(text), + "", + ["scriptc", "typePatches", key, "declarations", req.selector], + req.type, + ) + trailing, + ); + lines.push(`wrote ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +function listCommand(cwd: string, parse: TypePatchParse): TypePatchCommandResult { + const manifest = nearestManifest(cwd); + if (manifest === null) + return { + stdout: "", + stderr: "scriptc: no package.json found from the working directory\n", + exitCode: 1, + }; + const base = dirname(manifest); + const config = parseProjectConfig(manifest, readFileSync(manifest, "utf8")); + const lines: string[] = []; + for (const d of config.diagnostics) + if (d.message.includes("typePatches")) lines.push(`warning ${d.code}: ${d.message}`); + if (config.typePatches.length === 0) { + lines.push("no type patches"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + for (const entry of config.typePatches) { + lines.push(entry.key); + const copies = installedCopies(cwd, entry.pkg); + const covered = copies.filter((c) => c.version !== null && satisfies(c.version, entry.range)); + if (copies.length === 0) lines.push(` not installed`); + else if (covered.length === 0) + lines.push( + ` does not apply to the installed ${entry.pkg} ${copies[0]!.version ?? "(no version)"}`, + ); + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector) as Selector; + const op = parseTypeText(decl.type) as TypePatchOp; + const found: FileMatch[] = []; + for (const copy of covered) { + for (const m of matchCopy(copy, selector, op, parse).matches) + found.push({ + ...m, + file: `${relative(base, copy.root).split("\\").join("/")}/${m.file}`, + }); + } + const status = + covered.length === 0 + ? "" + : found.length === 0 + ? " (matches nothing)" + : found.every((m) => m.redundant) + ? " (already declared so)" + : ""; + lines.push(` ${decl.selector}: ${decl.type}${status}`); + for (const m of found) + if (!m.redundant) lines.push(` ${m.file}:${m.line} ${m.before} -> ${m.after}`); + } + for (const [target, local] of entry.files) + lines.push(` file ${target}: ${relative(base, local).split("\\").join("/")}`); + if (entry.types !== null) + lines.push(` types: ${relative(base, entry.types).split("\\").join("/")}`); + } + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +export function runTypePatchCommand( + req: TypePatchCommandRequest, + parse: TypePatchParse, +): TypePatchCommandResult { + return req.action === "patch" ? patchCommand(req, parse) : listCommand(req.cwd, parse); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d14ebde..92b39e78b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,3 +1,9 @@ +import { nodeFrontendServices } from "./frontend/services-node.js"; +import { + runTypePatchCommand, + type TypePatchCommandRequest, + type TypePatchCommandResult, +} from "./frontend/type-patches/command.js"; import { optimizationField, type NativeOptimization } from "./backend/optimization.js"; import { targetCpuField, targetCpuTargetRefusal } from "./backend/target-cpu.js"; import { compilationTiming, type CompilationTiming, type TimingClock } from "./timing.js"; @@ -197,6 +203,15 @@ export { } from "./backend/external-c.js"; export { warmNativeCaches } from "./backend/warm-cache.js"; export { mergeNativeProfiles as mergeProfiles } from "./backend/native-codegen.js"; + +/** `scriptc types patch|list` with the Node frontend's parser. */ +export async function typePatches( + request: TypePatchCommandRequest, +): Promise { + return runTypePatchCommand(request, (path, text, kind) => + nodeFrontendServices().parse(path, text, kind), + ); +} export { collectGarbageUnderHeapPressure } from "./heap-collection.js"; export { ANDROID_MIN_API, diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b3de05846..60f3d28da 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -12,6 +12,7 @@ import { mergeNativeProfiles } from "../backend/native-tools.js"; import { setDeclarationRoot } from "../frontend/dts-paths.js"; import { setFrontendDisposedHook } from "../frontend/dispose-hook.js"; import { FrontendServices } from "../frontend/services.js"; +import { runTypePatchCommand } from "../frontend/type-patches/command.js"; import { createNativeTs7Api } from "../frontend/ts7/native-api.js"; import { resolveProvenanceSourcesWithParser } from "../frontend/provenance-core.js"; import { setCompilerReleaseVersion } from "../library/sidecar.js"; @@ -169,6 +170,19 @@ async function main(): Promise { } return { cacheRoot: cache.root, profiles }; }, + typePatches: async (request) => { + const toolchain = getCompiler().toolchain; + const services = new FrontendServices((options) => + createNativeTs7Api({ ...options, executable: toolchain.ts7Executable }), + ); + try { + return runTypePatchCommand(request, (path, source, kind) => + services.parse(path, source, kind), + ); + } finally { + services.close(); + } + }, mergeProfiles: async (outputPath, inputPaths, sparse) => { const toolchain = getCompiler().toolchain; mergeNativeProfiles({ diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts new file mode 100644 index 000000000..7fc8a0d6c --- /dev/null +++ b/packages/compiler/test/type-patches-command.test.ts @@ -0,0 +1,120 @@ +/* `scriptc types patch` and `scriptc types list` + * (frontend/type-patches/command.ts) on a copy of the type patch fixture: + * matches are shown before anything is written, the patch lands in the + * nearest package.json under a range covering the installed version (or + * an existing entry that covers it), and nothing is written when the + * selector matches nothing. */ +import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { runTypePatchCommand } from "../src/frontend/type-patches/command.js"; + +const fixture = join(import.meta.dirname, "../../../tests/fixtures/type-patches"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); +let root: string; + +beforeEach(() => { + root = mkdtempSync(join(tmpdir(), "scriptc-types-command-")); + cpSync(fixture, root, { recursive: true }); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ name: "app", private: true, type: "module" }, null, 2) + "\n", + ); +}); +afterEach(() => rmSync(root, { recursive: true, force: true })); + +const patch = (pkg: string, selector: string, type: string, extra: object = {}) => + runTypePatchCommand( + { action: "patch", cwd: root, pkg, selector, type, range: null, dryRun: false, ...extra }, + parse, + ); + +test("patch shows the matches and writes the entry", () => { + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result).toEqual({ + stdout: + "termctx 1.2.0 (node_modules/termctx)\n" + + " index.js:24 number -> number | undefined\n" + + `wrote package.json scriptc.typePatches["termctx@^1.2.0"].declarations["OutputContext.width"]\n`, + stderr: "", + exitCode: 0, + }); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toEqual({ + name: "app", + private: true, + type: "module", + scriptc: { + typePatches: { + "termctx@^1.2.0": { declarations: { "OutputContext.width": "| undefined" } }, + }, + }, + }); + // A second patch joins the entry whose range covers the installed copy. + expect(patch("termctx", "Terminal.columns()", "| undefined").exitCode).toBe(0); + const text = readFileSync(join(root, "package.json"), "utf8"); + expect(text.endsWith("}\n")).toBe(true); + expect(JSON.parse(text).scriptc.typePatches).toEqual({ + "termctx@^1.2.0": { + declarations: { "OutputContext.width": "| undefined", "Terminal.columns()": "| undefined" }, + }, + }); +}); + +test("--range and --dry-run", () => { + const dry = patch("ms", "index.js:parse()", "| undefined", { dryRun: true, range: "2.1.x" }); + expect(dry.stdout).toBe( + "ms 2.1.3 (node_modules/ms)\n" + + " index.js:44 Number -> Number | undefined\n" + + `would write package.json scriptc.typePatches["ms@2.1.x"].declarations["index.js:parse()"]\n`, + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("nothing is written for a selector that matches nothing or is already so", () => { + const missing = patch("termctx", "OutputContext.height", "| undefined"); + expect(missing.exitCode).toBe(1); + expect(missing.stderr).toBe( + "scriptc: 'OutputContext.height' matches no declaration\nnothing was written\n", + ); + const redundant = patch("ms", "index.js:fmtShort(ms)", "| Number"); + expect(redundant).toMatchObject({ exitCode: 0 }); + expect(redundant.stdout).toContain("the declarations already say this; nothing was written\n"); + expect(patch("absent-package", "A.b", "| undefined").stderr).toBe( + "scriptc: absent-package is not installed here\n", + ); + expect(patch("termctx", "1bad", "| undefined").stderr).toContain("is not a selector"); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("list shows each patch and what it matches", () => { + writeFileSync(join(root, "package.json"), readFileSync(join(fixture, "package.json"))); + const listed = runTypePatchCommand({ action: "list", cwd: root }, parse); + expect(listed.stdout).toBe( + [ + "termctx@^1.0.0", + " OutputContext.width: | undefined", + " node_modules/termctx/index.js:24 number -> number | undefined", + " Terminal.columns(): | undefined", + " node_modules/termctx/index.js:14 number -> number | undefined", + "ms@^2.1.0", + " index.js:parse(): | undefined", + " node_modules/ms/index.js:44 Number -> Number | undefined", + " export=(): | undefined", + " node_modules/ms/index.js:22 String|Number -> String|Number | undefined", + "", + ].join("\n"), + ); + writeFileSync(join(root, "stale.json"), ""); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { typePatches: { "termctx@^2.0.0": { declarations: { "A.b": "| undefined" } } } }, + }), + ); + expect(runTypePatchCommand({ action: "list", cwd: root }, parse).stdout).toBe( + "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", + ); +}); diff --git a/packages/runtime/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c index c52a09164..cf9c6fc49 100644 --- a/packages/runtime/src/scr_type_origin.c +++ b/packages/runtime/src/scr_type_origin.c @@ -9,12 +9,13 @@ * site's cold exception path, which calls scr_type_origin_note: the * pending TypeError's message gains two lines naming the declaration and * the type patch (package.json scriptc.typePatches) that would make the - * declaration admit the value. The hot path is unchanged, and so is every + * declaration admit the value, as the `scriptc types patch` command that + * writes it. The hot path is unchanged, and so is every * other error. * * The origin is one string of fields separated by \x1f: package, version, - * suggested range, the selector of the declared slot, where it is - * declared, and the declared type. */ + * suggested range (which the command chooses itself), the selector of the + * declared slot, where it is declared, and the declared type. */ #include "scr_runtime.h" #include @@ -92,10 +93,9 @@ void scr_type_origin_note(const char *origin) { } } } - const char *pkg, *version, *range, *selector, *where, *declared; + const char *pkg, *version, *selector, *where, *declared; size_t pkg_len = scr_origin_field(origin, 0, &pkg); size_t version_len = scr_origin_field(origin, 1, &version); - size_t range_len = scr_origin_field(origin, 2, &range); size_t selector_len = scr_origin_field(origin, 3, &selector); size_t where_len = scr_origin_field(origin, 4, &where); size_t declared_len = scr_origin_field(origin, 5, &declared); @@ -116,11 +116,9 @@ void scr_type_origin_note(const char *origin) { } scr_origin_puts(&b, " ("); scr_origin_put(&b, where, where_len); - scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it: scriptc types patch "); scr_origin_put(&b, pkg, pkg_len); - scr_origin_puts(&b, "@"); - scr_origin_put(&b, range, range_len); - scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_puts(&b, " \""); scr_origin_put(&b, selector, selector_len); /* $[3].email extends the selector as [].email: elements of an array * share one declared type. */ @@ -132,7 +130,7 @@ void scr_type_origin_note(const char *origin) { scr_origin_put(&b, path + i, 1); } } - scr_origin_puts(&b, "\": \"| "); + scr_origin_puts(&b, "\" \"| "); if (got != NULL) { const char *arm = scr_origin_arm(got, got_len); if (arm != NULL) scr_origin_puts(&b, arm); @@ -140,7 +138,7 @@ void scr_type_origin_note(const char *origin) { } else { scr_origin_puts(&b, "undefined"); } - scr_origin_puts(&b, "\" } } } }"); + scr_origin_puts(&b, "\""); ScrStr *next = scr_str_new(b.data, b.len); free(b.data); diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7b8aaf7f5 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -120,7 +120,7 @@ describe("type patches", () => { expect(native.stderr).toBe( "Uncaught TypeError: expected string at $, got undefined\n" + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ` if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined"\n`, ); } } finally { @@ -178,7 +178,7 @@ describe("type patches", () => { const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); const native = await run(result.binaryPath, []); const patch = (selector: string) => - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + ` if that declaration is wrong, patch it: scriptc types patch termctx "${selector}" "| undefined"`; expect(native.stdout).toBe( [ "expected number at $, got undefined", From 062700bdde6125d6f38192d6d636dc9484c17b61 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:08:00 -0700 Subject: [PATCH 293/395] Convert asserted values that no ordinary conversion reaches `x as T` erases (or, record to record, converts), and the value then meets its slot with x's representation. Where no conversion reaches the slot the program was refused: - a union whose arms only partly convert into T (`api as Partial>` on `S | Partial> | undefined`) failed at the next typed slot with SC2003; - a record or array whose member types the assertion narrows (`models as Model<"one">[]` on a union-element array, `item as Narrow` with a narrower field) failed with SC2002 or SC2003. The lowerer now remembers the asserted type of an erased value, and a slot that would refuse it converts it as asserted: the arms T excludes throw the catchable TypeError of a lying assertion and the others convert, and records and arrays take the checked deep conversion, which validates the value member by member and names the deepest failing path, as a cast of an unknown value does. The conversions run only where the slot would otherwise refuse the value, so everything that already lowered keeps its code. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/coercions/asserted.ts | 87 +++++++++++++++++++ .../lowering/expressions/narrowing.ts | 10 +++ .../compiler/src/frontend/lowering/lowerer.ts | 9 +- .../checked-union-record-assertions.ts.json | 6 ++ .../corpus/checked-union-record-assertions.ts | 69 +++++++++++++++ .../checked-union-record-assertions.ts.json | 1 + tests/harness/dyncheck.test.ts | 26 ++++++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/src/frontend/lowering/coercions/asserted.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json create mode 100644 tests/corpus/checked-union-record-assertions.ts create mode 100644 tests/harness/corpus-costs/checked-union-record-assertions.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/asserted.ts b/packages/compiler/src/frontend/lowering/coercions/asserted.ts new file mode 100644 index 000000000..43c447cb0 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/coercions/asserted.ts @@ -0,0 +1,87 @@ +/* Slots reached only through what a type assertion claims. */ +import type { IrExpr, IrType, SrcLoc } from "../../../ir/ir.js"; +import { canDynCheckTo, DYN, isUnitType, typeEquals } from "../../../ir/ir.js"; +import { lowerRecordOvfCaptureHelper } from "../containers/indexed-objects.js"; +import type { Lowerer } from "../lowerer.js"; +import { unionRetagHelper } from "./unions.js"; + +/** A value an erased assertion produced (`x as T`, lowerer.assertedValues) + * flowing into a slot that no ordinary conversion reaches. Runs only where + * the slot would otherwise refuse the value (SC2002/SC2003), so every + * conversion that already lowered keeps its code. + * + * - A union whose arms only partly convert (`api as Partial>` on `S | Partial> | undefined`): the arms that convert + * into T convert into the slot, and an arm the assertion excludes (no + * conversion into T) throws the catchable TypeError of a lying assertion. + * - A record or array whose member types the assertion narrows (`models as + * Model<"one">[]` on a union-element array): the checked deep conversion + * a cast of an unknown value takes, which validates the value member by + * member and names the deepest failing path. */ +export function assertedConversion( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType, +): IrExpr | null { + const asserted = lowerer.assertedValues.get(expr); + if (!asserted) return null; + const loc = expr.loc; + if (expr.type.kind === "union") { + const from = lowerer.unions.get(expr.type.unionId); + if (!from) return null; + const values = from.arms.filter((arm) => !isUnitType(arm)); + const kept = values.filter((arm) => convertsInto(lowerer, arm, asserted, loc)); + if (kept.length === 0 || kept.length === values.length) return null; + if (asserted.kind === "union" && expected.kind === "union") { + // The arms T excludes trap; the others (units included) re-tag. + const trappable = new Set(); + from.arms.forEach((arm, tag) => { + if (!isUnitType(arm) && !kept.includes(arm)) trappable.add(tag); + }); + const helper = unionRetagHelper(lowerer, from.id, expected.unionId, loc, trappable); + return helper ? { kind: "call", callee: helper, args: [expr], type: expected, loc } : null; + } + // A record or array T holds one arm's values: extract that arm (every + // other arm, a unit too, contradicts the assertion) and convert it. + if (asserted.kind === "union" || kept.length !== 1) return null; + const arm = kept[0]!; + const helper = lowerer.narrowedArmHelper(from.id, arm, loc); + if (!helper) return null; + const narrowed = lowerer.checkedNarrowCall(helper, expr, arm, loc); + if (typeEquals(arm, expected)) return narrowed; + const converted = lowerer.coerceToExpected(narrowed, expected); + return typeEquals(converted.type, expected) ? converted : null; + } + if (expected.kind !== "record" && expected.kind !== "array") return null; + if (expected.kind === "record" && lowerer.shapes.get(expected.shapeId)?.indexValue !== undefined) + return null; + if ( + !lowerer.dynConvertible(expr.type) || + !canDynCheckTo( + expected, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + return { kind: "dynCheck", value: lowerer.coerceToExpected(expr, DYN), type: expected, loc }; +} + +/** True when a value of `arm` has a structural conversion into `target`: + * the same type, one of its arms, a width lift, or (into an + * index-signature record) the overflow capture. */ +function convertsInto(lowerer: Lowerer, arm: IrType, target: IrType, loc: SrcLoc): boolean { + if (typeEquals(arm, target)) return true; + if ( + target.kind === "union" && + lowerer.unions.get(target.unionId)?.arms.some((candidate) => typeEquals(candidate, arm)) + ) + return true; + if (lowerer.widthLiftPlan(arm, target) !== null) return true; + return ( + arm.kind === "record" && + target.kind === "record" && + lowerer.shapes.get(target.shapeId)?.indexValue !== undefined && + lowerRecordOvfCaptureHelper(lowerer, arm.shapeId, target.shapeId, loc) !== null + ); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f0559672..19f879bcf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1481,6 +1481,7 @@ export function lowerAsExpression( target?.kind === "record" && inner.type.shapeId !== target.shapeId ) { + lowerer.assertedValues.set(inner, target); return lowerer.coerceInto(expr, inner, target); } // `u as Arm` on a UNION value is `u!`'s spelling with a named arm @@ -1515,6 +1516,15 @@ export function lowerAsExpression( const asserted = checkedClassUnionAssertion(lowerer, inner, target, locOf(expr)); if (asserted) return asserted; } + // The erased value still answers to the assertion: a slot it cannot + // reach by an ordinary conversion converts it as asserted + // (assertedConversion). + if ( + target && + (target.kind === "union" || target.kind === "record" || target.kind === "array") && + (inner.type.kind === "union" || inner.type.kind === "array") + ) + lowerer.assertedValues.set(inner, target); return inner; // erasure, unchanged } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index feca34427..491ca7c11 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -118,6 +118,7 @@ import { deferredReadHelper, widthNarrowHelper, } from "./coercions/unions.js"; +import { assertedConversion } from "./coercions/asserted.js"; import { lowerUtilTypeValue, pathModuleValue, @@ -2247,6 +2248,10 @@ export class Lowerer { ts.Type, { tsBindings: Map; instance: ts.Type }[] >(); + /** Values an erased type assertion produced, with the asserted type + * (`x as T` keeps x's representation): a slot that no ordinary + * conversion reaches converts them as asserted (assertedConversion). */ + readonly assertedValues = new WeakMap(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -10599,6 +10604,8 @@ export class Lowerer { if (!typeEquals(expr.type, expected)) { const w = this.widthCoerce(expr, expected); if (w) return w; + const asserted = assertedConversion(this, expr, expected); + if (asserted) return asserted; // A UNIT (null/undefined) flowing into a plain non-nullable slot // the checker approved (`null!` casts, non-strict assignments): // the stranded-source stance (divergence 38) without a union in @@ -10617,7 +10624,7 @@ export class Lowerer { if (helper) { return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc }; } - return expr; + return assertedConversion(this, expr, expected) ?? expr; } if (expr.type.kind === "void") { // A void CALL RESULT flowing into a union with an undefined arm diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json new file mode 100644 index 000000000..ff77d1a95 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/checked-union-record-assertions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/checked-union-record-assertions.ts b/tests/corpus/checked-union-record-assertions.ts new file mode 100644 index 000000000..f5d43cee1 --- /dev/null +++ b/tests/corpus/checked-union-record-assertions.ts @@ -0,0 +1,69 @@ +// Assertions whose target no static conversion reaches: a union asserted to +// the one layout some of its arms convert into, and records or arrays whose +// member types the assertion narrows. The value is checked against the +// asserted type; these programs assert truthfully, so they run as in Node. + +type Api = "alpha" | "beta" | (string & {}); + +interface Streams { + stream: (x: number) => string; + fetch?: (x: number) => string; +} + +interface Options { + name: string; + api?: Streams | Partial>; +} + +function isStreams(value: unknown): value is Streams { + return typeof value === "object" && value !== null && "stream" in value && typeof value.stream === "function"; +} + +function run(input: Options): string[] { + const single = input.api && isStreams(input.api) ? input.api : undefined; + const byApi = single || !input.api ? undefined : (input.api as Partial>); + const all = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== undefined); + return all.map((entry) => `${input.name}:${entry.stream(all.length)}`); +} + +const shared: Streams = { stream: (x) => `shared${x}` }; +console.log(run({ name: "single", api: shared })); +console.log(run<"alpha" | "beta">({ name: "map", api: { alpha: { stream: (x) => `alpha${x}` }, beta: shared } })); +console.log(run<"alpha">({ name: "none" })); + +type KnownKind = "alpha" | "beta" | "gamma"; + +interface AlphaOpts { + fast?: boolean; +} +interface BetaOpts { + depth?: number; +} + +interface Item { + id: string; + kind: TKind; + opts?: TKind extends "alpha" ? AlphaOpts : TKind extends "beta" ? BetaOpts : never; +} + +interface Other { + id: string; + kind: string; + size: number; +} + +const alpha: Item<"alpha"> = { id: "a1", kind: "alpha", opts: { fast: true } }; +const gamma: Item<"gamma"> = { id: "g1", kind: "gamma" }; +const other: Other = { id: "o1", kind: "other", size: 2 }; + +const everything: (Item | Other)[] = [alpha, gamma, other, { id: "g2", kind: "gamma" }]; +const gammas = everything.filter((entry) => entry.kind === "gamma") as Item<"gamma">[]; +console.log(gammas.length, gammas.map((entry) => `${entry.id}:${entry.opts === undefined}`).join(",")); + +const items: Item[] = [gamma, alpha]; +const first = items[0]! as Item<"gamma">; +console.log(first.id, first.kind, first.opts === undefined); + +const stored: { list: (Item | Other)[] } = { list: [gamma] }; +const restored = stored.list as Item<"gamma">[]; +console.log(JSON.stringify(restored)); diff --git a/tests/harness/corpus-costs/checked-union-record-assertions.ts.json b/tests/harness/corpus-costs/checked-union-record-assertions.ts.json new file mode 100644 index 000000000..fc3725628 --- /dev/null +++ b/tests/harness/corpus-costs/checked-union-record-assertions.ts.json @@ -0,0 +1 @@ +{ "plain": 14493, "san": 16666 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index e1a869048..dfd037570 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -114,6 +114,32 @@ describe(`dynamic-boundary checks (scriptc-only${sanitize ? ", sanitized" : ""}) expect(r.stderr).toBe(""); }); + test("assertions narrowing record members or union arms check the value", async () => { + const r = await compileAndRun( + "narrowing-record-assertions", + ` + interface AlphaOpts { fast?: boolean } + interface Item { id: string; kind: K; opts?: K extends "alpha" ? AlphaOpts : never } + const items: Item<"alpha" | "gamma">[] = [{ id: "g1", kind: "gamma" }, { id: "a1", kind: "alpha", opts: { fast: true } }]; + try { const all = items as Item<"gamma">[]; console.log(all.length); } + catch (error) { console.log(error instanceof TypeError, (error as Error).message); } + try { const one = items[1]! as Item<"gamma">; console.log(one.id); } + catch (error) { console.log(error instanceof TypeError, (error as Error).message); } + interface S { stream: () => string } + function keys(u: S | { a?: S }): string[] { const m = u as Partial>; return Object.keys(m); } + try { console.log(keys({ a: { stream: () => "a" } }).join()); console.log(keys({ stream: () => "s" })); } + catch (error) { console.log(error instanceof TypeError); } + `, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "true expected null | undefined at $[1].opts, got object\n" + + "true expected null | undefined at $.opts, got object\n" + + "a\ntrue\n", + ); + expect(r.stderr).toBe(""); + }); + test("typed Promise exits validate fulfillment and preserve rejection", async () => { const result = await compileAndRun( "promise-exit-payload", From 1733a4c84bf815fa283ddcb8672d4ef3df18f5fe Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 15:17:09 -0700 Subject: [PATCH 294/395] Name new corpus programs instead of numbering them Parallel changes kept taking the same next corpus number (6992 twice, the Node island programs renumbered several times). The harness never needed the numbers: it discovers every entry by glob, and 818 programs already have descriptive names. AGENTS.md now makes a name, without a number, the convention for new programs and keeps the numbered ones as they are. The two scripts that sampled programs by number skipped named programs. They now key a named program by a stable hash of its name, so they sample it at the same rate; the numbered programs they pick are unchanged. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 ++ scripts/lowering-order.mjs | 24 ++++++++++++++++++++---- scripts/refresh-cli-profile.mjs | 29 ++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2350d7337..55ccb2eab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,8 @@ SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # sanitized lane Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways. +Name a new corpus program after what it tests, without a number: lowercase words joined by hyphens, such as `tests/corpus/intersection-restated-members.ts`, or a directory such as `tests/corpus//main.ts` for a multi-file program. Parallel changes kept taking the same next number; with names, two changes collide only when they add the same name, which git reports as a conflict. Leave existing numbered programs as they are: never rename or renumber them, and name a new program even when it extends a numbered family. + Record a new or remeasured program's CI cost (used to balance the test shards) in `tests/harness/corpus-costs/.json`, not in `tests/harness/corpus-costs.json`; see the README there. Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep. diff --git a/scripts/lowering-order.mjs b/scripts/lowering-order.mjs index 81611e6a4..6d68530cb 100644 --- a/scripts/lowering-order.mjs +++ b/scripts/lowering-order.mjs @@ -16,13 +16,16 @@ * [--seeds 1,2,3] [--jobs 8] [--entry ]... [--out ] * [--fail-on names|content] * + * --stride and --offset sample the top-level corpus programs by number, or by + * a hash of the name of a program without one. + * * Needs a built compiler (pnpm build). Writes /results.json and prints * a ranking of fields by the programs whose IR they change. With --fail-on, * it exits with status 1 when some program's IR differs between orders: * in any way (names), or beyond names and ids (content). */ import { spawn } from "node:child_process"; import { createHash } from "node:crypto"; -import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { cpus, tmpdir } from "node:os"; import { basename, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -306,12 +309,25 @@ if (values["fail-on"] !== undefined && !["names", "content"].includes(values["fa const out = resolve(values.out); mkdirSync(out, { recursive: true }); const seeds = values.seeds.split(",").filter(Boolean); +/** A top-level corpus program's sampling key: its number, or, for a program + * named without one, a stable hash of its name (FNV-1a). */ +const programKey = (name) => + /^\d+-/.test(name) + ? Number.parseInt(name, 10) + : [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261); +/** Numbered programs by number, then the named ones by name. */ +const programOrder = (a, b) => + /^\d+-/.test(a) !== /^\d+-/.test(b) + ? /^\d+-/.test(a) + ? -1 + : 1 + : programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0); const corpus = join(root, "tests/corpus"); const programs = [ ...readdirSync(corpus) - .filter((name) => /^\d+-.*\.ts$/.test(name)) - .filter((name) => Number.parseInt(name, 10) % Number(values.stride) === Number(values.offset)) - .sort((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10)) + .filter((name) => /^[\w-]+\.ts$/.test(name) && statSync(join(corpus, name)).isFile()) + .filter((name) => programKey(name) % Number(values.stride) === Number(values.offset)) + .sort(programOrder) .slice(0, Number(values.limit)) .map((name) => join(corpus, name)), ...values.entry.map((entry) => resolve(entry)), diff --git a/scripts/refresh-cli-profile.mjs b/scripts/refresh-cli-profile.mjs index 1285b5503..dde4e21b9 100644 --- a/scripts/refresh-cli-profile.mjs +++ b/scripts/refresh-cli-profile.mjs @@ -7,8 +7,9 @@ * Builds an instrumented native compiler for this Linux host, runs it on the * training builds below, and merges the raw profiles it writes into one * sparse indexed profile. The training builds are tsc-ts (a cold `speed` - * build, then a rebuild after an edit), every seventh corpus program at the - * default optimization and a further slice in `dev`, and the runtime + * build, then a rebuild after an edit), every seventh top-level corpus + * program (by number, or by a hash of the name of a program without one) at + * the default optimization and a further slice in `dev`, and the runtime * benchmark workloads. Run it on a quiet Linux x64 host after changes that * move much compiler code; a stale profile only loses part of its gain. A * refresh takes about 11 minutes on 16 cores. @@ -126,16 +127,30 @@ async function pool(tasks) { return passed; } -const programNumber = (name) => Number.parseInt(name, 10); +/** A top-level corpus program's sampling key: its number, or, for a program + * named without one, a stable hash of its name (FNV-1a). */ +const programKey = (name) => + /^\d+-/.test(name) + ? Number.parseInt(name, 10) + : [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261); +/** Numbered programs by number, then the named ones by name. */ +const programOrder = (a, b) => + /^\d+-/.test(a) !== /^\d+-/.test(b) + ? /^\d+-/.test(a) + ? -1 + : 1 + : programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0); const corpus = readdirSync(join(root, "tests/corpus")) - .filter((name) => /^\d+-.*\.ts$/.test(name)) - .sort((a, b) => programNumber(a) - programNumber(b)); + .filter( + (name) => /^[\w-]+\.ts$/.test(name) && statSync(join(root, "tests/corpus", name)).isFile(), + ) + .sort(programOrder); const training = [ ...corpus - .filter((name) => programNumber(name) % 7 === 0) + .filter((name) => programKey(name) % 7 === 0) .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "release" })), ...corpus - .filter((name) => programNumber(name) % 14 === 7) + .filter((name) => programKey(name) % 14 === 7) .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "dev" })), ...readdirSync(join(root, "benchmarks/runtime/workloads")) .filter((name) => name.endsWith(".ts")) From d33b083a3b4f49c258e4ff2a911235bcd3abda8a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:30:44 -0500 Subject: [PATCH 295/395] Cache the zone offset at UTC midnights for local-time conversions Local setters and the field constructor convert a local time value to UTC by bracketing it with the zone offset a day before and after, then checking the candidate instants: three to five localtime(3) calls per conversion. In glibc each call takes the zone lock and, for dates past the zone file's last transition, parses the zone's rule string again. The offsets now come from a per-thread cache of offsets at UTC midnights: a day whose two midnights agree has that offset throughout, given at most one transition per day, which the conversion already assumes. A day with a transition still asks localtime for the instant. A change of process.env.TZ clears the cache. --- packages/runtime/src/scr_lib.c | 58 ++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index a9fd25915..1a625cdf1 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -11588,8 +11588,8 @@ double scr_date_get_timezone_offset(double ms) { * from local fields reads those fields back. */ /* The host zone's offset (local - UTC) in ms at UTC instant `ms` (an - * integral time value within about ±8.65e15). */ -static bool scr_date_offset_at(double ms, double *out) { + * integral time value within about ±8.65e15), from the host's localtime. */ +static bool scr_date_offset_at_uncached(double ms, double *out) { long long cms = (long long)ms; double secd = (double)(cms / 1000 - (cms % 1000 < 0)); struct tm tmv; @@ -11611,6 +11611,60 @@ static bool scr_date_offset_at(double ms, double *out) { return true; } +/* Offsets at UTC midnights, for scr_date_offset_at. A day whose two + * midnights have the same offset has that offset throughout, given at most + * one transition per day (scr_date_local_to_utc already takes transitions + * to be at least a day apart). Each local setter asks for three to five + * offsets around one time value, and consecutive ones land on the same or + * neighbouring days, so most answers come from here instead of a + * localtime(3) call: glibc takes its zone lock in each and, for dates past + * the zone file's last transition, parses the zone's rule string again. A + * change of process.env.TZ starts a new generation. */ +#define SCR_DATE_MIDNIGHTS 64 +typedef struct { + long long day; + double offset; + bool valid; +} ScrDateMidnight; +typedef struct { + bool valid; + unsigned generation; + ScrDateMidnight entries[SCR_DATE_MIDNIGHTS]; +} ScrDateMidnightCache; +static SCR_TL ScrDateMidnightCache scr_date_midnight_cache; + +static bool scr_date_midnight_offset(ScrDateMidnightCache *c, long long day, double *out) { + ScrDateMidnight *e = &c->entries[(unsigned long long)day % SCR_DATE_MIDNIGHTS]; + if (e->valid && e->day == day) { + *out = e->offset; + return true; + } + if (!scr_date_offset_at_uncached((double)day * 86400000.0, out)) return false; + e->valid = true; + e->day = day; + e->offset = *out; + return true; +} + +static bool scr_date_offset_at(double ms, double *out) { + ScrDateMidnightCache *c = &scr_date_midnight_cache; + unsigned generation = scr_date_tz_generation; + if (!c->valid || c->generation != generation) { + memset(c, 0, sizeof *c); + c->valid = true; + c->generation = generation; + } + long long cms = (long long)ms; + long long day = cms / 86400000 - (cms % 86400000 < 0); + double start, end; + if (scr_date_midnight_offset(c, day, &start) && scr_date_midnight_offset(c, day + 1, &end) && + start == end) { + *out = start; + return true; + } + return scr_date_offset_at_uncached(ms, out); +} + static volatile const double scr_date_day_ms = 86400000.0; static double scr_date_local_to_utc(double t) { From 46785f8ebac9826fcc59ff3a87617e986c5f5f9b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:35:38 -0500 Subject: [PATCH 296/395] Address review feedback --- .../frontend/lowering/coercions/structural.ts | 6 ++- .../src/frontend/lowering/decl-origin.ts | 22 +++++--- packages/compiler/src/frontend/program.ts | 2 + .../node_modules/gridctx/index.js | 43 +++++++++++++++ .../node_modules/gridctx/package.json | 1 + .../node_modules/panectx/index.js | 43 +++++++++++++++ .../node_modules/panectx/package.json | 1 + tests/fixtures/type-patches/same-name/main.ts | 15 ++++++ .../type-patches/same-name/package.json | 1 + tests/harness/type-patches.test.ts | 53 ++++++++++++++++++- 10 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/type-patches/node_modules/gridctx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/gridctx/package.json create mode 100644 tests/fixtures/type-patches/node_modules/panectx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/panectx/package.json create mode 100644 tests/fixtures/type-patches/same-name/main.ts create mode 100644 tests/fixtures/type-patches/same-name/package.json diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 76c6c9c3b..863472910 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -398,10 +398,12 @@ export function recordWidthHelper( // Package code converting into a package-declared record type (an // object literal returned under `@returns {HelpContext}`): its field // extractions name the declared fields (decl-origin.ts), so the helper - // is keyed by that type too. Every other projection is shared as before. + // is keyed by that type's declaration too (two packages may declare + // records of the same name and shape). Every other projection is shared + // as before. const declared = lowerer.coercionRecordOrigin(); const key = - declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.name}`; + declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.key}`; const existing = lowerer.valueHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, "%rec.width", key); diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..72a135b67 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -40,8 +40,15 @@ interface PackageFile { rel: string; } +/** Installed package versions by package root, read once per program load + * (resetDeclOriginVersions): a dependency reinstalled at another version + * between two builds of one process is read again. */ const versions = new Map(); +export function resetDeclOriginVersions(): void { + versions.clear(); +} + function packageFileOf(fileName: string): PackageFile | null { const path = fileName.split("\\").join("/"); const marker = "/node_modules/"; @@ -268,6 +275,9 @@ export function slotOrigin(checker: ts.TypeChecker, node: ts.Node): string | nul export interface RecordOrigin { type: ts.Type; name: string; + /** The declaration's identity (package, version, file, line and name): + * records of the same shape and name declared by two packages differ. */ + key: string; } export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrigin | null { @@ -277,13 +287,11 @@ export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrig const name = symbol === undefined ? "" : ts.symbolName(symbol); if (!/^[A-Za-z_$][\w$]*$/.test(name)) return null; const decl = symbol?.declarations[0]; - if ( - decl === undefined || - genericDeclaration(decl) || - packageFileOf(ts.getSourceFileOfNode(decl).fileName) === null - ) - return null; - return { type: contextual, name }; + if (decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + const key = [file.pkg, file.version, file.rel, lineOf(decl), name].join(ORIGIN_SEPARATOR); + return { type: contextual, name, key }; } /** The origin of one field of a package-declared record type. */ diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 44e048916..44979ca68 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -145,6 +145,7 @@ import { type DeclProjection, } from "./decl-projection.js"; import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; +import { resetDeclOriginVersions } from "./lowering/decl-origin.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { @@ -1082,6 +1083,7 @@ export function loadProgram( // (chalk 6) compile from their own source. configurePackageShims(entryPath); resetDeclBoundaryOrigins(); + resetDeclOriginVersions(); // The project's type patches (type-patches/layer.ts), read by every host // this load creates; nothing at all without `scriptc.typePatches`. configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); diff --git a/tests/fixtures/type-patches/node_modules/gridctx/index.js b/tests/fixtures/type-patches/node_modules/gridctx/index.js new file mode 100644 index 000000000..44c282ad7 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/gridctx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// termctx's Terminal under another name: panectx and gridctx declare the +// same OutputContext (one name, one shape) in two packages. + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/gridctx/package.json b/tests/fixtures/type-patches/node_modules/gridctx/package.json new file mode 100644 index 000000000..36f6aba11 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/gridctx/package.json @@ -0,0 +1 @@ +{ "name": "gridctx", "version": "4.1.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/node_modules/panectx/index.js b/tests/fixtures/type-patches/node_modules/panectx/index.js new file mode 100644 index 000000000..44c282ad7 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/panectx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// termctx's Terminal under another name: panectx and gridctx declare the +// same OutputContext (one name, one shape) in two packages. + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/panectx/package.json b/tests/fixtures/type-patches/node_modules/panectx/package.json new file mode 100644 index 000000000..e73708257 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/panectx/package.json @@ -0,0 +1 @@ +{ "name": "panectx", "version": "2.0.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/same-name/main.ts b/tests/fixtures/type-patches/same-name/main.ts new file mode 100644 index 000000000..3cbfffc57 --- /dev/null +++ b/tests/fixtures/type-patches/same-name/main.ts @@ -0,0 +1,15 @@ +// panectx and gridctx each declare an OutputContext of the same shape: +// each failing check's TypeError names its own package's declaration +// (native only; Node never checks). +import { Terminal as PaneTerminal } from "panectx"; +import { Terminal as GridTerminal } from "gridctx"; + +const pane = new PaneTerminal(false); +const grid = new GridTerminal(false); +for (const read of [() => pane.outputContext(false).width, () => grid.outputContext(false).width]) { + try { + console.log(read()); + } catch (e) { + console.log((e as Error).message); + } +} diff --git a/tests/fixtures/type-patches/same-name/package.json b/tests/fixtures/type-patches/same-name/package.json new file mode 100644 index 000000000..fdf221cba --- /dev/null +++ b/tests/fixtures/type-patches/same-name/package.json @@ -0,0 +1 @@ +{ "name": "type-patches-same-name", "private": true, "type": "module" } diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7afb4433d 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -9,7 +9,8 @@ * type-patch-types-twin, type-patch-files, type-patch-types-entry); here * their unpatched copies show the patches are what makes them compile, and * a failing check names the declaration and the patch (scr_type_origin.c, - * frontend/lowering/decl-origin.ts). And + * frontend/lowering/decl-origin.ts), its own package's also when two + * packages declare a record of one name and shape. And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -193,6 +194,56 @@ describe("type patches", () => { }, ); + test( + "records of one name and shape from two packages name their own declarations", + { timeout: 600_000 }, + async () => { + const result = await build(join(fixture, "same-name/main.ts"), "same-name", [ + "panectx", + "gridctx", + ]); + const native = await run(result.binaryPath, []); + const failure = (pkg: string, version: string) => [ + "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", + ` ${pkg}@${version} declares OutputContext.width as number (index.js:24)`, + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@^${version}": { "declarations": { "OutputContext.width": "| undefined" } } } }`, + ]; + expect(native.stdout).toBe( + [...failure("panectx", "2.0.0"), ...failure("gridctx", "4.1.0"), ""].join("\n"), + ); + }, + ); + + test( + "a rebuild after the package's version changes names the installed version", + { timeout: 600_000 }, + async () => { + const copy = mkdtempSync(join(tmpdir(), "scriptc-reinstalled-")); + try { + cpSync(fixture, copy, { recursive: true }); + const entry = join(copy, "unpatched/main.ts"); + const declares = async (name: string) => { + const native = await run((await build(entry, name, ["termctx"])).binaryPath, []); + return native.stdout.split("\n").filter((line) => line.includes(" declares ")); + }; + expect(await declares("reinstalled-1.2.0")).toEqual([ + " termctx@1.2.0 declares Terminal.columns() as number (index.js:16)", + " termctx@1.2.0 declares OutputContext.width as number (index.js:24)", + ]); + writeFileSync( + join(copy, "node_modules/termctx/package.json"), + '{ "name": "termctx", "version": "1.3.0", "main": "index.js" }\n', + ); + expect(await declares("reinstalled-1.3.0")).toEqual([ + " termctx@1.3.0 declares Terminal.columns() as number (index.js:16)", + " termctx@1.3.0 declares OutputContext.width as number (index.js:24)", + ]); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From b372816337ced999adcc8653539a9fc08b7ea41e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:39:01 -0500 Subject: [PATCH 297/395] Address review feedback --- .../src/frontend/type-patches/command.ts | 53 ++++++++++++++----- .../test/type-patches-command.test.ts | 50 ++++++++++++++++- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts index d7fb817ae..99c321710 100644 --- a/packages/compiler/src/frontend/type-patches/command.ts +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -131,13 +131,15 @@ interface FileMatch extends PatchMatch { } /** Every declaration a selector matches in a copy, and the problems where - * it cannot apply. */ + * it cannot apply (`always`: a problem even where the selector matched + * elsewhere, such as an ambiguous replacement, which leaves those + * declarations as they are). */ function matchCopy( copy: PackageCopy, selector: Selector, op: TypePatchOp, parse: TypePatchParse, -): { matches: FileMatch[]; problems: string[] } { +): { matches: FileMatch[]; problems: { message: string; always: boolean }[] } { const words = selector.root === "export=" ? ["exports", "export"] @@ -145,7 +147,7 @@ function matchCopy( ? ["default"] : [selector.root]; const matches: FileMatch[] = []; - const problems: string[] = []; + const problems: { message: string; always: boolean }[] = []; for (const rel of packageFiles(copy.root)) { if (selector.file !== null && selector.file !== rel) continue; const path = join(copy.root, rel); @@ -165,7 +167,8 @@ function matchCopy( continue; } for (const m of result.matches) matches.push({ ...m, file: rel }); - for (const p of result.problems) problems.push(`${p.message} (${rel})`); + for (const p of result.problems) + problems.push({ message: `${p.message} (${rel})`, always: p.always === true }); } return { matches, problems }; } @@ -258,7 +261,7 @@ function patchCommand( const lines: string[] = []; let matched = 0; let changed = 0; - const problems: string[] = []; + const problems: { message: string; always: boolean }[] = []; for (const copy of copies) { const found = matchCopy(copy, selector, op, parse); problems.push(...found.problems); @@ -272,9 +275,16 @@ function patchCommand( } if (matched === 0) { const why = - problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + problems.length > 0 + ? problems.map((p) => p.message).join("\n") + : `'${req.selector}' matches no declaration`; return fail(`${why}\nnothing was written`); } + // A declaration the patch cannot apply to where it also matched (an + // ambiguous replacement) would stay as it is. + const unapplied = problems.filter((p) => p.always); + if (unapplied.length > 0) + return fail(`${unapplied.map((p) => p.message).join("\n")}\nnothing was written`); if (changed === 0) { lines.push("the declarations already say this; nothing was written"); return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; @@ -286,15 +296,30 @@ function patchCommand( } catch { return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); } - // An existing entry whose range covers the installed version takes the - // patch; otherwise a new one under the range asked for. - const existing = parseProjectConfig(manifest, text).typePatches.find( - (e: TypePatchEntry) => - e.pkg === req.pkg && - (req.range === null - ? version !== null && satisfies(version, e.range) - : e.range === req.range), + // The compiler applies every entry whose range covers the installed + // version, and a later entry wins on the same selector. Without a range, + // the patch goes to the last covering entry that patches the selector + // already, else to the first covering entry; with one, to the entry of + // that range. Otherwise a new entry, which comes last, takes it. + const entries = parseProjectConfig(manifest, text).typePatches.filter( + (e: TypePatchEntry) => e.pkg === req.pkg, ); + const covers = (e: TypePatchEntry): boolean => version !== null && satisfies(version, e.range); + const declares = (e: TypePatchEntry): boolean => + e.declarations.some((d) => d.selector === req.selector); + const existing = + req.range === null + ? (entries.findLast((e) => covers(e) && declares(e)) ?? entries.find(covers)) + : entries.find((e) => e.range === req.range); + if (existing !== undefined && covers(existing)) { + const later = entries + .slice(entries.indexOf(existing) + 1) + .find((e) => covers(e) && declares(e)); + if (later !== undefined) + return fail( + `scriptc.typePatches["${later.key}"] also patches '${req.selector}' for ${req.pkg} ${version} and takes precedence over scriptc.typePatches["${existing.key}"]\nnothing was written`, + ); + } const key = existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts index 7fc8a0d6c..12f8a97bf 100644 --- a/packages/compiler/test/type-patches-command.test.ts +++ b/packages/compiler/test/type-patches-command.test.ts @@ -4,7 +4,7 @@ * nearest package.json under a range covering the installed version (or * an existing entry that covers it), and nothing is written when the * selector matches nothing. */ -import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, expect, test } from "vitest"; @@ -118,3 +118,51 @@ test("list shows each patch and what it matches", () => { "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", ); }); + +test("nothing is written when a replacement is ambiguous in one of the files it matches", () => { + const pkg = join(root, "node_modules/overloads"); + mkdirSync(pkg, { recursive: true }); + writeFileSync( + join(pkg, "package.json"), + JSON.stringify({ name: "overloads", version: "1.0.0", types: "index.d.ts" }), + ); + writeFileSync(join(pkg, "index.d.ts"), "export declare function pick(a: string): string;\n"); + writeFileSync( + join(pkg, "more.d.ts"), + "export declare function pick(a: string): string;\nexport declare function pick(a: number): string;\n", + ); + const result = patch("overloads", "pick()", "string | undefined"); + expect(result.exitCode).toBe(1); + expect(result.stderr).toBe( + "scriptc: 'pick()' matches 2 declarations in more.d.ts; a replacement must name one " + + "(widen them, or replace the declaration file) (more.d.ts)\nnothing was written\n", + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("the patch goes to the covering entry the compiler applies", () => { + const manifest = join(root, "package.json"); + const entries = { + "termctx@^1.0.0": { declarations: { "OutputContext.width": "| null" } }, + "termctx@1.x": { declarations: { "OutputContext.width": "| null" } }, + }; + writeFileSync(manifest, JSON.stringify({ name: "app", scriptc: { typePatches: entries } })); + // Both ranges cover termctx 1.2.0, and the later entry wins on the same + // selector: it takes the patch. + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain( + `wrote package.json scriptc.typePatches["termctx@1.x"].declarations["OutputContext.width"]\n`, + ); + expect(JSON.parse(readFileSync(manifest, "utf8")).scriptc.typePatches).toEqual({ + "termctx@^1.0.0": { declarations: { "OutputContext.width": "| null" } }, + "termctx@1.x": { declarations: { "OutputContext.width": "| undefined" } }, + }); + // Asked for the earlier range, the later entry would still win. + const earlier = patch("termctx", "OutputContext.width", "| undefined", { range: "^1.0.0" }); + expect(earlier.exitCode).toBe(1); + expect(earlier.stderr).toBe( + `scriptc: scriptc.typePatches["termctx@1.x"] also patches 'OutputContext.width' for termctx 1.2.0 ` + + `and takes precedence over scriptc.typePatches["termctx@^1.0.0"]\nnothing was written\n`, + ); +}); From cc08abcbe72c4082a81a195d2627eb258c49119d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:42:53 -0500 Subject: [PATCH 298/395] Match the pending-flag fallback edge after the callback's receiver block An indirect callback call now gives the closure an undefined receiver when it reads one, so the fallback's edge into the pending-flag phi comes from the receiver block instead of callback.indirect. The flag is still false on the direct call and true on the fallback. --- packages/compiler/test/callback-pending-emission.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/callback-pending-emission.test.ts b/packages/compiler/test/callback-pending-emission.test.ts index 3b2ed3823..7e7996c83 100644 --- a/packages/compiler/test/callback-pending-emission.test.ts +++ b/packages/compiler/test/callback-pending-emission.test.ts @@ -40,9 +40,11 @@ try { const module = deserializeModule(readFileSync(output, "utf8")); expect(validateModule(module)).toEqual([]); const llvm = emitLlvmModule(module, {}); + // The fallback calls the closure's entry, after giving it an undefined + // receiver when it reads one, so its edge comes from receiver.done then. const guards = [ ...llvm.matchAll( - /(%t\d+) = phi i1 \[ false, %callback\.direct\d+ \], \[ true, %callback\.indirect\d+ \]/g, + /(%t\d+) = phi i1 \[ false, %callback\.direct\d+ \], \[ true, %(?:callback\.indirect|receiver\.done)\d+ \]/g, ), ]; // The quiet every and map callbacks; not the throwing one. From 5b40498c22c8ac7f4096e05e724a0b0bff177568 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:52:20 -0500 Subject: [PATCH 299/395] Keep asserted values in a map the lowerer owns compiler-weak-collections.test.ts refuses weak collections in compiler sources: the compiled compiler must not depend on which of its values can serve as weak keys. The lowerer owns the assertedValues cache for its pass, so an ordinary Map holds it. --- packages/compiler/src/frontend/lowering/lowerer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 491ca7c11..d665cc3ff 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2251,7 +2251,7 @@ export class Lowerer { /** Values an erased type assertion produced, with the asserted type * (`x as T` keeps x's representation): a slot that no ordinary * conversion reaches converts them as asserted (assertedConversion). */ - readonly assertedValues = new WeakMap(); + readonly assertedValues = new Map(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier From e1780421a12b0528384d829dd492fcf5e151603e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 15:32:59 -0700 Subject: [PATCH 300/395] Declare the origin note, skip it in library builds, and keep the cast order on the lowerer --- packages/compiler/src/backend/llvm/expr-async.ts | 7 ++++++- .../src/frontend/lowering/expressions/narrowing.ts | 10 ++++------ packages/compiler/src/frontend/lowering/lowerer.ts | 3 +++ packages/runtime/src/scr_dyn.h | 5 +++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/backend/llvm/expr-async.ts b/packages/compiler/src/backend/llvm/expr-async.ts index 28a4cf57f..037179d8e 100644 --- a/packages/compiler/src/backend/llvm/expr-async.ts +++ b/packages/compiler/src/backend/llvm/expr-async.ts @@ -374,7 +374,12 @@ export function emitSerializationExpr( // falls back to exactly this route whenever the fast path declines. // A package-declared target: the cold path names the declaration in // the pending TypeError (scr_type_origin.c). - const origin = e.origin === undefined ? undefined : host.mod.declOrigins?.[e.origin]; + // Library archives and reactors link a fixed runtime set without the + // note unit: their checks keep the plain message. + const origin = + e.origin === undefined || host.mod.lib !== undefined + ? undefined + : host.mod.declOrigins?.[e.origin]; const note = origin === undefined ? undefined diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 5c1b276f7..7043367da 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1640,14 +1640,12 @@ function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): * becomes a live capsule of the value built from it * (scr_dyn_decl_adopt), so identity and mutation visibility hold on both * sides. Scalar targets are ordinary checked casts. */ -const boundaryCastOrder = new WeakMap>(); - /** The origin the projection recorded for a boundary cast (the package * declaration its type comes from, decl-projection.ts), found by the * cast's position among the file's boundary casts in source order. */ -function recordedBoundaryOrigin(cast: ts.Node): string | null { +function recordedBoundaryOrigin(lowerer: Lowerer, cast: ts.Node): string | null { const sf = ts.getSourceFileOfNode(cast); - let order = boundaryCastOrder.get(sf); + let order = lowerer.boundaryCastOrder.get(sf); if (order === undefined) { const found = new Map(); const visit = (node: ts.Node): void => { @@ -1666,7 +1664,7 @@ function recordedBoundaryOrigin(cast: ts.Node): string | null { }; visit(sf); order = found; - boundaryCastOrder.set(sf, order); + lowerer.boundaryCastOrder.set(sf, order); } const index = order.get(cast); return index === undefined ? null : declBoundaryOrigin(sf.fileName, index); @@ -1692,7 +1690,7 @@ function declBoundaryCast( // failing check names it and the patch admitting the value. let originText: string | null = null; try { - originText = recordedBoundaryOrigin(cast); + originText = recordedBoundaryOrigin(lowerer, cast); } catch { originText = null; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 5b5ae3471..45e0067fc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3778,6 +3778,9 @@ export class Lowerer { * in a package file: union extractions built for it carry the slot's * origin in their TypeError. */ coercionOriginNode: ts.Node | null = null; + /** Per user file, each declaration-boundary cast's index in source + * order (narrowing.ts recordedBoundaryOrigin). */ + readonly boundaryCastOrder = new Map>(); /** The union type of that value: only its own extraction is the slot's. */ coercionOriginUnion: string | null = null; /** While a record projection into a package-declared type builds, the diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 1004a93c2..cce1f4b53 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -391,6 +391,11 @@ 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); +/* A checked conversion into a type an npm package declares failed + * (scr_type_origin.c, linked by programs with such checks): the pending + * TypeError's message gains the declaration and the type patch that would + * admit the value. `origin` is the compiler's origin string. */ +void scr_type_origin_note(const char *origin); /* Prototype members of a compiled class seen as `unknown`, emitted per * class by the compiler: the string names of methods, getters and setters From 7675b7d6538a86e4299439d54c57385aeb5b5471 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:16 -0700 Subject: [PATCH 301/395] Add scriptc types patch and scriptc types list `scriptc types patch [--range] [--dry-run]` finds the installed package from the working directory, shows every declaration the selector matches (declaration files, JSDoc, @types package) with its type before and after, and adds the patch to the nearest package.json: into an entry whose range covers the installed version, or under ^. Nothing is written when the selector matches nothing. `scriptc types list` shows each patch and what it matches. Both CLI hosts run the same implementation with their own parser. The TypeError note for a wrong declaration now names the command. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cli.mdx | 27 +- docs/content/docs/dependencies.mdx | 13 +- packages/cli/src/main.ts | 2 + packages/compiler/src/cli/command.ts | 34 ++ packages/compiler/src/cli/host.ts | 6 + packages/compiler/src/cli/usage.ts | 9 + .../src/frontend/lowering/decl-origin.ts | 4 +- .../src/frontend/type-patches/command.ts | 382 ++++++++++++++++++ packages/compiler/src/index.ts | 15 + packages/compiler/src/native/cli.ts | 14 + .../test/type-patches-command.test.ts | 120 ++++++ packages/runtime/src/scr_type_origin.c | 20 +- tests/harness/type-patches.test.ts | 4 +- 13 files changed, 632 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/command.ts create mode 100644 packages/compiler/test/type-patches-command.test.ts diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 4c1b7e7c6..7166d3f10 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -2,7 +2,7 @@ title: "CLI Reference" --- -The `scriptc` command has five subcommands: `build`, `run`, `coverage`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. +The `scriptc` command has six subcommands: `build`, `run`, `coverage`, `types`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. ```console $ scriptc --help @@ -21,6 +21,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -88,6 +95,24 @@ $ scriptc run fib.ts Analyzes the program without building it and reports what compiles and what blocks the rest. See [Coverage Reports](/docs/coverage). +## scriptc types + +Writes and lists [type patches](/docs/dependencies#fix-a-packages-types), the project's fixes for wrong declarations of npm packages. + +
    +
    scriptc types patch <package> <selector> <type>
    +
    Finds the package installed for the working directory, prints every declaration the selector matches in its declaration files, its JSDoc and its @types/ package with the type before and after, and adds the patch to the nearest package.json. The patch joins an entry whose range covers the installed version, or gets the range ^<installed version>. Nothing is written when the selector matches nothing. --range <range> chooses the range, and --dry-run only prints.
    +
    scriptc types list
    +
    Prints each type patch in the nearest package.json, whether it applies to the installed version, and what it matches.
    +
    + +```console +$ scriptc types list +commander@^15.0.0 + HelpContext.helpWidth: | undefined + node_modules/commander/lib/command.js:2481 number -> number | undefined +``` + ## scriptc cache warm Verifies and loads the installed precompiled runtime objects and vendored libraries, and prints the cache directory. Pass `runtime`, `tls`, or both to select which to warm. Ordinary builds don't need this step. With `--sanitize`, it builds the instrumented runtime objects using a C toolchain. diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 58b0a0a1d..2288aeb83 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -150,10 +150,19 @@ When the type comes from a package's declarations, the error names the declarati ```text Uncaught TypeError: expected string at $, got undefined profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) - if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } + if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined" ``` -Fix such a declaration in your project with a type patch in `package.json`: +Fix such a declaration in your project with a type patch. `scriptc types patch` shows what a declaration path matches in the installed package and adds the patch to your `package.json`: + +```console +$ scriptc types patch commander "HelpContext.helpWidth" "| undefined" +commander 15.0.0 (node_modules/commander) + lib/command.js:2481 number -> number | undefined +wrote package.json scriptc.typePatches["commander@^15.0.0"].declarations["HelpContext.helpWidth"] +``` + +The patch it writes looks like this: ```json:package.json { diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 8203b959e..5deb4ca29 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -10,6 +10,7 @@ import { mergeProfiles, resolveProvenanceSources, sourceTargetPlatform, + typePatches, warmNativeCaches, } from "@scriptc/compiler"; import { runCli } from "./command.js"; @@ -39,6 +40,7 @@ process.exitCode = await runCli(process.argv.slice(2), { sourceTargetPlatform, warmNativeCaches, mergeProfiles, + typePatches, run: (binary) => new Promise((resolveExit) => { let child; diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 8b2e4c480..1064b18ad 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -1,3 +1,4 @@ +import type { TypePatchCommandRequest } from "../frontend/type-patches/command.js"; import { rmSync, statSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { parseArgs } from "node:util"; @@ -205,6 +206,39 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write(`${outputPath}\n`); return 0; } + if (command === "types") { + const sub = positionals[1]; + const rest = positionals.slice(2); + let request: TypePatchCommandRequest; + if (sub === "patch" && rest.length === 3) { + request = { + action: "patch", + cwd: process.cwd(), + pkg: rest[0]!, + selector: rest[1]!, + type: rest[2]!, + range: values.range ?? null, + dryRun: values["dry-run"], + }; + } else if ( + sub === "list" && + rest.length === 0 && + values.range === undefined && + !values["dry-run"] + ) { + request = { action: "list", cwd: process.cwd() }; + } else { + fail( + `scriptc types takes "patch " (with --range or --dry-run) or "list"\n\n${USAGE}`, + ); + } + const result = await host.typePatches(request); + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + return result.exitCode; + } + if (values.range !== undefined || values["dry-run"]) + fail(`--range and --dry-run are scriptc types patch options\n\n${USAGE}`); if (values.sparse) fail(`--sparse is a merge-profiles option\n\n${USAGE}`); if (command !== "build" && command !== "run" && command !== "coverage") { fail(`unknown command "${command}"\n\n${USAGE}`); diff --git a/packages/compiler/src/cli/host.ts b/packages/compiler/src/cli/host.ts index 7a927ee39..eafe1882b 100644 --- a/packages/compiler/src/cli/host.ts +++ b/packages/compiler/src/cli/host.ts @@ -1,3 +1,7 @@ +import type { + TypePatchCommandRequest, + TypePatchCommandResult, +} from "../frontend/type-patches/command.js"; import type { AnalyzeOptions, AnalyzeResult, @@ -30,5 +34,7 @@ export interface CliHost { /** Merges raw or indexed profiles into one indexed profile; a sparse one * leaves out functions that never ran. */ mergeProfiles: (outputPath: string, inputPaths: string[], sparse: boolean) => Promise; + /** `scriptc types patch|list` (frontend/type-patches/command.ts). */ + typePatches: (request: TypePatchCommandRequest) => Promise; run: (binary: string) => Promise; } diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index f8b6a4869..2d4b101fd 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -13,6 +13,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -157,6 +164,8 @@ export const CLI_OPTIONS = { "profile-generate": { type: "string" }, "profile-use": { type: "string" }, sparse: { type: "boolean", default: false }, + range: { type: "string" }, + "dry-run": { type: "boolean", default: false }, "keep-llvm": { type: "boolean", default: true }, "emit-ir": { type: "boolean", default: false }, sanitize: { type: "boolean", default: false }, diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..a44c54eca 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -348,12 +348,12 @@ export function declarationSelector(decl: ts.Node): string | null { /** The note a union extraction's TypeError carries for a failing `arm` * (the same text the runtime renders for checked conversions). */ export function renderOriginNote(origin: string, arm: string): string { - const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + const [pkg = "", version = "", , selector = "", where = "", declared = ""] = origin.split(ORIGIN_SEPARATOR); return ( `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + `${declared === "" ? "" : ` as ${declared}`} (${where})` + - `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + `\n if that declaration is wrong, patch it: scriptc types patch ${pkg} "${selector}" "| ${arm}"` ); } diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts new file mode 100644 index 000000000..d7fb817ae --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -0,0 +1,382 @@ +/* `scriptc types patch` and `scriptc types list`: write and inspect the + * project's type patches (package.json scriptc.typePatches). + * + * scriptc types patch [--range ] [--dry-run] + * scriptc types list + * + * `patch` finds the installed package from the working directory, shows + * every declaration the selector matches (in the package's declaration + * files, its JSDoc and its @types package) with its type before and after, + * and adds the patch to the nearest package.json under a range covering + * the installed version (`^` unless --range says otherwise, or an + * existing entry's range when one covers it). Nothing is written when the + * selector matches nothing. `list` shows each configured patch, the + * installed copies it applies to and what it matches. */ + +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, join, relative } from "node:path"; +import { parseProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import type { TypePatchParse } from "./layer.js"; +import { patchFile, type PatchMatch } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { isValidRange, satisfies } from "./semver.js"; + +export type TypePatchCommandRequest = + | { + action: "patch"; + cwd: string; + pkg: string; + selector: string; + type: string; + range: string | null; + dryRun: boolean; + } + | { action: "list"; cwd: string }; + +export interface TypePatchCommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface PackageCopy { + root: string; + version: string | null; + /** True for an `@types/` twin. */ + twin: boolean; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +function nearestManifest(cwd: string): string | null { + for (let dir = cwd; ;) { + const manifest = join(dir, "package.json"); + if (existsSync(manifest)) return manifest; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function versionOf(root: string): string | null { + try { + const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { + version?: unknown; + }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +function typesName(pkg: string): string { + return pkg.startsWith("@") ? `@types/${pkg.slice(1).replace("/", "__")}` : `@types/${pkg}`; +} + +/** The copy of `pkg` (and of its @types twin) Node resolution finds from + * `cwd`. A twin is versioned by the runtime package beside it. */ +function installedCopies(cwd: string, pkg: string): PackageCopy[] { + const out: PackageCopy[] = []; + let runtimeVersion: string | null = null; + let twinFound = false; + for (let dir = cwd; ;) { + const modules = join(dir, "node_modules"); + if (runtimeVersion === null && existsSync(join(modules, pkg, "package.json"))) { + const root = join(modules, pkg); + runtimeVersion = versionOf(root); + out.push({ root, version: runtimeVersion, twin: false }); + } + if (!twinFound && existsSync(join(modules, typesName(pkg), "package.json"))) { + twinFound = true; + out.push({ root: join(modules, typesName(pkg)), version: null, twin: true }); + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + for (const copy of out) if (copy.twin) copy.version = runtimeVersion ?? versionOf(copy.root); + return out; +} + +/** The files of a package copy a patch can apply to (nested packages + * excluded), package-relative. */ +function packageFiles(root: string): string[] { + const out: string[] = []; + const walk = (dir: string): void => { + let names: string[]; + try { + names = readdirSync(dir).sort(); + } catch { + return; + } + for (const name of names) { + if (name === "node_modules" || name.startsWith(".")) continue; + const path = join(dir, name); + let isDir = false; + try { + isDir = statSync(path).isDirectory(); + } catch { + continue; + } + if (isDir) walk(path); + else if (PATCHABLE.test(name)) out.push(relative(root, path).split("\\").join("/")); + } + }; + walk(root); + return out; +} + +interface FileMatch extends PatchMatch { + file: string; +} + +/** Every declaration a selector matches in a copy, and the problems where + * it cannot apply. */ +function matchCopy( + copy: PackageCopy, + selector: Selector, + op: TypePatchOp, + parse: TypePatchParse, +): { matches: FileMatch[]; problems: string[] } { + const words = + selector.root === "export=" + ? ["exports", "export"] + : selector.root === "default" + ? ["default"] + : [selector.root]; + const matches: FileMatch[] = []; + const problems: string[] = []; + for (const rel of packageFiles(copy.root)) { + if (selector.file !== null && selector.file !== rel) continue; + const path = join(copy.root, rel); + let text: string; + try { + text = readFileSync(path, "utf8"); + } catch { + continue; + } + if (!words.some((w) => text.includes(w))) continue; + let result; + try { + result = patchFile(rel, text, parse(path, text, /\.[cm]?ts$/.test(rel) ? "ts" : "js"), [ + { id: 0, selector, op }, + ]); + } catch { + continue; + } + for (const m of result.matches) matches.push({ ...m, file: rel }); + for (const p of result.problems) problems.push(`${p.message} (${rel})`); + } + return { matches, problems }; +} + +function shownCopy(copy: PackageCopy, base: string, pkg: string): string { + const name = copy.twin ? typesName(pkg) : pkg; + return `${name} ${copy.version ?? "(no version)"} (${relative(base, copy.root).split("\\").join("/")})`; +} + +function describe(m: FileMatch): string { + if (m.redundant) return ` ${m.file}:${m.line} ${m.before} (already declared so)`; + return ` ${m.file}:${m.line} ${m.before} -> ${m.after}`; +} + +/** The indentation a JSON text uses (two spaces when it cannot tell). */ +function indentOf(text: string): string { + const m = /^[{[]\s*\n([ \t]+)\S/.exec(text); + return m === null ? " " : m[1]!; +} + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** JSON text of `value` with `path` set to the string `leaf` (objects + * created along the way, the leaf replaced if present), keeping key order + * and indenting like JSON.stringify(value, null, indent). The parsed value + * itself is only read. */ +function renderJson( + value: unknown, + indent: string, + at: string, + path: readonly string[], + leaf: string, +): string { + const inner = at + indent; + if (path.length > 0 && !isObject(value)) return renderJson({}, indent, at, path, leaf); + if (isObject(value)) { + const keys = Object.keys(value); + const parts: string[] = []; + const head = path[0]; + for (const k of keys) { + const child = + head === k + ? path.length === 1 + ? JSON.stringify(leaf) + : renderJson(value[k], indent, inner, path.slice(1), leaf) + : renderJson(value[k], indent, inner, [], leaf); + parts.push(`${inner}${JSON.stringify(k)}: ${child}`); + } + if (head !== undefined && !keys.includes(head)) { + const child = + path.length === 1 + ? JSON.stringify(leaf) + : renderJson({}, indent, inner, path.slice(1), leaf); + parts.push(`${inner}${JSON.stringify(head)}: ${child}`); + } + return parts.length === 0 ? "{}" : `{\n${parts.join(",\n")}\n${at}}`; + } + if (Array.isArray(value)) { + const items: unknown[] = value; + if (items.length === 0) return "[]"; + return `[\n${items.map((item) => `${inner}${renderJson(item, indent, inner, [], leaf)}`).join(",\n")}\n${at}]`; + } + return JSON.stringify(value) ?? "null"; +} + +function patchCommand( + req: Extract, + parse: TypePatchParse, +): TypePatchCommandResult { + const fail = (message: string): TypePatchCommandResult => ({ + stdout: "", + stderr: `scriptc: ${message}\n`, + exitCode: 1, + }); + const selector = parseSelector(req.selector); + if ("error" in selector) return fail(`'${req.selector}' is not a selector: it ${selector.error}`); + const op = parseTypeText(req.type); + if ("error" in op) return fail(`the type '${req.type}' ${op.error}`); + if (req.range !== null && !isValidRange(req.range)) + return fail(`'${req.range}' is not a semver range`); + const manifest = nearestManifest(req.cwd); + if (manifest === null) return fail("no package.json found from the working directory"); + const base = dirname(manifest); + const copies = installedCopies(req.cwd, req.pkg); + const runtime = copies.find((c) => !c.twin) ?? copies[0]; + if (runtime === undefined) return fail(`${req.pkg} is not installed here`); + const version = runtime.version; + const lines: string[] = []; + let matched = 0; + let changed = 0; + const problems: string[] = []; + for (const copy of copies) { + const found = matchCopy(copy, selector, op, parse); + problems.push(...found.problems); + if (found.matches.length === 0) continue; + lines.push(shownCopy(copy, base, req.pkg)); + for (const m of found.matches) { + lines.push(describe(m)); + matched++; + if (!m.redundant) changed++; + } + } + if (matched === 0) { + const why = + problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + return fail(`${why}\nnothing was written`); + } + if (changed === 0) { + lines.push("the declarations already say this; nothing was written"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const text = readFileSync(manifest, "utf8"); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); + } + // An existing entry whose range covers the installed version takes the + // patch; otherwise a new one under the range asked for. + const existing = parseProjectConfig(manifest, text).typePatches.find( + (e: TypePatchEntry) => + e.pkg === req.pkg && + (req.range === null + ? version !== null && satisfies(version, e.range) + : e.range === req.range), + ); + const key = + existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; + const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; + if (req.dryRun) { + lines.push(`would write ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const trailing = text.endsWith("\n") ? "\n" : ""; + writeFileSync( + manifest, + renderJson( + parsed, + indentOf(text), + "", + ["scriptc", "typePatches", key, "declarations", req.selector], + req.type, + ) + trailing, + ); + lines.push(`wrote ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +function listCommand(cwd: string, parse: TypePatchParse): TypePatchCommandResult { + const manifest = nearestManifest(cwd); + if (manifest === null) + return { + stdout: "", + stderr: "scriptc: no package.json found from the working directory\n", + exitCode: 1, + }; + const base = dirname(manifest); + const config = parseProjectConfig(manifest, readFileSync(manifest, "utf8")); + const lines: string[] = []; + for (const d of config.diagnostics) + if (d.message.includes("typePatches")) lines.push(`warning ${d.code}: ${d.message}`); + if (config.typePatches.length === 0) { + lines.push("no type patches"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + for (const entry of config.typePatches) { + lines.push(entry.key); + const copies = installedCopies(cwd, entry.pkg); + const covered = copies.filter((c) => c.version !== null && satisfies(c.version, entry.range)); + if (copies.length === 0) lines.push(` not installed`); + else if (covered.length === 0) + lines.push( + ` does not apply to the installed ${entry.pkg} ${copies[0]!.version ?? "(no version)"}`, + ); + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector) as Selector; + const op = parseTypeText(decl.type) as TypePatchOp; + const found: FileMatch[] = []; + for (const copy of covered) { + for (const m of matchCopy(copy, selector, op, parse).matches) + found.push({ + ...m, + file: `${relative(base, copy.root).split("\\").join("/")}/${m.file}`, + }); + } + const status = + covered.length === 0 + ? "" + : found.length === 0 + ? " (matches nothing)" + : found.every((m) => m.redundant) + ? " (already declared so)" + : ""; + lines.push(` ${decl.selector}: ${decl.type}${status}`); + for (const m of found) + if (!m.redundant) lines.push(` ${m.file}:${m.line} ${m.before} -> ${m.after}`); + } + for (const [target, local] of entry.files) + lines.push(` file ${target}: ${relative(base, local).split("\\").join("/")}`); + if (entry.types !== null) + lines.push(` types: ${relative(base, entry.types).split("\\").join("/")}`); + } + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +export function runTypePatchCommand( + req: TypePatchCommandRequest, + parse: TypePatchParse, +): TypePatchCommandResult { + return req.action === "patch" ? patchCommand(req, parse) : listCommand(req.cwd, parse); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d14ebde..92b39e78b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,3 +1,9 @@ +import { nodeFrontendServices } from "./frontend/services-node.js"; +import { + runTypePatchCommand, + type TypePatchCommandRequest, + type TypePatchCommandResult, +} from "./frontend/type-patches/command.js"; import { optimizationField, type NativeOptimization } from "./backend/optimization.js"; import { targetCpuField, targetCpuTargetRefusal } from "./backend/target-cpu.js"; import { compilationTiming, type CompilationTiming, type TimingClock } from "./timing.js"; @@ -197,6 +203,15 @@ export { } from "./backend/external-c.js"; export { warmNativeCaches } from "./backend/warm-cache.js"; export { mergeNativeProfiles as mergeProfiles } from "./backend/native-codegen.js"; + +/** `scriptc types patch|list` with the Node frontend's parser. */ +export async function typePatches( + request: TypePatchCommandRequest, +): Promise { + return runTypePatchCommand(request, (path, text, kind) => + nodeFrontendServices().parse(path, text, kind), + ); +} export { collectGarbageUnderHeapPressure } from "./heap-collection.js"; export { ANDROID_MIN_API, diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b3de05846..60f3d28da 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -12,6 +12,7 @@ import { mergeNativeProfiles } from "../backend/native-tools.js"; import { setDeclarationRoot } from "../frontend/dts-paths.js"; import { setFrontendDisposedHook } from "../frontend/dispose-hook.js"; import { FrontendServices } from "../frontend/services.js"; +import { runTypePatchCommand } from "../frontend/type-patches/command.js"; import { createNativeTs7Api } from "../frontend/ts7/native-api.js"; import { resolveProvenanceSourcesWithParser } from "../frontend/provenance-core.js"; import { setCompilerReleaseVersion } from "../library/sidecar.js"; @@ -169,6 +170,19 @@ async function main(): Promise { } return { cacheRoot: cache.root, profiles }; }, + typePatches: async (request) => { + const toolchain = getCompiler().toolchain; + const services = new FrontendServices((options) => + createNativeTs7Api({ ...options, executable: toolchain.ts7Executable }), + ); + try { + return runTypePatchCommand(request, (path, source, kind) => + services.parse(path, source, kind), + ); + } finally { + services.close(); + } + }, mergeProfiles: async (outputPath, inputPaths, sparse) => { const toolchain = getCompiler().toolchain; mergeNativeProfiles({ diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts new file mode 100644 index 000000000..7fc8a0d6c --- /dev/null +++ b/packages/compiler/test/type-patches-command.test.ts @@ -0,0 +1,120 @@ +/* `scriptc types patch` and `scriptc types list` + * (frontend/type-patches/command.ts) on a copy of the type patch fixture: + * matches are shown before anything is written, the patch lands in the + * nearest package.json under a range covering the installed version (or + * an existing entry that covers it), and nothing is written when the + * selector matches nothing. */ +import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { runTypePatchCommand } from "../src/frontend/type-patches/command.js"; + +const fixture = join(import.meta.dirname, "../../../tests/fixtures/type-patches"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); +let root: string; + +beforeEach(() => { + root = mkdtempSync(join(tmpdir(), "scriptc-types-command-")); + cpSync(fixture, root, { recursive: true }); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ name: "app", private: true, type: "module" }, null, 2) + "\n", + ); +}); +afterEach(() => rmSync(root, { recursive: true, force: true })); + +const patch = (pkg: string, selector: string, type: string, extra: object = {}) => + runTypePatchCommand( + { action: "patch", cwd: root, pkg, selector, type, range: null, dryRun: false, ...extra }, + parse, + ); + +test("patch shows the matches and writes the entry", () => { + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result).toEqual({ + stdout: + "termctx 1.2.0 (node_modules/termctx)\n" + + " index.js:24 number -> number | undefined\n" + + `wrote package.json scriptc.typePatches["termctx@^1.2.0"].declarations["OutputContext.width"]\n`, + stderr: "", + exitCode: 0, + }); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toEqual({ + name: "app", + private: true, + type: "module", + scriptc: { + typePatches: { + "termctx@^1.2.0": { declarations: { "OutputContext.width": "| undefined" } }, + }, + }, + }); + // A second patch joins the entry whose range covers the installed copy. + expect(patch("termctx", "Terminal.columns()", "| undefined").exitCode).toBe(0); + const text = readFileSync(join(root, "package.json"), "utf8"); + expect(text.endsWith("}\n")).toBe(true); + expect(JSON.parse(text).scriptc.typePatches).toEqual({ + "termctx@^1.2.0": { + declarations: { "OutputContext.width": "| undefined", "Terminal.columns()": "| undefined" }, + }, + }); +}); + +test("--range and --dry-run", () => { + const dry = patch("ms", "index.js:parse()", "| undefined", { dryRun: true, range: "2.1.x" }); + expect(dry.stdout).toBe( + "ms 2.1.3 (node_modules/ms)\n" + + " index.js:44 Number -> Number | undefined\n" + + `would write package.json scriptc.typePatches["ms@2.1.x"].declarations["index.js:parse()"]\n`, + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("nothing is written for a selector that matches nothing or is already so", () => { + const missing = patch("termctx", "OutputContext.height", "| undefined"); + expect(missing.exitCode).toBe(1); + expect(missing.stderr).toBe( + "scriptc: 'OutputContext.height' matches no declaration\nnothing was written\n", + ); + const redundant = patch("ms", "index.js:fmtShort(ms)", "| Number"); + expect(redundant).toMatchObject({ exitCode: 0 }); + expect(redundant.stdout).toContain("the declarations already say this; nothing was written\n"); + expect(patch("absent-package", "A.b", "| undefined").stderr).toBe( + "scriptc: absent-package is not installed here\n", + ); + expect(patch("termctx", "1bad", "| undefined").stderr).toContain("is not a selector"); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("list shows each patch and what it matches", () => { + writeFileSync(join(root, "package.json"), readFileSync(join(fixture, "package.json"))); + const listed = runTypePatchCommand({ action: "list", cwd: root }, parse); + expect(listed.stdout).toBe( + [ + "termctx@^1.0.0", + " OutputContext.width: | undefined", + " node_modules/termctx/index.js:24 number -> number | undefined", + " Terminal.columns(): | undefined", + " node_modules/termctx/index.js:14 number -> number | undefined", + "ms@^2.1.0", + " index.js:parse(): | undefined", + " node_modules/ms/index.js:44 Number -> Number | undefined", + " export=(): | undefined", + " node_modules/ms/index.js:22 String|Number -> String|Number | undefined", + "", + ].join("\n"), + ); + writeFileSync(join(root, "stale.json"), ""); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { typePatches: { "termctx@^2.0.0": { declarations: { "A.b": "| undefined" } } } }, + }), + ); + expect(runTypePatchCommand({ action: "list", cwd: root }, parse).stdout).toBe( + "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", + ); +}); diff --git a/packages/runtime/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c index c52a09164..cf9c6fc49 100644 --- a/packages/runtime/src/scr_type_origin.c +++ b/packages/runtime/src/scr_type_origin.c @@ -9,12 +9,13 @@ * site's cold exception path, which calls scr_type_origin_note: the * pending TypeError's message gains two lines naming the declaration and * the type patch (package.json scriptc.typePatches) that would make the - * declaration admit the value. The hot path is unchanged, and so is every + * declaration admit the value, as the `scriptc types patch` command that + * writes it. The hot path is unchanged, and so is every * other error. * * The origin is one string of fields separated by \x1f: package, version, - * suggested range, the selector of the declared slot, where it is - * declared, and the declared type. */ + * suggested range (which the command chooses itself), the selector of the + * declared slot, where it is declared, and the declared type. */ #include "scr_runtime.h" #include @@ -92,10 +93,9 @@ void scr_type_origin_note(const char *origin) { } } } - const char *pkg, *version, *range, *selector, *where, *declared; + const char *pkg, *version, *selector, *where, *declared; size_t pkg_len = scr_origin_field(origin, 0, &pkg); size_t version_len = scr_origin_field(origin, 1, &version); - size_t range_len = scr_origin_field(origin, 2, &range); size_t selector_len = scr_origin_field(origin, 3, &selector); size_t where_len = scr_origin_field(origin, 4, &where); size_t declared_len = scr_origin_field(origin, 5, &declared); @@ -116,11 +116,9 @@ void scr_type_origin_note(const char *origin) { } scr_origin_puts(&b, " ("); scr_origin_put(&b, where, where_len); - scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it: scriptc types patch "); scr_origin_put(&b, pkg, pkg_len); - scr_origin_puts(&b, "@"); - scr_origin_put(&b, range, range_len); - scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_puts(&b, " \""); scr_origin_put(&b, selector, selector_len); /* $[3].email extends the selector as [].email: elements of an array * share one declared type. */ @@ -132,7 +130,7 @@ void scr_type_origin_note(const char *origin) { scr_origin_put(&b, path + i, 1); } } - scr_origin_puts(&b, "\": \"| "); + scr_origin_puts(&b, "\" \"| "); if (got != NULL) { const char *arm = scr_origin_arm(got, got_len); if (arm != NULL) scr_origin_puts(&b, arm); @@ -140,7 +138,7 @@ void scr_type_origin_note(const char *origin) { } else { scr_origin_puts(&b, "undefined"); } - scr_origin_puts(&b, "\" } } } }"); + scr_origin_puts(&b, "\""); ScrStr *next = scr_str_new(b.data, b.len); free(b.data); diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7b8aaf7f5 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -120,7 +120,7 @@ describe("type patches", () => { expect(native.stderr).toBe( "Uncaught TypeError: expected string at $, got undefined\n" + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ` if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined"\n`, ); } } finally { @@ -178,7 +178,7 @@ describe("type patches", () => { const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); const native = await run(result.binaryPath, []); const patch = (selector: string) => - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + ` if that declaration is wrong, patch it: scriptc types patch termctx "${selector}" "| undefined"`; expect(native.stdout).toBe( [ "expected number at $, got undefined", From 20a4a92aa604ee1a6159cdd78d8bd38c1c9d3146 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:05:55 -0700 Subject: [PATCH 302/395] 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 699755157..f71f96a3d 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 e19ea65cc..c4c9b0ba9 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 afbe4d6af..8d11d3806 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 171e420c2..34c9fdc1d 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 000000000..159235ce9 --- /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 000000000..181d0beba --- /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 30cf205e90108ea02cd109b71ad0cc6c439655ef Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:10:32 -0500 Subject: [PATCH 303/395] Run untyped pipeline function stages and later iterable stages A pipeline stage that arrives as an untyped value now follows Node's stage ladder for more shapes. As the first stage, a function is a source function: the pipeline calls it with { signal } while it is set up and pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE otherwise), and a { readable, writable } pair pumps its readable side. After the first stage, the pipeline call is lowered once per combination of its first three untyped stages being functions or not, and a typeof test picks the branch at run time. A function stage is called with the previous stage and { signal }; before the last stage its result must be async iterable, and as the last stage a promise resolves the pipeline's value and an async iterable feeds the PassThrough pipeline() returns (SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes Duplex.from of it, which the next stage reads from without the previous stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for the values it rejects. An untyped stage followed by an untyped function stage hands its value to that stage through a cell created per pipeline call, so the function receives the previous stage's own iterator, as Node's makeAsyncIterable answers it, instead of an async generator over it. Untyped web streams, pairs after the first stage, and function stages that return a stream throw the not-supported error. RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports. --- .../builtin-apis/streams-and-readline.mdx | 8 +- packages/cli/test/native-link-info.test.ts | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 12 +- .../compiler/src/backend/llvm/lib-shared.ts | 3 + .../compiler/src/backend/llvm/lib-stream.ts | 2 +- packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-stream.ts | 667 +++++++++++++++++- packages/compiler/src/ir/builtin-effects.ts | 3 + .../compiler/src/ir/builtin-signatures.ts | 30 +- packages/compiler/src/ir/runtime-features.ts | 4 + packages/compiler/src/ir/validate.ts | 6 +- .../test/native-codegen-integration.test.ts | 4 +- ...pipeline-untyped-source-functions.mjs.json | 6 + ...-pipeline-untyped-function-stages.mjs.json | 6 + ...-pipeline-untyped-later-iterables.mjs.json | 6 + .../7511-pipeline-untyped-stages.cjs.json | 6 + .../corpus/7512-pipeline-any-stages.ts.json | 6 + ...513-pipeline-untyped-stage-errors.mjs.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_dyn.h | 3 + packages/runtime/src/scr_dyn_builtin_stream.c | 8 + packages/runtime/src/scr_stream.c | 272 ++++++- packages/runtime/src/scr_stream.h | 12 + ...7508-pipeline-untyped-source-functions.mjs | 85 +++ .../7509-pipeline-untyped-function-stages.mjs | 112 +++ .../7510-pipeline-untyped-later-iterables.mjs | 64 ++ tests/corpus/7511-pipeline-untyped-stages.cjs | 50 ++ tests/corpus/7512-pipeline-any-stages.ts | 45 ++ .../7513-pipeline-untyped-stage-errors.mjs | 86 +++ ...pipeline-untyped-source-functions.mjs.json | 1 + ...-pipeline-untyped-function-stages.mjs.json | 1 + ...-pipeline-untyped-later-iterables.mjs.json | 1 + .../7511-pipeline-untyped-stages.cjs.json | 1 + .../7512-pipeline-any-stages.ts.json | 1 + ...513-pipeline-untyped-stage-errors.mjs.json | 1 + 40 files changed, 1466 insertions(+), 72 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json create mode 100644 tests/corpus/7508-pipeline-untyped-source-functions.mjs create mode 100644 tests/corpus/7509-pipeline-untyped-function-stages.mjs create mode 100644 tests/corpus/7510-pipeline-untyped-later-iterables.mjs create mode 100644 tests/corpus/7511-pipeline-untyped-stages.cjs create mode 100644 tests/corpus/7512-pipeline-any-stages.ts create mode 100644 tests/corpus/7513-pipeline-untyped-stage-errors.mjs create mode 100644 tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json create mode 100644 tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json create mode 100644 tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json create mode 100644 tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json create mode 100644 tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json create mode 100644 tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 561840268..25427a307 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -4,7 +4,13 @@ When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects. Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol. The first `pipeline` stage may arrive as an untyped value that is a native stream, a `Cipheriv` or `Decipheriv` value, or an iterable: a string, an array, a generator object, or any value with a `Symbol.asyncIterator` or `Symbol.iterator` method, which the pipeline pumps into the next stage as Node does. - Later `pipeline` stages and `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. + It may also be a source function, which the pipeline calls with `{ signal }` and whose iterable result it pumps, or a `{ readable, writable }` pair, whose readable side it pumps. + Later `pipeline` stages that arrive as untyped values may be native streams, `Cipheriv` and `Decipheriv` values, functions, and iterables. + A function is called with the previous stage and `{ signal }` as a function stage: before the last stage it must return an async iterable, and as the last stage it may return a promise, whose value resolves the pipeline, or an async iterable, which feeds the PassThrough that `pipeline()` returns. + An untyped source function, function stage, or destination that returns a stream is unsupported. + An iterable becomes `Duplex.from` of it, which the next stage reads from while the stages before it are not read, as in Node. + A pipeline branches at run time on whether each of its first three untyped stages after the first is a function; a function in a later untyped stage, an untyped web stream, and a `{ readable, writable }` pair after the first stage throw a not-supported error. + `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one. `Readable.from` accepts strings, Buffers, arrays, Sets, Maps, Map and Set iterators, generators, async generators, and class iterables and async iterables, and in JavaScript any iterable or async iterable. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index f63cc790c..24db2c4c2 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v45"); + expect(error?.stderr).toContain("scr_runtime_abi_v46"); }); }); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 8cefda8f0..30eef4b99 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -671,11 +671,13 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll // which also takes those streams. e.fn === "stream.unboxDyn" && host.usesDynBuiltinStream ? "scr_dyn_builtin_stream_unbox" - : // for await over a checked-dynamic value iterates a native - // stream behind it (scr_dyn_for_await_stream.c) - e.fn === "dyn.asyncIteratorOpen" && host.dynForAwaitStreams - ? "scr_stream_dyn_async_iterator_open" - : LIB_FN_SYMS[e.fn]); + : e.fn === "stream.unboxDynStage" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_unbox_stage" + : // for await over a checked-dynamic value iterates a native + // stream behind it (scr_dyn_for_await_stream.c) + e.fn === "dyn.asyncIteratorOpen" && host.dynForAwaitStreams + ? "scr_stream_dyn_async_iterator_open" + : LIB_FN_SYMS[e.fn]); if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); // The runtime's Date functions take and answer time values: a Date // operand passes its time value, read at once from the borrowed object, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4b3f34b80..016815819 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -818,6 +818,9 @@ export const LIB_FN_SYMS: Record = { "stream.stdioWritable": "scr_stream_stdio_writable", "stream.unboxDyn": "scr_stream_unbox_dyn", "stream.dynIterableSource": "scr_stream_dyn_iterable_source", + "stream.dynSourceValue": "scr_stream_dyn_source_value", + "stream.unboxDynStage": "scr_stream_unbox_dyn_stage", + "stream.dynTransformResult": "scr_stream_dyn_transform_result", "stream.handleStream": "scr_stream_handle", "stream.pipeDyn": "scr_stream_pipe_dyn", "stream.unpipeDyn": "scr_stream_unpipe_dyn", diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index fdbf13d4f..53f455964 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -743,7 +743,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV const helpers = packed(5 + n + k, helperCount); let at = 5 + n + k + helperCount; const take = (present: boolean): string => (present ? args[at++]!.name : "null"); - const box = take(((flags >> 1) & 3) === 2); + const box = take(((flags >> 1) & 3) >= 2); const signal = take((flags & 32) !== 0); const abort = take((flags & 32) !== 0); const head = diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index fe13879a2..892e9281e 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 45 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v45" as const; +export const RUNTIME_ABI_VERSION = 46 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v46" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 492b71217..9c5b61f58 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index fdb0ba29e..812fd9531 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -129,7 +129,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index c8699d856..2ae952aaa 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -2837,20 +2837,24 @@ export function streamClassAliasDecl( const PIPELINE_STAGE_EXPECTED = "of type function or an instance of Blob, ReadableStream, WritableStream, Stream, Iterable, AsyncIterable, or Promise or { readable, writable } pair"; -/** A checked-dynamic pipeline stage (an untyped source or destination): - * a boxed native stream unwraps at run time; other values (functions, - * pairs) throw the not-supported Error there. */ +/** A checked-dynamic pipeline stage taken as a stream: a boxed native + * stream unwraps at run time, and a value that is not stream-like or a + * function (an iterable after the first stage) becomes Duplex.from of it, + * as pipelineImpl's last branch does. Stream-like objects, web streams and + * { readable, writable } pairs throw the not-supported Error there, and so + * do functions, which reach this lowering only past + * MAX_DYN_FUNCTION_STAGES. */ function unboxDynStage(lowerer: Lowerer, v: IrExpr, node: ts.Expression): IrExpr { noteUntypedStreamFlow(lowerer, null); return { kind: "libCall", - fn: "stream.unboxDyn", + fn: "stream.unboxDynStage", args: [ v, strLit( lowerer.dynNodeStreams - ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" - : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + ? "pipeline stages other than native streams, iterables and functions (JavaScript stream objects, web streams, { readable, writable } pairs after the first stage, and functions after the first three untyped stages) are not supported yet" + : "pipeline stages other than native streams, iterables and functions (web streams, { readable, writable } pairs after the first stage, and functions after the first three untyped stages) are not supported yet", locOf(node), ), ], @@ -2930,6 +2934,102 @@ const BOXED_SOURCE_KINDS: ReadonlySet = new Set([ "string", ]); +/** The most checked-dynamic stages after the first that a pipeline + * branches on as functions; each one doubles the lowered pipeline calls. */ +const MAX_DYN_FUNCTION_STAGES = 3; + +/** A pipeline's arguments for one of the run-time branches + * lowerDynStagePipeline lowers. */ +interface DynStagePlan { + /** the first stage when it is checked-dynamic */ + source: DynPipelineSource | null; + /** argument values evaluated before the branches, in argument order */ + bound: ReadonlyMap; + /** a typed iterable first stage's source, lowered before the branches */ + preSource?: { closure: IrExpr; out: SyncGenType } | undefined; + /** the checked-dynamic stages this branch takes as functions */ + fns: ReadonlySet; +} + +/** pipeline() whose stages after the first include untyped values: + * pipelineImpl calls a stage that is a function with the previous stage + * and { signal }, and pipes into or takes any other value, so the call is + * lowered once per combination of those stages being functions or not, + * up to MAX_DYN_FUNCTION_STAGES of them, and a typeof test on each + * picks the branch at run time. The arguments other than function stages + * are evaluated first, in argument order. */ +function lowerDynStagePipeline( + lowerer: Lowerer, + stageArgs: readonly ts.Expression[], + src: DynPipelineSource | null, + loc: SrcLoc, + build: (plan: DynStagePlan) => IrExpr, +): IrExpr { + const candidates: number[] = []; + for (let j = 1; j < stageArgs.length; j++) { + const arg = stageArgs[j]!; + if (pipelineStageFunction(lowerer, arg) !== null || isValueStream(lowerer, arg)) continue; + if ((lowerer.typeOf(arg).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) { + candidates.push(j); + } + } + if (candidates.length === 0) return build({ source: src, bound: new Map(), fns: new Set() }); + const stmts: IrStmt[] = []; + const bind = (init: IrExpr): IrExpr => { + const local = lowerer.declareHiddenLocal("%pipelineArg", init.type); + stmts.push({ kind: "varDecl", localId: local.id, init, loc: init.loc }); + return varRef(local.id, init.type, init.loc); + }; + const source = src !== null && !src.iterable ? { expr: bind(src.expr), iterable: false } : src; + const bound = new Map(); + let preSource: DynStagePlan["preSource"]; + for (let j = src !== null ? 1 : 0; j < stageArgs.length; j++) { + const arg = stageArgs[j]!; + if (pipelineStageFunction(lowerer, arg) !== null) continue; + if (j === 0 && iterableSourceType(lowerer, arg) !== null) { + const lowered = lowerIterableSource(lowerer, arg, null, loc); + if (lowered === null) lowerer.noLowering("pipeline over this source", arg); + preSource = { closure: bind(lowered!.closure), out: lowered!.out }; + continue; + } + let value = lowerer.lowerExpr(arg); + // an untyped array, string, Set, Map or generator object that keeps + // its static type is taken through its checked-dynamic value + if ( + candidates.includes(j) && + BOXED_SOURCE_KINDS.has(value.type.kind) && + lowerer.dynConvertible(value.type) + ) { + value = { kind: "dynFrom", value, type: DYN, loc: value.loc }; + } + bound.set(j, bind(value)); + } + const dynStages = candidates + .filter((j) => bound.get(j)!.type.kind === "dyn") + .slice(0, MAX_DYN_FUNCTION_STAGES); + const branch = (i: number, fns: ReadonlySet): IrExpr => { + if (i === dynStages.length) return build({ source, bound, preSource, fns }); + const j = dynStages[i]!; + const asStream = branch(i + 1, fns); + const asFunction = branch(i + 1, new Set([...fns, j])); + // a function destination's promise resolves to its value + const type = + asFunction.type.kind === "promise" && asFunction.type.inner.kind === "dyn" + ? asFunction.type + : asStream.type; + return { + kind: "ternary", + cond: { kind: "dynTest", test: "function", value: bound.get(j)!, type: BOOL, loc }, + then: lowerer.coerceToExpected(asFunction, type), + else_: lowerer.coerceToExpected(asStream, type), + type, + loc, + }; + }; + const result = branch(0, new Set()); + return { kind: "seqExpr", stmts, result, type: result.type, loc }; +} + /** lowerPipelineStages' source arguments for a checked-dynamic first stage. */ function stagesSource( src: DynPipelineSource | null, @@ -2938,6 +3038,22 @@ function stagesSource( return src.iterable ? [undefined, src.expr] : [new Map([[0, src.expr]]), undefined]; } +/** A pipeline stage taken as a stream under a DynStagePlan: the + * checked-dynamic first stage's stream, a value bound before the + * branches, or the argument lowered here. */ +function planStreamArg( + lowerer: Lowerer, + plan: DynStagePlan, + node: ts.Expression, + i: number, +): IrExpr { + if (i === 0 && plan.source !== null) return plan.source.expr; + const bound = plan.bound.get(i); + return bound !== undefined + ? streamArgOf(lowerer, node, bound, "pipeline") + : lowerStreamArg(lowerer, node, "pipeline"); +} + /** A stream-rooted argument, lowered — or the pointed fence (pipeline's * iterable/generator/function stages have no lowering yet). `what` picks * the validation ladder: finished() rejects EVERY non-stream through @@ -3304,7 +3420,9 @@ export function lowerStreamModuleCall( : null; const stageArgs = opts ? args.slice(0, -1) : args; const build = (src: DynPipelineSource | null): IrExpr => - lowerPromisePipeline(lowerer, stageArgs, opts, src, loc); + lowerDynStagePipeline(lowerer, stageArgs, src, loc, (plan) => + lowerPromisePipeline(lowerer, stageArgs, opts, plan, loc), + ); return lowerDynSourcePipeline(lowerer, stageArgs[0]!, loc, build) ?? build(null); } return null; @@ -3312,17 +3430,23 @@ export function lowerStreamModuleCall( return lowerStreamModuleCallbackCall(lowerer, call, bi, loc); } -/** stream/promises pipeline(...stages[, options]), with the first stage - * pre-lowered when it is checked-dynamic (lowerDynSourcePipeline). */ +/** stream/promises pipeline(...stages[, options]), for one branch of + * lowerDynStagePipeline's plan. */ function lowerPromisePipeline( lowerer: Lowerer, stageArgs: readonly ts.Expression[], opts: ReturnType | null, - src: DynPipelineSource | null, + plan: DynStagePlan, loc: SrcLoc, ): IrExpr { - if (src?.iterable === true || hasPipelineFunctionStage(lowerer, stageArgs)) { - const staged = lowerPipelineStages(lowerer, stageArgs, loc, ...stagesSource(src)); + const src = plan.source; + if ( + src?.iterable === true || + plan.fns.size > 0 || + plan.preSource !== undefined || + hasPipelineFunctionStage(lowerer, stageArgs) + ) { + const staged = lowerPipelineStages(lowerer, stageArgs, loc, ...stagesSource(src), plan); const type = staged.destPromise ?? { kind: "promise", inner: VOID }; // A promise destination's value settles the result (same type). if (!opts) { @@ -3346,9 +3470,7 @@ function lowerPromisePipeline( } let streams: IrExpr[]; try { - streams = stageArgs.map((a, i) => - i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"), - ); + streams = stageArgs.map((a, i) => planStreamArg(lowerer, plan, a, i)); } catch (e) { if (e instanceof StreamArgTypeThrow) return e.expr; throw e; @@ -3530,7 +3652,9 @@ function lowerStreamModuleCallbackCall( ); } const build = (src: DynPipelineSource | null): IrExpr => - lowerCallbackPipeline(lowerer, call, args, src, loc); + lowerDynStagePipeline(lowerer, args.slice(0, -1), src, loc, (plan) => + lowerCallbackPipeline(lowerer, call, args, plan, loc), + ); return lowerDynSourcePipeline(lowerer, args[0]!, loc, build) ?? build(null); } @@ -3544,23 +3668,32 @@ function lowerStreamModuleCallbackCall( return null; } -/** pipeline(...stages, callback), with the first stage pre-lowered when it - * is checked-dynamic (lowerDynSourcePipeline). */ +/** pipeline(...stages, callback), for one branch of lowerDynStagePipeline's + * plan. */ function lowerCallbackPipeline( lowerer: Lowerer, call: ts.CallExpression, args: readonly ts.Expression[], - src: DynPipelineSource | null, + plan: DynStagePlan, loc: SrcLoc, ): IrExpr { - if (src?.iterable === true || hasPipelineFunctionStage(lowerer, args.slice(0, -1))) { - const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc, ...stagesSource(src)); - const cbNode = args[args.length - 1]!; - const cbParams = - ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) - ? cbNode.parameters.nodes.length - : 0; - if (staged.dest === 2 || (staged.dest === 1 && cbParams > 1)) { + const src = plan.source; + const cbNode = args[args.length - 1]!; + const cbParams = + ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) + ? cbNode.parameters.nodes.length + : 0; + // A branch over untyped stages whose destination is a stream: the + // callback still receives (err, value), value undefined, as in Node. + const dynCallback = plan.bound.size > 0 && cbParams > 1; + if ( + src?.iterable === true || + plan.fns.size > 0 || + plan.preSource !== undefined || + hasPipelineFunctionStage(lowerer, args.slice(0, -1)) + ) { + const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc, ...stagesSource(src), plan); + if (staged.dest >= 2 || (staged.dest === 1 && cbParams > 1) || dynCallback) { // callback(err, value): the destination's value (undefined for // an async generator) reaches the callback through the // checked-dynamic path. @@ -3595,15 +3728,15 @@ function lowerCallbackPipeline( } let streams: IrExpr[]; try { - streams = args - .slice(0, -1) - .map((a, i) => (i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"))); + streams = args.slice(0, -1).map((a, i) => planStreamArg(lowerer, plan, a, i)); } catch (e) { if (e instanceof StreamArgTypeThrow) return e.expr; throw e; } const last = streams[streams.length - 1]!; - const { cb, dyn } = lowerEosCallback(lowerer, "pipeline", args[args.length - 1]!, last.type); + const { cb, dyn } = dynCallback + ? { cb: lowerer.lowerExprExpecting(cbNode, DYN), dyn: true } + : lowerEosCallback(lowerer, "pipeline", cbNode, last.type); return { kind: "libCall", fn: dyn ? "stream.pipelineDyn" : "stream.pipeline", @@ -4213,6 +4346,397 @@ function lowerDynIterableSource( }; } +/** The async generator a checked-dynamic stage's iterable is pumped + * through. */ +function dynStageGenType(): SyncGenType { + return { kind: "generator", async: true, yieldT: DYN, retT: VOID, nextT: UNDEFINED_T }; +} + +/** A checked-dynamic pipeline stage after the first that is a function at + * run time, as the stage closure the runtime calls with the previous + * stage and { signal } (pipelineImpl's `typeof stream === 'function'` + * branch): it calls the function with the previous stream or generator + * as an untyped value. Before the last stage the result must be async + * iterable (ERR_INVALID_RETURN_VALUE otherwise) and is pumped on through + * lowerDynIterableSource's generator; the last stage answers the result, + * which the runtime classifies (SCR_PIPE_DEST_DYN). A previous stage + * whose type has no untyped form throws the not-supported Error. */ +function lowerDynFunctionStage( + lowerer: Lowerer, + fnValue: IrExpr, + srcT: IrType, + j: number, + isLast: boolean, + loc: SrcLoc, + handoff?: DynStageHandoff, +): { closure: IrExpr; out: IrType } { + const out: IrType = isLast ? DYN : dynStageGenType(); + const stageT = funcOf([srcT, DYN], out); + const take = handoff?.take === true; + const give = handoff?.give === true && !isLast; + const key = `dynFunctionStage:${isLast ? "dest" : "transform"}:${typeKey(srcT)}:${take ? 1 : 0}${give ? 1 : 0}`; + let maker = lowerer.valueHelpers.get(key); + if (!maker) { + maker = `%stream.dynFunctionStage.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, maker); + const stageName = `${maker}.stage`; + const frame = new LiftedFrame(loc); + const fnId = frame.local("fn", DYN, false); + const labelId = frame.local("label", STRING, false); + const cellId = frame.local("cell", DYN, false); + const srcId = frame.param("src", srcT); + const optsId = frame.param("opts", DYN); + const inputId = frame.local("input", DYN, false); + const resultId = frame.local("result", DYN, false); + const nextId = frame.local("next", DYN, false); + const cell = (): IrExpr => varRef(cellId, DYN, loc); + const body: IrStmt[] = []; + if (take) { + // the previous untyped stage's value, handed over as it is + // (makeAsyncIterable answers an iterable itself) + body.push( + { kind: "varDecl", localId: inputId, init: handoffTake(cell(), loc), loc }, + handoffPut(cell(), dynUndefinedExpr(loc), loc), + ); + } + if (!take && !lowerer.dynConvertible(srcT)) { + body.push({ + kind: "return", + value: nodeThrowExpr( + 0, + "SC2020", + `pipeline function stages after a stage of type '${lowerer.fmt(srcT)}' are not supported yet`, + out, + loc, + ), + loc, + }); + } else { + body.push({ + kind: "varDecl", + localId: resultId, + init: { + kind: "dynCall", + callee: varRef(fnId, DYN, loc), + receiver: dynUndefinedExpr(loc), + calleeName: "transform", + args: [ + take + ? varRef(inputId, DYN, loc) + : { kind: "dynFrom", value: varRef(srcId, srcT, loc), type: DYN, loc }, + varRef(optsId, DYN, loc), + ], + type: DYN, + loc, + }, + loc, + }); + if (isLast) { + body.push({ kind: "return", value: varRef(resultId, DYN, loc), loc }); + } else { + body.push({ + kind: "varDecl", + localId: nextId, + init: { + kind: "libCall", + fn: "stream.dynTransformResult", + args: [varRef(resultId, DYN, loc), varRef(labelId, STRING, loc)], + type: DYN, + loc, + }, + loc, + }); + // the next untyped function stage takes the result itself; the + // generator returned for the runtime is then never read + if (give) body.push(handoffPut(cell(), varRef(nextId, DYN, loc), loc)); + const iterable = lowerDynIterableSource(lowerer, varRef(nextId, DYN, loc), loc); + if (!iterable) + throw new InternalCompilerError("dyn iterable generator without a result record"); + body.push({ + kind: "return", + value: { kind: "callValue", callee: iterable.closure, args: [], type: out, loc }, + loc, + }); + } + } + const captured = take || give ? [fnId, labelId, cellId] : [fnId, labelId]; + for (const id of captured) frame.locals.find((l) => l.id === id)!.boxed = true; + lowerer.liftedFns.push({ + name: stageName, + params: frame.params, + returnType: out, + captures: [ + { localId: fnId, name: "fn", type: DYN }, + { localId: labelId, name: "label", type: STRING }, + ...(take || give ? [{ localId: cellId, name: "cell", type: DYN }] : []), + ], + locals: frame.locals, + body, + loc, + }); + const makerFn: IrLocal = { id: "fn.0", name: "fn", type: DYN, mutable: false, boxed: true }; + const makerLabel: IrLocal = { + id: "label.1", + name: "label", + type: STRING, + mutable: false, + boxed: true, + }; + const makerCell: IrLocal = { + id: "cell.2", + name: "cell", + type: DYN, + mutable: false, + boxed: true, + }; + const makerLocals = take || give ? [makerFn, makerLabel, makerCell] : [makerFn, makerLabel]; + lowerer.liftedFns.push({ + name: maker, + params: makerLocals.map((l) => ({ localId: l.id, name: l.name, type: l.type })), + returnType: stageT, + locals: makerLocals, + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: stageName, + captures: makerLocals.map((l) => l.id), + type: stageT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + closure: { + kind: "call", + callee: maker, + args: [fnValue, strLit(`transform[${j - 1}]`, loc), ...(take || give ? [handoff!.cell] : [])], + type: stageT, + loc, + }, + out, + }; +} + +/** The cell that carries an untyped stage's value to the untyped function + * stage after it, created per pipeline call. A stage that gives stores its + * value; the next stage takes it instead of the generator the runtime + * passes, as Node's makeAsyncIterable answers an iterable itself. */ +interface DynStageHandoff { + cell: IrExpr; + take: boolean; + give: boolean; +} + +function handoffPut(cell: IrExpr, value: IrExpr, loc: SrcLoc): IrStmt { + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.keySet", + args: [cell, strLit("v", loc), value], + type: VOID, + loc, + }, + loc, + }; +} + +function handoffTake(cell: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "dynKeyGet", value: cell, key: strLit("v", loc), type: DYN, loc }; +} + +/** `(value) => generator over value`: the async generator an untyped + * destination function's async iterable result is pumped through + * (SCR_PIPE_DEST_DYN's box), interned. */ +function dynStageIterHelper(lowerer: Lowerer, loc: SrcLoc): IrExpr { + const genT = dynStageGenType(); + const type = funcOf([DYN], genT); + const key = "dynStageIter"; + let name = lowerer.valueHelpers.get(key); + if (!name) { + name = `%stream.dynStageIter.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + const v: IrLocal = { id: "value.0", name: "value", type: DYN, mutable: false }; + const iterable = lowerDynIterableSource(lowerer, varRef(v.id, DYN, loc), loc); + if (!iterable) + throw new InternalCompilerError("dyn iterable generator without a result record"); + lowerer.liftedFns.push({ + name, + params: [{ localId: v.id, name: v.name, type: DYN }], + returnType: genT, + locals: [v], + body: [ + { + kind: "return", + value: { kind: "callValue", callee: iterable.closure, args: [], type: genT, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type, loc }; +} + +/** A checked-dynamic first pipeline stage pumped as an iterable + * (pipelineImpl's i === 0 branch), as the stage closure the runtime calls + * with { signal } while it sets the pipeline up: a function is called + * with { signal } and its result must be iterable, a { readable, writable } + * pair pumps its readable side, and any other value is pumped itself, + * through lowerDynIterableSource's generator. */ +function lowerDynSourceStage( + lowerer: Lowerer, + value: IrExpr, + loc: SrcLoc, + cell?: IrExpr, +): { closure: IrExpr; out: SyncGenType } | null { + const out: SyncGenType = { + kind: "generator", + async: true, + yieldT: DYN, + retT: VOID, + nextT: UNDEFINED_T, + }; + const stageT = funcOf([DYN], out); + const give = cell !== undefined; + const key = give ? "dynSourceStage:give" : "dynSourceStage"; + let maker = lowerer.valueHelpers.get(key); + if (!maker) { + maker = `%stream.dynSourceStage.${lowerer.valueHelpers.size}`; + const stageName = `${maker}.stage`; + const frame = new LiftedFrame(loc); + const srcId = frame.local("src", DYN, false); + const cellId = frame.local("cell", DYN, false); + const optsId = frame.param("opts", DYN); + const valueId = frame.local("value", DYN, false); + const genId = frame.local("gen", out, false); + const src = (): IrExpr => varRef(srcId, DYN, loc); + const sourceValue = (v: IrExpr, returned: boolean): IrExpr => ({ + kind: "libCall", + fn: "stream.dynSourceValue", + args: [v, boolLit(returned, loc)], + type: DYN, + loc, + }); + const iterable = lowerDynIterableSource(lowerer, varRef(valueId, DYN, loc), loc); + if (!iterable) return null; + lowerer.valueHelpers.set(key, maker); + const body: IrStmt[] = [ + { + kind: "varDecl", + localId: valueId, + init: { + kind: "ternary", + cond: { kind: "dynTest", test: "function", value: src(), type: BOOL, loc }, + then: sourceValue( + { + kind: "dynCall", + callee: src(), + receiver: dynUndefinedExpr(loc), + calleeName: "source", + args: [varRef(optsId, DYN, loc)], + type: DYN, + loc, + }, + true, + ), + else_: sourceValue(src(), false), + type: DYN, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: genId, + init: { kind: "callValue", callee: iterable.closure, args: [], type: out, loc }, + loc, + }, + ]; + if (give) { + // the untyped function stage after the source takes the iterable + // itself, as Node's makeAsyncIterable answers it; a stream (a pair's + // readable side) goes as the generator over it, as Node iterates a + // stream there + const v = varRef(valueId, DYN, loc); + body.push( + handoffPut( + varRef(cellId, DYN, loc), + { + kind: "ternary", + cond: { kind: "libCall", fn: "stream.dynIterableSource", args: [v], type: BOOL, loc }, + then: v, + else_: { kind: "dynFrom", value: varRef(genId, out, loc), type: DYN, loc }, + type: DYN, + loc, + }, + loc, + ), + ); + } + body.push({ kind: "return", value: varRef(genId, out, loc), loc }); + const captured = give ? [srcId, cellId] : [srcId]; + for (const id of captured) frame.locals.find((l) => l.id === id)!.boxed = true; + lowerer.liftedFns.push({ + name: stageName, + params: frame.params, + returnType: out, + captures: [ + { localId: srcId, name: "src", type: DYN }, + ...(give ? [{ localId: cellId, name: "cell", type: DYN }] : []), + ], + locals: frame.locals, + body, + loc, + }); + const makerSrc: IrLocal = { id: "src.0", name: "src", type: DYN, mutable: false, boxed: true }; + const makerCell: IrLocal = { + id: "cell.1", + name: "cell", + type: DYN, + mutable: false, + boxed: true, + }; + const makerLocals = give ? [makerSrc, makerCell] : [makerSrc]; + lowerer.liftedFns.push({ + name: maker, + params: makerLocals.map((l) => ({ localId: l.id, name: l.name, type: l.type })), + returnType: stageT, + locals: makerLocals, + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: stageName, + captures: makerLocals.map((l) => l.id), + type: stageT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + closure: { + kind: "call", + callee: maker, + args: give ? [value, cell!] : [value], + type: stageT, + loc, + }, + out, + }; +} + function lowerIterableSource( lowerer: Lowerer, node: ts.Expression, @@ -4356,7 +4880,7 @@ function lowerIterableSource( interface PipelineStagesLowering { args: IrExpr[]; last: IrType; - dest: 0 | 1 | 2; + dest: 0 | 1 | 2 | 3; destPromise: (IrType & { kind: "promise" }) | null; wrap: (call: IrExpr) => IrExpr; } @@ -4377,6 +4901,7 @@ function lowerPipelineStages( loc: SrcLoc, preLowered?: ReadonlyMap, dynSource?: IrExpr, + plan?: DynStagePlan, ): PipelineStagesLowering { if (stageArgs.length > 52) { lowerer.noLowering( @@ -4390,7 +4915,7 @@ function lowerPipelineStages( const helperArgs: IrExpr[] = []; let positions = 0; let mask = 0; - let dest: 0 | 1 | 2 = 0; + let dest: 0 | 1 | 2 | 3 = 0; let destPromise: (IrType & { kind: "promise" }) | null = null; // What the next function stage receives: null before the first stage, // else the previous stream's type or the previous stage's generator. @@ -4399,17 +4924,69 @@ function lowerPipelineStages( // before all stages, so when a stage precedes a stream the arguments // are bound to locals first, keeping JavaScript's evaluation order. const argOrder: { list: IrExpr[]; index: number }[] = []; + // An untyped stage followed by an untyped function stage hands its value + // over through one cell per call (DynStageHandoff). + let handoffCell: IrLocal | null = null; + const cellRef = (): IrExpr => { + handoffCell ??= lowerer.declareHiddenLocal("%pipelineHandoff", DYN); + return varRef(handoffCell.id, DYN, loc); + }; + let takes = false; for (let j = 0; j < stageArgs.length; j++) { const arg = stageArgs[j]!; + const givesToNext = plan?.fns.has(j + 1) === true; if (j === 0 && dynSource !== undefined) { - const source = lowerDynIterableSource(lowerer, dynSource, loc); + const source = lowerDynSourceStage( + lowerer, + dynSource, + loc, + givesToNext ? cellRef() : undefined, + ); + takes = givesToNext; if (source === null) lowerer.noLowering("pipeline over this source", arg); + // a source function takes { signal } + mask |= 1 << stages.length; positions += 1; argOrder.push({ list: stages, index: stages.length }); stages.push(source!.closure); srcT = source!.out; continue; } + if (plan?.fns.has(j) === true) { + // a checked-dynamic stage that is a function at run time + const isLast = j === stageArgs.length - 1; + const take = takes; + const give = givesToNext && !isLast; + takes = give; + const lowered = lowerDynFunctionStage( + lowerer, + plan.bound.get(j)!, + srcT!, + j, + isLast, + loc, + take || give ? { cell: cellRef(), take, give } : undefined, + ); + mask |= 1 << stages.length; + positions += 2 ** j; + argOrder.push({ list: stages, index: stages.length }); + stages.push(lowered.closure); + if (isLast) { + // its result is classified at run time: a promise settles the + // pipeline's value, and an async iterable is pumped into the + // PassThrough + const genT = dynStageGenType(); + const helpers = fromGenHelpers(lowerer, genT, loc); + if (!helpers) lowerer.noLowering("a pipeline destination with this generator type", arg); + helperArgs.push(helpers![0]!, helpers![1]!, helpers![2]!); + dest = 3; + destPromise = { kind: "promise", inner: DYN }; + srcT = null; + } else { + srcT = lowered.out; + } + continue; + } // compose's head and tail functions arrive as Duplex.from streams const fn = preLowered?.has(j) ? null : pipelineStageFunction(lowerer, arg); const syncSourceFn = @@ -4423,7 +5000,9 @@ function lowerPipelineStages( !preLowered?.has(j) && (syncSourceFn || (fn === null && iterableSourceType(lowerer, arg) !== null)) ) { - const source = lowerIterableSource(lowerer, arg, syncSourceFn ? fn : null, loc); + const source = + (syncSourceFn ? undefined : plan?.preSource) ?? + lowerIterableSource(lowerer, arg, syncSourceFn ? fn : null, loc); if (source === null) lowerer.noLowering("pipeline over this source", arg); positions += 1; argOrder.push({ list: stages, index: stages.length }); @@ -4448,7 +5027,12 @@ function lowerPipelineStages( } let stream: IrExpr; try { - stream = preLowered?.get(j) ?? lowerStreamArg(lowerer, arg, "pipeline"); + const bound = plan?.bound.get(j); + stream = + preLowered?.get(j) ?? + (bound !== undefined + ? streamArgOf(lowerer, arg, bound, "pipeline") + : lowerStreamArg(lowerer, arg, "pipeline")); } catch (e) { if (e instanceof StreamArgTypeThrow) lowerer.noLowering("pipeline over this value", arg); throw e; @@ -4527,6 +5111,8 @@ function lowerPipelineStages( ); } helperArgs.push(pipelineBoxHelper(lowerer, destPromise!, loc)); + } else if (dest === 3) { + helperArgs.push(dynStageIterHelper(lowerer, loc)); } const argDecls: IrStmt[] = []; const firstStage = argOrder.findIndex((a) => a.list === stages); @@ -4538,6 +5124,15 @@ function lowerPipelineStages( a.list[a.index] = varRef(local.id, init.type, init.loc); } } + if (handoffCell !== null) { + const cell: IrLocal = handoffCell; + argDecls.unshift({ + kind: "varDecl", + localId: cell.id, + init: { kind: "dynObjLit", fields: [], type: DYN, loc }, + loc, + }); + } const bindArgs = (call: IrExpr): IrExpr => argDecls.length === 0 ? call diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index b0cc96687..86f9ad233 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -92,6 +92,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "child.stdioStream", "stream.unboxDyn", "stream.dynIterableSource", + "stream.dynSourceValue", + "stream.unboxDynStage", + "stream.dynTransformResult", "stream.handleStream", "stream.pipeDyn", "stream.unpipeDyn", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e230932ba..b7ef7d660 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2870,9 +2870,27 @@ function buildLibFnSignatures() { result: { kind: "object", className: "%Duplex" }, }, /** Whether pipeline() pumps a checked-dynamic first stage as an - * iterable (a string, or a value with an iterator method that is not a - * stream with pipe() and on()) instead of piping it as a stream. */ + * iterable (a string, a value with an iterator method that is not a + * stream with pipe() and on(), a { readable, writable } pair, or a + * source function's result) instead of piping it as a stream. */ "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, + /** The value pipeline() pumps for a checked-dynamic first stage: a + * source function's result (the flag set), which must be iterable, else + * a { readable, writable } pair's readable side or the value itself. */ + "stream.dynSourceValue": { argTypes: [DYN, BOOL], result: DYN }, + /** An untyped pipeline function stage's result before the last stage, + * which must be async iterable: ERR_INVALID_RETURN_VALUE naming the + * stage ("transform[i]") otherwise. Answers the value (+1). */ + "stream.dynTransformResult": { argTypes: [DYN, STRING], result: DYN }, + /** A checked-dynamic pipeline() stage taken as a stream: a native stream + * (+1), or Duplex.from of a value that is not stream-like or a function + * (an iterable), which the pipeline uses without piping into it. Other + * values throw: Duplex.from's ERR_INVALID_ARG_TYPE, or the given + * not-supported message. */ + "stream.unboxDynStage": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, /** The Transform a Cipheriv or Decipheriv reads and writes as a stream * (+1, created on first use and owned by the cipher). */ "stream.handleStream": { @@ -5237,10 +5255,12 @@ export type IrLibFn = keyof ReturnType; /** The function-stage runs of a pipeline of `total` stages that a stream * follows (positions bit j: stage j is a function), and an async * generator destination's run (flags bits 1-2 = 1, pumped into the - * PassThrough pipeline() answers): each pumps its last generator through - * one (next, read, return) triple. */ + * PassThrough pipeline() answers, or 3, an untyped destination whose + * result may be pumped there): each pumps its last generator through one + * (next, read, return) triple. */ export function pipelinePumpedRuns(total: number, positions: number, flags: number): number { - let runs = ((flags >> 1) & 3) === 1 ? 1 : 0; + const dest = (flags >> 1) & 3; + let runs = dest === 1 || dest === 3 ? 1 : 0; for (let j = 0; j + 1 < total; j++) { if (Math.floor(positions / 2 ** j) % 2 === 1 && Math.floor(positions / 2 ** (j + 1)) % 2 === 0) runs++; diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 494c4e3e8..630ebdc67 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -278,6 +278,9 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.stdioWritable", "stream.unboxDyn", "stream.dynIterableSource", + "stream.dynSourceValue", + "stream.dynTransformResult", + "stream.unboxDynStage", "stream.handleStream", "stream.unpipeDyn", "stream.workerStdio", @@ -314,6 +317,7 @@ const READABLE_DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "stream.duplexFromAsyncFn", "stream.duplexFromGenSignal", "stream.duplexFromAsyncFnSignal", + "stream.unboxDynStage", ]); /** libCall name prefixes of the Readable entry points that await through the dyn-async unit. */ diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 45ebf5c79..84751cc06 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5761,7 +5761,7 @@ function validateFunction( } const lastIsFunction = Math.floor(positions! / 2 ** (total - 1)) % 2 === 1; const helperCount = 3 * pipelinePumpedRuns(total, positions!, flags!); - const groups = helperCount + (destKind === 2 ? 1 : 0) + (flags! & 32 ? 2 : 0); + const groups = helperCount + (destKind >= 2 ? 1 : 0) + (flags! & 32 ? 2 : 0); const cbArgs = e.fn === "sp.pipelineStagesOpts" ? 1 + (flags! & 64 ? 1 : 0) @@ -5776,7 +5776,7 @@ function validateFunction( total > 52 || positions! >= 2 ** total || functions !== k || - destKind === 3 || + (destKind === 3 && e.fn === "stream.composeStages") || (destKind !== 0) !== lastIsFunction || total < 2 || e.args.length !== 5 + n! + k! + groups + cbArgs @@ -5795,7 +5795,7 @@ function validateFunction( err(`libCall ${e.fn} stage function ${i} must be a func`, e.loc); } at2 += k!; - const helpers = helperCount + (destKind === 2 ? 1 : 0); + const helpers = helperCount + (destKind >= 2 ? 1 : 0); for (let i = at2; i < at2 + helpers; i++) { if (e.args[i]?.type.kind !== "func") err(`libCall ${e.fn} helper ${i} must be a func`, e.loc); diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index a57d96f01..bdad62203 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v45"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v46"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v45"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v46"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json new file mode 100644 index 000000000..4027063d4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7508-pipeline-untyped-source-functions.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json new file mode 100644 index 000000000..028a5c1c6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7509-pipeline-untyped-function-stages.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json new file mode 100644 index 000000000..be18c801a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7510-pipeline-untyped-later-iterables.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json new file mode 100644 index 000000000..2353cc44c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7511-pipeline-untyped-stages.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json new file mode 100644 index 000000000..acea0e98d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7512-pipeline-any-stages.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json new file mode 100644 index 000000000..39a5dba57 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7513-pipeline-untyped-stage-errors.mjs" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index 3305d69de..f8f5d0bf1 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -366,7 +366,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 8c0863b04..ed32f43fb 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -469,7 +469,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 67d4aca15..18c715cb2 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v45(void) {} +void scr_runtime_abi_v46(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index 206463e18..92392897a 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v45(void); +void scr_runtime_abi_v46(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 2d916a825..63dc0d209 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1548,6 +1548,9 @@ ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); struct ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message); +/* stream.unboxDynStage in those programs (scr_stream_unbox_dyn_stage's + * contract, also taking constructor-function streams). */ +struct ScrStream *scr_dyn_builtin_stream_unbox_stage(const ScrDyn *value, const ScrStr *message); /* stream.dynNative in those programs: scr_stream_dyn_native's contract, * also taking constructor-function streams that keep the runtime's write * and end (+1 or NULL). */ diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c index 2875cfa06..52c1e7269 100644 --- a/packages/runtime/src/scr_dyn_builtin_stream.c +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -1752,3 +1752,11 @@ ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *messa } return scr_stream_unbox_dyn(value, message); } + +/* stream.unboxDynStage in programs that link this unit: a + * constructor-function stream as scr_dyn_builtin_stream_unbox takes it, + * any other value as scr_stream_unbox_dyn_stage does. */ +ScrStream *scr_dyn_builtin_stream_unbox_stage(const ScrDyn *value, const ScrStr *message) { + if (bcs_of(value) && value->kind == SCR_DYN_OBJ) return scr_dyn_builtin_stream_unbox(value, message); + return scr_stream_unbox_dyn_stage(value, message); +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 201d6e34b..a7593b551 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -93,6 +93,10 @@ struct ScrStreamState { bool is_transform; /* the writable side routes through transform/flush */ bool allow_half_open; bool auto_destroy, emit_close; + /* Duplex.from of a pipeline() argument that is not a stream: the + * pipeline takes it as that stage's stream without wiring the stage + * (scr_stream_unbox_dyn_stage) */ + bool pipeline_body; /* shared lifecycle */ bool destroyed, destroy_calling, error_emitted, close_emitted, error_scheduled, close_scheduled; @@ -3850,7 +3854,9 @@ typedef ScrPromise *(*ScrPipeNextFn)(ScrClosure *, ScrGen *); typedef ScrDyn *(*ScrPipeReadFn)(ScrClosure *, void *); typedef ScrDyn *(*ScrPipeBoxFn)(ScrClosure *, ScrPromise *); -enum { SCR_PIPE_DEST_STREAM, SCR_PIPE_DEST_GEN, SCR_PIPE_DEST_PROMISE }; +/* SCR_PIPE_DEST_DYN: an untyped destination function's result, a + * checked-dynamic value the pipeline classifies (scr_pipe_dest_dyn_kind) */ +enum { SCR_PIPE_DEST_STREAM, SCR_PIPE_DEST_GEN, SCR_PIPE_DEST_PROMISE, SCR_PIPE_DEST_DYN }; /* flags bit 7: the callback is typed code's (stream.pipelineStagesDyn) */ enum { SCR_PIPE_TYPED_CB = 128 }; @@ -3868,7 +3874,9 @@ typedef struct { * async generator destination's PassThrough included), in pipeline * order, or NULL */ ScrClosure **helpers; - ScrClosure *box; /* SCR_PIPE_DEST_PROMISE: boxes its promise as a dyn promise */ + /* SCR_PIPE_DEST_PROMISE: boxes its promise as a dyn promise; + * SCR_PIPE_DEST_DYN: answers the async generator over a dyn iterable */ + ScrClosure *box; } ScrPipeStages; static bool scr_pipe_is_stage(const ScrPipeStages *fs, size_t j) { @@ -4234,6 +4242,71 @@ static void scr_pipe_dest_promise(ScrStreamPipeCtx *ctx, ScrPromise *p /*moves*/ scr_promise_release(scr_async_spawn_after(d->boxed->v.promise, &scr_pipe_dest_entry, d)); } +/* An untyped destination's promise: scr_pipe_dest_promise over a promise + * that already holds dyn values. */ +static void scr_pipe_dest_dyn_promise(ScrStreamPipeCtx *ctx, ScrDyn *boxed /*moves*/, size_t pi, + bool end) { + ScrPipeDest *d = malloc(sizeof *d); + if (!d) scr_stream_oom(); + d->ctx = scr_stream_pipectx_retain_v(ctx); + d->typed = scr_promise_retain(boxed->v.promise); + d->boxed = boxed; + d->pi = pi; + d->end = end; + scr_promise_release(scr_async_spawn_after(d->boxed->v.promise, &scr_pipe_dest_entry, d)); +} + +typedef ScrGen *(*ScrPipeIterFn)(ScrClosure *, ScrDyn *); +static bool scr_stream_dyn_has_method(const ScrDyn *value, const char *key); +static bool scr_stream_dyn_transform_like(const ScrDyn *value); +static void scr_stream_throw_web_stage(void); +static bool scr_stream_dyn_stage_returned_stream(const ScrDyn *value); +static void scr_stream_throw_invalid_return(const char *expected, const char *name, + const ScrDyn *got); + +/* What pipelineImpl does with an untyped destination function's result: + * a promise settles the pipeline's value (the `then` branch), and an + * async iterable, or a { readable, writable } pair's readable side, is + * pumped into the PassThrough. *out answers the promise or that + * iterable's async generator (+1). Other values throw + * ERR_INVALID_RETURN_VALUE, and thenables that are not promises and web + * streams throw the not-supported Error; -1 then. */ +enum { SCR_PIPE_DYN_PROMISE, SCR_PIPE_DYN_ITER }; +static int scr_pipe_dest_dyn_kind(const ScrPipeStages *fs, ScrDyn *r, void **out) { + if (r->kind == SCR_DYN_PROMISE) { + *out = scr_dyn_retain(r); + return SCR_PIPE_DYN_PROMISE; + } + if (r->kind == SCR_DYN_OBJ && scr_stream_dyn_has_method(r, "then")) { + static const char msg[] = + "pipeline destinations that return a thenable other than a promise are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); + return -1; + } + if (scr_exc_pending() || scr_stream_dyn_stage_returned_stream(r)) return -1; + ScrDyn *read = NULL; + int kind = scr_dyn_iterable_kind(r); + if (kind < 0) return -1; + if (r->kind == SCR_DYN_HANDLE && + (r->v.handle.tag == SCR_DYNH_WEB_STREAM || r->v.handle.tag == SCR_DYNH_WEB_TRANSFORM)) { + scr_stream_throw_web_stage(); + return -1; + } + if (kind == 2) { + read = scr_dyn_retain(r); + } else if (scr_stream_dyn_transform_like(r)) { + read = scr_dyn_obj_read(r, "readable", 8); + } + if (!read) { + if (!scr_exc_pending()) scr_stream_throw_invalid_return("AsyncIterable or Promise", "destination", r); + return -1; + } + ScrGen *g = ((ScrPipeIterFn)fs->box->fn)(fs->box, read); + if (!g) return -1; + *out = g; + return SCR_PIPE_DYN_ITER; +} + static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosure *cb, ScrStreamErrInv inv, bool end_opt, ScrDyn *signal, ScrAbortAddFn add, const ScrPipeStages *fs, @@ -4294,8 +4367,18 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu : (void *)scr_stream_retain(ret); ret_gen = NULL; void *out = scr_pipe_call_stage(fs, k++, src); + int dyn_kind = -1; + if (!scr_exc_pending() && dest && fs->dest == SCR_PIPE_DEST_DYN) { + ScrDyn *r = out ? out : scr_dyn_retain(scr_dyn_undefined()); + out = NULL; + dyn_kind = scr_pipe_dest_dyn_kind(fs, r, &out); + scr_dyn_release(r); + } if (scr_exc_pending()) { - if (out && dest) scr_promise_release(out); + if (out && dest && fs->dest == SCR_PIPE_DEST_DYN) { + if (dyn_kind == SCR_PIPE_DYN_ITER) scr_gen_release(out); + else scr_dyn_release(out); + } else if (out && dest) scr_promise_release(out); else if (out) scr_gen_release(out); scr_stream_pipectx_drop(ctx); scr_stream_pipectx_release_v(ctx); @@ -4315,9 +4398,12 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu size_t pi = ctx->n++; ctx->streams[pi] = pt; ctx->finish_count++; - if (fs->dest == SCR_PIPE_DEST_GEN) { + if (fs->dest == SCR_PIPE_DEST_GEN || + (fs->dest == SCR_PIPE_DEST_DYN && dyn_kind == SCR_PIPE_DYN_ITER)) { /* pumpToNode(ret, pt, finish, { end }) */ scr_pump_start(ctx, out, fs->helpers + 3 * h++, pt, end); + } else if (fs->dest == SCR_PIPE_DEST_DYN) { + scr_pipe_dest_dyn_promise(ctx, out, pi, end); } else { scr_pipe_dest_promise(ctx, out, fs->box, pi, end); } @@ -4332,6 +4418,20 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu continue; } ScrStream *s = streams[i]; + if (s->st->pipeline_body) { + /* ret = Duplex.from(stream) for an argument that is not a stream: + * no destroyer or 'error' listener, and the previous stage is + * neither piped nor pumped into it */ + s->st->pipeline_body = false; + if (ret_gen) { + scr_gen_release(ret_gen); + ret_gen = NULL; + h++; + } + ret = s; + i++; + continue; + } bool writing = j > 0; if (reading && !scr_pipe_is_stage(fs, j + 1) && (streams[i + 1]->st->close_emitted || streams[i + 1]->st->destroyed)) { @@ -8593,10 +8693,90 @@ ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) { return NULL; } -/* pipeline.js's first stage: a value with pipe() and on() methods is a - * readable node stream, which the stage ladder tests before isIterable; - * a string, or a value with a Symbol.asyncIterator or Symbol.iterator - * method, is an iterable the pipeline pumps into the next stage. */ +/* Whether a checked-dynamic object's member is of type "object". */ +static bool scr_stream_dyn_member_object(const ScrDyn *value, const char *key) { + ScrDyn *m = scr_dyn_obj_read(value, key, strlen(key)); + if (!m) return false; + ScrStr *t = scr_dyn_typeof(m); + bool object = t->len == 6 && memcmp(t->data, "object", 6) == 0; + scr_str_release(t); + scr_dyn_release(m); + return object; +} + +/* pipeline.js's isTransformStream for a value that is not a stream: an + * object whose readable and writable members are both of type "object". */ +static bool scr_stream_dyn_transform_like(const ScrDyn *value) { + if (value->kind == SCR_DYN_HANDLE) return value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM; + return value->kind == SCR_DYN_OBJ && scr_stream_dyn_member_object(value, "readable") && + !scr_exc_pending() && scr_stream_dyn_member_object(value, "writable"); +} + +/* Whether a checked-dynamic object has a function member `key`. */ +static bool scr_stream_dyn_has_method(const ScrDyn *value, const char *key) { + ScrDyn *m = scr_dyn_obj_read(value, key, strlen(key)); + if (!m) return false; + bool fn = m->kind == SCR_DYN_FUNC; + scr_dyn_release(m); + return fn; +} + +/* pipeline.js's isNodeStream and isWebStream for a checked-dynamic value + * that is not a native stream: a stream-like object or a web stream, which + * the pipeline would pipe or pump into and has no lowering for. */ +static bool scr_stream_dyn_stream_like(const ScrDyn *value) { + if (value->kind == SCR_DYN_HANDLE) { + int tag = value->v.handle.tag; + return tag == SCR_DYNH_WEB_STREAM || tag == SCR_DYNH_WEB_TRANSFORM || tag == SCR_DYNH_WEB_WRITABLE; + } + if (value->kind != SCR_DYN_OBJ) return false; + static const char *const state_keys[] = {"_readableState", "_writableState"}; + for (size_t i = 0; i < 2; i++) { + ScrDyn *v = scr_dyn_obj_read(value, state_keys[i], strlen(state_keys[i])); + bool set = v && scr_dyn_truthy(v); + if (v) scr_dyn_release(v); + if (set) return true; + } + bool on = scr_stream_dyn_has_method(value, "on"); + if (on && (scr_stream_dyn_has_method(value, "write") || scr_stream_dyn_has_method(value, "pipe"))) + return true; + if (scr_stream_dyn_has_method(value, "pipeThrough") && + scr_stream_dyn_has_method(value, "getReader") && scr_stream_dyn_has_method(value, "cancel")) + return true; + if (scr_stream_dyn_has_method(value, "getWriter") && scr_stream_dyn_has_method(value, "abort")) + return true; + return scr_stream_dyn_transform_like(value); +} + +/* A checked-dynamic pipeline() stage that the pipeline takes as a stream + * (the stream.unboxDynStage libCall): a boxed native stream or a stream + * handle answers its stream (+1). Any other value that is not stream-like + * or a function takes pipelineImpl's last branch, Duplex.from(stream): + * an iterable answers a readable Duplex the pipeline uses without piping + * the previous stage into it, and values Duplex.from rejects throw its + * ERR_INVALID_ARG_TYPE. Stream-like objects, web streams and functions + * throw `message` as the not-supported Error. */ +ScrStream *scr_stream_unbox_dyn_stage(const ScrDyn *value, const ScrStr *message) { + ScrStream *stream = scr_stream_of_handle(value); + if (!stream) stream = scr_stream_boxed(value); + if (stream) return stream; + if (value->kind == SCR_DYN_FUNC || scr_stream_dyn_stream_like(value)) { + if (!scr_exc_pending()) + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + return NULL; + } + if (scr_exc_pending()) return NULL; + ScrStream *d = scr_stream_duplex_from_dyn((ScrDyn *)value); + if (d) d->st->pipeline_body = true; + return d; +} + +/* pipeline.js's first stage: a function is a source function, which the + * pipeline calls; a value with pipe() and on() methods is a readable node + * stream, which the stage ladder tests before isIterable; a string, a + * value with a Symbol.asyncIterator or Symbol.iterator method, or a + * { readable, writable } pair is an iterable the pipeline pumps into the + * next stage. */ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { ScrStream *stream = scr_stream_of_handle(value); if (!stream) stream = scr_stream_boxed(value); @@ -8604,7 +8784,8 @@ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { scr_stream_release(stream); return false; } - if (value->kind == SCR_DYN_STR) return true; + if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_FUNC) return true; + if (value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM) return true; if (value->kind == SCR_DYN_OBJ) { ScrDyn *pipe = scr_dyn_obj_read(value, "pipe", 4); if (!pipe) return false; @@ -8617,6 +8798,79 @@ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { scr_dyn_release(on); if (streamish) return false; } + if (scr_stream_dyn_transform_like(value)) return true; + if (scr_exc_pending()) return false; } return scr_dyn_iterable_kind(value) > 0; } + +/* Untyped web streams as pipeline stages have no lowering. */ +static void scr_stream_throw_web_stage(void) { + static const char msg[] = "pipeline stages that are untyped web streams are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); +} + +/* A native stream returned by an untyped pipeline function, which Node + * pipes or passes on as the stream: throws the not-supported Error. */ +static bool scr_stream_dyn_stage_returned_stream(const ScrDyn *value) { + ScrStream *stream = scr_stream_of_handle(value); + if (!stream) stream = scr_stream_boxed(value); + if (!stream) return false; + scr_stream_release(stream); + static const char msg[] = "pipeline function stages that return a stream are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); + return true; +} + +/* Node's ERR_INVALID_RETURN_VALUE TypeError: "Expected to be + * returned from the "" function but got ." */ +static void scr_stream_throw_invalid_return(const char *expected, const char *name, + const ScrDyn *got) { + char detail[160]; + const char *type = scr_dyn_specific_type(got, detail, sizeof detail); + char msg[384]; + int n = snprintf(msg, sizeof msg, "Expected %s to be returned from the \"%s\" function but got %s.", + expected, name, type); + if (n < 0) n = 0; + if ((size_t)n >= sizeof msg) n = (int)sizeof msg - 1; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_RETURN_VALUE"); +} + +/* An untyped pipeline function stage's result before the last stage + * (pipelineImpl's `isIterable(ret, true)` check): an async iterable + * answers itself (+1); other values throw ERR_INVALID_RETURN_VALUE naming + * the stage, as `label` ("transform[i]") does. */ +ScrDyn *scr_stream_dyn_transform_result(const ScrDyn *value, const ScrStr *label) { + if (scr_stream_dyn_stage_returned_stream(value)) return NULL; + int kind = scr_dyn_iterable_kind(value); + if (kind < 0) return NULL; + if (kind != 2) { + scr_stream_throw_invalid_return("AsyncIterable", label->data, value); + return NULL; + } + return scr_dyn_retain((ScrDyn *)value); +} + +/* The value a checked-dynamic first stage pumps (pipelineImpl's i === 0 + * branch): for `returned`, a source function's result, which must be + * iterable; otherwise a { readable, writable } pair's readable side, or + * the value itself. +1, or NULL with an exception pending. */ +ScrDyn *scr_stream_dyn_source_value(const ScrDyn *value, bool returned) { + if (!returned) { + if (value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM) { + scr_stream_throw_web_stage(); + return NULL; + } + if (scr_stream_dyn_transform_like(value)) return scr_dyn_obj_read(value, "readable", 8); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain((ScrDyn *)value); + } + if (scr_stream_dyn_stage_returned_stream(value)) return NULL; + int kind = scr_dyn_iterable_kind(value); + if (kind < 0) return NULL; + if (kind == 0) { + scr_stream_throw_invalid_return("Iterable, AsyncIterable or Stream", "source", value); + return NULL; + } + return scr_dyn_retain((ScrDyn *)value); +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 7acad0eeb..c10978298 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -149,6 +149,18 @@ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *messag * to pump rather than as a stream (false with an exception pending when * reading the value's members throws). */ bool scr_stream_dyn_iterable_source(const struct ScrDyn *value); +/* A checked-dynamic pipeline() stage the pipeline takes as a stream: a + * native stream (+1), or Duplex.from of a value that is not stream-like + * or a function, which the pipeline uses without piping into it; NULL + * with an exception otherwise (`message` for the unsupported shapes). */ +ScrStream *scr_stream_unbox_dyn_stage(const struct ScrDyn *value, const ScrStr *message); +/* The iterable pipeline() pumps for a checked-dynamic first stage: a + * source function's result when `returned`, else a { readable, writable } + * pair's readable side or the value (+1, or NULL with an exception). */ +struct ScrDyn *scr_stream_dyn_source_value(const struct ScrDyn *value, bool returned); +/* An untyped function stage's result before the last pipeline stage: the + * async iterable itself (+1), or NULL with ERR_INVALID_RETURN_VALUE. */ +struct ScrDyn *scr_stream_dyn_transform_result(const struct ScrDyn *value, const ScrStr *label); /* Handles that are Transforms (ScrDynHandleOps.transform: Cipheriv and * Decipheriv). scr_stream_of_handle: the handle's Transform (+1), built on * first use, or NULL when the value is not such a handle. diff --git a/tests/corpus/7508-pipeline-untyped-source-functions.mjs b/tests/corpus/7508-pipeline-untyped-source-functions.mjs new file mode 100644 index 000000000..987221ffd --- /dev/null +++ b/tests/corpus/7508-pipeline-untyped-source-functions.mjs @@ -0,0 +1,85 @@ +// pipeline() over untyped first stages that are functions or +// { readable, writable } pairs: a source function is called with +// { signal } while the pipeline is set up and its iterable result is +// pumped; a result that is not iterable throws ERR_INVALID_RETURN_VALUE +// from the callback form and rejects the promise form; a pair pumps its +// readable side. +import { pipeline, Writable, Readable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + final(cb) { + console.log(label, "final"); + cb(); + }, + }); +} + +const arraySource = pick(function (opts) { + console.log("array source called", typeof opts, typeof opts.signal, opts.signal.aborted); + return ["a1", "a2"]; +}); +const before = pipelineP(arraySource, sink("array")); +console.log("after pipeline() returned"); +await before; +console.log("array resolved"); + +await pipelineP(pick(() => "xy"), sink("string")); + +await pipelineP( + pick(async function* ({ signal }) { + console.log("generator source", typeof signal.aborted); + yield "g1"; + await new Promise((r) => setTimeout(r, 1)); + yield "g2"; + }), + pick(async function* (source) { + for await (const chunk of source) yield `[${chunk}]`; + }), + sink("generator"), +); + +await new Promise((resolve) => { + pipeline(pick(() => new Set(["s1", "s2"])), sink("set"), (err) => { + console.log("set done", err); + resolve(undefined); + }); +}); + +// a result that is not iterable +try { + pipeline(pick(() => 5), sink("five"), () => console.log("five callback")); +} catch (err) { + console.log("five threw", err.name, err.code, err.message); +} +try { + await pipelineP(pick(() => ({ value: 1 })), sink("object")); +} catch (err) { + console.log("object rejected", err.name, err.code, err.message); +} + +// a source function that throws +try { + await pipelineP( + pick(() => { + throw new Error("source failed"); + }), + sink("throws"), + ); +} catch (err) { + console.log("throws rejected", err.message); +} + +// a { readable, writable } pair pumps its readable side +const side = new PassThrough(); +await pipelineP(pick({ readable: Readable.from(["p1", "p2"]), writable: side }), sink("pair")); +console.log("done"); diff --git a/tests/corpus/7509-pipeline-untyped-function-stages.mjs b/tests/corpus/7509-pipeline-untyped-function-stages.mjs new file mode 100644 index 000000000..18749e55a --- /dev/null +++ b/tests/corpus/7509-pipeline-untyped-function-stages.mjs @@ -0,0 +1,112 @@ +// pipeline() with untyped stages after the first that are functions: a +// transform is called with the previous stream or generator and +// { signal } while the pipeline is set up, and must return an async +// iterable; an async function destination's value resolves the promise +// form and reaches the callback; an async generator destination feeds +// the PassThrough that pipeline() returns. Results of the wrong kind throw +// ERR_INVALID_RETURN_VALUE naming the stage. A stage after an untyped +// source function or transform receives that stage's own iterator, as +// Node's makeAsyncIterable answers an iterable itself. +import { pipeline, Writable, Readable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} +const upper = pick(async function* (source, { signal }) { + console.log("upper called", source instanceof Readable, typeof signal.aborted); + for await (const chunk of source) yield String(chunk).toUpperCase(); +}); +const join = pick(async (source) => { + let text = ""; + for await (const chunk of source) text += chunk; + return text; +}); + +await pipelineP(Readable.from(["a", "b"]), upper, sink("transform")); +console.log("joined", await pipelineP(Readable.from(["x", "y"]), upper, join)); +console.log("two transforms", await pipelineP(pick(["m", "n"]), upper, upper, join)); + +await new Promise((resolve) => { + pipeline(Readable.from(["c1", "c2"]), upper, join, (err, value) => { + console.log("callback", err, value); + resolve(undefined); + }); +}); + +// an async generator destination +const wrap = pick(async function* (source) { + for await (const chunk of source) yield `<${chunk}>`; +}); +const out = pipeline(Readable.from(["p1", "p2"]), wrap, (err, value) => { + console.log("generator destination done", err, value); +}); +console.log("returns a PassThrough", out instanceof PassThrough); +for await (const chunk of out) console.log("passthrough", chunk); + +// a destination whose promise rejects +try { + await pipelineP( + Readable.from(["r"]), + pick(async (source) => { + for await (const chunk of source) console.log("rejecting after", chunk); + throw new Error("destination failed"); + }), + ); +} catch (err) { + console.log("rejected", err.message); +} + +// results of the wrong kind +try { + await pipelineP(Readable.from(["i"]), pick(() => 5), sink("number")); +} catch (err) { + console.log("transform result", err.name, err.code, err.message); +} +try { + await pipelineP(Readable.from(["i"]), upper, pick(() => ["sync"]), sink("array")); +} catch (err) { + console.log("second transform result", err.code, err.message); +} +try { + pipeline(Readable.from(["i"]), pick(() => 7), () => console.log("never")); +} catch (err) { + console.log("destination result", err.name, err.code, err.message); +} +// the iterators handed from stage to stage +let transformIter = null; +const handed = await pipelineP( + pick(function* () { + yield "s1"; + yield "s2"; + }), + pick((source) => { + // a sync generator, not an async iterator over it + console.log( + "transform gets the source's iterator", + typeof source[Symbol.asyncIterator], + typeof source[Symbol.iterator], + ); + transformIter = (async function* () { + for await (const chunk of source) yield `${chunk}!`; + })(); + return transformIter; + }), + pick(async (source) => { + console.log("destination gets the transform's iterator", source === transformIter); + let text = ""; + for await (const chunk of source) text += chunk; + return text; + }), +); +console.log("handed", handed); +console.log("done"); diff --git a/tests/corpus/7510-pipeline-untyped-later-iterables.mjs b/tests/corpus/7510-pipeline-untyped-later-iterables.mjs new file mode 100644 index 000000000..7fe6e27b3 --- /dev/null +++ b/tests/corpus/7510-pipeline-untyped-later-iterables.mjs @@ -0,0 +1,64 @@ +// pipeline() with an untyped stage after the first that is not a stream or +// a function: Node takes Duplex.from of it, so an iterable becomes the +// readable stage the next stream is piped from, the stages before it are +// not read and are destroyed when the pipeline finishes, and a value +// Duplex.from rejects throws ERR_INVALID_ARG_TYPE. +import { pipeline, Writable, Readable, Duplex } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} +function watched(label, values) { + const source = Readable.from(values); + source.on("error", (err) => console.log(label, "error", err.code)); + source.on("close", () => console.log(label, "close", source.destroyed)); + return source; +} + +await pipelineP(watched("first", ["unread"]), pick(["z1", "z2"]), sink("array")); +console.log("array resolved"); +await new Promise((r) => setTimeout(r, 5)); + +await new Promise((resolve) => { + pipeline(watched("second", ["unread"]), pick("text"), sink("string"), (err) => { + console.log("string done", err); + resolve(undefined); + }); +}); +await new Promise((r) => setTimeout(r, 5)); + +// a generator stage before the iterable is never read +await pipelineP( + Readable.from(["s"]), + pick(async function* (source) { + console.log("generator stage called"); + for await (const chunk of source) yield chunk; + }), + pick(new Set(["set1", "set2"])), + sink("set"), +); +console.log("set resolved"); + +// the iterable as the last stage is what pipeline() returns +const last = pipeline(Readable.from(["u"]), pick(["tail"]), () => console.log("never")); +console.log(last instanceof Duplex, last.readable, last.writable); +for await (const chunk of last) console.log("last", chunk); + +for (const value of [42, { plain: true }, null]) { + try { + await pipelineP(Readable.from(["i"]), pick(value), sink("rejected")); + } catch (err) { + console.log(err.name, err.code, err.message); + } +} +console.log("done"); diff --git a/tests/corpus/7511-pipeline-untyped-stages.cjs b/tests/corpus/7511-pipeline-untyped-stages.cjs new file mode 100644 index 000000000..124372e12 --- /dev/null +++ b/tests/corpus/7511-pipeline-untyped-stages.cjs @@ -0,0 +1,50 @@ +// A CommonJS helper that passes its untyped parameters to pipeline(): the +// same call runs over streams, function stages and iterables, whichever +// each argument is at run time. +const { pipeline, Transform, Writable, Readable } = require("node:stream"); + +function run(label, source, middle, destination) { + return new Promise((resolve) => { + pipeline(source, middle, destination, (err, value) => { + console.log(label, "done", err ? err.code || err.message : err, value); + resolve(undefined); + }); + }); +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", String(chunk)); + cb(); + }, + }); +} +const double = new Transform({ + objectMode: true, + transform(chunk, _enc, cb) { + cb(null, String(chunk) + String(chunk)); + }, +}); +async function* exclaim(source) { + for await (const chunk of source) yield `${chunk}!`; +} +async function count(source) { + let n = 0; + for await (const _ of source) n++; + return n; +} + +(async () => { + await run("streams", Readable.from(["a", "b"]), double, sink("streams")); + await run("function middle", Readable.from(["c"]), exclaim, sink("function middle")); + await run("function ends", () => ["d", "e", "f"], exclaim, count); + await run("iterable middle", Readable.from(["skipped"]), ["g"], sink("iterable middle")); + await run("generator into stream", ["h"], exclaim, new Transform({ + objectMode: true, + transform(chunk, _enc, cb) { + console.log("transform saw", chunk); + cb(null, chunk); + }, + }).on("data", (d) => console.log("data", d))); +})(); diff --git a/tests/corpus/7512-pipeline-any-stages.ts b/tests/corpus/7512-pipeline-any-stages.ts new file mode 100644 index 000000000..a2039a533 --- /dev/null +++ b/tests/corpus/7512-pipeline-any-stages.ts @@ -0,0 +1,45 @@ +// pipeline() in TypeScript with stages after the first typed any or +// unknown: functions run as function stages, streams are piped, and an +// iterable is taken as Duplex.from of it. +import { pipeline, Readable, Writable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function sink(label: string): Writable { + return new Writable({ + objectMode: true, + write(chunk: unknown, _enc, cb) { + console.log(label, JSON.stringify(chunk)); + cb(); + }, + }); +} + +async function main(): Promise { + const shout: any = async function* (source: any) { + for await (const chunk of source) yield String(chunk).toUpperCase(); + }; + await pipelineP(Readable.from(["a", "b"]), shout, sink("function")); + + const total: unknown = async (source: any) => { + let sum = 0; + for await (const chunk of source) sum += Number(chunk); + return sum; + }; + const sum = await pipelineP(Readable.from(["1", "2", "3"]), total as any); + console.log("sum", sum); + + const relay: any = new PassThrough({ objectMode: true }); + await pipelineP(Readable.from([{ k: 1 }]), relay, sink("stream")); + + const values: any = ["v1", "v2"]; + await pipelineP(Readable.from(["unread"]), values, sink("iterable")); + + await new Promise((resolve) => { + pipeline(Readable.from(["x"]), shout, sink("callback"), (err: NodeJS.ErrnoException | null) => { + console.log("callback done", err == null); + resolve(); + }); + }); +} + +main().then(() => console.log("done")); diff --git a/tests/corpus/7513-pipeline-untyped-stage-errors.mjs b/tests/corpus/7513-pipeline-untyped-stage-errors.mjs new file mode 100644 index 000000000..ade4370b4 --- /dev/null +++ b/tests/corpus/7513-pipeline-untyped-stage-errors.mjs @@ -0,0 +1,86 @@ +// Untyped pipeline function stages next to typed stages: an untyped stage +// after a typed generator stage or a Transform, the { signal } an untyped +// stage receives aborting when the pipeline fails, and an untyped stage +// that throws while the pipeline is set up. +import { pipeline, Writable, Readable, Transform } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} + +await pipelineP( + Readable.from(["a", "b"]), + async function* (source) { + for await (const chunk of source) yield `${chunk}1`; + }, + pick(async function* (source) { + for await (const chunk of source) yield `${chunk}2`; + }), + sink("after generator"), +); + +await pipelineP( + Readable.from(["q"]), + new Transform({ + transform(chunk, _enc, cb) { + cb(null, chunk); + }, + }), + pick(async (source) => { + for await (const chunk of source) console.log("destination saw", String(chunk)); + }), +); + +let seen; +try { + await pipelineP( + Readable.from(["x", "y"]), + pick(async function* (source, { signal }) { + seen = signal; + signal.addEventListener("abort", () => console.log("signal abort event")); + for await (const chunk of source) yield chunk; + }), + new Writable({ + write(_chunk, _enc, cb) { + cb(new Error("write failed")); + }, + }), + ); +} catch (err) { + console.log("rejected", err.message, seen.aborted); +} + +try { + pipeline( + Readable.from(["t"]), + pick(() => { + throw new Error("stage threw"); + }), + sink("never"), + () => console.log("never called"), + ); +} catch (err) { + console.log("callback form threw", err.message); +} +try { + await pipelineP( + Readable.from(["t"]), + pick(() => { + throw new Error("stage threw again"); + }), + sink("never"), + ); +} catch (err) { + console.log("promise form rejected", err.message); +} +console.log("done"); diff --git a/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json b/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json new file mode 100644 index 000000000..805df1394 --- /dev/null +++ b/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json @@ -0,0 +1 @@ +{ "plain": 4900, "san": 6400 } diff --git a/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json b/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json new file mode 100644 index 000000000..93a0d0f3b --- /dev/null +++ b/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json @@ -0,0 +1 @@ +{ "plain": 6100, "san": 8500 } diff --git a/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json b/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json new file mode 100644 index 000000000..e1920ef92 --- /dev/null +++ b/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json @@ -0,0 +1 @@ +{ "plain": 5100, "san": 8600 } diff --git a/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json b/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json new file mode 100644 index 000000000..eafd03265 --- /dev/null +++ b/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json @@ -0,0 +1 @@ +{ "plain": 5300, "san": 6900 } diff --git a/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json b/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json new file mode 100644 index 000000000..ac0e8497d --- /dev/null +++ b/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json @@ -0,0 +1 @@ +{ "plain": 4500, "san": 6100 } diff --git a/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json b/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json new file mode 100644 index 000000000..f363efcf0 --- /dev/null +++ b/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 6300 } From d74cc3b8bdd7e140bc1c81d3341da680d7bb540e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:16:41 -0700 Subject: [PATCH 304/395] 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 58a2cafa6..dabbc9617 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 8010cbb17..534e03c07 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 bdb545ff3..e64f35824 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 d941d7b60..2c0145c34 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 84196476a..4cf3a342c 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 634c361c2..65b66c298 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 000000000..3502a9bbf --- /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 b75e491a8..0baedd7dd 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 000000000..ed4d30e8c --- /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 000000000..ed727b604 --- /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 fde16a901..3f9a60bce 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 693b038b15432b25d446b1352391858e7c545e9f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:17:32 -0500 Subject: [PATCH 305/395] Keep the result of void generic methods called through class-backed interfaces A class may implement a void interface method with a method that returns a value, and JavaScript passes that value on (`console.log(c.publish(m))`). The call now keeps the dispatch's untyped result, as a call through a non-generic void member already does, instead of replacing it with undefined: printing or interpolating it shows the value, and binding it to a void-typed name checks it. --- .../src/frontend/lowering/lower-calls.ts | 15 +++++--------- tests/corpus/generic-interface-void-calls.ts | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index e2f7ade90..265b7553b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10074,16 +10074,11 @@ function lowerObjLitGenericMethodCall( loc, }; const resultType = lowerer.irTypeOf(call); - // A void method's runtime result is discarded, as JavaScript does. - if (resultType.kind === "void") { - return { - kind: "seqExpr", - stmts: [{ kind: "exprStmt", expr: invoked, loc }], - result: { kind: "libCall", fn: "console.noop", args: [], type: VOID, loc }, - type: VOID, - loc, - }; - } + // A void method's result stays the runtime's untyped value, as a + // call through a non-generic void member's dispatch keeps it: a class + // may implement the member with a method that returns a value, which + // JavaScript passes on (`console.log(c.publish(m))`). + if (resultType.kind === "void") return invoked; return lowerer.coerceInto(call, invoked, resultType); } } diff --git a/tests/corpus/generic-interface-void-calls.ts b/tests/corpus/generic-interface-void-calls.ts index d810383f7..63c137c10 100644 --- a/tests/corpus/generic-interface-void-calls.ts +++ b/tests/corpus/generic-interface-void-calls.ts @@ -43,3 +43,23 @@ for (const channel of channels) { console.log(channel.name, result === undefined, channel.count(["a", "b", "c"])); } console.log(delivered.join(" | ")); + +// A class may implement the void member with a method that returns a value; +// JavaScript passes that value on. +class CountingChannel implements Channel { + name = "counting"; + sent = 0; + publish(message: Message): number { + this.sent++; + return message.content.length; + } + count(items: T[]): number { + return items.length * 2; + } +} + +const counted: Channel[] = [new CountingChannel(), new LogChannel("plain")]; +for (const channel of counted) { + console.log(channel.name, channel.publish({ content: "four" })); + console.log(`sent ${channel.publish({ content: "again", details: 2 })}`); +} From c7ac4d7001feb1a4312ea92bcdb107cabd1c1dc5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:25:10 -0700 Subject: [PATCH 306/395] Expect the types patch command in the same-name origin test --- tests/harness/type-patches.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index dfd084572..c7975a36f 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -206,7 +206,7 @@ describe("type patches", () => { const failure = (pkg: string, version: string) => [ "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", ` ${pkg}@${version} declares OutputContext.width as number (index.js:24)`, - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@^${version}": { "declarations": { "OutputContext.width": "| undefined" } } } }`, + ` if that declaration is wrong, patch it: scriptc types patch ${pkg} "OutputContext.width" "| undefined"`, ]; expect(native.stdout).toBe( [...failure("panectx", "2.0.0"), ...failure("gridctx", "4.1.0"), ""].join("\n"), From a848a7b349338d91ec3000ccebd3d5bd5d1242b0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:44:18 -0500 Subject: [PATCH 307/395] Resolve the pipeline promise to undefined for an untyped iterable destination When an untyped destination function returned an async iterable, the promise form settled its result without a payload, and code that read the awaited value as an untyped value dereferenced NULL. The promise form for an untyped destination now resolves to undefined when the destination returned no promise, or its promise carries no value, as Node does. --- packages/runtime/src/scr_stream.c | 24 +++++++++++++++++-- .../7509-pipeline-untyped-function-stages.mjs | 5 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index a7593b551..fb416e5c3 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -5312,13 +5312,31 @@ static void scr_sp_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value, ScrPr scr_promise_release(p); } +/* scr_sp_value_inv for an untyped destination (SCR_PIPE_DEST_DYN), whose + * result promise carries a dyn value: when the destination returned an + * async iterable (no promise), or its promise carries no payload, the + * result is undefined, as in Node. */ +static void scr_sp_dyn_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value, + ScrPromise *value_p) { + if (err != NULL || (value_p != NULL && scr_promise_payload_kind(value_p) != SCR_EXC_NONE)) { + scr_sp_value_inv(cb, err, value, value_p); + return; + } + ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */ + scr_promise_fulfill_ref(p, scr_dyn_retain(scr_dyn_undefined()), scr_dyn_retain_v, + scr_dyn_release_v, NULL); + scr_promise_release(p); +} + ScrPromise *scr_sp_pipeline_stages(double n, ScrStream **streams, double count, ScrClosure **stages, double positions, double flags, double mask, ScrClosure **helpers, ScrClosure *box, ScrDyn *signal, ScrClosure *abort) { ScrPromise *p = scr_promise_new(); ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal); - ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL; + ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL + : fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv + : &scr_sp_value_inv; ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv, true, NULL, NULL, &fs, abort, vi); if (dst) scr_stream_release(dst); @@ -5370,7 +5388,9 @@ ScrPromise *scr_sp_pipeline_stages_opts(double n, ScrStream **streams, double co ScrPromise *p = scr_promise_new(); if (!scr_sp_opts_signal(p, &outer)) return p; ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal); - ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL; + ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL + : fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv + : &scr_sp_value_inv; ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv, end, outer, add, &fs, abort, vi); if (dst) scr_stream_release(dst); diff --git a/tests/corpus/7509-pipeline-untyped-function-stages.mjs b/tests/corpus/7509-pipeline-untyped-function-stages.mjs index 18749e55a..590334f90 100644 --- a/tests/corpus/7509-pipeline-untyped-function-stages.mjs +++ b/tests/corpus/7509-pipeline-untyped-function-stages.mjs @@ -53,6 +53,11 @@ const out = pipeline(Readable.from(["p1", "p2"]), wrap, (err, value) => { console.log("returns a PassThrough", out instanceof PassThrough); for await (const chunk of out) console.log("passthrough", chunk); +// an async generator destination in the promise form: the PassThrough +// takes its values and the promise resolves to undefined +const promised = await pipelineP(Readable.from(["q1", "q2"]), wrap); +console.log("generator destination promise", promised, typeof promised); + // a destination whose promise rejects try { await pipelineP( From 68e148add9b6dc5182e79592031ddcc27ccff577 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:51:56 -0500 Subject: [PATCH 308/395] Wait for every released package to resolve on npm before the GitHub release The release workflow publishes each package and moves on once npm accepts the upload. A package whose processing on npm stalls was visible only on npmjs.com. A new npm-available job runs after "Publish to npm". It checks each package of the release with `npm view @ version --prefer-online` every 2 minutes (scripts/wait-npm-available.mjs) until every package resolves, and reports each package's time from upload to available in the log and the job summary. When a package still does not resolve after 2 hours, the job fails and names each missing package and version. It only reads from the registry. The publish job lists every package of the release and records the time npm accepted each upload, keeps those times with its upload receipts, and passes both to the new job. Create GitHub Release now waits for the new job, so the release is created and marked complete only once every package resolves; a later release run checks again and skips uploads npm already accepted. --- .github/workflows/release.yml | 68 +++++++++++-- scripts/wait-npm-available.mjs | 150 ++++++++++++++++++++++++++++ tests/harness/npm-available.test.ts | 114 +++++++++++++++++++++ 3 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 scripts/wait-npm-available.mjs create mode 100644 tests/harness/npm-available.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0a..73f000431 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,7 @@ jobs: spawnSync('git', ['diff', '--quiet', run.head_sha, 'HEAD', '--', '.', ':(exclude).github/workflows/release.yml', ':(exclude)scripts/verify-native-cli.mjs', ':(exclude)scripts/package-native-cli.mjs', ':(exclude)tests/harness/native-cli-packaging.test.ts', + ':(exclude)scripts/wait-npm-available.mjs', ':(exclude)tests/harness/npm-available.test.ts', ]).status !== 0) continue; const artifacts = api(`repos/${repo}/actions/runs/${run.id}/artifacts?per_page=100`).artifacts .filter((artifact) => !artifact.expired && artifact.size_in_bytes > 0); @@ -394,6 +395,8 @@ jobs: contents: read actions: read id-token: write + outputs: + uploads: ${{ steps.publish.outputs.uploads }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -413,7 +416,7 @@ jobs: VERSION: ${{ needs.check-release.outputs.version }} RECEIPT_RUN: ${{ needs.check-release.outputs.receipt_run }} run: | - touch "$RUNNER_TEMP/npm-accepted.txt" + touch "$RUNNER_TEMP/npm-accepted.txt" "$RUNNER_TEMP/npm-accepted-times.txt" # Include this run so re-running only failed jobs restores its receipts. for run in "$GITHUB_RUN_ID" "$RECEIPT_RUN"; do if [ -z "$run" ]; then continue; fi @@ -422,6 +425,7 @@ jobs: if [ "$count" -eq 0 ]; then continue; fi gh run download "$run" --repo "$GITHUB_REPOSITORY" --pattern "npm-accepted-$VERSION-*" --dir "$RUNNER_TEMP/npm-receipts/$run" find "$RUNNER_TEMP/npm-receipts/$run" -name npm-accepted.txt -exec cat {} + >> "$RUNNER_TEMP/npm-accepted.txt" + find "$RUNNER_TEMP/npm-receipts/$run" -name npm-accepted-times.txt -exec cat {} + >> "$RUNNER_TEMP/npm-accepted-times.txt" done - name: Setup pnpm @@ -471,6 +475,7 @@ jobs: done - name: Publish to npm + id: publish shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -490,10 +495,14 @@ jobs: # Upload acceptance and public availability are separate states. # Save acceptance immediately so retries never depend on registry lag. + # npm-release.txt lists every package of the release, and + # npm-accepted-times.txt the time npm accepted each upload, for the + # npm-available job. publish_dir() { dir="$1" packed="${2:-}" name=$(node -p "require('./$dir/package.json').name") + echo "$name@$VERSION" >> "$RUNNER_TEMP/npm-release.txt" if grep -Fxq "$name@$VERSION" "$RUNNER_TEMP/npm-accepted.txt"; then echo "$name@$VERSION upload already accepted, skipping" return 0 @@ -510,6 +519,7 @@ jobs: publish_log="$RUNNER_TEMP/npm-publish.log" if npm publish "$packed" $PROVENANCE --access public 2>&1 | tee "$publish_log"; then echo "$name@$VERSION" >> "$RUNNER_TEMP/npm-accepted.txt" + echo "$name@$VERSION $(date +%s)" >> "$RUNNER_TEMP/npm-accepted-times.txt" return 0 else publish_status=${PIPESTATUS[0]} @@ -533,7 +543,8 @@ jobs: # pnpm pack resolves workspace dependencies; npm handles OIDC. # Publish dependencies first. Registry propagation after an accepted - # upload must not block the remaining packages or the GitHub release. + # upload must not block the remaining packages; the npm-available + # job waits for it. publish_dir packages/runtime publish_dir packages/runtime-darwin-arm64 publish_dir packages/llvm-darwin-arm64 @@ -561,23 +572,66 @@ jobs: publish_dir packages/cli echo "All package uploads accepted; npm registry propagation may continue after this job." >> "$GITHUB_STEP_SUMMARY" + # Each package of the release with the time its upload was accepted, + # when a run recorded it. + { + echo "uploads<> "$GITHUB_OUTPUT" + - name: Save accepted upload receipts if: always() uses: actions/upload-artifact@v4 with: name: npm-accepted-${{ needs.check-release.outputs.version }}-${{ github.run_attempt }} - path: ${{ runner.temp }}/npm-accepted.txt + path: | + ${{ runner.temp }}/npm-accepted.txt + ${{ runner.temp }}/npm-accepted-times.txt if-no-files-found: ignore retention-days: 90 - # Publish standalone native distributions and the surface manifest after - # npm publishing succeeds. The release body comes from the marked changelog. - github-release: - name: Create GitHub Release + # Waits until every package of the release resolves on the npm registry, + # and reports each package's time from upload to available. Fails with the + # packages that are still missing after 2 hours. It only reads from the + # registry; the next release run retries it, and publishing skips uploads + # npm already accepted. + npm-available: + name: Wait for npm availability needs: [check-release, publish] if: >- !cancelled() && needs.check-release.outputs.should_release == 'true' && needs.publish.result == 'success' runs-on: ubuntu-latest + timeout-minutes: 135 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Wait for every package to resolve + env: + UPLOADS: ${{ needs.publish.outputs.uploads }} + run: | + printf '%s\n' "$UPLOADS" > "$RUNNER_TEMP/npm-uploads.txt" + node scripts/wait-npm-available.mjs "$RUNNER_TEMP/npm-uploads.txt" + + # Publish standalone native distributions and the surface manifest after + # every npm package resolves. The release body comes from the marked + # changelog. + github-release: + name: Create GitHub Release + needs: [check-release, publish, npm-available] + if: >- + !cancelled() && needs.check-release.outputs.should_release == 'true' && needs.npm-available.result == 'success' + runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write diff --git a/scripts/wait-npm-available.mjs b/scripts/wait-npm-available.mjs new file mode 100644 index 000000000..26d7db391 --- /dev/null +++ b/scripts/wait-npm-available.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env node +// Waits until every package of a release resolves on the npm registry. +// +// usage: wait-npm-available.mjs [--interval ] [--timeout ] +// +// Each line of the uploads file is `@`, optionally followed by +// the Unix time (seconds) at which npm accepted the upload. Every interval +// (120 seconds by default), each package that has not resolved yet is asked +// for with `npm view @ version --prefer-online`. A package +// resolves when npm prints its version. Each package is reported once it +// resolves, with the time from upload to the check that found it. +// +// Exits with 0 once every package resolves. When packages are still missing +// at the timeout (2 hours by default), prints their names and versions and +// exits with 1. The script only reads from the registry. +import { execFile } from "node:child_process"; +import { appendFileSync, readFileSync } from "node:fs"; +import { setTimeout as delay } from "node:timers/promises"; + +function usage() { + throw new Error( + "usage: wait-npm-available.mjs [--interval ] [--timeout ]", + ); +} + +const args = process.argv.slice(2); +let file; +let intervalSeconds = 120; +let timeoutSeconds = 2 * 60 * 60; +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === "--interval" || arg === "--timeout") { + const value = Number(args[++i]); + if (!Number.isFinite(value) || value <= 0) usage(); + if (arg === "--interval") intervalSeconds = value; + else timeoutSeconds = value; + } else if (file === undefined) { + file = arg; + } else { + usage(); + } +} +if (file === undefined) usage(); + +const packages = []; +for (const line of readFileSync(file, "utf8").split("\n")) { + const fields = line.trim().split(/\s+/); + if (fields[0] === "") continue; + const spec = fields[0]; + const at = spec.lastIndexOf("@"); + if (at <= 0 || at === spec.length - 1 || fields.length > 2) { + throw new Error(`malformed upload line: ${line}`); + } + const acceptedAt = fields[1] === undefined ? null : Number(fields[1]) * 1000; + if (acceptedAt !== null && !Number.isFinite(acceptedAt)) { + throw new Error(`malformed upload time: ${line}`); + } + packages.push({ spec, version: spec.slice(at + 1), acceptedAt, availableAt: null }); +} +if (packages.length === 0) throw new Error(`${file} names no packages`); + +function resolves(pkg) { + return new Promise((resolve) => { + execFile( + "npm", + ["view", pkg.spec, "version", "--prefer-online"], + { encoding: "utf8", timeout: 60_000 }, + (error, stdout, stderr) => { + if (error === null && stdout.trim() === pkg.version) { + resolve(true); + return; + } + const code = /npm error code (\S+)/.exec(stderr)?.[1]; + if (error !== null && code !== "E404") { + const reason = code ?? error.code ?? error.message.split("\n")[0]; + console.log(`${pkg.spec}: npm view failed (${reason}); checking again`); + } + resolve(false); + }, + ); + }); +} + +function duration(ms) { + const seconds = Math.max(0, Math.round(ms / 1000)); + const minutes = Math.floor(seconds / 60); + return minutes === 0 ? `${seconds}s` : `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`; +} + +function uploadToAvailable(pkg) { + return pkg.acceptedAt === null + ? "upload time not recorded" + : duration(pkg.availableAt - pkg.acceptedAt); +} + +function summary(missing) { + const summaryFile = process.env.GITHUB_STEP_SUMMARY; + if (!summaryFile) return; + const rows = packages.map( + (pkg) => + `| \`${pkg.spec}\` | ${pkg.availableAt === null ? "missing" : uploadToAvailable(pkg)} |`, + ); + const heading = + missing.length === 0 + ? `Every package resolves on npm (${packages.length}).` + : `${missing.length} of ${packages.length} packages do not resolve on npm after ${duration(timeoutSeconds * 1000)}.`; + appendFileSync( + summaryFile, + [heading, "", "| Package | Upload to available |", "| --- | --- |", ...rows, ""].join("\n"), + ); +} + +const started = Date.now(); +const deadline = started + timeoutSeconds * 1000; +for (;;) { + const pending = packages.filter((pkg) => pkg.availableAt === null); + const checked = []; + // A few checks at a time keep each round short without flooding the registry. + for (let i = 0; i < pending.length; i += 8) { + const batch = pending.slice(i, i + 8); + const results = await Promise.all(batch.map(resolves)); + const now = Date.now(); + batch.forEach((pkg, j) => { + if (results[j]) { + pkg.availableAt = now; + checked.push(pkg); + } + }); + } + for (const pkg of checked) console.log(`${pkg.spec} is available (${uploadToAvailable(pkg)})`); + const missing = packages.filter((pkg) => pkg.availableAt === null); + if (missing.length === 0) { + console.log(`Every package resolves on npm (${packages.length}).`); + summary(missing); + process.exit(0); + } + const now = Date.now(); + if (now >= deadline) { + console.log( + `${missing.length} of ${packages.length} packages do not resolve on npm after ${duration(now - started)}:`, + ); + for (const pkg of missing) console.log(`::error::${pkg.spec} does not resolve on npm`); + summary(missing); + process.exit(1); + } + console.log( + `${missing.length} of ${packages.length} packages do not resolve yet; checking again in ${duration(Math.min(intervalSeconds * 1000, deadline - now))}`, + ); + await delay(Math.min(intervalSeconds * 1000, deadline - now)); +} diff --git a/tests/harness/npm-available.test.ts b/tests/harness/npm-available.test.ts new file mode 100644 index 000000000..0970d1f98 --- /dev/null +++ b/tests/harness/npm-available.test.ts @@ -0,0 +1,114 @@ +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { delimiter, join } from "node:path"; +import { expect, test } from "vitest"; + +const repoRoot = join(import.meta.dirname, "../.."); +const script = join(repoRoot, "scripts", "wait-npm-available.mjs"); + +// A stand-in for `npm view version`: each spec resolves on the check +// numbered in registry.json (0 never resolves). Every call is logged. +const fakeNpm = `#!/usr/bin/env node +const { appendFileSync, readFileSync } = require("node:fs"); +const { join } = require("node:path"); +const dir = process.env.FAKE_NPM_DIR; +const args = process.argv.slice(2); +appendFileSync(join(dir, "calls.log"), JSON.stringify(args) + "\\n"); +const spec = args[1]; +const calls = readFileSync(join(dir, "calls.log"), "utf8").trim().split("\\n") + .filter((line) => JSON.parse(line)[1] === spec).length; +const registry = JSON.parse(readFileSync(join(dir, "registry.json"), "utf8")); +const resolvesOn = registry[spec]; +if (resolvesOn === undefined) { + process.stderr.write("npm error code E404\\n"); + process.exit(1); +} +if (resolvesOn !== 0 && calls >= resolvesOn) { + process.stdout.write(spec.slice(spec.lastIndexOf("@") + 1) + "\\n"); +} +`; + +function run(uploads: string, registry: Record, extra: string[]) { + const dir = mkdtempSync(join(tmpdir(), "scriptc-npm-available-")); + try { + writeFileSync(join(dir, "npm"), fakeNpm); + chmodSync(join(dir, "npm"), 0o755); + writeFileSync(join(dir, "registry.json"), JSON.stringify(registry)); + writeFileSync(join(dir, "calls.log"), ""); + writeFileSync(join(dir, "uploads.txt"), uploads); + writeFileSync(join(dir, "summary.md"), ""); + const result = spawnSync(process.execPath, [script, join(dir, "uploads.txt"), ...extra], { + encoding: "utf8", + env: { + ...process.env, + PATH: `${dir}${delimiter}${process.env["PATH"] ?? ""}`, + FAKE_NPM_DIR: dir, + GITHUB_STEP_SUMMARY: join(dir, "summary.md"), + }, + }); + const calls = readFileSync(join(dir, "calls.log"), "utf8") + .trim() + .split("\n") + .filter((line) => line !== "") + .map((line) => JSON.parse(line) as string[]); + return { + status: result.status, + output: result.stdout + result.stderr, + calls, + summary: readFileSync(join(dir, "summary.md"), "utf8"), + }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +test.skipIf(process.platform === "win32")( + "waits until every package resolves and reports each package's time from upload", + () => { + const accepted = Math.floor(Date.now() / 1000) - 90; + const result = run( + `@scriptc/runtime@1.2.3 ${accepted}\nscriptc@1.2.3\n`, + { "@scriptc/runtime@1.2.3": 1, "scriptc@1.2.3": 3 }, + ["--interval", "0.05", "--timeout", "30"], + ); + expect(result.status, result.output).toBe(0); + expect(result.output).toMatch(/@scriptc\/runtime@1\.2\.3 is available \(1m 3\ds\)/); + expect(result.output).toContain("scriptc@1.2.3 is available (upload time not recorded)"); + expect(result.output).toContain("Every package resolves on npm (2)."); + // A package that resolved is not asked again. + expect(result.calls.filter((args) => args[1] === "@scriptc/runtime@1.2.3")).toHaveLength(1); + expect(result.calls.filter((args) => args[1] === "scriptc@1.2.3")).toHaveLength(3); + for (const args of result.calls) + expect(args).toEqual(["view", args[1], "version", "--prefer-online"]); + expect(result.summary).toContain("| `scriptc@1.2.3` | upload time not recorded |"); + }, +); + +test.skipIf(process.platform === "win32")( + "fails with the names and versions of the packages that do not resolve by the timeout", + () => { + const result = run( + "@scriptc/runtime@1.2.3\n@scriptc/compiler@1.2.3\nscriptc@1.2.3\n", + { "@scriptc/runtime@1.2.3": 1, "@scriptc/compiler@1.2.3": 0 }, + ["--interval", "0.1", "--timeout", "0.5"], + ); + expect(result.status, result.output).toBe(1); + expect(result.output).toContain("2 of 3 packages do not resolve on npm after"); + expect(result.output).toContain("::error::@scriptc/compiler@1.2.3 does not resolve on npm"); + expect(result.output).toContain("::error::scriptc@1.2.3 does not resolve on npm"); + expect(result.output).not.toContain("::error::@scriptc/runtime@1.2.3"); + expect( + result.calls.filter((args) => args[1] === "@scriptc/compiler@1.2.3").length, + ).toBeGreaterThan(1); + expect(result.summary).toContain("| `@scriptc/compiler@1.2.3` | missing |"); + expect(result.summary).toContain("| `scriptc@1.2.3` | missing |"); + }, +); + +test("rejects a malformed uploads file", () => { + const result = run("scriptc 1.2.3\n", {}, []); + expect(result.status).toBe(1); + expect(result.output).toContain("malformed upload line: scriptc 1.2.3"); + expect(result.calls).toHaveLength(0); +}); From e884e53001b0a1a79b94807eddf070699750ce34 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:53:05 -0700 Subject: [PATCH 309/395] 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 4b3f34b80..0bbdff1d8 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 000000000..698bf4dc3 --- /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 9f0559672..d4407538b 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 f57e52159..6e4ff8a33 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 84196476a..37e14ec0c 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 634c361c2..7c2d32f17 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 e230932ba..405dc5765 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 494c4e3e8..cd6eb55e9 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 000000000..e1643bcab --- /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 000000000..e1a06ecc5 --- /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 000000000..e8ecb066e --- /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 000000000..fdf360d21 --- /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 000000000..41fde2c81 --- /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 7372826ab..fc1c4456c 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 2d916a825..eb752a52b 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 000000000..f0ef0fd53 --- /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 000000000..55b311462 --- /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 000000000..9ee2e42b9 --- /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 000000000..5ac30c52f --- /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 000000000..915b8f73e --- /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 000000000..8c0e66c3c --- /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 000000000..8c0e66c3c --- /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 000000000..8c0e66c3c --- /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 000000000..8c0e66c3c --- /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 000000000..8c0e66c3c --- /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 e1a869048..57bf9ecf9 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 4c3a1d9c98b87320c0271c9d1b5b50489141e8f6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:53:25 -0500 Subject: [PATCH 310/395] Link the Linux release helpers against LLVM built with ThinLTO and a helper profile The Linux helpers linked LLVM 22.1.8's release archive. The helper's code generation is LLVM's code, so building that LLVM from its release sources with ThinLTO and with a profile of the helper's own work makes code generation 5-9% faster than the helper as released, with identical objects. - native/llvm-codegen/llvm.profdata.gz is the profile, collected by scripts/refresh-llvm-profile.mjs: it builds LLVM with IR instrumentation and an instrumented helper, and runs that helper on tsc-ts, five Effect fixtures, the runtime benchmark workloads and every 50th corpus program (scripts/llvm-helper-inputs.mjs). It follows LLVM's sources, so it needs a refresh when LLVM is bumped. - scripts/build-llvm-dist.mjs builds LLVM with that profile and ThinLTO, and packs it with a checksum. The llvm-dist workflow, run by hand, builds it for x64 and arm64, requires a helper linked against it to give the same objects as one linked against the release archive (scripts/check-llvm-dist.mjs), prints both helpers' times and the profile's mismatch count, and attaches the archive to the LLVM_DIST_TAG release of this repository. - The release workflow's Linux jobs, and the wasm32-wasi job, which also uploads the x64 GNU helper, download that archive and build the helper with SCRIPTC_LLVM_THINLTO=1: clang, lld and ThinLTO, and the profile for the helper's own sources. Without the archive, or if that build fails, the helper links the release archive as before. - The four Linux helper packages share their build in native/llvm-codegen/linux-helper.mjs. Without SCRIPTC_LLVM_THINLTO it builds the same executable as before. --- .github/workflows/llvm-dist.yml | 93 ++++++++++++++ .github/workflows/release.yml | 37 +++++- native/llvm-codegen/CMakeLists.txt | 33 +++++ native/llvm-codegen/README.md | 17 +++ native/llvm-codegen/linux-helper.mjs | 79 ++++++++++++ native/llvm-codegen/llvm-dist.mjs | 96 +++++++++++++++ native/llvm-codegen/llvm.profdata.gz | Bin 0 -> 6404815 bytes .../llvm-linux-arm64-gnu/scripts/build.mjs | 40 ++---- .../llvm-linux-arm64-musl/scripts/build.mjs | 42 ++----- packages/llvm-linux-x64-gnu/scripts/build.mjs | 39 ++---- .../llvm-linux-x64-musl/scripts/build.mjs | 41 ++----- scripts/build-llvm-dist.mjs | 96 +++++++++++++++ scripts/check-llvm-dist.mjs | 105 ++++++++++++++++ scripts/llvm-helper-inputs.mjs | 114 ++++++++++++++++++ scripts/refresh-llvm-profile.mjs | 112 +++++++++++++++++ tests/harness/llvm-dist.test.ts | 74 ++++++++++++ 16 files changed, 893 insertions(+), 125 deletions(-) create mode 100644 .github/workflows/llvm-dist.yml create mode 100644 native/llvm-codegen/linux-helper.mjs create mode 100644 native/llvm-codegen/llvm-dist.mjs create mode 100644 native/llvm-codegen/llvm.profdata.gz create mode 100644 scripts/build-llvm-dist.mjs create mode 100644 scripts/check-llvm-dist.mjs create mode 100644 scripts/llvm-helper-inputs.mjs create mode 100644 scripts/refresh-llvm-profile.mjs create mode 100644 tests/harness/llvm-dist.test.ts diff --git a/.github/workflows/llvm-dist.yml b/.github/workflows/llvm-dist.yml new file mode 100644 index 000000000..233e97e98 --- /dev/null +++ b/.github/workflows/llvm-dist.yml @@ -0,0 +1,93 @@ +name: LLVM distribution + +# Builds the LLVM that the Linux release helpers link +# (native/llvm-codegen/llvm-dist.mjs): LLVM from its release sources with +# ThinLTO and native/llvm-codegen/llvm.profdata.gz, for each Linux +# architecture. Each job builds a helper against it and one against LLVM's +# release archive, requires the two to give identical objects on this +# checkout's helper inputs (scripts/check-llvm-dist.mjs), prints both +# helpers' times, and attaches the archive to the LLVM_DIST_TAG release. +# The arm64 build uses the profile collected on x64; its job log reports how +# many functions the profile no longer matches. Run it after a profile refresh +# or an LLVM version change, with a new LLVM_DIST_TAG. +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: ${{ github.workflow }} + +jobs: + build: + name: Build LLVM (${{ matrix.arch }}) + if: github.repository == 'vercel-labs/scriptc' + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runner: ubuntu-24.04 + llvm_asset: LLVM-22.1.8-Linux-X64 + - arch: arm64 + runner: ubuntu-24.04-arm + llvm_asset: LLVM-22.1.8-Linux-ARM64 + runs-on: ${{ matrix.runner }} + # About an hour for LLVM on 4 cores, and 5 minutes for each helper link. + timeout-minutes: 240 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: pnpm + - name: Install LLVM's release archive and sources + run: | + sudo apt-get update + sudo apt-get install --yes ninja-build zlib1g-dev libzstd-dev + base=https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8 + curl -fL --retry 3 -o "$RUNNER_TEMP/llvm.tar.xz" "$base/${{ matrix.llvm_asset }}.tar.xz" + tar -xJf "$RUNNER_TEMP/llvm.tar.xz" -C "$RUNNER_TEMP" + mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8" + curl -fL --retry 3 -o "$RUNNER_TEMP/llvm-src.tar.xz" "$base/llvm-project-22.1.8.src.tar.xz" + tar -xJf "$RUNNER_TEMP/llvm-src.tar.xz" -C "$RUNNER_TEMP" + rm "$RUNNER_TEMP/llvm.tar.xz" "$RUNNER_TEMP/llvm-src.tar.xz" + echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH" + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @scriptc/compiler --filter scriptc build + - name: Build the distribution + run: >- + node scripts/build-llvm-dist.mjs --source "$RUNNER_TEMP/llvm-project-22.1.8.src" + --prefix "$RUNNER_TEMP/dist/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}" + --archive "$RUNNER_TEMP/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}.tar.xz" + --work "$RUNNER_TEMP/llvm-dist-work" + - name: Build a helper against each LLVM + env: + HELPER: llvm-linux-${{ matrix.arch }}-gnu + run: | + LLVM_DIR="$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" pnpm --filter "@scriptc/$HELPER" build:native + cp "packages/$HELPER/bin/scriptc-llvm-codegen" "$RUNNER_TEMP/helper-release-archive" + LLVM_DIR="$RUNNER_TEMP/dist/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}/lib/cmake/llvm" \ + SCRIPTC_LLVM_THINLTO=1 pnpm --filter "@scriptc/$HELPER" build:native + cp "packages/$HELPER/bin/scriptc-llvm-codegen" "$RUNNER_TEMP/helper-profiled" + - name: Compare the helpers + run: >- + node scripts/check-llvm-dist.mjs --baseline "$RUNNER_TEMP/helper-release-archive" + --candidate "$RUNNER_TEMP/helper-profiled" --rounds 3 --work "$RUNNER_TEMP/llvm-check" + - name: Attach the archive to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE: ${{ runner.temp }}/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}.tar.xz + run: | + tag=$(node --input-type=module -e "import { LLVM_DIST_TAG } from './native/llvm-codegen/llvm-dist.mjs'; console.log(LLVM_DIST_TAG)") + # Both architectures' jobs create the release if it is missing. + gh release view "$tag" > /dev/null 2>&1 || + gh release create "$tag" --target "$GITHUB_SHA" --title "$tag" --latest=false \ + --notes "LLVM 22.1.8 built with ThinLTO and the helper profile, for the Linux LLVM helpers (scripts/build-llvm-dist.mjs)." || + gh release view "$tag" > /dev/null + gh release upload "$tag" "$ARCHIVE" "$ARCHIVE.sha256" --clobber diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0a..3f19a4858 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,6 +173,7 @@ jobs: runtime: runtime-linux-x64-gnu cli: cli-linux-x64-gnu llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true - platform: linux-arm64 runner: ubuntu-24.04-arm @@ -180,6 +181,7 @@ jobs: runtime: runtime-linux-arm64-gnu cli: cli-linux-arm64-gnu llvm_asset: LLVM-22.1.8-Linux-ARM64 + llvm_dist: arm64 use_zig: true - platform: windows-x64 runner: windows-2022 @@ -195,6 +197,7 @@ jobs: seed_helper: llvm-linux-x64-gnu cli_target: x86_64-linux-musl llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true - platform: linux-arm64-musl runner: ubuntu-24.04-arm @@ -204,12 +207,14 @@ jobs: seed_helper: llvm-linux-arm64-gnu cli_target: aarch64-linux-musl llvm_asset: LLVM-22.1.8-Linux-ARM64 + llvm_dist: arm64 use_zig: true - platform: wasm32-wasi runner: ubuntu-24.04 helper: llvm-linux-x64-gnu runtime: runtime-wasm32-wasi llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true runs-on: ${{ matrix.runner }} # The Intel macOS build took 24 minutes for 0.3.2 and passed 30 for 0.3.3. @@ -240,6 +245,28 @@ jobs: mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8" echo "LLVM_DIR=$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" >> "$GITHUB_ENV" echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH" + # The Linux helpers link LLVM built with ThinLTO and a profile of the + # helper's code generation (native/llvm-codegen/llvm-dist.mjs), from a + # release of this repository. Without that archive they link LLVM's + # release archive, which gives the same objects more slowly. The + # wasm32-wasi job also uploads the x64 GNU helper, so it selects the same. + - name: Select the profiled LLVM distribution (Linux) + if: matrix.llvm_dist != '' + env: + ARCH: ${{ matrix.llvm_dist }} + run: | + tag=$(node --input-type=module -e "import { LLVM_DIST_TAG } from './native/llvm-codegen/llvm-dist.mjs'; console.log(LLVM_DIST_TAG)") + asset=$(node --input-type=module -e "import { llvmDistAsset } from './native/llvm-codegen/llvm-dist.mjs'; console.log(llvmDistAsset(process.env.ARCH))") + base="https://github.com/$GITHUB_REPOSITORY/releases/download/$tag" + if curl -fsSL --retry 3 -o "$RUNNER_TEMP/$asset" "$base/$asset" && + curl -fsSL --retry 3 -o "$RUNNER_TEMP/$asset.sha256" "$base/$asset.sha256" && + (cd "$RUNNER_TEMP" && sha256sum --check "$asset.sha256"); then + tar -xJf "$RUNNER_TEMP/$asset" -C "$RUNNER_TEMP" + echo "SCRIPTC_HELPER_LLVM_DIR=$RUNNER_TEMP/${asset%.tar.xz}/lib/cmake/llvm" >> "$GITHUB_ENV" + echo "The helper links $asset from release $tag" + else + echo "::warning::$asset is not available from release $tag; the helper links LLVM's release archive" + fi - uses: vercel-labs/setup-zig@v1 if: matrix.use_zig == true with: @@ -278,7 +305,15 @@ jobs: - name: Build native packages (POSIX) if: matrix.platform != 'windows-x64' run: | - pnpm --filter @scriptc/${{ matrix.helper }} build:native + if [ -n "${SCRIPTC_HELPER_LLVM_DIR:-}" ]; then + LLVM_DIR="$SCRIPTC_HELPER_LLVM_DIR" SCRIPTC_LLVM_THINLTO=1 pnpm --filter @scriptc/${{ matrix.helper }} build:native || { + echo "::warning::the helper did not build against the profiled LLVM; it links LLVM's release archive" + rm -rf node_modules/.cache/scriptc-${{ matrix.helper }} + pnpm --filter @scriptc/${{ matrix.helper }} build:native + } + else + pnpm --filter @scriptc/${{ matrix.helper }} build:native + fi if [ "${{ matrix.use_zig }}" = true ]; then CC=zig AR=zig pnpm --filter @scriptc/${{ matrix.runtime }} build:native else diff --git a/native/llvm-codegen/CMakeLists.txt b/native/llvm-codegen/CMakeLists.txt index 07abb35ec..7ce20086f 100644 --- a/native/llvm-codegen/CMakeLists.txt +++ b/native/llvm-codegen/CMakeLists.txt @@ -17,6 +17,14 @@ set(SCRIPTC_HELPER_ARCH "arm64" CACHE STRING # Linux packages ship this process-isolated helper as a fully static host # executable so it runs independently of the destination libc and libstdc++. option(SCRIPTC_STATIC_LINUX_BUILD "Build a static Linux helper executable" OFF) +# The release Linux helpers link an LLVM built with ThinLTO and a profile of +# the helper's code generation (llvm-dist.mjs). Its archives hold bitcode, so +# the helper compiles and links with ThinLTO too, which needs clang and lld. +option(SCRIPTC_LLVM_THINLTO "Compile and link the helper with ThinLTO" OFF) +set(SCRIPTC_LLVM_PROFILE_USE "" CACHE FILEPATH + "Indexed profile the helper's own sources are optimized with") +option(SCRIPTC_LLVM_PROFILE_GENERATE + "Instrument the helper's sources to collect that profile" OFF) # These must be cache variables before project() enables the Apple toolchain. # A target property named OSX_DEPLOYMENT_TARGET is not recognized by CMake and @@ -219,6 +227,31 @@ else() endif() target_link_libraries(scriptc-llvm-codegen PRIVATE ${SCRIPTC_LLVM_LIBS} ${SCRIPTC_LLVM_SYSTEM_LIBS}) +if(SCRIPTC_LLVM_THINLTO) + target_compile_options(scriptc-llvm-codegen PRIVATE -flto=thin) + # The thin link optimizes and compiles all of LLVM's code the helper + # reaches; the cache makes a relink after a helper edit incremental. + target_link_options(scriptc-llvm-codegen PRIVATE + -flto=thin + -fuse-ld=lld + "LINKER:--thinlto-jobs=all" + "LINKER:--thinlto-cache-dir=${CMAKE_BINARY_DIR}/thinlto-cache" + ) +endif() +if(SCRIPTC_LLVM_PROFILE_USE) + # The profile covers LLVM's sources and the helper's. Functions it does + # not cover, or whose control flow changed since, are optimized without it. + target_compile_options(scriptc-llvm-codegen PRIVATE + "-fprofile-use=${SCRIPTC_LLVM_PROFILE_USE}" + -Wno-profile-instr-unprofiled + -Wno-profile-instr-out-of-date + ) +endif() +if(SCRIPTC_LLVM_PROFILE_GENERATE) + target_compile_options(scriptc-llvm-codegen PRIVATE -fprofile-generate) + target_link_options(scriptc-llvm-codegen PRIVATE -fprofile-generate) +endif() + if(APPLE) # Homebrew's static LLVM archives contain broad component object files. # Export only the process entry point so archive-global LLVM symbols do not diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index 70c118202..02ac6e3ef 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -58,3 +58,20 @@ scriptc-llvm-codegen prune-import --input unit.opt.bc --output unit.bc ``` Both commands keep only what a program build can import: the importable bodies within the size limit at their call distance from any public function, and the constants they copy. Every other definition becomes a declaration, so a program imports exactly what it would from the whole unit while reading a fraction of it. Unpromoted unit-local symbols stay local, so release bitcode offers only bodies that need none of them. + +## Linux release builds + +The Linux helper packages are released linked against LLVM 22.1.8 built from its release sources with ThinLTO and with `llvm.profdata.gz`, a profile of the helper's own code generation (`llvm-dist.mjs`). The helper's code generation is 5-9% faster with it than when the helper links LLVM's release archive, and its objects are identical. The release workflow downloads that build from the `LLVM_DIST_TAG` release of this repository; when the archive is missing, the helper links LLVM's release archive instead. + +To build and use it locally, with clang, lld and llvm-profdata 22.1.8 on `PATH` (LLVM's release archive for the host has them): + +```console +$ node scripts/build-llvm-dist.mjs --source llvm-project-22.1.8.src --prefix +$ LLVM_DIR=/lib/cmake/llvm SCRIPTC_LLVM_THINLTO=1 pnpm --filter @scriptc/llvm-linux-x64-gnu build:native +``` + +`SCRIPTC_LLVM_THINLTO=1` compiles and links the helper with clang, lld and ThinLTO, since that LLVM's archives hold bitcode, and optimizes the helper's own sources with the same profile. The link takes about 4 minutes on 4 cores. + +The `llvm-dist` workflow (run by hand) builds the archive for x64 and arm64, checks that a helper linked against it gives the same objects as one linked against LLVM's release archive (`scripts/check-llvm-dist.mjs`), and attaches it to the release. The arm64 build uses the profile collected on x64, and its log reports how many functions the profile does not match there. + +The profile depends on LLVM's sources and the kind of code scriptc emits, not on scriptc's commits. `scripts/refresh-llvm-profile.mjs` collects a new one after an LLVM version change: it builds LLVM with IR instrumentation and an instrumented helper, runs that helper on tsc-ts, five Effect fixtures, the runtime benchmark workloads and every 50th corpus program (`scripts/llvm-helper-inputs.mjs`), and merges the result. A new profile or LLVM version gets a new `LLVM_DIST_TAG`. diff --git a/native/llvm-codegen/linux-helper.mjs b/native/llvm-codegen/linux-helper.mjs new file mode 100644 index 000000000..68d738af6 --- /dev/null +++ b/native/llvm-codegen/linux-helper.mjs @@ -0,0 +1,79 @@ +/* The static Linux helper build shared by the four Linux helper packages, + * scripts/refresh-llvm-profile.mjs and scripts/check-llvm-dist.mjs. */ +import { execFileSync } from "node:child_process"; +import { + chmodSync, + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { helperLlvmDistArgs } from "./llvm-dist.mjs"; +import { nativeTargetCmakeArgs } from "./targets.mjs"; + +const DATA_LAYOUTS = { + x64: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + arm64: + "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", +}; + +/** Configures and builds the helper for `arch` (`x64` or `arm64`) and `libc` + * (`gnu` or `musl`) in `build` against LLVM_DIR, and writes the stripped + * executable to `output`. musl builds take the compilers from CC and CXX. */ +export function buildLinuxHelper({ arch, libc, version, build, output }) { + const triple = `${arch === "x64" ? "x86_64" : "aarch64"}-unknown-linux-${libc}`; + // CMake drops the other definitions when a reconfigure changes the + // compiler, so a build directory configured for another LLVM or build + // kind starts over. + const kind = JSON.stringify({ + llvm: process.env.LLVM_DIR ?? null, + thinLTO: process.env.SCRIPTC_LLVM_THINLTO === "1", + profileGenerate: process.env.SCRIPTC_LLVM_PROFILE_GENERATE === "1", + cc: process.env.CC ?? null, + cxx: process.env.CXX ?? null, + }); + const stamp = join(build, "scriptc-helper-build.json"); + if (existsSync(build) && (!existsSync(stamp) || readFileSync(stamp, "utf8") !== kind)) + rmSync(build, { recursive: true, force: true }); + mkdirSync(build, { recursive: true }); + writeFileSync(stamp, kind); + execFileSync( + "cmake", + [ + "-S", + fileURLToPath(new URL(".", import.meta.url)), + "-B", + build, + "-G", + "Ninja", + ...(libc === "musl" + ? [ + `-DCMAKE_C_COMPILER=${process.env.CC ?? "cc"}`, + `-DCMAKE_CXX_COMPILER=${process.env.CXX ?? "c++"}`, + ] + : []), + `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, + `-DSCRIPTC_PACKAGE_VERSION=${version}`, + `-DSCRIPTC_DEFAULT_TARGET=${triple}`, + `-DSCRIPTC_DEFAULT_DATA_LAYOUT=${DATA_LAYOUTS[arch]}`, + ...nativeTargetCmakeArgs, + "-DSCRIPTC_STATIC_LINUX_BUILD=ON", + "-DCMAKE_EXE_LINKER_FLAGS=-static", + "-DCMAKE_BUILD_TYPE=Release", + // Later definitions win, so these can select clang for musl builds. + ...helperLlvmDistArgs(build), + ], + { stdio: "inherit" }, + ); + execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { + stdio: "inherit", + }); + mkdirSync(dirname(output), { recursive: true }); + copyFileSync(join(build, "scriptc-llvm-codegen"), output); + execFileSync("strip", [output], { stdio: "inherit" }); + chmodSync(output, 0o755); +} diff --git a/native/llvm-codegen/llvm-dist.mjs b/native/llvm-codegen/llvm-dist.mjs new file mode 100644 index 000000000..aea90dd54 --- /dev/null +++ b/native/llvm-codegen/llvm-dist.mjs @@ -0,0 +1,96 @@ +/* The LLVM distribution the Linux helpers link in releases: LLVM built from + * the release sources with ThinLTO and with a profile of the helper's own + * code generation (llvm.profdata.gz). scripts/build-llvm-dist.mjs builds it, + * the llvm-dist workflow attaches it to the LLVM_DIST_TAG release of this + * repository, and scripts/refresh-llvm-profile.mjs collects the profile. */ +import { execFileSync } from "node:child_process"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { gunzipSync } from "node:zlib"; + +export const LLVM_VERSION = "22.1.8"; +/** The release of this repository that holds the distribution archives. A + * new profile or LLVM version gets a new tag. */ +export const LLVM_DIST_TAG = "llvm-22.1.8-pgo.1"; +export const llvmProfile = fileURLToPath(new URL("./llvm.profdata.gz", import.meta.url)); + +/** The archive name for a Linux architecture (`x64` or `arm64`). */ +export function llvmDistAsset(arch) { + return `scriptc-llvm-${LLVM_VERSION}-linux-${arch}.tar.xz`; +} + +/** Writes the uncompressed profile into `dir` and returns its path. */ +export function writeLlvmProfile(dir, profile = llvmProfile) { + mkdirSync(dir, { recursive: true }); + const path = join(dir, "llvm.profdata"); + writeFileSync(path, gunzipSync(readFileSync(profile))); + return path; +} + +/** CMake arguments for an LLVM build with the helper's targets, static, + * without tools or tests. `profile` builds with that indexed profile and + * ThinLTO; `instrumented` builds the IR-instrumented libraries the profile + * is collected with. clang and lld come from PATH and must be LLVM_VERSION, + * since the profile's format and hashes follow the compiler. */ +export function llvmCmakeArgs({ source, build, prefix, profile, instrumented }) { + const version = execFileSync("clang", ["--version"], { encoding: "utf8" }); + if (!version.includes(`clang version ${LLVM_VERSION}`)) + throw new Error(`the LLVM distribution is built with clang ${LLVM_VERSION}; found ${version}`); + return [ + "-S", + join(source, "llvm"), + "-B", + build, + "-G", + "Ninja", + "-DCMAKE_BUILD_TYPE=Release", + `-DCMAKE_INSTALL_PREFIX=${prefix}`, + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DLLVM_USE_LINKER=lld", + "-DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly", + "-DLLVM_ENABLE_PROJECTS=", + "-DLLVM_ENABLE_ASSERTIONS=OFF", + "-DLLVM_ENABLE_ZLIB=FORCE_ON", + "-DLLVM_ENABLE_ZSTD=FORCE_ON", + "-DLLVM_USE_STATIC_ZSTD=ON", + "-DLLVM_ENABLE_TERMINFO=OFF", + "-DLLVM_ENABLE_LIBXML2=OFF", + "-DLLVM_ENABLE_LIBEDIT=OFF", + "-DLLVM_ENABLE_LIBPFM=OFF", + "-DLLVM_INCLUDE_TESTS=OFF", + "-DLLVM_INCLUDE_EXAMPLES=OFF", + "-DLLVM_INCLUDE_BENCHMARKS=OFF", + "-DLLVM_INCLUDE_DOCS=OFF", + "-DLLVM_BUILD_TOOLS=OFF", + ...(instrumented ? ["-DLLVM_BUILD_INSTRUMENTED=IR", "-DLLVM_BUILD_RUNTIME=OFF"] : []), + ...(profile ? ["-DLLVM_ENABLE_LTO=Thin", `-DLLVM_PROFDATA_FILE=${profile}`] : []), + ]; +} + +/** Extra CMake arguments for a Linux helper package build. With + * SCRIPTC_LLVM_THINLTO=1, LLVM_DIR names a distribution from + * scripts/build-llvm-dist.mjs: its archives hold ThinLTO bitcode, so the + * helper compiles and links with clang, lld and ThinLTO, and its own sources + * use the same profile. SCRIPTC_LLVM_PROFILE_GENERATE=1 instruments the + * helper's sources against an instrumented LLVM. */ +export function helperLlvmDistArgs(build) { + const args = []; + if (process.env.SCRIPTC_LLVM_THINLTO === "1") + args.push( + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DSCRIPTC_LLVM_THINLTO=ON", + `-DSCRIPTC_LLVM_PROFILE_USE=${writeLlvmProfile(build)}`, + ); + else args.push("-DSCRIPTC_LLVM_THINLTO=OFF", "-DSCRIPTC_LLVM_PROFILE_USE="); + if (process.env.SCRIPTC_LLVM_PROFILE_GENERATE === "1") + args.push( + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DSCRIPTC_LLVM_PROFILE_GENERATE=ON", + ); + else args.push("-DSCRIPTC_LLVM_PROFILE_GENERATE=OFF"); + return args; +} diff --git a/native/llvm-codegen/llvm.profdata.gz b/native/llvm-codegen/llvm.profdata.gz new file mode 100644 index 0000000000000000000000000000000000000000..5ebb5076ffb548be48ed7012ccd255fe287abbd8 GIT binary patch literal 6404815 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

    2kd&c#&BqIOm{g-B|a`> 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

    V;aFX_om|2xYYe+RC_t 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

    -cKkZsic_}124oYhJG5p&?`rG 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 u`Pu=c{m$S$dZv5_eNkLL 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%0DJbr}RkVp;STh_zUd_6A%g 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

    7C!x5L%7#+-V#g+=-Ukra;5j)kR8tn^4ca(y+D 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%0An;Dt9Ht> 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�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

    8!h(^(Ilu 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%0AO(MYsWk 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 z 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 `+%dg$`}+g;y^i@i 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⁢K67S1af6ii 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

    yu0dv@|z7yA!1LH1I##SD3(=6`^`DYW| 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�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

    +}S)hp}C&Rl&)zdkH0`Zezk>g%^ypfpO%+a- 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�Cvd=gIGfm6V55i_)z~^rUjAB&VkzJ2HY~E!(yLG}~Qa_V{ssG-*>gHwrkI?Bj z)(G3kgjxxE7-ZVOXz|C{5ZAyJgWc$27?e8u$VpSL4&18~3neXi9>Hn#@?4!{MWk!5jvAp3DWBXB_U1Uyza;?Vvy znyF~2vwKx4nNJrUiaUm9GfS01InBF{W_Ss2J zb+(&&`m)g%-V@3bqSX@t+nu<>}a&IRci zaE2v&TvpMLq6gR;6NdCZ;fNPD_8jy-98{r4aCS(lf7d@+m)`WG4)nJ}1$qVZ^j9d* zLw?Rq)Sxdn3{9Q#z%4tts8Dc*EH?xSP@0h1F$A^S(xMbVQ?)K3u>xdJXyGL96px6* zX>2%~UV)&^b6;FOJtF<(-rLYrYSS&gp?9CC0gh#gnt&1->;v%Gg5r0h)FiM>7!qRT zAaFoNhoowF6nm!MjketzodP21dn(Fj2^jc&>dIW7nTK z0PSxJq1kH!uBos-h_)%se(m-njN13(J~N?52V2eea~Fl-sb>#;8BT_d#bnD^Wy6-4>NawA_(Lqh=sQi@#tY~LwgMp1vD(SK9 zY;SBYz;$RnKGEYxJuJ2COtJD>Hm36+?7-Dba_mCF3T%EiFz|6Ip0L7z#56v(kk9C> zgO#Qpy+yzo5`3?kWg1|S1BKy%Ca2I?&54Shd*O)mk*RMT-NPdrXXqv02!%lwJcfv0 zaztZ%Oa$e3_$iSl)e%#eNCOB=zy}5x=&dNYOGP4nf}Y|X+VGf%@1}%RZgpi};0nz| zw0JARvYr3*n7o%iGBs=Y0fK7SMWNrx{s5REs1qA0i{2ZpV;@gZ_uZAIl9#q>?xOtNXnZ>^fepxs{1V2ofzj0mL*K zOi~t(k0&HeCeoYq1-${Yb3_S&<|9(Kk6AqR-lEGMNfrm@En=4KJnB8k)ueRh$u-Q z$|WZs_OT)>1-K~3H{#OetWajZ!M~jG@|{QY=(hR3M1lS9=+rx#PPtq$o#I}pt+#fPb6`7ckdEe$I>A+@D? z=#qB^Zq9{$t8<~v>2yc4-$>&-)vmYTk4>dr04b=w>Hpl(@-A7kpmTh=dM+iSsIuQr6hhL{Lk z_gRgQRSph6)X-=}JdGZ(FcqT?QEsrO?3ehS+c`@~@njT_{1QpfGzgc{QC4VFaZ=!c zVFUdd#3_(ZnhOrFAaFn^7LCL4?D0#sMuBDEpXfpXJ-Y+aNjL$uM1Z6R4g2_xht@yQ z<WzNs#mp(Q%X27CFFP}6eckRf_`9WCS9wo|$!{}%#0+|yeIErW=1cXTc)pPp# z(&#QHrPfS(e3En18>IxjGH6DHu_YE~!&4ZsDFCjy9_T8Xff^zc&eR!oj^=y2kub9+ z66S8tTUNZhM|IoigjDZGdo&!_vUTsG2xiR_1r#2ol67Q)gK52v;(~+ry$KGM8BRn%D{oI_;za4^=w2{r|o zOQI)$^H5$@POA0T)y<$JXYYAi+^IdbyDD$I<(PakTpA~kLp;HtwF44D|NmL{`JSs! z9bA&BOr@5E-J|Y1{^3&RvAmV%rA$}Nc(MYPatA^CoQcY@ccZd0^^~>W_uq{?U#||T zltze_(g;!92vOV!HjELVnoEtmlo_g0_5<(i`qRpw-Xk_fnfWZu3Id9MC{iQ#Dwbyj zE`;g`Dey5h)#o%@=xHGUu_Z`!Ao?bSCK?Mm4n2wDnaHQ4&i=gLHs+aBjwM!1l_TMB zvkj~uuxR;2p8yMOFKQLbzZ-ShNvobVXic(i`^Y3KGfYjL{o||8t{8oF!Kq-SFb8)= z1rDY`W9T#SXz&+S{|ug!fXr|W3nk;*M<$W<&d(021O{{TQ{2)cB^vFg+_Pf(LTec|ZbF{O}sU{EGz{{hWM5S-|WMZypi zL`DDju+lWgOF~SH2@)LvIZdw;Pf$OoNC95c z98{g`jz!_~#x@e+XQN!L%L!*!G?;Vyg`ZG>QdS|syDR-3@ODnEzk()-Br9+Yt$r>D z%nwY<&VMtFX9gP%D$B+z6^?xu8WUiUdxcmurq)s_(0q?*W)z!K zX!J6Iq(inBX>W0iVB0;CvD^tl5?Ymzn`bIIAYI^)x57H}ZPX=#ly6!DM%KRn?7|z= z>~ozxpLVbW$yBC;=-lV^N0V0o7I3jzDW^o})GGIc7Kw zi!jG3_Qo9Zrfv_J)=lN>^=ucH_E8BEX=yJWM(cvV}+?;>3x38-SC&%VyOt7iTVbl!T$%+_^ zoH-0e-wyO--R46DKFltLK8wN)B`oj2hT!zhnj_5s=sz9$;4=upq%i0Qcft-lbS7SBL=zM*_(A{5ppD#^h! zJHts%j0wtt6mrWY2g`$)9?dm)X7Q3Jm_#W|IXICPnJ7yqJpm+tm!!-R2rh!>wB%d! zgKAmWQq$^XhS{m+vGmJBdOwmkif_(PM1Tw$VWPoGLPv3TQ#MPYvOp5t)3NlXQCx73KlkId_a&Gue4`ZbS)t>gIZ{w~0fvKruliEF2#KPGln;1h%$FCC9vX#_eY>8@Hcw z6%#tx8dM!!EeBPG-&HR^@urX7IEPh^Kf!+12Yq^PSvw&eUpOVT^6~i9>ozY^;PE*~ zlw86iGpONDFapC%EE}$kNr6}e3zXg(>I83$6Jva3Oi+omXBi|=s?q87@QSolwsjJ0 zuK0@F9VmD8w*O5rZ;HlB@4XbU7*gQ(i~Sv~R*YV1x7$9BFY1kyQ&QmwdDCxq9;KJI zw{jB0bBQP&W}^(>7-0#5;uFzo2vUg@Q)^RZU{Za5Joxwn)(qAAF_U|y(^m<(Gpl2v z;!Yk+dAG;5y>Y^4`IZ|ko3{-MsyWijN*1mK6?>>%Hykn+7(UeG6=Fdq$bp51!h~@- zNEUl8lEto|h19O9f)?^lgP&{GY*7Hh7ih7DA#Jcsdjd2?S>XHh%I%*@`=`=cn!)pX zWN#;R(QCX=YS;7!F1ph|>P?pp_+(}~YpTtl=n3-ZI)g!hO~TwYSX3%nJ#H$xV3hmJ zkyCEz_h(@k$_;GF`(&THg@e!3OFg#7K~I?LeQaY5BhIe_Nm2>fy!olJ z_);{8cJD@0(L1}#in5IwQfC_HZm)OTtg(y=V$cJoUmbArbR;N@HPv2$iwT_63Uz_7 zNeQe+&`&@!5P3WgC_s^RPNrhKGf45FpM>?vB30h+sL_}9j`k|u8f#2_%}ZMkeEEW0 z)S-f~%}Vc)bv!#mWYxJnvy$=4+9{%%?@*G7(6DbCX*-iSM6qoEs~PD~NK7r6kCm#x zpf^DO@}KGTAdRKK2(fCWVNO~0`@=O`rCgw|?(H|Fwrpo_JMyd2YODcCSm#z)o3c(> zOntQlf-`Vgb@~SakWplBYOKo)TksEi57tyVsE zA|y6p&fWo5Xse1Nla6pAgrmUH50DC*dqCg+q{G!$1+%vX)uYR&b~R@0TK;41);E;n z%MFoK;MP@dy?aN69_A;FJRyvDZEB6Pq@$6Q0z!fZHc1#~TQeU+_)D7T19 zaiL@L;00fs?*FV;d1p(gYg3V34XvqQudT23yW;kIRTw!cU9b)iIMy~l+X$yZSf-+8 zG$eCfb23w`p)N21NMMrIt==I!sNNxIDvt{&+Kz-(N(*AgalUxiRhPeU)C~=|cMo~wgpqZkYZ#(y(QEKSuI3AVhzv@CdI_6DX zbgu5__and8-MZ9JD!3#~kEr~`iaK76-RZ=%Nd7p0dO(7Jj0u-!0aZ~hQak5=)UxFJ z-+Fc?$D6u8wx+_zt(v+$sT(h|AO<&&PXQ|dp5NMfZ(!C;Mu>ad#e}p@Pu;ZpoO$D1 zN9rtcBv#~1)8infDbYnzTrd~}KXg=|DdpWe%{E$`I^{g)rs;<%WAWl*o<1hIr~w5v z9#=H3Ja)h20Aq$07lQO<2a`|^{cnYfaz*1xX96WP{~&hF zBfa+j+gO`zsS6+ZVdQh?U4-}aFYB;HHJV9ii^cAu(G2U4B((ZLg&H4X>l*5KR*d@e zKq=4~#b=^DH8lk3iAFuV$U|BtilV9pzFDzi`^i0q8~$&A;F zqz*=)X23P3q5_GJbrNkg1(!P|UPlpDu}R?g`830aX)G-?_M6EwRt#BD-lNl-e(;2}D&Vhx{aMsob<|}) zPB^fkry}6E1lzsS@%v{m$@yuG~!b?DVK`|I3K z_Zw=yjNNmB9(dkZs& z^mA7I`vje-?7UR}t7iRtepJEP4=!}MSEQ)H6`+M%i9=*~@hk!x1V(3C$UBb!rdCo1 z2gsZMup2I@-qNF|mfj)$H(P**b@KV*9j-nXr1+;Tzb@~k_+z`YL&6eR2#`58$i-a8 zv#usC685oiAuk`Zk+#4CP_6>x>L5nvtxdN(A3puY?*CiZ0=0g8x#8l}cdp)p2fc80 zp&=c6NueB@hX=5tK((iaiN_(AOlJjh8p}!x9SfQqi>4j%c_Uh!^Un%Z^p&?oMD>;j#O*K(knvN_NpOa$njd8R?y^E5|kKDdaL z6KU{uxDPbD8ZDAdjgEV|7f|X|&^Ns^x=Kwie(1Gm!aL}T`EYw`5Dn*3N9AY9yEOIn zt6RJGQ440+INlz?r{(hAv@R3gD`6VVazlMp6q!Fb^0heAJ6yF0-y{Mq; zvK74HKCQ$SsB6e6UxAB7nh+W_g0I$Zqp?ya2z%Bg04@c8c9{;q zdwk@@E9eF7Cz-O@ptIHh(wv3Sz=Su2P(qu81W$p)lh1D{A}4)iYRvcEHy+u3SHXFE zm9>Th;AYvaATOqXHFZ2r;2C=`{1jTuD1$E$7KmbJLi2GTl!``_sok!W`yz%1IB!7- zB#+-s7@Y!SRS*NMMX*pWPyK#=zpuI;eY7q* z^P)yH9}dHpt-OB|$A6y~IUw)dU7yWBu?09Zh$zm%T`5==!fwZt}x)X;ag?s#gkr^~?q zVYYc@ps!3-u6?Vm?@1l$&IYwXhhSQF)|O9qHdw0e?0w~WmwHQT&X1#CTy)>!!spQn%fmecEjTfdYD)>|}n-5UPK+I zxfn!nPJ}2UEumNhK%o&oHPlg8(y^#&7mz<%G4+0)V%>BxcLfcjNMJA6tsVRY9eArg;>%dzyhQhJcuPmlF8--ZCeybncm0 zcs+$>2G285HVlGt;PQ$qd$=z(@YA1fc$4?$I+xjjWWw$gqe#RAy1qabQ-^cQib7d% z4oB8hG&p;|=okE|cgjk4gYQq>zH9Y!`&FH&x9FJR)diY_Sfa5BWwt944Glosu920X zsAq%=DS=BEb)%xuLYwOTRNvr~IZw^KTVrk(DX{_wKGW~A(YO}SVo`1pH06*U9WYEJ zRxm?O&N*-pXPp2TJp>UKVxS5Qa)-kBEc^@1QYL7ldfkCgq@mgvrzxuJ!s}YqmTJGf zHU=I@Eq?Vn+H9qB!rgq~BbGueB}} zIppSRx7$aS<$42%Q6}3DXosQpui%yORt7Pl%b!UanR^x&3RqZSEtS*cZ=x%`{! zQ-|DS8SZXV3M}r)$pUHk4+uMpVrt$BPrygjQ$9b^u~N!W@-QFNEuwc^7`?vFfd3&@ zbGhN6)Xpsr9rV`qD|M4&7St@za{!N)G^0{u?-Y-l6(!J#amH}bT(7HT`8TT zvJkC)2mRz_bH6zI7h69i6n53%9woKHL#Y#{JUpT7;AI7uv6PJeBJJ1^Pv^5hBvi4& zJ4AwM5&gx(FCR$_|M0aZUwCIuj&;#qfC9&K{WXw{S zVKd2259NtPQPO9hs!I=pK8#W}ztNIFhK*2X5b5Ud;? z6ag;E0=B+H9N>aEUeS|L!R5@f^32gf)NWUG(Y}C@KIh}IK0rUPvvEX^rJk}ZW{$dm z!0&I+I?Q6LNLb)~z{qGoU_$w%Z@EVfzRvjdwF|raT@^aY44YHS=N|63#rTNMt#*JR z!@2t$5`LN>lqnN`p2NOxrm1$YW%_mqlsrHnGpMoVchMAK46~vn#9Ez^2$f_nT5-TD z+f!Y0TK$tF3TvJYj=KaKywl1{YxORM=TbWk7+?M?!|0%D3uNLTArl8gQt*Yba10$X z(AAlA8_%U4+Vl1&@2^Yf zKv_#7#04W@8I}YPLWwP`<0GvEpx6Zd3-DQ?$68Wu_qo)j9lw0@j+rYszL&0x1;kOl z1P>;F`5fP<>__*c+=ZA3(jK~qa-tVfs}8$ixnv!o7n!nQ z)XD#2?@QpECiC|@!zqV&3y6q-EQ(0eyPkA98R#5LX*&ag8ZvEWCPUlQBrP*|A*rb7 zE-J1kt{1u<;I*jWiLUpxqIlnmiYMN>9{B%!-}gvRxpbU_(I zAlQg3G>AVU>0|Q#dVW6Q`ByOx>cZ_Yd_LiEfX^o6Z73{m_csUYSy|?zATL29E4|3N zU)BA(Uxn6rA`IYX!ZZW%_3q5yyCVzz>jwXtLtd;OuYY9AP4>n|`rP9Ce!1!JbziA! z6FsY{;ywn_^sGF^*HAqz*rci+Hv{P}Z>XU+rae$;t~(*MrP%0vceM~DY6`~eN0@Qp zPj^la`x>fk&34LV{2~{o83y4A2AoKD^3$#AB_($JGGY_4lmi-n8 zE%p!}W6bEdi-a{u$c%EKw&|*_3C~DFbvYno!Vc!2Kh5N&lXvO+e_eX9!t%o63;KP% z|64;YH?b~Dp|Yn`SP$m02RtXO5nL41t_=eRV&uV z^6KK)&NVB){_FmR^#&S&X^FG*IXeo-0(OMy6j~XO0ffb|)D`E|#Z$kjdu!T-HE6gp1TJjmxUU-VrbDrXK;HC;U7(K;?)PdI> zI5*sP`xhd;zBo1K`ENJ$ooix=bc+_XTdR4t4b*V?DEwA!QU|W_sPyPMlxVp`PnDLp z7w`4l2`g4zSR_+OqRAAp#&MP$2^!Q3QD7~Rs%JG43YOA)+uMs@eC)<~cmMj3VI?h7 zm8p2d+KX_Q)qHC!iUlIM2nUwjk}zwtME6>}$(0jt%CVArqC%<*PzBJ1{mhED<|sFf zSp0`Cs1=rX7FQ<5W_I6ui&-|7ayJL#Y*M=dc>M_-sgVo)ox}gCRx#4@zrC4D?|!uSjEBxjJrv|jyz%b2`wS3iO~xw^6F1eZv2QosnvWKLK7aYp z$<>#Z3llcfaQnoB%R&2#;e|-{lesUgN+pYGnN?HP;c*>C{Edk1SI*5L}%18o- zPz#1@m>_x%+B4`Qj2b|tH^-L6*Umn3Sw^y(*x#tikb?TuS*(;m9Yy3?BV9#E8B2## zezEwFi#lcuOHC^@?bHHQJh*BEET;&2Y=Y#~1&IBNh+Z`@rw*DfH{|ri;>8!7)3(ZT zkD)H4)Ot>+5vcY}iwM=eX=JPy04trdJudYcxSn#LWu`e8p+ht38>gu?=qlEN$XwbP z7)Ln=4^n&%rk$Qq2W48(IJqoD$O1I~-ther=QlT6{ye24UoLj^Jx1Q=nk6RHH#ScL zmjK2i(x0k%iA}WdZMc4)vJ-q2p;rqTsMZVtAmsI#L%C1~%EyFhnx6blWPSY+n+E=Q z+Uu*ueTSbl@4hWJn}vEEx$-KkmxBhRwmu8_LCryA;D_6RE^0wUWHX#7v2hioiXBIa zT!nZ>=^pC0oZZ%MVM8C(EsLv4_8?Q_tk!O%Tp|bN4ER1l zRs=7!jJ5oy#rMtVjC}E~!z|)p$CAXVlsJWOpg2%R>*3)1hB+f@n>JokQR=!eQDa~0 zVK&CsKQE5o@ECtP?;}S#&7mxgE;bR*6}i@?6Dh<-?WB{k*p(waW5eJkI+>%se)%I{ z{~7$d56G>Hn{SoEwNrO90k$|pTpQO3ZcOhX*uO4*j7G)g&R( z)0o5=4|P5Zn4r{AASc2Ym&eWlK@>2YGbPZ!*j@d)_?rWEzV(NxFLfWH)7ShJrSj`e z4#lRH!s(*X8ZDF_ssmwx;FEHWL$g@fe!()K9}YM_Qu;`=D0{tmu5|WZPSY%;R_#oe zJaV84oFHbVivSM8C}uYD8GT4?IC&jC8~3UW7Ys1DtxDyJYnMNFaa~L}Z?WZ0e)d6oVtqm`gx~n?KEVf0;=k)aFBp+l-tYb!bPPx^*8(rUF zx8ko*sdhYsYG>K7F?~`a-tBK}*)0#?E_!DVEq7@Tuho|Ar~K{Ai*7#sf&)z=dTO0Y z%vh8Hs}C%V6W&oK+PAA((E#Ow)z^pLcyofuvTSo`;-fg$2XhH4NpL^Cl@YG6?4;cD z=KMRY7hF(oz-SiN;*%sU(Gu^{{vG7FRJ#z*m7~-dtXOZ6Ybh51d&rvDB(ZII6t!J{ zxOC@yuyV)4>{ZV$pKR)P;Hnjo>W^Jh(&<9yHz zZc@N7)VgI%W3lCLp8+whW<=qR+iV6&APVH~T00F*mLMWI%u(+^Ej5~4OeOXzjy=-DX9gp75&XRz%V6sSMrd<-g>{h7zhy=sb<9md~*BBj5rM+ zT8_|&qo~kGl_YJO&d4z3+NGDr(wib?ewQof{D!e!9_Vp|1rfwJ3ggyBjzA4*2Tg}g zdTH)CbARch&P4O9nCN^$a3{!)sP` zAV(uc)4^ycfs;NFX6TYtpT2VR>4SS~IGHg&?y1B-dAo7$UV?emL{D5ZcFZ+{Ib_UP zXFM~~iNz>6QtzdWA`FRkyUMj4?<*t$P##wcSt% z6_$OJ6{`mS+;92!hThYy1%UF+c)h^n5cyz`gJA$?#lW32EQRabgJIbj16uil<=&=E zbLV6=Kr%#Is7Nu4jGx}6o zMk`Yfyl?dxgRdyeb86kVM9yPNs2-gMGlgQ_qI4p`g8a47e{<&jw*-1VpypzO#jA~z zRTP27fn!pF)VI$Aj<{_blZzO|*~x~c9A5>-bI6WdZoMAVcLuslOsAKf=#}U?SnTAfwVzP9<%n;1J@v;Uv1{*gHOYgWnjk%&_d( zc5Hv#X4RP9#jx-d2I-5j%Ff?Tc=xl@jxRkA(Q7gnM=>%N2+NmKWLjw%r`)jDN&D|{ zv{wLV2-d)diwap{@F5kA)BFL8 z9{$VUr~f?g_mYXPupFQ~8~?SUbJCKMJG;7KhuhhhumF*bO!N1^#9FC4C_pa|vl^eN zdGK=ddi9el(Ei+&`1Q>{UDCh!Q=6oW{A=^150+h9X!;gK6}a_9K%nhLZ~$s?s5_8^ z7`ntq1J$G!6vrsl`m~-Tc~C+I@-py0O~ELoFA4&TK%Y2j&%xVjH&0T2Sn$Nt zw#e40 zYlC>C4a7VfML!~u3yaZuAsUvAP|PsJbaaF|#y>mzlaVjZ8S;N!K_=f^R%Kz};|rSR z&92awo|LCHneeB&NX)iJ-4qf|L6Zlv5i;0sr|h}UL5!U_L^(Q2;~EYteudr_!kW=M zlxya(&Q=j_&#ZTPD zYIP_l!z8%{43a^{k*plBxFgI$AqeduCpkJ1tjm0P@8|2aA^ia*H;G-@e8@RV=+EOO zQ(h?*ZYm5EC+VbkDwBimxvPYxRJcCq1yCeT?U7V;H^s^FfS&H!sf>BEf2HSzJ{9Fp z)EI3mN$CXY&VDx9&e`m~)>b4>hn;|2c&8ZF#>P-qTp)&EhGsSBn!r$5CB!}^T;1)& zUoh=gmon{->SxxN9_4N7tbTrBMPUhD&#NBRjln@9LYQ8VKQA9|;KU7r{sKgXkD)zk zT>%X-^@;CY_c=dT8qoW>;4sCu-!p@!o_kHXAh)9KO;C{JT)3>4)sdd=KC8F&k<*pk zq~jcu{Qof--`)n6&^-q_io;0s6Mct^ban$QrK6)bxTK@IN8QnZnSdxJ2W#noL@J&l zXv!svT{-9$l9=j1J>Spt)a!@!$D-6Tos}jHQ>(1nb9g1i))vF0<%lP>G!?l#zAg|) z=Ta5kR9b3B=sH}_AuOtYc6%^mRaokj6}P>-`o$Z%%ATaPXd#1ttSd;y*xTYLFj9tx za9~c111pzKqQWvmx#7_lHa~oIs(kmBZRL=^s>Yw~?tFGNcM=9nv@JvN)IwyJT+ex$8wzsTC)*^I9 zP)P$QhZ;d7FjDd`pWA!ZA?#1<2CMVumP^h?>%%*j;k|PC+5Niz9U4w z&gSMGs0L#(rP?=5Whv+>ccms19cqkDEhx1ybU|etrTJjOh1ndY>uG-ko5MNyNB63L zjZh;|&HQ}U#96ZgZ&z%8E|`e&H}RbJ_CLR~>~YZ$2m$Azs1+tMMAAI!HQOIeU16D{ ze16W_xs@B2nE>XyGaGW#Qk^Dy=49xWBG!VdduzePv>;o-#k8pGD;i6|&&*uvJ?z#S zodf%Cw?}rYvU%Kp=B&GSyjiRyCxHlYF7@vw2{L>;efRHcwULUe$?WnLDf;VgX!oo4 zG`oxAl*Ly}z4Ov9M^qHdBPg*xot4@VUsZl-&FSs3Nv?{Q6Rb2JTb;NN5hd}7$LD1N zF)k_dGOz)7xZ~4ooRLug1ME(K^^mtX8F1Bv7}0?w03dY2W@U)EgHOiLS@l#<c}pNPBLl~0((f9EL!7qq=LZCMjJa$P|r(EtXuStS$;;TMvUFRa>!TM+$i zF4FM8V_QBCzBRqyu7C1i(LCim@#DP@-8<52keSUz1kK9UF8fD?S$}4gMY|q2?}78q zR41~^a`J0>-g^8RNDlaT-u0=ctVsV|gjJ}@i2zo)a?IR2;EyUywW9SLLoH`DGv^Iw zPaW*vkvrY7Z4)G}RT<!-=9Gs`e)xtjPp3!MbP* z9kw$4&S*HOx-9>vO&Nda){CFq<9TxAqFw}xq1qz=%m>kSSU*a%m(_$yp{lFl^HfBb z&B;rcRT9>&w8%9jW1J|-f)JxKCD_X+9W-R#w=aHg8TtqJ0*is-yTa10Eb_npb?f!d z_a@S%sX(ARaf+viv_a1H!&lA_l3Y=R17~rCgPtpuJh@n*M~xLyE~#DQlk*`B$3){C zE7F<%cSm0Ma+|E3gtGGJ5B~G{?tU{goI4zC=O8-_#6K{XOO!Llv9W5dbvi4}5Sk>S zg;r3Pu$sDR%=RdPtFWY$+y4Cwmw3x8+l;VkGtvpf;aww$O_3T8-CnwrbG$NP^6U4{ zd&6&*$fbzvBWer85ka{{P7>l74ju9X-626`>EkbNJMXfz9kZdo+C{%vdOq5pdpo|Z z-OoB-cT*D$;UV>Jx!=e=CVta!&vNp<3X7zy+V!L80k3%r8-?7oM{k1RL^Ll&#B>sJ z3N;X{2h1Yf9D)2@>Fp&ep{Fivd$4MG$xdNW8c&_&+ySIK+asB(bqu`_LOv2Wz*Wak z-K)wVgNkJKyZ`YEl+TmF>nGgJnRLbyB*ZDKH4i_q4E^wOFiL$bUCOgd4>|k7=Trv;eOj1m%{+1kol-&Fw)*x)gf%TX68UOGICti_M5!5E#IXq88Cct zz-7wBNGxb*MC&X*2_P3dJ@H8-HBOs}N#X@FUzmXs_uBlmi+OizQIP`{8!!J+|6PE!(k3Z71!)d4sK9Lwk&?LOhy zlhfNJ0>1 z3FNke#}0m9cskR*<)|aHQC(wyt?aRlPEfuGY<=a3X`{^6Q^&OC23F=oQruLY1jk9r zs>5Ht>5j^!W+p{XPr$$oU}t*O#P(bh=d{wC#B6$k$<}Cf2z%e#PF6fq4mtbC|C-6k zI5Yx*z@XTbL_km$3$t)yKA}MZAH7UtoT8k6;lbXwubE@o=3zhGtbVJ_&P(L)m@gI! zaZ$Q6B?hMl(BO0jrw7pJEF2D4HyhQX4|~G@p8cofK(!5HWAuB`WM3FdTBLk_|IW?y z>G$>qk5!k^W$#+hW8Yp3-{s-5s!B=Fzt=phXfrHQuKj-K_XkCeGiw9~wi!TMP)ds& zWff9fvPzI;A_OHE>V7{&pdI@^QwyJEv4tyAQ#%erc@AZeqLS(Dl zslIQCGW?x29qU${+M5*!F#+Y6kc$F5dxW-h5MWCO%q8CKzwq?2oUVM}St!id^N>Pl z8S(zSvpue9TryY12e*zi%%?Aaff?pxI#W4p(i$!AvZ`ljrfaBv|LMv_H@w$0Y|zHS z*-Dj-2!wLOCs1z!cXm*Cs>TeagwYipsS<=Z$0pGU+C&S6a5+oyJlJ}4{iQdk$-1)a0z(=MGsuNmuTOZ9TohBy!uC3X=2+KVGg%h$zA16p~QD} zBpUa!u{hJhweiVloQPezV_YA0VXSG(%JF+))U=wh2&=Pa_z-&Dtu63s^Cx`y`^i z30oteV{4e_r|cGoa<$)|I(nIsyqkKz`TW0-BSzgOk55X}bDdCvRnK)!=Q>enu^#=L zINee|IQDJp;hBDayfo6e$`4YVXVIN^8Z5(dRZ@!39OY=RD4l|AuKLc4^KY2Oolmwk zn4ldH6&WHUYSC)rfi>Bp5l%o#3U{4;?%3@U$T&}lyno%eIrpm-0-Jkm7;xP<3GE1o z(QF#TENUwJL&VSqiE!0xVzBiW%zI|q8@uhi_y1s`T3}k_vSJ@F#8<4!!3(*|lsr=}RBNu(a zQXpQ2c0)XrVI`Gj91|O2tOy~$5I7>xMleL|VwxCz+8SkZDd+O*`Yu++w*EMETD;aYALu2bG$Nbe@ zHrXU`J}D>)aiJ}(W^;@1sFxlBzEpYn=vChxu_{{VostlPs!khDr$BO(qL4LI!}4)1 zriyxK=!EE6bS&qx&~ygfrZchtk>%Gx+$~fWu@>;2l|BWoP;9Sp>mCRs$dUCWHK5HJ z1IefapF{;k90!u%#OEwlmd<#qco`c?IKXNPac$u!o8gjBCO3?I*^>D7s=;cgWw!_M z4SxJeAKmMgG=2(9TK_c6UK@`uAp3rXy+!lHDsst0^mm0e)Dk0~ck%eXqn@B|`ES2m zGhU{gIDEGi4~C8@9Rs6d5cFjP9&^LjV5Hf6xDS@i{iRQ-*yc~4ZSA3_U!`PsJ7xXa%vmMJ$Bg;=M8ZVHJJH=lpMXLdI*}mZ z_HwkldpluQTCP!E{$bGS|9r19>7@!lVS!MXPw-%!$Xz*Ek9=M}H+8!-+D)bt_p5h> z-`V=qE<62RoD$b45AOM&=382iH3Oace3YA)P$?u(iLx<-Rw|5%)fwe{L*|;irLP3n zD)*clvilou=`~cO7!|Tf8Cuxcy`AACfmtWhLFx_7ldevbboJq+D-B84p|@!Y3~CI& z*f{g3YMM=^E%Q%h>?30r-niwWUZlM`f+bb)d5Wf|yD!!wd9aRr77W+#c1%CbyKgxb zNu}jF#c}+QOUDkqsIY6Ak?4{*h*x5hu@<3|7Y#%Zb1>aYHJ9R&F)kVHLM7S2T?e2K zug|B7Vj}_=cTQ`(2_)}nraMTZy<`B?b;{C*{;{dzns~`6rrrsCIYH(DiRt#}bL5vjs=$SR%}lsYCCI3nW1;A)ttz+qP!5-2xHg^WvZ_$ABY3WX(_!sezWpp&D?Lt9p7o@ zI+cLw^XBFxu80lMqxzSXmqOg}X%w5J87x;i{ zziVv|eRR;!-#dzXr!r&qvTxfG_ZSi&rNXi_$teeVNMTOa*dC512}xetdJBmwBF;fM z>kJyv^3e@5? zG^6FHb~8*;5EGjGLLdl*eh?``<@t+euJ70$`1-ZVJx9E7?qR2x1Ig`J`+|!hYYV!D zySjQBio}q==~x>_(+-JW$dx{QtyK>H^~xtMUOBfr8O7N)(yf zpcl6E#sO;~4p>Vs9I!XEy?w7W`rrMyUH|&656#-;UgfIOce#Gykh95=mkVOJ8l^Iy zlmJXKicX@aJG_!`&VrOW>IGQBvO5Fm%cLBrg=ujCD<#~`Y#d>?!AY@Wz8ouK_a(cC zkU&s~cO?Wd)y|_5PdahqQTvbGaN_O*_xztref(QF=f@d0ef-&~!t_I4P1g!o zN-UUY==z$YeGT=LqpHdmi-`?v3gwL5RaJSdU0iYnCA4%RC3odKKq!3o>PY`7Pdh6y zVPNx5MmeJY9ARkMnIU{mI0K7m)B=rkDC}ceLJ=!tR}Mb?zb}7k*&dXV19YwPkg_Bw zJi{D#PH(WVngF?G2-?vF0QZ9S=w@wps3*dltgg!HlI$d_a&LnH6_$sU^ImUzQ2K9k z$vk?hwLGPv9)oRsu?#QuD8yqd*CR_!4eEXfqahx$Ww$%N`e?p zanYRC9F9UDQKJwS=U9n@k0y7ABM+KTAyv%quO`@GEp!=Oo1+XZjYTi`8?os1u{@&0 zZ@hKIgv0-0nxeGUvoeHWf+8=7SYsf~IXTqJO11$WQC@!O$G$6{S8ae)g}%eP>+AEt zf@;2vmm5VSe9mV$I`hWv%!SW9G;L>9x7<@^UDA)_`X-b2sW$rQHM4ccU#T72?dL99 zR9Wkb^AHr{9I8|w|GR^ z_}5Wu7u^0x=^!ctd_t9BQ;!sgy%}Xg9ppk%RSIRxu$0EI>ru~3azi>U^Pu7gFjIGk zYeU#l8}dbf5(8x_>N~t%SOiESubOAul7b}j(Y%O3X22oySA8%jGWfRwSoN_ys@$+o z_Db&;LSc$AKku}M*+4tcI)R^n_*RIQ5Ugu2!AEFEs8t+}Gl3ReUKL+Edh!)J|5;(0 zV>H%7F-PrDW#9vA?t6Hc&D3M@XiT_BBmj{F$}QnsvlLJj`c*R; zO~28h@=#$J4Lc=rh=3)`12=Uwhxjib*QJ(6hYCXpWe|)#ro8^e#jid1>wn6r!%$Zx zM0iTVO$9pkPE;)blxuPq&cNqH?1}VAH*qYeqx@sU>>pRGhnzR!_1hM7d~&l@mkbSi9TkbuqPGI{XaJh zKR|Vkj8(xJawB*gxipNLNujJNYigicZ@y%TAXXs)1~ybxl?vA(ko~-$X=i%Rpl!~F zWJifB!iVJKMAW?DdS=;m?O#k-H*lxlTEP8DWzKi2LQg+?h#CIH6Ji(Sm~kK}%ZY&w znxfcPhD}B}ARI=80YkLa8Kw8br<6VZMeiFK{ZB*AyG+_XwUQS?Fm0TOTwfK0VWYyV zoED*QgO71g`V?rc3Ct!|n~GWt<s)w zlm5A}jw+lzjA1?(FWNF()JrE?xEO#_a*;hK=2-szaSOJEf9z@HuG6j=aMQ?ZO3s-Y zh~Mn>^$}Vfc}4WUz#UPaYY1e8IN5Gp0FRJ>lH#%y5%Av)d@>9Xy^an@pt=~^+D!(v z3<2Dyl_ia>8?OGbH>83ItP@>CYOY)`3UO}}`TAfFC=Fujh`O*BYB(nzFmB60)i&Q` z^0Ouy2#Ip(xJv!|DlN|_we!4V>Nf=n=={PmB2qWf;91Rh@T@%$;px5vl_7mIsjZ+YUOC6+=o*TLcx^fB(R#E1YZOzjS`QmN`5DO$Q)%RhjWbdWGY5bSU zKUA2EacGh;)dk6z?T_Bypq#u@)l(b(BNe8wh!yU{FmpO1#F3Rnxq;o7i?wkPc0Pv~ zS5T=5;LWtv>>%PDESi5LeD_d3pLZ2$`qW)d{bk77OZw(uScIGCK6Q!{aSzIpklB?%MRcLK~uZh^rv0ipO^0)Ik>wf7!_jW(3eu`)xuo${~OM7#+Ic;gLn} zc%$;;p(`d1ze0_}UtxSW?gU;rDXJk5!mB4IQtl|uCg+w@iO#I6owE0viRlA(`IACH zDl9K4+5Yv)ebc6ytzfREB=RnD#<1Upsok+^j4`2#^By_qU2^zdC$H5Q{x|OSqV7f} zSG=Hmy<*~HXg})IGfjHaE6P=@^~bIs(rSdt1O=pukK}S)zYzpzq%>U|@<)lDy`oI~ zyZ_23FLjy1(~Z*z#Cn?=_$2UTka+_UYJ}}f%PC(u+J@hF=`eM`oMbS64&2LZh`**h zw9np)M$dZGa3AKYjjDAuB_Oj&KAlis>lYdqkI&{bvTQaL#n}L_VbxZPOR%84{-BXR zfDrupRHYKUG|)DCI4Y#Ln94XzTwcYC zDyZq6*?(x92MrXk|CB#>y`hNjEZO&!uEMl?q&?}dGC3LCAm)gHjvnHoe2OcN2k0GT ztvUcYS5$>~WWr z41+ZkdJyy>jyJ{yImAJgtw|!l<|y_SB!ty#+Iw@z$j8d6lbcph5uX_}#YWN+$f?S6 zP1wA^5}AYSVlub+Nnt^f2A5RAco-}BJ!3IrET`y891ondqL?9 zJlEtp8DhU7p+~qJRW^z3_g*g-c=RT>AU{((e)=aK09=Kr^3v|L$@e%o`3qC8wZsrHC@2H)PvOYqH(20 zg}zc;k6yd`6T?(Qtr6dEb7}Bp;FW30dMG>MrPa};2 zy{Bh7Zc+@PmhHhA9BEH_tc)R~Zg=^7Sc6(C6$b&wFfVgCGGU6&{B!h6&Gp3WuSYNP;IXlF*o9!jZ-hT|^eI!)ZU`743+;e`u)>_D7{Td}hmiNAV^j62%d1 zfkqpZ5(UWB)p1<_R3%v-EIZr4cC~Q$2^Ouc$rx1KB21_b)Hd}zV)T1#1=zi_l$Cbx}VsWcEMQ*I#{@2UYVY2eF4!nr81?rsj^wt4^Cp^pAfMu!bCd`GF< zP{Y2|_fIcco!7_WrrL#t6_X?C=bZQXIHB|KH`>*4H~K}_+vDzf?!e02y3i*(VYB;- zmbNu!OW3xQ;Odp#!%n6*s>em5ls|K2c_i5jD#pUzM<8m@h_yRCV2*x-&#>28#s)S48nOwj_B&>`)d?u7g) z7lT&kli~JsYipc{`08n^S0BE7$(bYKmA@BkyVBAxd)}g>9;v$4j6uK=>uibQLQMgZ z#b6}L1jt@`F1BBG@#LxUOh)aRWlKugssq637e{azT0Im_x1>a&O;oF4%0-bl4b{0m zz4l!dXy1L#Z&N+Xfb8q%U;owAc4KN4d6o7Am_vldKtkL{DN$&ptg4kAh|!6#>;TaK z1dxYONhUQ&O#>Mq-${qUF6NoZi}u{U^6i7Np48;OEp9l{#1QuYZ)Aq&7TByJ$Hp*T z2wEWwP3NI{jMR?CcQu$HFX zY%G>5-LeIe88*%X;f}Ga%ZmZ?lFvueg~jkmnQ)zZd5MwO1jl3o5ll6rR>3!heatCe zIqt0a<;(%ws*qP<8Is-W7S4A3+OtdcxK+~~3Pv%n(iEMHo(ZKI3DZ2Yj;!oNSbhsiVsq{2>Ks*3o_A$s09 zJKTA6%b1z}F0eiT{>v~>?7+Z;!LSrU_XmS@>Tqspd;{AVhyt5rJ-+w4p@#YDjs&d~MR$7W;dh3I@HCB*q?7j2_4<3Nm;Qi8-4%F|3c zYd{YjZ@28L)#u*P?`y?$u8Xy5$``_nJCMw9qQnufy;X-*M^F#V8yFS1E^#tgd6?!B z>wW@hG;~oF$BC9mC(?cZyUxj6ziIw?H*N<%XSeL8#5~XX@z1L9QDc~Z|C;dA~=naqupZ93P%$W%;#@@JAb)+LWSO7?G8~-zIO_m+428XcKRNuW-=K%r|GMEpUsI&S# z;#xVRJ1n89Nn|=F!m=Iau$+yYfXO+5y>X4HoWRvsDAi;rnMXC(Ui8&XNnWNCrLo4$ zFky|^=h*`<{9*801Gb+TJ|a7$!8h&BGi`-S7wHtvX`D>QIY9LW0p6FN%qS}&NyVZP z8KYEX%ctQUzI&hS!F%kn`ElRn1{*UsM^n?dBq#DwQa6Q}DzV^(Lw}6MQD1G9Bvz#h z3X-fpOj9;Cm5O)acS?G4Fi=~MwzjzlDQ2*6wWI0ugKPQ$^{Sfjx$tY8Oc?SF-mkbH z*I&6vAaK<}{p;~$ZHrB-=wY26mHomq_2P5uPA>HB>PUW2NAYMTjjUGR+n5~Xv_~b9 z3J}hSVnJ{kQ&fARLQ-b=qy)hxiaD~+hC$lerzc63&J$d{N(6o)t-gol*scAQf zg#)*^f``qTo9qguJX|Q+Aqr`kPhu7GT3o2Z$t=uC-3fY>&-c1tcI&NQD5sqoC@oM= zS*sVYVk^3R%GJOp`2@d^1A@6gve~&ULHqr(!=F6!8_Pe`V*cyO3C|Ik!O#pu@S~NP zGg2hkv=<=-!p5|mO3O8ZAR`_Y+Dw9wDo{5^gV+uvu1jW28u0j(Lw5SJYdKa}#$<>8 z^t1E67x$_t5~cE}WzzxGyG0gSBjrNGqOEL{1N95zyhl9CmmyJ#yH|)OWb-m~-EW--hWnf)VEU zBPQ8^#Y(-^b#Lk;a$~mfr}wc;$e#0tc;imPA|@FxH7QG3^GR84{o&OXx5F1!oc&Ze*g z0+uSIX^M&x9yO*l2|PVc#zqyr3(79>$@%D(Q9L#jrT})z*eE{EYZiEB0n-98Pc$vy zQ);j;Da1Gir?@3d>|;e8D+3E`p$iH}vDQPV1(-sFS<`*MyZwn2oc88Pa!M}4oDp_2 zl#9Ts(Fnq0#mf|N!3EdK0oNb4rsDsKe2{t8&zg;Y`_lvW-_u~`rc#s!;7WP?LNY3H zG8Yy^8SF(NiD{$AijH`UXv)^c$urUkte99%)Z3mNc-(1w-#NXZ8?V_eq~rhvYR|Jo zn0T5o(|x21r(dj$>Q-Yy7>(yVi;v0e<=l=dn>j%m;qI7k#nS7z%q-?e$CKUizk9WMNb|{E&T1x6;=c=OFfZc70~5y z$mYmhqT<33feLb{FjGs!ie?iR<3(XwmFvSnX0hs2C_({y9S~0*OocF|{MI4~<#(+{Ud^ z0iGr-+Z+s&0Ny(WQ5P=Y<#whe-P+2DYF5k*E(=HyKs2~#-omi#ZWB2aj7MuB&W0gk zsR+d+A3u5l(DarI!Z{*)-|%N!p7CB}l1WtOH(2AXv-o68Sdf>10*YX|_$=qQ1!{ei zorz+r7teRo`B;o2Sa}Mt+dO#!M9SLAIAWX>B^ThC#UBS28XDw%ApU8bE0>x+oFlV` zc;D;)^5vA-)U5_!yt6^sCL*#?4kkIo7vklm`Vfc~mmxfMWOmM)vzzL?c9Sk=^Q1U# zexzL#(rxW@Ct{uor(5s@A?bYi@?|BbW;gwKNusXbWV4)-svI4Fj2-1gG7XXt&v0U{ z{sVq#J?0sjxM#o;&5H4^K&xLs7zbWRnjn?j_n>ioHJLmC09eJs+2G*=oU$BeB2moCi81!vaz+5rr{PZz4V$rhWDcWz?VJQ z`r!$*hXt>Hb=-)f5Kh*vb zDf+SK_l5p+O}6C@cK36?oKWbh;4W*YnJt#IL^IB z495ojaJ*M_t-L1RUz0uVzz5G#ZaSspzMC_p3PlZbNr;|$iYNcx2qz)j9Zh?424U%0 zl$z}8A5Q-F{vWO=jKlN{zr}zejK4)+%lC#4l_zYdJUG4H9=TAxzXKO)ZMJgUGwIW} z+}Vp(NkA?m(1HG+Nc2j?dq&vC5G7$J6X?jR_o3Gbl=lE`WgkPx*bAF1k#Df7-=V?k zvT@fBSKmMRYO^)VuCdisN<++rO!7hyzskF(sAGX2z~R?8T|s$3Ajy}E(Kz1&^s^=A zc|w>*Gwf3nJs_otQZ`0&t!pHXW1l#(pUID{&mLzx_^5jiJJ1x!XxEjVB0*YT6jXKa zob$_y8!NRLl}TUXFW-agD-9usO4D^|M)r~eU%C3<^7p-|Q6TZTFlwwc%<%r2X(iX{ z72qpSkB3gGpQ}8u=H?!1Z-Mx3W=7b{sNw?znqP$=Tz-ajM&leXihl6^_)p#&{^pS1 zt4Lx-Hgn#kLH2}kX5AhepG39TkMW6u2u#4`dI=Slnc1rzo8)OYa9%;T!~Etk0aBky zUPj>KKv<^i2uzn(uQ6oVY*uW_p-fv2Es5zz!5cuW>tYUHHzsk;++X@^d;Tw+9oV_@ z$eo_M*wmEnwXlk}o^OHhf|~Wg@9BlOO0Tqn!`Ww2A6+`hdXO28QjcJxoCFL5sEB$< z7?lybY0BE3dVA8rx6UuC5p{aXlxr5Bq^Ac(F2#!6ZzrK#X_=Kh`Nj2D4pNRQ*>1)u z>?t8m&F-r1g>GUZK36^E5b^17iWQ@F8=Xib67iX5|GYQ2J>?qBv$8jh89(ymBWIam ztDRarM~=W8vlY{n4NFO3U8w+(9oK=lfZf)CmY+;xq@XUe&olZ4RbTHyNRRtJZPq8| zWG&Vof@eQU_27L+jZQWwVuIO-$%)J9hR0-Ij68S$F%$1F3yhRdeF+2C=c8~q%9o6( z>8u%HJ40Eq{sgM(Do=8e6lS^$Bo|$|dQ42b@81T=#bdKA(=NW``mYC8l$x}MamGng z!ZHhb*D>sC0W^~>lrvC&tfvX(Gt|M0nle+X&-FnPAJk59hjRx-PU6H27sI2OgP;cr zsiF>xQ!uE=2hE-Oo1okx!Aht~L>N17^Ax!_e+dLtZ8V(;X2Lcm1S_+PaoW$|>yPyh z-u7~)dD&H0JvedyC04V^wSbYEa^+^bWK;m&`|`+@Te64T_uaU_A>++(Dn2QJg%h3i zAaEtHay?pQ<8(&;S7pc0wU_k$y-bh_3zxlCd}qn3Ga3v)xco@e0vV7os+y-YLDFli z#v>Yl2;Cf^TTZ?B&HMg7+S^C1FnGDW_-!qV;UToh(8_-y$4@S?KX?q?K^>SYXa0QD zJ=fj%W}1+Ij9E2i?dtlKCr2l!OHONc)24S|3U&r3||aF*-jj5I)ByiB=j7H??Zt=oG)O5Dzk}YecM~+l4b9bq4hkd?hh4bDs_= zK06br!9KdvKnp}8)O&EU`-Xmv6+?`Qd*|HgM@%-Jfr7#<-$K z|21-(z9)G_Kg%Dv{c`Ih-Rmjw+q=DfOApUu(H#7F<45EDVPt=bn=k9WoNtco_IuA= z*-zI${&M1hy7QMfv6#apryf+aKYhCMUL`%}Qv0JaX=2gi!JCdKdc5)md|!e8^ws^I z`@X}kpI=?xeMQFMarPyl+h4z|G&1d)w{bf^K)%2AP5e2jefsG3W0LszgC=wCh^PAY zs{bwD|F@-der#}MxBFjDDDV3p+ilzK_ti(bU$nM-hgX7up3>%p12=bnKSQsn>~=4I zsDJ<9{q_58-K>vo-{(8!VZ87Em(o7>ZX(cr{0qg;w-|o^i9Zy-Zq#nww|B_o`}e5c zzQ14n{)4RgeYx9;%kciP|8(CUYwzs7zp3w6`;qbdCc||-_1E8O^B>v$dE+=x=TY^2 z#`9*|e#e$JfAn_|Nhn_em&;K zgMQQB^9`Av*%5gE3_1QUQmwHcdA-luWE_?G`JHFr_PUkXw)@;<*jqGND78le9<-vO zLgy>LciR^SX7_pgAIFk@KU{vM>F>k$o~q85hrKlXnp1XPa&_2no+Ym>^BcX6aAR3k_D%#G9jEo_nQ7F`jqQV-r{>RA_ zmDsaqTBTeXAdqa}5`x%;sW6J5IFW$0u|#NEzC;LdF~9-Z=3p9_Hclo&?~MAzf<%YH zZsv$<1|1cvoj73d?=+~i>==4TX?wQ$%C8RY=bK)T$1WP*S1Yc($hT`?QxauE!t1<@ zb7n!vcxmGJ9du&XZBfsyu<+TtYR@|9yPKw&xz;uc*Xt&uK$Mh}c}~nBUyHD;p`@Mz z&~S7xOfjVgXy#`>ccm{}yywM*`B_xJ1sO&`WW_EODy605a5GF2RrF~xBe{qqZqjyE zYG;7829X!PIFGpUsKaj~M`mk#e6IK9SPco& zkY=oDiTE(vK{y?a2J%EPMtymVzoXUeOu3l`M&uBYHJM5SwYZ~adK_I|oV7*y_6c9! zeC+adGj~=|w=2J@(Ea>);QLc(lLRdLB7|<2=B3F>ax3$||mE#o?L{b`3 zi?ZHm$t86rUZ#Pa&p|Q(SQLSBT;d_p&EPy3YFR7u@cGAlwmm8})7h=lUOW1N11~E? zRFy{It0NDiC`8`W%mRG4D1%OqD1Z)<@$Vp7;*?8j7y0CThE2v8lKby}cjSdHw`qAh zDf`Hy{oWYy^f84gEzKBXRPHBRAQa{ke4G_|xywdrU*(hOs(igln(0lYrFI6=CcstV z02y>oc?ivuv;9}ybmGXG&4!tvXRsuh1N}m(3v|WVHk8IGhn(5l;16+ahz+HX3r< zW^(!(4~mQIOr3-F;me+^DAuwkXP3@evuwooX|qo6z$v!|HiZE0s6Ajk0SsgWt)|uf zYQneRiXw=0fUG?*Y32o9YeR?|m=5CV9(~y!9?5O(7v3}H`2m*7GOS5`*6ft*n=QXy zYQLk_6p;lv4>20z+5&A!0q(#rqOy`2#QMI_hEcY|BOYR#+Bg+iyQP_}#kG z4VkKP%X{-Qg=TQyf(t1t2vGRIN=VS^@`!pf{+&<{vd^eHrE`~z?K|vG;(wb_vsYNM z*^~R8bJx4|d=L5l4r9K*Lxt2kC=99B0_C+xdS2!C)gkUp;qwzKi$C)|mL=K0O?d9h z(|&rm&{QuGU|Jl5tf@5l&AjH8i8=-~5H1{yslJZMINU0hVILj|c*dhX_T#{$; zF%I~PFZ?}#OJqhF>+s>14dN$}E+xUFf zjfLI-@j<-B&>b!@okUvDswo;U(PP#;F0a-fgeplcB_ot)*u`u4hyjSrw{L;CD%A6d}$Zk5ObI3^BdV|8by2`Y)O@VbL$pRo3Ej5$D z)ob(?V2o$M#OXrShcVz{A*{#6!dS=-z+K3y(lgO>X1JxO z$fx9}QC|tFxcE2+b288<#em$ON9ZB6Q2QB*q7ytR0>HpQH(WehPijDTuj+QiTV#N&XVLiX+n z^yZil=#9PS=#?PFp^pE!p>FfGK0TSvXJ%`6|Lob@Kf6Iqd}|y;ZCPf}3|Qnq@yNm= zI~y*tOLA>s^vyxev^~d&9%Q5QQRzCxc;N#OwF=a)!{et&L8Kr4MXY5OME+iz+JBLjeX8E5?InZPZWHf#ukzk>NAQJ--YE_{~9sSW|fBSgj>H`LBZy~Ba zmZjOstxx-JduXY_Xe!xpYD)3|oaf~(L%mMD!=1OCOfQBB6#i>WMmbeO-3AA+5h`}k zr4}VF&7Qrcw(Yg|7aGF!zlR;|h50duM-*aRbTUR~ehlvX(p57K>pS=lFpafOd1>~T zrsHng@8SK4J?h!3%$Ctf-vI$-uDq8p1&M|lE$g%Zn%PjfC*8?KL8y#!bLcNFe=1OC zlqs`y_brGX0+wN@7~$jqo>?2hcBZaIAIdXgjI}5-OvmBXhDy~!a72iM3Skf>r$K&? zd{YMLlOUr``x!KpY_)(46&}FoS8`%lhKL7V%P1?f1sFn+t^h7Aj@TITZH1bC!Mb*0 zMn#APCCOsmj4JAX;x~Bk8;YE9smH3=cdZm z-B)SZvF+G)Y&*6c+m3C=wqx6|?bvo~JGLF$j%~-bW81Oq*mi6?wjJC5;AYh%6h@SA zeTC(3*^!feZvraB)TH*yMAv8!tQXlJyO=p#@5yzyr}bl0{QfBT~AB2{5@6rRKycEX%Xm;9HO1 zc}OEUVuXw%UVX~mz@}1svW-xd16w9tUvsptq27gMP-xntJ`7dH7Nt3CfkqmDd*{Cz zdbxej9z)BQDZVIsoV0&%`2v+nu#h**8V>u2DjMaQ&Ps>^rA81jgu8%otIglAKU8$6 zxzceXa3#~@q!RSqOEGQjpif7m{n!!xO>XUy?9@48?bN$ZEBO)XB}hQ#D=2ai#B!Bv z<#VbV4o5ySTI@PVd_}vqPg=CXa%uL4%eLG#+jX&N3%i>E1EG_au!XoNpW=vqM4mDZ z^O@Ng_)D)T;cgD9q}E7g&4X~;wP?6D2enWu^$IV(0>NOg;sofiIhZXCyN+OgT9SV7~J>JHB)4G9|`*6)XseJ7~xMBfGTa4F2s-MYC*K zN=NAD@x9$B2zHCnw&gI|HirCW6Dp;t@$1q4waY3^e#}+bSoq4zy|4VuWCis~Y%i*~`k^y4O|A{w7|J2(&l(>)MQP8{ zn2ERa_3-NK)C*5tL2V#MiJ$ zIb2AmdMLty=aesEpir3_rvHWlH#4$uwL4k!x8#ipJJy>WrPt8eYJ|{Fantr1;<$k}*EVY+Yg} z_-FPXbH1Ncv&&&-vywso&dLx*%|J3E%tts2y1v7PmJf=ifiiOW`^PQN7I1FdfZOvG zayLyD{QBaU*-c%Sy~N^UMqHsTE1mh9%6E=X5*Jn-NpXsP2@<>*h}mUT*9B~YD^)zHZbmyebHsV$J<`YE)&b2~on(WA(2shVveicDXV8&DPMK z$E{aOEYC2uN>Swxs<|cyxm3HajcLF5xw_irPvsPU&o?}jo?iE~P1JPXKBVKqwu|BO zDlKcWhmX4FGWI7QIb*DZP>VKfR8F7@K=ol+WM!_+L)4Uh&rYjZf@zKRMD~ zs=JZ{KuQTrd8vg$q620+uxe2+LCD!0yacT4M0cg0M-PgRaWN#e=?-yiNPfwNCUKBr zCi2Y$o?p$gZAn3r`Dk9#2{`a=(|?*Z=(n0`^9^`MR-VwZ&)}KIn%a+~1RkLg1GUDv z!Ht#oBswourPKO>=|WRQkBf$k&$-Vd4p^~J4wy!6krR->Xoj9q*A&mtOf*hsKz$4f6$yHqhJQVx zw}q{jFZ=M}7y76?P45v^nQO_u*|5;5^Pk!u>dC%JohQ}#)X-s6`ZA-rb%^2hB*Utc zE4C*d_AX0{_oH9x^$*HoxbKN3*O%Tu*JTfS@?YQldWPG?B>+FWD$XTi*mqaqwgg_< z9LyQi7?WDM*>+#HcFSw4FRA+6V7B!{SPTKADTw453NSQ?>TXL&h?E_M(dvS97)9$v zp9r7rz?G$QkRTIiS!lnjs%r+=9fkr9FvzXlms3J<`8i$0Gt~YkOP?lOs3&#rhUs%ZyYvvWsX1kLjz&a zsk<3s)nmpF#>rG)LQk|GGfR!~vG4QOLv zY*1Ac@mEN4@p>hj zD}O}|$I5AulhB(Na-z*6Bq6Cp$9Z6=Lb~y(lpxCKBrkWB6WQ62tzDnK`?woiMUo7Z z6Hkmt5*Mg2pb7;E6vR|Fh@voqPK=Lo5}o89uU zHlpYTl}~E@(R41J>d}bFsQH@0f*zVgWgYqtw7F60po*7Uc`hC!>U9tp2UVY3IgzH+ zD5NcDLbDw*YrISY!=p$U_>dZ_CTs(8f>TaChN-M5)6cE2JfHpU*Ms;Io=TM!aVlh& zLNmo|j|-`h;AB^-s}!A$G6P5zmW|nsC++**qE{OV!;IQW-CpqZu>myU77IMyN(itU z>+tKtNE)K0miIgCb*lSz+g+oUE z_;WvXIywG;Ar+;KjF+;9zF^<9s=li?<_!eoeX=a_El}B~{>4dVfdZ$`2gUSstc{Z( zHbuGGS*a;GKPfCoVnwTRp6|=qD-QU2){|cK`K~Q6V{)uMn7}_M2yvTL!|@LXFxxz` zRz2!*B&F?(^KD7``mxRZKRe~MomJWABM-%GyyObJpdLY9FD0+1T~3U8LJ*ZCskgpl zIK6(b{^xvZASRx>r~bIQ{~}o4oL6aiBfEBF-Ix0`w3hs2)j2Q3wIN2HVAaf^N%gA- z&NcH$5ZagS1T)2p(<=Nzj&AKJS!C~xtUP<4Cm-yy+RTn}VA>6&UGfm8)oNMf^|&Is`|8h=I`OAX47HGMd5XG4$A>H4sbnxtHBw0~V9hjiS?*K@HZ ziIbo_JkY8QCxs~-csJZ8X(Ek@8EfN;p<7zemG&l3l!ir#rs;QXXJYRkx_M_cN*f`+ zs^iCL724PT7=RN-pI778SCH2~euD*4?9e+eR%gTUm0wWavU}!iRus2r4xV|DZj!z5Mbhm zgk>m@Tu*E|E8(K&X0R&kj4m|TE7RYTGyO3+Pj$}pzijlb?OOZkT?*HrFS-(|SS;EF z2cP0kVhqg9HD(x?cd}J?ta$&UD`uLlQAMT59d1;v9Ou!%6(qTeOLDEetgi84Hp3^| zq=s}{=2LN=h%EKWKB=_4mmO=d?D_rU#}uTCU>#?$M07ic+%IjG5l2-5^&KuK`8E}a zj;jGntO@OALJ=$T`1D&UA6U0!=(c7T)P{a9d)o`UANt;bH<@B|N>h%~0@mzIc_dII zCgUt1fpX-a8!8VJ?TQICd@@F&FLhxb3cu8LG9Uaf_^kokZnkgAUh?$aH=Oj|BL=j9 zF~*(fnRY~Hq6TQ1M$$wSBBuZw8xHw`futNjNC*c*QNAF$23neON?ZcHa?!R1d(8|& z*ng+heJmj%$Q){n z5GOG;K_=ALG@}~La;|VSqJdd=QQN;>e_~9(p??rm)tu?%!|dU2ALe$wvX>d4X12%G z;HNuNWvO6)nC<`Fhqgt6Y=UTTCAfslwo&=8N2Fck*jT-gY(s&0Z_YhLUXvrIIVB*M z5h;`9E+JCbIQoe)?V44jqjK1!Z1w|jKxG+iq`mgT?2^-NXbvnpw|meo<<{bHi=%h# zj&Qq>j>l#Qawwft&tS1P4L_`@+h<_P((xrb3yAjo^=7KHo3o3qnYs1qU&opJB%2*` zQi52a&o@hCQz@V(lB9Cnk&m-0Pi?&}{K~0jIqYtr%xzAOo;CdRoUAH%%=>J(^@_Lp zHV;zqnvtdWOFkp^;hS##ZCp$mbgmD7DP!2rGrT^|r+-$*lGn!?j!)P8jJ~R50KEVF zk)OCGPJLSayw+=m4d`=P-v#RHQM>N6@w!L99<6?Fh2_)is;OV~@y)!MeA?_D@THC_ zKFNw*dU&ZLD#;l(j?$|bON)3XC5Da186kKKDjilu`3k~Hy$@_{$;O1Gfg}E^_Vuad_zMt-BVoKLI>tvh#3$Q&iW25*Q5`aR zPQ9H{@df$gQe%b_)z3ij!2D#3!-HPU)w2X~KBSdkZLl(4pHIbWg$U)gB&8E6ELMrw z>oE5nHskC07E#s8BOhJFz9*-Qz9*^(aB4~PfZSc2PcpLjhKdUKp))qBN@SDK_Mj-V zuq}L?mo+Dgs`(LGC6DG|s5E~iBzl`mO!OZ5i{2kSw|Q6(-TsSg>+|oV-|g%&HLo;x zjKErU2l5;F)-K`{aZM6e4KcqM7pn>SVUW|sSxYH$I>HgG8)d75xSIN|u#M^X$7{lQ zcf9pT#h-=6H{VoWW_Ou)>Fzr{kThMPyTegAP{zMT5EFRXL-QTw0WTP0=Lr-^se+_o zEmrW1Envk~2fP96-BVcazVyZZ>+Dmb*Nm;$IL)G;Q}Q4T2&R!3z98B>;Hxx>eq?6g zo-c^g1zwUNfm)Cd{A^U(Kj_8PLq`37R$`WK;BT`}eLAiA#_W+M31Z5TuLr71$LCX& zFBWTNMILd@>CK=#5X)&lwb<2}B*@P5KFhz=yV1q`e5nCD?mH}%0@XLJ*8<6xssz3!MeC5@TvvMY#sUATgQM6S>6zu|{=qoyBZW{V$sRIA9@JI9J%;H&1WPM4&GlNt_&JMBolTn!8 zaE7uP&)ke7BFkb6#$JR|L7|1ymyQsPbFa5C68E5kxqCt@-a7XAM@EeJi}bQz7IwTl z@AShSt}#NAS>T3JJ@x%&Dm2!2m23unyM#Mlpk?xy@;74|gZa-b8-&5}APICS$_ZQz^apj(x z_3d`q11)D=HV?PIyQZQG8@J0oMbEE$5buBRiK+d{xvueV;^o9QyFczaD}K(C$Z>RO z(=GL%uOuvFe+bz>XU{z}%fap1NIF%yx1NNz???9UN|xt#;pJ7o;^(#ExC1ojcg(`; z@1C`PxA*fYdCwbWY0r0_a6q@`?f*SqP8f*QH6M8tKkpC7^BG01bGYulCXo9-M7Ol` zuuPEl^6dTEb{IUozd@F|Uv}Ky-+%bnuSQxbE~sGW5QVucQw5kMfGYb0S&)cS3ghSy zWif;N&^jT}Bv3rJ&D)6A2}lrcbpViCv%OUFpkA{8yxy2darCT7$oqLqKrHf~o)01f{SPf%vl zcG=NijlFExGxKTkRPWAO@^cX5Xc02hmGyY4QY2;J@%T|_GK!5aNC<{`T(%huP^1#B zBX@e5WaJs=<}+MF=M&O6hS9|}HC^=J#|s?aUTEI_FE*{KBZ=xz=?}+_srmlEjRQvf zg|2UaCek@5d(e>wk9p{Ub4rX;vkggzKWU=WMMBqV`Ue%1!NR*DX;nm;q)DOZ%SDPK zX-pcK-4KEnzW;R@R7=w5n*IQLg$c6kAj`#<;AM)8%ukXVPVH~h!Utzpov>TvZyR?t z7|N_OTn@J=HwNnb_0y(uCF7U+nMP#4x&OQUs_ya|luFox;aYzs<0c@aTCph#Qnb)X zST+`s=8DPc2vn`gkZDJc9~(1{umll6ZilG01T5h{UO+9R%MoKCZ2{%}t@)`D0K-09DE}wkRYfa`|m{ODdeI6k5b2*KzY+ zUAyn43U6#6N>6h)U~9DLL!;{*{k40-gr^TPf`&QjytqOEtH=lvV*TrK2`;*GWFAPUHN^28Ym1|ZRs*R&@+UJX z9Nj``YkW$`#`^;yj&I@`sBocC>pRwLgc967d(GMp4|si1r2ABSH~d+Q6XkgXf*dFX zXI&2NVf*Z!HJ7|vb>?o}jH--9lp7I5Bor!rTs50^$Syl*{LmMUI=T>3L1rs!P-p=G z3nFuF2d@{ul0ZsKX57s*xrE148@l_KFS^>@wnYRpqq5H*(K4>%fV~Q*Q9R(I>;kZ9 zMa5)u3df}fClw&?4(df!Zp6t1mS!#rO3YS;e({bPyFC0Be+kF+Jv=)THBkO;{qNcLqeu5BF}b6LWdw_u!G_xg)3$J{+R6PFMc=E z_T0wa!t1M38E+~c3#H>?Cew35nO2<`r)%@?9V%umJZsVa1iX6;v{4H^2Mv^(o#-X^ zRC!|KODFyqo9+M6Ci4xm_AX$uz*s($U^GDn0%BBe(0f0P!NuAR4F+1tJmt@QZ*roFS$F8_Mcetm7Xg*>=F z=c>XP1{F>NU5FqPS@+shG)^{lae3isKqVc!;-@#aL_u@!>^aNs{MV+P_v&sYw2N}% zWKX!73kIqzQv<%zr^CIo$9y;H*K2lsz`%6su3k28A-Nzn3|omGZjmE^Qcp{%X2wp( zg%H{%bKPgrLm8Gyi$YYGE6M_ojDJ8Q@R>YGU;|3@g*YDmBqEo=kPD~U%MD2Hojo9N z`MGygU!%*Pl*+=WHj<(3=3>zdxC}udEoV}4kdkGva0ZnuC1?uuH(-Op9`XTk3uGli zK2ne2;_-hlcS77})Zq5tPC9Pkz(HFrcC$}*^;1XB@ZOy&G)MHk!&&F`Ho_%ShBL;= z*YT2oveCSVt=TDvcn0vzJlfOzvfVRnDxG+B^JKj~0B$#Om{adVh zx7=&1Fip(P|8DdJ{jMW1tsHG&xtRy9GaX6B0H%cm`^o23-+3j>KYnz@bw4#&VHunzG8Ga+qA(?|)1=_MPRQjrg+^PU+L1 zn2nnkudItwrmkxW9EGBsSFD|QdMXGdF{zP7tuC*$@tOx9a@=0OJb3&SlYSo3+qA@h zt^82TrbDvxf9ZGcsyQzdGnZX_u!+w|g5am)kIDvQYsDmR(M1TI3Z9K>NbZVZv1v%g zKoIS=@FeyE`7j3$r$C{cpN8FFfCm#Jno4*1F-so^ar3CWm``FHM*sm}$Yr68_`9n4 z07B-f_aI0Nl-_lWs=0Mbdal`YXg1z4_7>|^>&TY>TyYsoi_5}QR`!3RvW(5n3e7$F z>EO8rNs$tPiE0MzHW;;ggGg*6h{QI6NF>#<;mhuQv2WN#9NF3pvlgFsd{v==s<*(c z@l48E8*X6iL7}BR9+7Ip4G8On@T)?M1Y6Xesoql>*yHuMvTNrA&%fGxh7J|l2hbg> zEs{>hyO2hCG6us16eR$;qa02Vzzv)e z*BOP8*=U@yYsEOmu42~miOtQVxWIw8a4}qMqhj9j$2cx=%0}D3`k{)cx1&*i)8X08 z3ufHjcFVjTEcR09Rgi2p*%6Ai3ecoV3jITaL?)#^z{9goP5jb6u#N(uJ9|W{m6|=%XU9~Buq<*W zsyselRZ6O=^N<~MYIUL_Yh+$~Q^}0bnL&whHXkksz#1)@9nO9`_@(s|2gdrS55#5{ z-|iyg)5OEI%dP$C>G#UrzU{N^Lq~KvMh!uQoZ9_YeR%9{FWtDyJG33$S$nBCeC8id z(tdDw_8${&I;5GIS#pksjvaOWAU26fQCcyzo?^$8uG(@$c1_KUTd)3jjgjlylYoa^ zML}Z2k+8Z*8b!eU0mPHtD;*D4jtnA4I?@w2rw?;;Rs%QZks$0sC(DuZuRylVg?Hdg z__w5txTc?x(|vTd@|M<}cmAE4?iO=V4<~PYB|;H@g4235c=C5mZ%v_h(;`I@KA{6q z8p(v%$6ndxky;8p9ao{J0}q6S{8x*RA(2$RRZM31_M|wcUEtFxnG0puyq}}R%b+g^ zhHU)O{!7PwYTq2CN>_O@D1*ea?v255(KoHXK?n#2%XvSw5QpFc)Z<2(T zpfDHHp;}vnOuf*V!5g6JER<@OqL3&hMEp3=lZ&MwD=5_76ck#Z8$svE2De*bAF^&l zxz6mQCs?7U8{Bn+=&E-H*p>Ws_r4up2j4=6(4Q~XS4zaseXWb9?Mhoni?5Y@V9dq@(vPWY?+T?Hcrji|) zE-WXsWyuR|X%5N=QeTQ}RhWF)Lszbzc*C%(%Ch3~;dbZb_J|alF2;l;NyKwoMVX^_ zhQ2R*&rthfChjB$URExcp-xD^EJ%!DpD0HXO=3$s3OD6}4J8iX0ewfcK!{WpGXnYk z$x;O(*cM_-%xpei_9O7MoQ*h~#em|Fmws@Ksf1-wsFo()6bbCjxCx$`K-xu&){< zY2)E(LfF$E6OtKVT!4gRu1(wd9Oo&^B8(4e&k|Jlkyh=8ATCnjQsswHQnNmkaSKR` z3&N>0WC?P~awH+}9q{(~Ojla)he7-gt#cimErTE=)nF_ZR9uz}-Sua~cRes~@J>e? zCBR&m|4qpF)fs$6k% zP1d*fhk@AG`ob8rUc`;Hhs0z{T&S&WtW(W$R4ghF65<#za&w2tC_ESTm4Vu+$sQ4( za>%3K-J(P7-%mkotB1IpkTj*j{Ts(1z-> zyH#&ke&s_nhuw4icb#`qoi_)RQsb&@Kx$4*D|P-@Ey99|g#5T{mwpG~rmtz_IBxca zxgDDue;V{xF_6@9eCl^tpS`2z$wktJD~(!tsX?0JilaS8W9+$TOb&BYgm%NGqTTQS z?Z$KcZ;g+num5gpK#)75ywp_ocn95j&sJnOn+}h=`qeX5^(GC~NYnO{kScM0kB*X8 zPiG9zsWAYUC9VzZ_9#LA@Xbj-&=AB4lcgsaqmI8n86yN+LG&)*_Yk0&;kc$a@Fsz- z^&wTGhv^4_#vY!9OHZ*#Muer_;ro3a88MC~3`f@s`_#O6--Fxdp5659{9iIx9e&I> zqv2d5$_m56ims7L8mmnUf2?oa0l+QESn4ke2N)LlmtPJ)geFB+` zI|M}kmD4)p2tNkR6ty+AH63~ArtIN~r~c!`!!xt@Og!aHGcLQQHvxQv{Sz{g7!u?w zhsBvZ3|j$PJ+p}-0IbTP?Q zyAekuLZ*c>C@cU(xO_~A3mE}lFNGbI0`F@v_jJLL?8~nn`)&WG+AZ+TJU%a1%parM zOAjwLWgj{Dw42uLaic+BD;ix?Tg;erF-J>R5+sh7nKEttjplxRY(Y0= zZ#eFl_ph$HrI$TSry*wLdo{M?`xWk5uAcjq3->E;AM~qOw&UKq2`d(yTXJ=TKRAhc z$vJQj>jDEj5W-cHg1?yxrq$ti8bLMC^MI`bnf6=)lXI%|Q!HvV4!GZ$q2!?9tC#hs z7yVifV#3RFfgJ7nc7_jOc6QX5N3ZWUZjS;$pQ;4MD$0#jb)jjMEMtXw)QhgTwi>E3 z|8r2eMD~)8Di@6orndmCT5vxxrLw*$7$omZIB*&U@LPhC}edFg{|QL`&3#M+awNHPObQY!1x zK!(W5^l%=k<)`Tpc$Uy_%cZX=LMj%Vt?&nv>l;Z0hBSMTZwmB%uuB;Q3ez!n#Q@gOR&5YHVwhQ%RMx%7raF z62k~N`0BI&@up?W+5vrSLe0tkuzaT(^$%L!oXE zzBVv7t2}olCWOD?%6&CIU2WF-`8(-mq5F>?r@6%Gznck)4}ad8rE>Xzm_DF&q34r^ zP6Y-h5uPY2!?0jitqcC>FfH=4d7ySEymdP*wESAKPTk36-`;r_SbP z-2h7ENGPy|#u&}yyghX@Z{Ce<&))IL9-prj))%_5<-mY7tmg$zLvL}x#Y8i^FV(Ul zqo~X(t-DXEFzAJIvlp7oo^@}IE6h-+m}6j4uJv&<0;qhAYYcO~8ya`|dFTfN`zi%7 zH+$IXws9+G%rk4RqhtnjsU6G&GvLlH>1pH#X|qBSRPfeTc@grNDs0x#O3 z{}t9L=B@+k_JtU*m^4VCOiB`v&?_Mcw}91xpJVrd*m+);qi6E*S_AU&-Cv$?>83t>#TBN`Y{f3Sr>0KY zy%3s^w~iQ5u9^`Hg&TqnTj|Bgo!M7MR~(hyozmUu3-(ziEOcfFOaq=jR)MvF0t(l0 zRQZDVW{%<{`0@H-u>0!ih+H6!)DzL&&w`&=y3P%Uo18lt{7X$r-21H!h(de?@=`{ zz=C=;4>$nK9fGw`lQC!^_&}7$M_>TE>O?0R^mTY@6wy{2MlbC?roX!WaLX<9`PtRS z@n@tyZ8vERf!vL|8qz|tF3^z9cqfOvUNwOR&sb|CCw0NNfZ!(9kk^a17YFs=((8~N zqjsF=`!H??qk6a-mo9DjVdLk$PHEm=J;3x={)|&~w_*y+X48V~IMaD&+_PYT!AMr1 z&uBwhB9agjfvF-!jfuKQC$Jd^u@)g5nJpwSiv~)_U__RRg_wjRY4#b+6ff79`_8gB zX->@s_85BY>$#HA(h+WkV#MA^zGAfR6p0xt?u?z8hGwoaLv=Q^^`kJg7SkbSd(t5; z@oAlw{o#c-mbQ;Q%J^-2y#cruQtM4hywx*!uprD+`tP5fy=CFMzh8f2Mu&UQ4)Qg{ zbX=$geesdrTG~iY?9`$o{gKDvzm6bFd5*SkUofZpzJ8f*q#vZa`yKvf))@5tPBv>C zg|_X@8pHqXWm?B!-$w0#Is9PlJUU~P){)p_7(M)q>|;M~KH#^-r;`&_>fd^ggruy+ z<3SUGHp=n;9W7~v>CEhccRqe+&d?5{=}XW1=mbs-pa}_R(tX6wVy&vft0VDdnEX^! zkS^MI84H4*q4o}>?wBsbfAQjn&Rigx{(qG=IWv322Z!H$!*TUS;~5)(czc~cs5K)B zD*b}prC&=Q-_FV&udz0rte6m=*193W(jWKB>2$Qy=x)|d> zS@DgyMWl-7W>=aHe}2C+4>9l|os+@FX-GSf&|RvT4B1*+{F4oZ(p$SA|n)r-nX(%E8*wV;8qulY?EXP2${uwu7EE+{#EneTH6~ zso)IxjD9f~L!!HWm>?m`L_T0i01SEsdQ*=#*7(F@-;Z+JjAkL1uc-yg7UfVVj71hQ zB=DvtNhy7^`h)eRZGua*zr(rGwQ{OiQJVa!3AX)y$aXca^d=%I@6y3 zorjK?bL{;hR29oSpsW}hUt&7A>L;}@+UFc36B{%_!xssLJQZKj;C<2-G{jViLuC# zFR2x03y>QiYG-KZMHm|0kD%5IwMYy{29mOnT9vrndkc_SmAJWALh}t(K`jhtkH!Uv zYdy;^tm+HunhMjh?0H|Fw&_tDg)T2CUOtqQsZ}T9)Wvv-f|@vcVNINUOKYUf?-kkkk2O8EXw-(@r+aa5wJ2|! z8|i!@?p~rqq0(EHGn|29WseVz;zAuNTxj=sUaZ*rh^ZLSpFX?N6hPghI7DQy^>;i_J^m3 zZZKUw!N@Fi;OR3zF)}bQPDUx0VhEiUvr${UGJ8i=Dl)9!*9Jf3PDIkU!Vym`IexX= zHrCQC#(@Vew@W1X0mfb{b_hP9sU53^%24|+r{Wz#%-7V?kcN0WW5-E!K_aa3j6-z{ zI*nm)z&IvG;oi7u@gNL=i2SWdjL}(36)y(zH)zWc&xayr{rO-sSs-|DPIEn}BkB{4ah#0%pYDhLQiAp$rsINZ6U1*WpZ&7mrV z(io`DAa?-ee9fohxeFE^KJO8KvH+tbPwBUX^XPTiV>n5?GUeg8yy(fJL;x~$?k#+lm+ImMO~CaL#Bf44NJq`46(rtRst z76v{IU?fiFr=-{JwP5)tL;4yDparPu)9S14Ki<5qmo9IoCMAc8lbu@qvMiMMxjOO9NC!ol}w-q{MwyDdbQ zugI?0XH#nMLyJqthiSO?0g6RbncHxefGyP#3DcbPl(#1MBq`SH0vIerpx~J^ywfK% z6D?qFW+BYi)$>n7i(_n1Jr{|=zlacoD4i7KTWU1hvCuK7s?=Z^6|%ihmDa+uHqFjt z6e3H>j0lrbeGQ<*qH(}{zzhu&eD|2K`acabc6;_aVaYcaE$LL{i%XvL8=Bg@o}ia5 zA)pMoRU>Il-C;IBA5#hd8uA^#+rKZjA93w=bmF`8(w#L|ejsQ5k2rw0XRCf6chgV5 zxs4n^dk*MLkQJ6V=*x#ZJ|79BXcHL=?Axz7!|pNl>%};&)qK1oYZ`pYhwHAGVPKdr z4j-shc~gnB7#F4`#f;UhPN$QMc&NQ8jXBaNd5@oxhO<6@elK_J@`B$hD_$N+YoqDiQf4Vn2fBozWCTyN%@RK=P zmvQ6AKNGMwQl*%I9qO2S{xYxgAd%ri70?K@!N?W08MNhlv(>Bb3%)cz#6lx2ff82LIWV{QXQ1Wq<$et(4Q1TbB> z4IIuwVp>&qjmulp4?8E(;zHvLH1gR;3Ov z4`pW_{@6>aez6-tqOGK6MLbDpXl|ATn)Y!SyG?mgG-Z*&5uyh25OD$SG4dHnN3BWn! zM7a`Ok7l>O{{Lp4KdIIr#R)K+n9wQ2a(Z^S4uC<00GLaYYVYu@1@v>(hoW6`&wI%i z^ISBd{}z}5{WNC46WJA?AM@DA9qS9PT2J23*u9hC=>-sCA|oX7I$ErN<pO-G#J4Pi5;n-@NzB``*w6?Go;ZxUFB4fyY>9CxAfRSbTq<&hy-lT{$yNC8g>)Lr9MnRJ{IF9)^%ki-iq?Z#Rhg1 zUo5Vo+^>3{b3UwCHDa7;+o!Z#!7Evdl$bj;eOsY{t2eP+srC$dU2bnA8AFKee8HsN z$ZEqjhovXUJ4M-HFO_1lA+2G1v0Kj) zYmfI1?M)6q>*2hS?Z4^b>rcMW)k7O#EUFi81+aIpF4W!xp)vH@9brjK$IAhHzn4Az zuyr3N-82|@VnO9Js9^x0yj>Cq4#pXYbwG*?N46OUmSGSiGJeS1fF<)5-(lZx$oOq@ z?pAVP-p_todu1q7|6R#0U8!nn207j)YJ)j)E>M7U1IX-lr@`im;=iiu+T#sUl~3#d zm0SSlrslHP`Jp{3pKEP{Oe+hSR>LCYh7ikt_xd~4JN$FV2@R%c zA7o!&yWgIFOh2(OnggH{9`P{okgxF5`kn42KsgsZhXyXJ(_{f9nboXKYaV-Q;|%|Y zW(pX2_ z4@Wb(L|a=M)KNHmez-b^R~5!Ez*!V&LA3{^zlYDVvyQ)iFh6Oc(G|0# z^~5^FsKB@+6i=rvG`jpUT=>tkn;-f61*v%I!K3llqVR-}jERwC5=zn-Dh0zhWr6-Uf%S1%F+y#m z!W3YIDS+#MKT5y(Z?hYZ3~u`6Adzf2wUo%a+M9FYzw=pVIMNe~q-kkX57mn;t(VT) zVc7RMFW{G<+NDV~iKs0?>TUfp=SSDPi|?2}W&pi`jl(8i_Rd+9K)e6UWsB>Uj9o<6 zf4#8zv)jjxzlN??m^Ne`&m4XF6PL9VGu!iJBW<}7k+wXDi(#on=V@t?xb6x^+Hz$h zR74ym>B?RcA8yFLowsW!+fN}|R+emnah+*!vxDG+ z%LXB2tLC7*1{wp%i-x3%V?Za4_0~s`czrlHn9`-zo`c)Oegt`SfDVC`dI`UyDdaf1 zpCYyP*PXYYZo5g`~8FcTCvr z2AYEIIohZJFBai+uVtO0T-7c^NhYG1OCy>&VshkarO-xVdaCQE@zkxhl)1JbuIz+w zqhy~UmjAlrog>#CxO^l{pV#i9UEVab`(-ZG*{}HiQ*|j(x~^a9Kc#N(X}z}}qx;>` z>(6(;pR%2Mt{t$`(D!tqF5^~%sy!eGGzsPA@V|QfBO{1w2bY5=Y0hID7$F0xy=s&! z#}kZ+k2GH8-edhAl?7}hUxPCwGk&I-2PVV}?+^L-M$Q}LeAB!fAFA`$^AZs_wAM9- zabAewU33QKW_KR{#Q_ft`qOb@X4B^ExG4|ak%-ZXnG<^i$f-&sIY%U&7LqY66{Xe( z%VFZING*ix2m@e#!XN2ZfEa}S{K$Y=vy58%YnH3mn>EYb*WzVppi#^JEqli^htB@_ zgG!?q8{^0Y=?O(3O4?IPZ<-uwA;DFvc3Y{f@3-um?&^nrTEiCDWUN(`;m5-%Zxs?4 z&Dou_-xQ#akLZmot=VK!Rvghe{JC|D4E=&`3%Ko`fFH5Pmr*~>;^GNwLEN3iad(!_ zl}%8xj8dOye^Z5Wi}cf~r#4s(0}VRlAj@DKn>r!kO{9^P1xmDBQ`4x-$uFs^Yt_D( z{Pu(YQzZBm%BHpQqvy7a(WP^+^c*#+LAn@|CwTRAC)AEMWRX%{eYAPm-+y{aH`tUu zGRuhoas}v?+y(=Z`CP*wVQbbd%HXyVJbA4sXOPAZ0Di%fbq0kNq?^ogwQcaf1q4mu z54Rmk+K619PmHuAQ*uU(=D8<31|B;5wtox>4;{3%UXQ82!g?FOUA@X_IJ((=sRUHK zK+DUkO{)^B9Pn7Gel3vhdGCk2{_9`<~ zVH%+9`NmyG9X0Dt9lX)_5mt(sXeuU5nvQhN=_+0`61Bns4b7n}g9u7cafp;^&xlDJ z(aOy^j)Gl5WZM?Z`w5O~n-hEcL=b4|tJQmlG8YCe-QVXJY_c7&M^H60|ZROIRMjJ5-xS+*$h{9af0)^yaT=2FE(b;l) z0wTv{J)RNDnjz=DbMK+mM&&1VOcR|OO&A9#RfHq*?70zEq2<<+cYC!dd5Kdo>*EWIO8H z6M_VZw@@b1Bto!6ui#P3vOw41OSLz}HUCm;Nt5|1{WkVcmdtyF!`+aEC(e{UOV@bWZ+?|pSD-abpWeK&*qaq5<)Tu7+<&(f)QIniLhy_fH$)R25{ zzn@+={p?%f@9OtY?B(yjLlP{E!Ttxzq}!+c?gWGVv$n9G(Dr;9$dYy-+x+4+icoTX|tp*cbxE~LB$UZ zA~aUbK`aA~r?(OO8Gnq>pLPgbTeAmvN<6yVbSJ8O&{*tNml$ z(Nr8P0n|7|xF~p!0%1JJy>s+33mKtRl(ZxrgoqWuBz*UT&y_obqleL|or>MC&grBH z+Vwlyw89i$kyQK3#GInXe@2dbEwCe6E{Ndu-E{?qgFe)*-#Cf%q`mMmc_2Q2`9NIV z{{;Cydfq4Z;pMQG@p7|{ScUGV%OC-vpLHf)#>xBm;C;Mo(oK-`P42I}Z(cHS=U<*N z!myXXVcM1`O~9S)Ljm`m7@)nF25WKw%W3aB=>f_b>#h9RZyare)6508SP-59IC3Kl zIRa9k@klLlF{EG_@Ye&Ck2hp2TEKO*V<;d zs2n4rF~p^Fw4|y|NYIHVs7*b$jcxn;n^k*O4+w69-f#Q-g+E5gl>?R7f~wtTy?Cb% zBUHMY0jVzywKrwZA2brixNF5GfWy`TOBmpD>5j6Q1MNBHz?@`+3xJeZ+5o_m7rh-E zzyH494yq{eF%-sK4^`gXlWDl&%A<hb0&PG;sfI{c4M* zVT&@lVVALYPg&iY&ZiPXLUI+AQY0B|4M?db68rpb6sTa7S1-WJSZB(g7+T#kTW&DlIb>9sqEB zS`uRZJn&3^I%@}pM^tE!OKxzXeU8nPRhe%nv5)tt#_|x$8&&GNc@ASL87_(g)j{zCSr?N9qcE(NC8&LZRx0w9Gm}K5)4lszAK zXvOO{M~o(I#vUfF0U5j*wx-kH!No0l%Ah;D5u|*K0Cc} zx?Wt*7v4+g$8;Y#E%oo#u%iDuK`$}eLt+VpJr z?q2&}42B7+%5q%XoOZDz5*LzD0crjUGO`|=oHv(^EZ+&!=*xM)+|%gGaa*h}S7ACr z>HpDA=RE(;WU{3Yd{h9Z*J=?`M125R$2jer2hg8weSXj0XnGx?1Z$pLFAku2n6&<~ z+_;hjif6#?3`3)K3b?3+FhCS9cFDAAv>%|t)Xqr~5V&wlr@qBJn-2EtYe1|z5}M4dXetuKAU&!_%BlZ{wlx*v*{Qmu9ta6suAiX_)V$hGkXcWyu9LS?AAJPm3Mgc}hfUxEiuuRi&h`-WP!&AET6 zk442oI<%+(pr@ntC_nD{%d!)$Kg?jZH6~RDQV6V+qmh^_W2UcEuB+JL)b(Fwjph)S z%9BJY<$}awg4dwX0(p*5tJs|3Ix{elAf6J@VdH9}3~PaZ89QcTKog7?p{Cn#H@DY$ zwL#EAmMP0}`SP}2QL%fK?QS~xiI0DrQg~a~#$p+bHYW|by3rrraCoK`gH%|)+uM5d z3X@N%8u{ig%i8I+tt{)>s_LfI_H=CrJ~Za$C$3CZ?D${eRQr^3ZoRef^U<=-sw+EK z5)yLZ^PI*QgSJ!#y$$s~0<7rf(LLX=QaN#plyf+x^OrU6-yq&Xwyf@_H}Bo#a%qeh z#bV+4lJ_icMB2gJRz}jy&{$!f-qud#qez7}GlznQqI?zL`yv zmHxN9`Oznt7YtmW9%_G8Ff^U4ftCiaG6EWr%_)XO<91ynod#;9VuA(^p6jM!*vMaJ zQR0S_4x=zNFwm2FgNJKpJl~gfXlBzC<)BY@n6R)pN0F#bo-$4z1RqjTMC#(3+MAmN zh@ES(@Pv!34dM%&mj#Nq_zmNxo}sRHA3bFs&2p*1YkiOGf7g$fUy`N%viq58l=X+* zbn1Y41ZHG}fqH}Z(ZyCU0)a%9OE^V<|VOUy6f@MdQp7dlj7hCTl(sfGu;_U{vzD(gK zt{$U7C9)o<;AiNV~0P3q$u(wzcZ~xi>2G=}Qsocx+to-= zP1u5qE|`MrHzG9&DWldMVVm~Jw?^T*?hS6nMGa`Gl-^kFSEWq`MkL&QWj2>US;?2;3FT}`t0R;q$Gh%rG@gm4}b@c%T zwg>YEk{Se50?ndF(Gd*uFwOq(`TA3)-}=z7zAh=PFohM@4=Y>usQI|$@LADGNWD@j zcejmlcjuV9am&}#c3Nc+T^F0XaHtrL$jxE!#t|7?x9oMU64~oU9p%tGqkoLvvD;@C zR%-2g-antGb;#e9Udf^>DAL*?E+|k63WyDbh&<{|5yk<>0MijUPU1K_swwAGq-Pht z`{GycnJ*qnQv*%wv^PR)&lZy9ZF_3>Q*hEX1d6bUw7xLe{{8QG9#P1RuO~)ax{?mbo=T3ZQrl>{rB{lZ?Ii| zJuhI2^W66i+P-_M_M0-5sP-D6T^7zy)0DCIP8xjf@=J~9Cp0M`$T;P428L%jC81gj zJ&!FWMMF%sjEgYYYG`ZG0o_hm_#D(>Wi-VZv)dX?T+@_)B*wT`zImlCGorJR-B`8><@=ay{4^sZ9h2H6r+uoD!LkwhK@mLpPh z)p<$r4iuZIu!GOd*yX)Xc71=?)}jS9n@&*9+U22{$G&z_p?#`RG-aG>&C=r0F=wi& zEgF%cZXl}l1icZJ+j=MKt`}P5^WV& zOM6nH$`!d?q)(#_mW&Mg>4!?)A40`|OQf237P%hEV++`kPvm+g1+Q*p{4T5U+Fr zH!rj%hV>$dbX5ShaH^Oj%F5)#u6TQRE`)?pR1&wq99Z~|KmNG7|IEh+mkY^jQNkbI zyY{PVZZwDr_9X7ZxPUj*lT0OXZ9yr~eFkGF17cc$in^i_n*|e}7AKAbnYmYHj+ruT z{f*{QZ+g7y9hr4jmU_KKskD9hhVld<+cLeWA1f`mnl_hvX7PF$YmI4!df>=qBe7Vh zy@{0Is>^YkE^Zro*G?TvOuPJ#C6HQ_;OhCwG51%QOO4A`Nk9#fj7`F*|DOx$YqWoX(?)blquRozQ9%;V)wZjiE!UHi@njSzjIE>{aK}un$ zIM??5q*qKYzkkNa(IvjOM+W07dY_10JUd29yENs5-t|^W2YB9O?Rxp`DXFZu`lb&i z{nJ+-cH z5?#7uspVBnJF(nHo>3}?zxd?FU>8|mUEntAA^6lSL6|(at42|ni%>^Uj)22MKB2h? zyE?<;I@P>~4-9CvZCEff65<*{mHhZwXD4Ck{BI*q$SC(Ld;Ix(tIsz=FSr_k5_@NOGAn7W5n~_BI_@xx>^|_^;?Gn6{HhOGrkTXn|4sWo6Lj`}4LA0Y zPu6L+?=VI?2QDG|_4%Md^|*b#NT&WDF6`a?-q}Z{!(O_j$zZ*w^Yzcw{3pME`W_C_ zZU2Z=GSKB0yOw9GnCrJQ80t<^OtXclFUP2y-eqQ8ASf(>co*Z6g$$SM5T#TS)D);) zrE8%8CKIHq-?VF^WPlc0w2&mPDkr#{icb4X%d7 zlgjXX`z=BYkls)^J=hU#TzHXl#Py*s& zEd;Vqgjfn5p@YBR_vQZgjyuRiCsDzveNOuIo;{2O$`g0(@!jrko^A-V=z-UhFc)f#PFg#t(%BT7vsv$jS%c zI^bxZ0QInmutN-+4vQdB{*VVX8%ritB}71I zQm(XvE*Qf#H9fF)&GkEaP8+bTk^HYVl6z|G)=|5jZjkNoZnkODh+ei8&Ngiw#N05P zaN3Av4@<%mNbUM_```8WGh=GaeJ?t9y0Y@bpJzt@9WlD+o~3W=89T7VpfD+}1aQ=* zqW)MX2U?His^D(%ql@g9YN0YNpTAb<1PdFP(wNP!TL;I2 zjao`y0w%1jz`L>Pf&_mi`@Gq7mU8Nx3)fygd>;cVu&|QXE(s7iE91$Zt#mH@;ct(M zQw%X0HjPK4;+QRpON4}6_n^cVD~v4DbCmDisxVD?y3?%pQ$iWm&hzcbmiCAg6Jk6- zx*`&lAq~)K$s#^gA)mXAZ;ZqN<&j2~zaLjr+K9tPi-4GrkPz#=$d18Mvrf%(Kh2x@Eia#0HB zD6^*Dedes!orUpz{)0K$J2#Wamj*yg(Ybu;ir3`*nB!esfX{gbfRq;&-E;5v&QhbqX4jw@g9fJY~(A z`>u1YtSRBgy>n=qhl;_Hfw$=QPBO(haYh^z)ll-YrxhipRw+v~LlPXK0~ z!CTqus4$(cTzKYBp_L!f^1f3GVv~8;f5-_tUzyRh&(w3CI!X6(d&UOkQ6Mq$E-9gk0l2_Io(K?+u#}sL zu_HMuybFQLpHA+3_3+ip`fvS)ut<4-pPTG=pCK0x2cZnZsA7bePq)d!g)*!I-drld z14K`jb3s8q6KUaG^c>0$hsQ++LeHYfifU~EJst!NQ7qh9_6)j6>D+nb1uHL$8~KqI zPc$mT5u-Z_S6dK|yFI@ZQMz-^NGt|fQ~wmynyo%R$F(&fmG%(NT77dPQZr7b22waIU}ovVF`Uy z9T;_tT}}&8v004rVlvYaiK8GiXHURR4MAK}Ak2@vW76;$w?34sCmW?(_VgLm!{uM7 z+;Q=QyZ1WkR0GFK4}rF7A<%#-=7C(qU(*KDS;j3Sp_TDsvwwyxF%D7Y;S975pkDqk z4>TJTAEv@VbG8iP#3nQmeKSOSDtt6~#SM&_ur)#VpOu>98CFTmJ|$);3gEy3dTD|n8_ zQcuw6;~`rCV0`C^MpODy<=M{lt*5+fC0kbL@D;XXM@PbHs|tiuFwDa*LPl#XY0d}|UhghSfSq*Yp@;cUHT)O0@ZB3P?Q4R&$<@*@ zYKb1Edjq3;SXK`yz6_K7cHO%EMO?1jxBt>F*+of%AXd@hzd~wtuv>t+q)O~;M`HBh zb|B^{r4$e#>ZJ$7u2tB*&;7sGAEMGsPY*5P60=r9PnB#>5UQ+vU7nMxtvc3ISL@(v zt9Y)C>);qh$RDV7f~|a_D>44*%({M4%-hDaZJ%wQZJ%wQZJ%wQZJ%wQZJ)mToM_HR z9F z#ib4;j?nDSC%x<^iRm=(d^0>_J4sCQ)C&T7DYTUplQE%_CyXpXJ!L3|_>OtTfn4)& z*bmPvmyC5y7n(qPBheJ^BKsf`C(5C8Jd&x4$Ouv078JQ@almAN-`vy?@`2BM`oF$> zr#I?yH!2(F-!$pIj<3jiXIXJaloy6XC~^cR0cnsR%b^I6#hzruUdA%H{3_(A0x3~W zB?XCLAtez`g<2!A)Lf2gqtmC?ic-gkL=X{}IoRVmnrNX}SW?jR!YK>4R| z#*?9e>)7veKK^{b75)DVbd$Pu-l%+I{(ALJpPfP0AJgYXimLHy!aAITAVyGzF95Ne z9QK>xbFq2G)h-KCK8hj|z#is-MV#Y+^kE@$8|DESe&~HmUpVB9KTQwtnM0>;(W5mN zJld7V|MuYzx5N#y1jYsUZS|pyljjk(mtmXWQA2MQlP&%zB-^5`5sA;>&`1=rG>Dxa z*otIXY)J-%L@Fb24Iwwb^s^J@0xv@E;Zip%_grz=0b|djh(%TMEDu=7UpNW)=WuR- z4nuh)z;Q87zK)j!ROd}$MpJ?{7!cN>3J)?)o=>KvL?oWCBM4CV2Fj-$u;I?*mp(9c z1chjJ-lDw$ezCjRmoGX-b&O7?p-`e*i-9;;NW#NIUErU~qB*ZC466v^*mQu}( zg^nr!)SxVHA(l^|XJF?!l#}BLd_hI=#)UF2VNQD_&Sz3I4+m);^ml#8$pevmictLX zB)^1quoj*SEqG@P?WMZ{l9&M29h5Rkr{ZF?3xAz6eN^V25=kxJm9e_I+i^EkpYXR-~`pSfQ-^<&sb1OI(I_0FH}GU6}M} zvX}BC5cyY*bJ3<0D+}@ZH)U1u`E%|%L)86ppCMiuYc+nIm`aneAYy$qK5~JYXhMIf zpBi53ZHlAwv}c#^N(b?42A4CB1{x;S2?+q)lFB`wC`S@aVoQ5Otreh~M=gLMmc@)f zPMR!5(kLU#lB8U*{d8i@Ykby+SUoR>lTaye9bX1-$h(zjAm_6BP*?%>VMwG{3}=GKB&+iI zEdTf6e}AQU)+^ifhT2(s;VYH-e{7mI{iFQ~{RmR+g4BrwkDQ=|6b>lNPD$nX40kDS zM;?E5W$n~Lrv*{jX#paFothkb*4|g@|qewbX z4=SZY{GX`m6}6(E&@6KuLvG;?`u(sYw%fgjixF!+=u=1QWP|V9(%)HdAg;1Fh%*}w zU+S1A{d?;Eca6Me;>?RCkn_x@)yg+3-gqy2-n!m4c5Pt;;(-rMONyC3D^lvFh$Upz zm9TiaD`ENS7Tlxv*?eKq-qFwWfHJfG?_WN(|ILMCc~NM#6M?XE@Mfq{fxtFF>*cVp zejkqx#JX%YkJN&K_B@4f!^RxQyE)wviF26u*^0l)*#y|aOqT}j&E+uQP?C{IcxKH0{?!~mXtkks$H!2bUNSY1G1AHvkF@68j!b=xhx7+bAKCkC*F|+E) z(7HE{F`5i*xi~J2E;8eeNCKW8y{SYxk`Yk>W{n27T}0RRkr&4pjKph{r>^^Cmm_QA z2I()ICa1ea$j~f{uc;+3wE(k?R=kMy`MJ(C5;2DOQcb?q;OosXr?%;-Q%j#CA6BLu zeD7ELefS#L@iD?|Bqo%GzyTE>|cBnZvbwa00_kXE9-n1VJUO4iYX*;PW_xGolz zSW!Znylfp;Ut`^mw@2e>hVuZcEf<7BIxoNGurq!V-#=u~nEz2zH~&%}Q~ptLr{k*S zKk3poJr{Z5+5{v3aly6u%a-6xt21|!CnLn!3r)LBcj7|#>~FEDn9R|v6uni{$8v1ZK% z*4Y?9SP7pXq{C9ALy+W1oREHC2ZA9-fPaD2PFSnzTlg4B@ycNEJNVi*7snCNWS(JA zQjJ7a5NAAr_^{+#5luT|X@b9H(CNUu3ylGyhD4|P&o`Y%`OLIz^8?Z)BN`` zG�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®~QgGY&rO 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

    0eng`_@uXFwI@yVSgt=Xp}aItmm6&XGrO?PvG z9#J3abi*9KjU>H5zYI1y2w%A>mIw6ogZg4L;n zGr>wtkX99pFptPun9L_Mk}3Q5NR z^L&ZYCWof9DNt0})bscbur)vKx(E)(w@*Ok(spZ4gZn?+;}N($WzA{U^2X{Da{P_@ z_deqfDV|@dk#=E>a3DoF1Q!%l5&#TyAdM>LBl`K#C*-AP4g2%^+pRN;9Q8>JrzA=Y zktsBdd0-8qmVzD`o* zP0_Lfdtlpw08FS;0gMM|U@>+!Q6#N0-PEIf=!6Cueb7!*P3i{;x0doAQUgIcVHmja z(7`Yj=__b69lIFcjV;~!Ps;hb){<{fN1DV|$(DCe9W>ANI$YBf9VP|RX zS2&IJK>+K*1h6hhF6E)gYD}F*Zh>CYbMhzdw_vvL|JkBrTiLlzr`9IL>PY_M|M!$9h0>^~HCU=(v2|_G7wL_oAPLtKRnMfdpX~c3mv~rDz9o&l}_KaA_832a` zIbJyOh9k#>S7l&IB!mXG2Nunusypz#BwEMa>TRhDwB{F;<-8fXMMy&ugaphYn!;{o z4gR|Po^2fS-22Cs9gkM{?m?h6%OpWnF4ZGSY^rn!+7n^XNp4YMq^wBhERi2WTc#=h zhJ4xeFUa?SM*dH^DE|MT}tiZMR)YJf}AtZH-MNY5z=cF&L zn*Q_X?f=W?);Hv(r|)v-vwI$55(!rC*KQYCDUfExrnKaPjPS~2YNk(R{w&^>=k0e} z*PQ8QgfezFpkOSX(!pF_O1jcDwDd2%EuTN_vh#ayyUF0_Y(3d=yWMaajP9ichYgC4 zD~M3&RUG$ z&58(H=TE0HX&&f9mk1hDw4H$YS{9$Jd_OFJ!~4V`+lv#WoZU;&oDVFr|bv!gbAlsas>ns`=-XF2a{WSRu+z zW89dN1|T$O32@y|(B=8V7VdQW&8|VqUu~4idd zayZP3T@AQ>;6m=i)Q%-qV)b;i4uh4q%5udoTfBVE zWGUhE^CD(biyN>WL4cd0An#`4s76dpNEXH2aXPLR0<2=Ft*a&+(h{g3dHSBbYs)TQ zzV*Tk6ZeU5!;~GcDlrxV4H~dBz_DN==UFx`h8cn7MG!Y?{O6n<1~VmHWqDs-{;xY) z#dRC;o_vvgW)CwUWyV8FEyC$fWgxp7odWNX3v6%Z*;bL&?sC>}JR|fpXVjez8j;UL z!Kjsbf_lPI8|`5V=PLDn04r*D;Yb%4#GUqP4Mh$3oj9l*64-bS#Qb{D+znbW=uc%3 z<`jhETi9VyY}mc2PTvCk-Y!?b-r7M z-rsi-2jSHNlu9G{2~VA0d9S05au)Vux3WwsDcH?`2&+;cfT}rA;!;(bLEi*{m(^vb z-#?P4o%!uqU({bdkQzyMAP6H17>yhVs&gKi^oT$-(FRCdqe<4RSklw?(u_Q7)2hkx zjpxthRm?9B-DjOzTw{J8v7nfsQeo(Fjj98>+X1i{~uB`{otJ>PdB^HCedX?o1 z`JR0U?~_f}mF@DVW`BUquzVcUJ{rw_l@N$

    #c*F8%3DPf--fQ}~t^R+JF!b2TX1 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

    ;e>Snt8CW!1P!BS8S3f*)%u6c&ap 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 z%0A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 G)F^(z@DepND1YY6CPjp-bg> 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%0DF|!%_hD*K^A@u65N6IzGu>p{YQ{ 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 z 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

    bi zw6sD+PK=PGynyj~+w`;zX!Ez^rkN<&v@Qt)gh4*ZMZ$%K(>1?jbB0y>RQ?c__UZeH zgtbyQy%vIl={zoaiP3gcIbtRJdmJ>-%A8Dqr(}>X5+}71uQkpgH*-;JbVW(6-r7if zI->V&sU;^;jbf6{!&-EoapdyTs_xS{O1^QTn~|#}AMAJUJykh2sN!c4{&f8`no2I$ z;(V1~tnQ8W>v}DyD=(Mz!qjvfjcmNEwdY8;yc_+a1D|wSb^X@zohaG&xVbH5l~UTo zWmXCEoc1R?XQlLdD~HNhR%%s8Cr zwgV*?h>xM;@Z6HX4kwbVH=$z|i z>9(Kqz*POzYoVG38Xb=bgKTbjzYm3&N$oQDau7Z&<2z&=+awVQoqXPTb7Aj$ykN-B z=Xh*!CFWm5G!F<9q3{RL0gmE0z!_{AF)SHG!(3=LeG{+*H(pS*9-Lyk45OJ2kg9V} z$Sg;rRMu!Vbp8AvgSK_KO!f#YO$x1+vgOEFY zQ5Zr%Cx)7mf&=m|$}I}Nv~c2gx%_sclg)YGd~sZBmEZGsp>Vr+9sbT5sBFTzz?_KA z@hnIm|8)X(m4@|7!j(SF`kxu~>SUjPnsF>@9mnPScXCWKgWn&)WhCKdT`DX@XsB%r zR#}sr#SP?e?>RDP#<+AbRq>GUvNcOC9Y^g1veeLo*n;2%om$56du#iCZh%D0NANLf z)Y3_&z8s%v6Y|)vvVnR;RjFVsE!CrYukcCc8u4hwe^sN{u?iaW|4`+mrG!k=MQ|92 z;*GdW#e-J(jbjp)Do9UV(Z4=vlccnt)iP?wU*-CI?`CIV*(WsjkQ?vZ$n>*dv!8m=_Pc0 z{b}u?+GmLrgyxOSCd~INOrQP0kBKw(#YyWd+&N%! z*isEaXp6W>^_BYS{JMudoR}KIe&6o-6oJBdaSPy}iXi-heL%3Dwz*Vd1Wim*A~c*J ztn}IrA=hWf2e~eQmetA;?gu>xjF!=!?;n7Nc*i)64Im4vq^zSNgsK|BW2c=m!Y6>PUIhFo&W|+2R zA9|fWIhyJJ9FXVEU{&38;aqQOcD6-Gih6E<8SLLL4u2C^A5o5yH|7-DXtZi-3iCOZ zGTYMsGS#CsDlI8 z^V`-ou}9gHJ!3*$tqS~8e|ORu?V@uvH!CX^1a#rZ7|kDGI)N~~9&ZnQ5x<|Fb_g!r znO#!G} zLYmtR1z0WiOmMP*#k-S#z_hz3YS*vFeHKV1}@rYY$hP2*n>6CLORSZ}l zv#C;}7HTQ5JLcD5aH0()2tGb(yZ$!d_K_O2Krb@AosiKT8E0mu+ecd}I7&7*+Js}3 z0N5-l?p1NjIa*6Fg5G~{PgwotIHxWy*JJro6`A5Z-e1S=x!reZ_E|fHXb0zaRAJ#1 zome9_vxdbl0S*6yZNB9k!z#Gi&!rtH*5ND zMClX5oko57ubCEUPlB$x*iai}y+!pw_pa)h#A(IKmBQ!##bhc?&oQ>o20y3^7~sDE zdO(H0$)jBLK868dEg~NPM7WyMrrx?O^yVr|4`d!4aN^x_o|s@5mtp)SYw^awhuMQN zV+px~s?#%0oZROzHM&J?lj7a6<$5R+oiK9g7hj&Jy9V79D2324w`LG%k!%cfvVkTy zzoKmr zuFSmaTW{|B&P7JJJ#Ft}K|t%;gzSn&H8G-6QlT|aB9!EGw03+29Ih5cm4%R+gLf2a zt$-43OH0f8BgO?HTZPd*ir%P$P45lR`XP&Kwn^z=JFwDB%@ge7LJJ4=XQR z6+Hb3qcTF5$k@{Qebq=b8RfBZpP@CWyke0)s;P#k28LA^U$&edhyMzCK%LV_MZ)C4 ziyaM26RJ$BGmA&{KIOWllZuB@Qk`Jy-Kd@fQkck_8e*Vf0mnePY zvf`bLDv;Qq5DGbHvsD#li&QZJ+7#MKY?T@Ofa7N!RjYRX_5HrnuQ{&o_No+X@0K

    HRn+(LsDT{lW`tj?fme)nMThE!{Sj20N`^Au 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ⅈw~Gi1-;V8hf-p_03h0#D7gN*h8bMfS;~ip9CYeG2COz1J2V`- zRS@PM0>&_?Ye+$UHb_V4WJwYQzSC+hJ_o*dnvn7)fo<%UDQg&)A%yuTFC(?td|i*~ z-gKm}=?I*QV-KHnwYrYOXsIvn{uQ02yfboBbk-eHb27n35P?iJN1mEphT;6Ep1OJH zH&dfeF7CIxbG5md$g`Evx5qvAo|iB%1FT(KEJ}NtDKid6*7HD?Q9ZpvQ{-f4V?!mf z{)wP4j6u?HBQ4Gwh%(bfO4Rh)ucIcPUShnAa==&zm}LUIBuk|d@NYDv5WPg1 z_WY#3t-mXR_pHcGB!UQz*uh2d#AF8*zkq5w*TV*R5y<|a`br%aZEa5SaWd5aEoNlB zVMXGTHw_$d_;D@$M*L2(RAr`1mBq^Wmppr6ok4@f!!U8XX>+3s3){PAx{zW8Dn$kj z9Y7l)z7ZB0!%4N{>LVWlK3-3gYFk-PBhLcmYI(@X+=p{=)52=C`^;P#0c3X`z{8Q9 zi6VXu;!X5g_qMFoJ$Q{^asojj%FH@ba$=XM4HqcKFxNY$EGCyepiI{gF@HGF`YurT z8y8J-@vxW>!mP+gIf*3wEKIbO^|yyb7Ficy3^?8M@X@FLKt@xi-K;#XW7c z@bT?e8sVJ`&9hj(kR?SkhzApLB!qYtNJe<30sc{%N(y|4pDRc_s$d0U2`l^MN;#E` z@}6nUF$Uz~J=2fb|Ujy9Q(ms4V#4;Pafyjp2`@xb%;Ix%46-d2K1uhHxAVJ=^) zDis$K(I{{!>-dx?fEs89S&0S5v&627&<{od=8gQsnH?B^Gc&kgTr zsZ}T|?4o{N&hkr`Nx&Gv8-jqBtJzi1j(HZe!i#!GtNNAFhB+ zVF|BObnAcXcoACn&cC0=`+dW;`%gL2=wVH`{MS?ebTwlH7Na5h#pBauO=)v7k%U$8 zskob$LZXnAQK^8+DJ(qCeZBJ9%0D`vT3u_DpRqT(W(aXtb4^VKRbc~yB#1E#!!f)X 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

  • o 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 %!!-sel)E{ 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

  • m*OE7LK= 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%0A{?@7M z`(yne@Qk9cn#^-fCx^NsXrXhrD68&!Ys*QeC-?pUx@_AWirppF5t_@R4E8AMYzIrw zltO5#BB;)(i^b38er~RhL+RFY18HV%Y$yl6L*c986+>b4JG{x^57zciw>`IU4jjlq zWyT^tzfY1&40|*|auOsj6ReY^?>PR|tJ_Ma*@j6=D>eu3&Z)>Yg_@e6c}pep<|z4^ zs{aS;W6KIMR=fo3yC*Df!g35J%Mzz*tnmF^r~jgMWt3hoUE1!`^C`sRosR@a#c8mP zIwDP(6=#*M+4Ph3nJZSCM?BUN--kS!ARUDsm)A2?E#5c(j-N9&Jeb%ZPDiKzblo0c24pCJo&MA|GK$1!G&aON*bWHtG6~vI28W~)g z0aXWONdBjF*h?R*z49n_{R*V0QH-DvvZA5u*WMu3>dsopt#40xs4?WtribIrRE84; zK~^PEVb1erTZSF%@qPOn>qFJ=Kgs%1lPP>2FO>gdoj5&z)uT_f4AZ{StKNjDNfIxU ziYTM%0T@>rrs&n~v&e4N18IpF!;qUzDYia`io1Tl9&&-@45iDgb_tbdNrvJ@*(nTt zVBd3dul&Od4JOlvqy?olhjVGR(a#Q}c3lFylA2!Hl`P6Mal`&UKYg<0 zfzoR!w6TH>xB!5@K>I^qr2RMW!~g@|3^1UoSjTi*n~b@eI{gsQgO$M3l#^9eNungr z(39tGJIiN627MWl>aV)Kt^JdCZCSV{iXTu>Q+Sc2oCGbQs)y%0>aDAhZL@RXvbXxe zx@Lzx8&i0Zq8N^rIFh^lx;5h5FNuVqb!19USF_z~Mh&&Avh~8}YoolkR%bohgTW~* zFV=pSIc8~Q+q89M;*octnyh8`yONsMmn+HB9hAyCMVTiBnH75Ud2h@dk4hm4iEYZ)RJ(cWWYJh{gcc|#al}42g1^0$*J(NAWDLhoQnIlDw6r2(% zlH!L`znC!Ty{QSf^EeK^A=)cNIKQIHnjxSqE4*PFA5rET$yQXa`L0e2H4ed;im zM=kiNUD0#poupg~Qqml4tz2T8{`|uFrUGc7i*ftO3MA_H6L$F=*6G+~wl|l~w-pTP z99nibLM%xv%dnE@lw=ik#d6}R)QBCq%=Y-MA0EBA)LNjNez#QfEQ^#Ni;_YxoP^FF z8ommRT!@CXnt;q7YAS+y#6TUoqxbOZm6p}Z)@;$bZ)fl51)l5(9W;OhBH_p>@oo+s z9G67u8Y{WnIg;DGbf>F@@L-}TQV*J%Fzu;VLmOHdf|9l~*#$@5nitV2o-Anu zA*wX5i6Kt$Y>jb6v$mI}rY}F(8scYuLPY#jXo_b0_M8U2(qLrDc>C^1 zFkRRXFP<=(M?VPN;#{u*@r6NA>40ea`HMv}yY~yDyC~x{c!?w=UQ-!W74LZBxud66 zH>(Gi<|JT{xE(m_dH|u>4!9KQgH(r&Rdtb}YtrR+DV?~bRI7$#Wh7a+}b{r6tFR+5B z)%JQ$a?XaO*cuo{qzf3EuY{h+Cba5tKT0t1V7pb2E$fDP-m!uc*-^LHH^_GSx;>i{hD0Qfv?{2)rjVj6uqb&Pt>8v_sBImy zevV~r{ZK4Y@*Je#aC(*EZCT2K5^Xy~nFdkP&}xR+p83zJw&{;|#!qN@Zki?~Nl-MI zU}f%#TOZup^Q-#xn#W0FxuKdhoT0nvaNE8q-FElb6WP307A2Y#G@5qO%Dbt#w#M$U zW;nom!Nhi{p*{?^y|ZEBN83ED@U$m@k&2>(N_AS-i>*epX{;^j#g%V=)vFy| zVUS1Cq{HR$)6{vlcVpb%5Zcohq${xXjJGX#rN1ZpV8q2na018i1R=`|hb}f>N8H3; zZTn;A^P9K)0f%j0qj19}${OXQB~>F8_WK4Sm)*MbhSZjpVA1bsL`)~!613aN1@qr4djtJ3cy9r+ib?QZe}rHEFGxYR zP21r2sb#dc1bzqS0oQ9aO0~Or~3ceG=yMEI(P+-cM;HVuP z)<1hb{ri{j{hj^orhp#)s)UFt3g6zqIJ85)gMj+`g68Z(=z$nx`4C?#3wp;fRzT0ZYf2=&9}E+uGnMUkWykCC3Vs*w@#|j6 z{-NutvT&Qu_g>NY-qWCYR}X3yQ#&%NsDITxo_gs=b;W>&`$j)UIo8vUBT}(3_eviJ%#nb@)ls|V!H!P zLi9KBgk%I?s+(Vd>%O(AdI63*Za#7Uqxs|**MC|S(}~&wRoy>S4l4J zR~>@+KDXbpWO)mE;IBZ?-~WEj!mGD59tHDP$lIQ}*Yn$KnBRKzdh+)7Mm!4h&XHt+Wf{iF z2ukRI-~sITOtw*-wkczOj$Ah5q$o~Q6;WjvwCrP)dt3oxQe0gKrk)38+eZ{JmuFZfWJ3X{B zP?hfJTi_q=Rn8ybja6s+e`^2jn<(#h-}A!djt7K8Dov zHIm4HJhtZ$h0Lql-K}3MWw3R=4ftYot#%4~EAZ`$AEt%&wW? z1kK4R3?(bH|M~&9vY$^#M}-xWHr<; zmR>qyF%{d|KD(;t6RjiFB+?{L@Dj(Vq>9$`0ytqoQ?#aHTbH?;FRSm0=;TaOM1fKX z$;pW_>g4QGIyg0%!sp8d@kZOCgrjYH{~aODrO2{8P0}Jk^Q0JJPvrtZqYq<8Z;tKT zMcjQ)ixC2M6C}lRBG1qQ&+-&n#n3Z>^v;IitC(Z+J^J_iUFJi?oO*32VlpBTaEzBb zJKs0h<57LTPkTJ=#O>pgcT{o(e)3>&ej!jpqK@U?%``=nmOrnguby}x@#_tcwkM@( z7^IV!ftqD9pemp~vPYt)gU^EW4WN!WiaTGkxXT+}T=Ree6qlqJBNfMcKKriRz@S@Yy>djVIXE2(D)_L5_*@j<;Kj?EE4hzY5S`mDo%Asp--5O`duW0# zKDnwO6X_=o(Ep>I8NXIV?H!8jS5G#m8on`J=nUm{NKaov3QioESS#lFk_z%kDBP~| zz0OhiUA=g=v5LnHwiBwwT7l)-%w#}qltTfeG{-tI{;)>2)7lsbl22Gs5OY89iNBHnjKW`y9fw%Myhjy+mg_+I|_C507?Z^$_NLSl+j3vGK{5#A$r>*7swgzVG$QDNye_ zrd92hY)0#Ruenb89Idw}b=s3Pz~mc5dEZ>Y`MOZXKVEV-e4d>ec^w(*nG2JDiZ{N- z8{)OjFO+lfy6!MN-gIQ)NP{vNV1NMz7*PFip&qj&7~@u_$JYP@45+*NNTx9)1{h#~ z0R|XgfB^;=V88_qsGYiD#%zEA1{h#~0R|XgfC1%w>;K#KZBm2nPvpTH082p{cy z9n+g^5=Ck(E#;BpXAbZ`p9+m5*SsSKr4SyFQVk3>pcW8^+ilM#KhW=cTcnsGGA&6? zmg5M;sh}}MCS%gjn(nZ5&EN5E;z#Yllc^b@WQe6GM}aJP`nzVSzGI<*aY2~{(!uPUK2#y|Idqo^b^qEXnuPeBkk1N-cRczdYmD_`D>Ah_!h}; zz9W3OaAWFkifvrKLdr+&ZBV^ZvN`g&qR(;v=f4qt93f1O;%_<|xe#><_%;4>Q{;N- zmipQk?OG{L-}X&baC@S`iB{qB)+dA0%KFXXKskLYSx7RmsWy?eLqr&H6{Z>Hg1RW3wtnLa`yNhisxoI)vV_ql_+-*%SIgiZwBOAH8I z&{_Y`J_$j2=wz)UMv6-?EwuH_9`eCR8cK2Sb{!IsG*8NRNTgFBM26y>qW0j7??=4S zd|4t+I%^jtA1}bwQ`!2{qK;4S31#v+h0-;^61kd3deE{M&*gz}h0R8c-0fF=ypm1soFu&ca zfz*c?w7(Jk^CX~F7zQGR!%)&|{o&O;s5?(?N`TxBc^UKr;alf{)p0kVwGS5s(q5Ya zWA_5!YxnF7q}$$Ch z_i74K(C%DBEbOfW(l3x*3Ku5lHAD@z6byV9z<19Da`+y&15FKHlW^L1fPT&f@0}Q5 zMjg)A1ODIEJ@A6FTOeK1IG9S!#>gFyg7)z6*$O^DD#)AKC$m070_%+x7 z${7xxKYVRM#6@T^Ew+8|(nfZ}VHiZ52wjAElt?(#S(5uIDL-FT#;SSwZpp9O#raK! z97#;OCW=V42c#a5{*3&;Bg!V{LCWcXJV)R2=-h0pdh_%|dENmTHwx2^Hkj0ant}2R zvn33}ybQ^UAYE+hG0%DBb6p|3+@owC2MbN}I0%At(h8|4lBWLi&p$tYoPJe0tnA%2 z37DO9iS4`n^HTq6lNMUEB0ETuM2mXhRWqijrMBW7SFug{!%cZd%P?FGNtCeFQt=p7 z^2yV4uPcm->!b{w+l94` z47@bJ0D~SFbjSb$jCEmv0eYZ;PQEKcr@UsVAU~H8W4U#O4kXu-c3kL?Tu0lT6yy7b z$5{gmFu;JwP<9CNi0$;AUy08g&?r2NX@}-tOv`O8T6!8} zb%n=(`Jr{>F(l=1dy7t|?4j;o38c(sB;&gHjMJ@3Ayv3;H&G?Mq6b1Bo55)2Y-Nss${`Z7!yESX> zO`pOmg=@o37sF`^%kT`Zki_|C%K2M`OZ`1t4|t?kA~Zqh={5(=AiP017uZNViof zErJFIBw1O-0n8b?(T3&;gxAVFpv!Zg6{?SV=aQKz7J`F8`wOh7&}Ov0<+OB0}QD3&}eQT z_1zXoXI>Zle+tmYt*GL4WRBDOW}SJx&-MOqfB^;=Q1{gn8*Qi6Td(=g;@G z7Y5R&L63d`hU`bc``7FUR>(j>3$>t!yGA#Vt^^gf2WHv1oltPi~CwSJY@14nI_P zwvTZJlaTcqulDM-(LYte`Lb2B^xqc2v&Pjlj(Xv0%sAnoWt4HjT^2}= zah|%ysec`iJiLlRBF~3L>3@SL>)DE#Z|Kjd6{a1@7Zvt!6E&f5_lh6S$_ZsmL0)~XL>@gJX+ay>irI$;GRMd70Uft(31d= zA8%Pes@KIlG)FbD51aGnnUe7py#|18L#M*5FBZln?Z9`d_DZDD#DdH-Du zzV!lWalPPv{|Zvn5TG~m{V;?cqx8HpvQPF#>c!Ewt?9x1=@_J(WXwK-wzEML`D96Y zaQP+-eQm6ht~Q9O*^CT})55W-ZC&i7odD;dG9cZV0j*PUwdzJG>`D&E@v1K8H%n9H|j3PgC@Q zvy-nA7o9bCLR$8G1y%0P$JxKex`gm~+;bGpVYadXQR+p%!+Y|3!jTCi& z(ioc8c##k!8jU(|r~94R<+G=4eKBa=ap!tza*3 ziPktJd275Tb5ZtbW*BJXia+j^}zbZMXC42c~Qr@0-7NC zSE{LAOBL6zw|w}X66bkH!3a-#bbha3%CYDyF9q|yDaQWRmNR5a1@R*>PR!!P4lNPa zab@$e)3#{z!u{8Lkrql`-AbW@q0tLQ?!?IQ?M_?Ee-iJwt5e6&y{7CYg2YplEYUJe zF(kaUgYG%!u<@wgxA}G)JeqS)`^YRyiSuPrl{rG-Xo1(nuhyNL)MWshi0(A4YG_hg zVe+ji*uEweY)2E5K7Pe)`oKm^$Xx&Jb1-;7`CBF>muP%W&B-ycljC^O`D>@q3#Ojk zSbA^f(%S>+)F{m^B@Og@*8 zjNp5;gBJ_79s6xd&R(}j@kcg$bCN2hs5DKf5_{|D`yOHhT3s;eQE2& zoBn#^e#jDryPZd|w8Q0@rTYA;;<75#)D2gqKsEUHAmGE?(NWY)pu11%M3OR&?|Lc3adqw`kucyJwT!p}IRyLh16>y+FD;+o6pmgBx?aA}lg%|}!ngOQol07Y18 z3!lybjq-r>3gk$Yu?9c?lMDXdgLJqn^F* z`e)mdpLvS*Pk<8fJ>?|g((o4~%_{P&N&}*!In~ zx^Btpf~VC15Bwgf1Z^D(ORy555h9}~A(vwtM62yrTiXY&Vy3;(DwJ(0hnh6Q@T|ZJ zq|6h1kEeX)d6zC=Qnw(>Pt!0RW4p~Ib-Ah^lj|&UueMdOqiw25S*jH>+=s)i$|-HqWZph z?BsLlH;+qi1f>}yg;n`ur16$GFOf601NFaK)Vw~H)0QYzlVyUGoScf@P=6Hs7!{>; zW!U2;)4eC2M+7XG z=osnqPV-55eLa%9xWHB5klm`}Lm54kvXxbpXwRKB^6P_tM?CW2d4|*&r^pfpn=LXkLGpd-K0`i?3X-Ly^Wvq#`lArV)&WQ=}cJA$2oaFU;efM}u<}WN!2x{FzM+ z?5&6Xe941vb;E0Uz(U{!M~;;5FZ8J;iUfyCDH!az@}>Mn6NdLIoya5R@cU2vM2<{* z(SOhWwB&4eyw0b5#4JXTS(R0tgyz(GxoB!+qYc(@D!Gait0wlx5~xQ!6Cvi#?i5B% zMwY0_+%AvWwCiEiWex)_LFsN1-rE!y(^<8jquwQz|MO_C(4z$BFH% zDlQ(jt6K%Xr;+>%a0LUaz8p-$vypy@;71-6%+4c*5+Tqq#_{z+8N?Ed4=6m_G5q-J z6*F#(aU3&11w(Wi167ypf4nvdRA>Agf)^D%7l!lEwvy`>2icX>dB+NUOmzdM)zWsE z+trTx0f${`%skn2alsXJy!|uVnhY?&z>BKlE5QCQR#SbCfb;{PN=spCrr#54No-D( zzcrwS*8@m5TxN_#ZGr(8SbshP>6Nv-{5DYp&)(I%4<{gUCX_pGujTTPUWhiHH7!?F z7W7`~g8f|Du6E#EP04NZYYE;}H6N~*9ZRm^d5^Wf^=qh|57ZL*thUH$kl$|}?y|_52NOV=|xy0OAdr z8W5j#SX}@uh?4;QncY16ey|%b%I{$40d76Q{9Bm%-!ay)0ksIqThNrpZL_JVeOs&6 zS#6kzci99d!AnkAW>lICdzUT!B}-6>0(}1M^TGd@8O}7fPt5B!as56B%s zBC?u@T5{G_NT{ZAah9BBQ+ zX*nA@xtbg5si3?a7Ab0+NYWI|O2Q|1zMRo`<=yGfpKUaY5ZtxM1k8K5@j~y%K!0J0 z2o1UfXKZ%U9Na)j+vg4UMDZ>k6b*b9rfX zynnT={xoAL)@mrD{~hd~$tO=<{WvQ!cgmnCqwO)eC8Bu?MqZ*O$6_gTdB z-oNT{@Z?Z($iU~(ln{v2+qXz^`Bi0{$EA6Fc_V$^JXe9**X{mp#=ebvPLiqnkZL~! zb=`q5Ah#oV=xAhu&hFt%qfx)Xm+1T+eXn_p?P!0IuHSf@s9P{{TKhUO4owldR$Iq8 zqmuPx#JT`9>SpJ+zdK_0ak#vGGKE=Qj3!e8L5rm1q+4t69VqeD0^Rm} z^|d)Dh2XqErm35~`Gnt(i_eDha+T*0L!2_-jrFr|seS2X2izMT?TGN)!R}lLSrciN z5Lk|ZJ~&bLV2M#?J5jd`LfG%$HRFcnKjPgu(;`8W1jUPtNHR2mdY5p4E4+KLy(aBz zTYPxwr#F`R&?pmg%FbN~o|F|rpb3W2ws?9=6Ea^hAWpa>D&s9QwuB}dGp@+Bv__hrh3QsSH4R^4 z6_tat&|vImckLSX(&zBxU~V}+OLmYX_u1Sl-ky8qA7)e)u_(@G4Y>(8JYwztNQcL@ zexpMu9+mT0DJfFE-@&qiSj}X#$+1w=PUT9YPXP#dSyZ~ zCxjChRD~5anxtu#;P-c$v(x{=k)*fk$hLHYu`?TR0YhEKZ{yCx#Ud6Aw_koP>v*Tx zk$vL}G|f3BK_M8HL3MrgM&QaGVV5mOvu?Q@>iWV2C-JGWqsXQBr#nSvY=;+V z%6nffJ=@R}MHP8T5-8Pq{vyrsiFba{V)a9ra81IvC%81U-bwaF%T|BCVjUEl_C*kz zG(r*;Mx%LBV{e*a`#1afg!E=ek03>VXL$JM@q&{{m3tq5q%+yxF!ABF!g%GT=*ch;}5_hBD7Flie!0xWlg2`3$fym5>z;Kz6ieX^P( z$%I(b9n(z_w+@2f7)IcES&(FD&7>#0ZA?vr z`(C`=o|HAxY47>*C1+Y)8Zo3SO_#VlmqcEZ(U7tZbNDp0CfWW*L-pf%JJLhyyM zrULuejK6-$=$;m4S5zF7NT8cyH_U(**s~s!{&!+0ET~+hx*0_dnj+WVQgmPWD3a~W z6%z{}G}X5T}3WBK^6YzP|686LBF= ztZL*2EJzMSo_u8G#EAdL3h=wP#{`Jk<+rgFDlgjmS-tg@gL`j8Lij%#JsglSF!nGqe zteexjr~&-oKuU0Wp-}HadmCG;Uk6Um6RnUZMJMTG7?R;BG+JTr3bvznCBN5OepQ!? zcM37r{^R|Yu1O@6N0wsjHe7Ch3AGdjO_d~ul~g{Z`(^2W4*xI{-rj0f$u>8uCfv%@ z`F~~w%Zt_8O0SWES@)dYYX80HirGJVBFWkcqp2mq?q!MLQCYj$-?PrAjgd5T$Ni7} z=~d15JvXaysL~4{kIUooJ1BvTFpV786UK-RZ=(>f zJ&{RXQ7N7$SdntL^77qHx}EI3@?1(nV!6Sq49$MP{_W34`uDgrE0VTPAZSUFRa)g# zZujEl;*7}!DSGYGL;JtP{^t{)pDJS#%gw4=@i_#+ktg|8pG$JPu2&t?r2;hA`>#Nw z_au;w2Gt8+)e`%nXCHp`Y-U84KoaHTRYs8nS|-A}1gejuD4<3!p&edkU;kPG@o~rY z_zBOWM8@GPbh-U5Pmb49;2-Al`0b{uf>PPOQ2$AJ3=M8V zmms>=tq%4S_$5z?Hreg|?(ZYM4Qu|$SW>|eu5OFoMuvhy7jUVOw?_$zJRqd7a!Vjp zL4JAT;Q7TlN2%<5G>(EtjdkWL`@FiqY@lW}Y44t}ce~8{i^mb+eilhmtUz!qMborM zp{lW*v}jeVv`=X`rs=y@TnTSfJh-FBwsGY4Nv4b`40c@COv zp{?wb)N*7hdWN5$)F3FmXH$5q(nEt&V#=4b>=}EA%U{C9NU~g*SLl}fF3+@* z{Vw-t)|nqxT#>F-5WjHTcR!uqH;faWH?1S&IS!^p(L}{6g`wr)_0ntY51cxB<` z=Ta0ylcd0jB+s*?5V9v^fgjIe@NTWWm}~#=%Ec|hs8RerNmd;`f{>gt<8(?IK~9^# zd*1!Ib9xA8Q_+t6*y!knLFV_ z<%63~q(F7VH!Tu_QWNqVLdJ^l&r$ecdE{w(c~#`^aQ{~o;C)KJWZ$DUTQA%OXSv)p ze?@k1#JksIoZPbUw)(ikioNwKM#apg4ffl5NMGU7>1)!guVLD zK>8roB%p1-(;RD$-n2Wr zfAg>HQz54i#<(kwWlMVJ%I{MA(Gxcu+fZu73<)Q5Hc&r-q!5~)O^m5k7SlHSTfY&! zIut+70w<6hEpnVlvpf+(7`nF#jKl|N;j7qY?{wLkcjtTz7u&IfFbWZtawx@~5&3?X z*HbWFau=$DdmPTq^PWvCHbf``B0yUtHK1$@Tbs#pj<;9M8&x#tEd8K;0JI2k6>d zB#pi?yk}qA_m!f|y>K7eSDply+%#5YnWIT3%}O$-?f+rG$CI6JYBu1Zf7+ypMr);)St+G#Qb1hk&D~1S_1+0DKE}{?|Z$KPd17 zb{KZrFIk>{FvE^lTaY{}usqAMG))VPfbK-A?+&DW8se&W-|qOc-QI4Gw!&}0n#aMb zB1cLh@8l%m`ti*lPuMIZK?Y~aOu!D$s649`%+Kz!AHL5bcFM)8$4V~XQw&8Dq(Bf< zNcYW&fdW6pGCUvH-_D*n_pTkCLswcj^Kyj~DN1Gth83ir+7-R-DtG9Ks+pTb#qtmB zzY+I)21&ZYg3ydg@GPe=PEJO{f>hK_*HX#v^_E}N<>K$%e`L=&NB=_~jeO}wp`9$n zhzd`rQe`jQB**w1haMl<9~n3PpL4t5S?RHXVR&Oo2;4Eu@v1&q{VQ?OKO2uclW-*5#eV1Cn?_!`#R9dR{u^?h0T(mfGx)IZaI-N)1{m_aAs5wg zzPhIQujD#5u10h@0}TGlkc%$1ajh)AUhH~qW1lj>fV#`q8(@F|2B1(~7klfPoNRyr z1{h#~0j4_S0OLZ1tsot$^Zi^TKO6Jhf;@j5r0|{udVaJNc|Tg@RSUxZ;2dqGBK5)d zp$F%XTOql^0Tq(xsr4~ zkEZX8=G=BxjP06*`7X7B<>BU~uIXD6FOc@?#pig*feWL2pNx5bA?E#sCIbw9%m4!n zFxH&`1{h#~0R|XgfB_c@lzEQ&*q;B}z~|MsBLzVs2#H`7Mv^t2MuQ+Nx-onjTGJ=? zZpmLh@x?nZ>RdRda12K}rl}s)=aT!mJc`RRZKO{vP(89b#_#p1qc&NuD(@C9vBKTn zYd4?soq6R5^wvd$Hm1C{E=DFKo+kvB;hd;zuxXD)t8AZLo7DE`7&qSg7$fooO_CzT zF|;7?Xcd;I{rTMf*_nB!e{Y5TnH@BZrGEZ6N|1R)5+o5u?{p}4F6pr)z5UN=&}&k+*=;?{(E{*vkMnY`Ywj1ydtoc@{n%{!+J3`|2M9B=}kE-fQvae7~9O@pF?;MHIcQcRDN!Y4fs;^*aaoj( zoU{{(~W?(t#1=ZaTfnKn#0%E1^J*4><9cGp!!2##w-cJ%NmBZNCQ!( zRj?!O1n)fs{Ff$z@#q55o5BB^!1r(s?f~q{0p#cqZu$PuLn#$a(}6&2gZ-*c@_T)QRCm7WD~U_fI{DpRZC_hydNB%lspxf-e}Qz} zb%7K{AV*WZ<=dkCKhTgUss--JH^x8@q!~kCKpfyLV+bw^FjsQFHO1+B0}L?000U|V zlnH#N?LWSo*NV%7!ey91QzgQsAQGIDaB4iI40*-!=%q@B22GGMAKMqcu02Tp$dX{q zLt`Y*)A8MHgoNNYZCQx3=={fn-{+IVD|jGeAoQANqF;0C;n1c}{>%+KqH z0w*2Gzk?{q)*$)fDEXmCzAuumkL15V6m`!c`RkGVXe4h(@|}@qo(}r(>AVrEbZ8m6Y#LRF$Vxn&(wsNZGn{ z&gvifr|{s@+gr4c>!N-wslBfMqrHbKZ_8bgZwTzFWW@MrV=>^-}r!yJw}g zzzC{uRZwt!OQ+!57_&j@6_``88>}UpC^jAcZ7tVldZqA@ue=p4$j>Qb{m_>;2yb7|>uR%*B|Q6$GX z1*gCgg7m;^+5PPIE>5hEpzRw-9uCg^ps(Y40~!YA2e$b*IE;8=P9SaaDR>`@*>({6 z6P`JoL^eT~%Rp%N3yp$Nd>k12?m%nbHakFU?*NB3IEDI!)~^l7y_YFX%E*GLTztAe zqZ~W-H3hdHLLM z+LjYdK`CxM!Tg(G{`&4W2c>WvQ!v}T9T^V`(qEXKY#5x*1^p9SgwLa}!W=mdWers3 z(2E8sNP3}!?=zFY@7tguY~oZ?Sh35c%pT!$Fp46PPKwe9N`7a_SlfibhsSh z9?OX((S7|cd2n97*XJKD<*5bvlB|{(_{|yq;EUd7OOr2079r&A$AMkEImlmU1TTs! z;G*DEeuo?T#@TH;cRRDC8?*`&aMF3n${JQ;)T8>Kp>~d2DkvCy zUOo>ju#YBXt!h_qR07-;PKx^IhQ-t*o0|Q~8;?5ihSD4*QanrX9L-Q1#h^0qlR%)k z3sXgt?Dfw)@Watp$*BEg2*+^AljBuXWwa_O1rASX&R^*BEbMySnil1(N=NAqNZu;@ zKc75)j!NPUaO@0d+XCHU;}OdIS`)Y<*Kg&w8gu^lU3d-eFr zG^n=j05A&nyI#z@aQEyD+us=Q_qtKg&j__+9xjyjUF=Z5is#Xo z|BGwCHRjvc*9<(xLz=O!YBjyQ(D;4>47eESw6Wd{s7rWY;Hv=!81%*f0}L?Ml>rwg zz9kq#Z$K@?PqTsl23){kiPQ6o9pL&k&a(_Cj|Ww?Ud&PQ)s2tw+P`W$eQ8+7@v?rL z@U6bidAI1~r)n$xw8ZKA%JwU*g7c(zzRYo6@0My>*OnN^qq6*bb@A!Ac3WymdksGD z;*h7S8s7~(GC&W0)|7t4z)u5W!OH{#&kTqIUZMC^S^5_(B$7qvd)3X~8xWuQjy0_= zNQi>}<_lyVE;KnI+WhFnr;F9T!CcMbftwG*zQn*kgI*e7&;bJsFu(uO$|-ZjL}k~z+;!N|K8#1HGH*`=MG7=2 z(K5l)sJEw&`-bmDnF}~R`{;+QkKfv>b7*T}z~&MkfCXNkf2dmQkQqTDc~#|9r-=6B z-s13;@Y$=T4t?$2ueyQ!gt98|e!DzgMeXY!=9NFNP0byB{F3_6_i19I@Tp!X8(ksW zlBQli=emd?bP1VIWr>stmS@op52bKLi`hrodx!mgh29XlQi%_Efub3T4Dqoybz@ zIVfC}5=HZ@q!22lI`4Smxud66H!F?W+}BjmY0J*uHNV~G#A&RXHIm^NQRHco5jmO> zDRe*&fwg@YTSdR@vzz*keD+vmf2st-kfbJQoWg{x1vHvWS=l++Tc(V4^yrEY!cK|2 zKvT3pax6zN9NPRx9uAD*qvMh=Yr22-zP@|^T>ECbNccdjq9m~lsSqURC~$j=j!(Sv zixL-$iBU9aI1jn~vwMwkfAy>q5ha`iFOekAshlXHDADWA;DXO~kDMeF)lkuv-@M7 zchmVkuH4!RW%Dp9d&KESK3dZU;?`_SINXwml1X7McVrS<@Q85 z)=3ennda)S=?!C4Tfxn(sq=kuCBcnI)o&oW*=8;88xz@gyCa;RvZ`x4?(wHDLU;$F z6lOswpwFE(1n4;3pYGG1!`^#8Y^Ief_|tCyK55S99p=c|87m2iw|O$?|1j&+?Y;Ir z@^*O6hs1D7;W?4P842cmEXbX2g!S9Djvmu%0uHrj@XqIS`|<%Bf;-#=d4n%T0VwiH z-hEP0B!B@f!M*wMp5r%smYbG+nbF;I=J0s~$_LOv?M7K~EA`6AfoOmAEJlzx#b6kV z^Ayg)veVA<8X2*mkp4F7n>D5$?{YU_lwYtQBzKSE6LK7~D66W12@J=wO(x#PSMB{> z+Mo&^ua0pl=R%5sdshRLAiKjl;jZ}y>Z}LIUdp3B5N)*}ku>EP>=UGMy_G`mMFF?g zk1fghqs}J)M#yXlOoJE|?fHL5#fWU}(t6AbZ%+epVkV&kUVP%%Vut$A<%Z|4XMusB?4B=reSw}uuJMmh%Pdwjh-hyNRLH=X`Y zRbV5=Bhp|TbXoUi-FE){kL-2% zG5cnf-ou2Pj^h~x!zF?cC4pC;dw%^AZ%Z#mFPZfAspm37IS%D9VHL^>0bMRUlxF1_FW(9~^^Fc5tlt<%~ zL%JA`$ftM&U%scmddo1S&7+GNXAVVFkWs-DDyxi=Sgev;MPEoY90YJLoFvA!5tcJ9&SP<<)mzr- zFdOxSq~WPL?)+P~3PxK`u0tU&zic%wfZ zBUMcO%bQiM@vui|%$;nCQo;lKj=5~}gn}IiJl$VT<22(E&Cisov3pU3UIDODyUL6qb%bMO0j&w+ch>cPxWgo_I9E3mcMqY=4z zml#(h;)$Z;1h{J{$l~W(cRh65)O{11LE=v+{N!np5(I$} zX_a`r|Cg!ExXDBl^%vrwLZHCsAV9?o~r$zO+H?os(`bn-2Wab><$+Vl5q%}*dN?~pWuV>nN; z97Pf=6Xf{dqelLTeL6Ap0-bN2`O%u!#vTK<(bIyt{ty=k1g}@|6dCPJ?7Fz#tezRE zKzFgv9H^_1gdakY7iaS!{X8I(MD;!_J%0o-JsG#7K(dF`UO~EXb(0mnNP% z5mh{79oe%>!}%?11XmD1n=vLQchPg%rAq!YEi`Q|A&!q&J0{+}YViHxWiFA!BmyT{ zOjUw>Fr*^Oe$=|;WV^HV{sY@qDVuqpN05}_d1V~KoF3nJ!DaU!TGHgKV+S^EFSfPD z5<{n^Qd*7DJ12Z)9NrsX!!gwF7SkeYt(hy=y?a}DJ8_&5NQUJ}MUV)Zob^?cjDZQD zjBG>-57#2=(rd`2IaNYj#Bj+T2T3^c1fSw@3NGhl#gU`<;CaYyikgSVtnQbm{JiUV z&G+rbf;#7itP>MRoKOi>plE5a zh~HYUY)cw^R-tF*6mKgi(1&H#jctduUjANqcPy+xD2mFk7)^xqATla7d>TAkPgoZ} z^Ypq-*VPVDuUnbu@CX#aW4OW*qN@JgV$j?%XEzs5loB=b^^~=H$Hv`WO$XNzw>CzR z-vILh+3A%8k6h%bWRN1rTiPG1`+Z-c>MNI-O(x8L%(F4i z#ynsC^K8s-<(%ItS4qFGH2gExp+SEQe5(BGt-Rq*NHK6H)ZRT&{*EetPlV;7_Lt63 z3csJIQ~n-A`JJO2uMEUEpyoR<#(MPXPuKZf4?17r>n+|eCC=q_zQlCLjKG`np7+jC z;<fg z&hOw*R8)#2aFxUb=E>HduiQ2u$=qLiS@EU@q0w-G5`YS?4246xK2bOE~gTYv!$>WPOr()oq z889ANt)*QxJFMq4Tf-fi&%bBd`_rC1{OJ(w{Y=OSFu133T)UqDF6skwu<ej@2w#hG~cR^)$!&pK0v}E(p^h{J%jnzN)W1uLsS`AKJRfF7T(@Av6LPW-t*lSYt!u z2u9-!OAe^{rtQ4yWEliEdDn`jo)0x@!wod^SU=q-=Xp0P3y_j zpIUtJFCb@Yg7+`E(Czo$>nQN#7bqTIp#x`_#0NKwSa-wNOknr?b5dyM8mQ-}-(xKY zjgMQc*F3Q2^q2Rx1|@~ORUpVhfzKhy1W(HXMMxa(aOM@blJ}0<*W>(b-~rpD7ZB$) z=wd+w9qN9yIHJcSOylQ%@jIV4`^Zuj6=GccRqIwFwEhx3}1-R>Eo!_Gc)OXe5G)L7U9JyWA+7CZwpF64r#GVIwUuPX^zGtG~ zkrjE!_yU(w6nN?Fri-MRo|?rPll^_m#)4(&IP2)3p&eH}5ZB5JMwr6a0|*n4h}$Fb zmphyDWsxM3IKrbZr4IwpFGTsrnwXUw1cHu-U;320D2=)RwUNZ%!N3IpnSv{dsG z>xsk4H`DhwfZ7qrO~|rHQHsJ~5-q%Yy=w|nup+Z=kSrM+b6;68>J#fk%i@_^f7J;x zM`8+r2^>Ka9L)GPvY^x~yRDlX9X6j@1NH|iYJc$f90V)i9K-M|CrUp~|6*?IYA3Q< zU156E@(wZwMt-;TC;8voGU|Z)nPY-Og_d??A~GUQv7#sm7&FBByKm2huTyg`^P#S0 zG9VUcHSX%{ck3bM3R#+F)2G%^&m669SrYcfAj>lXE=!C^&|JtHgYC`X)8KmdTSqqk z?bG|-2hvq#v5zX+@r1>6z}jMYy`S#1M4;9tR7nz82@^y?hSl0r%eeCOzK^zi z@P4G<>Ag3)f=TJ<2-TPetYd4oob%0o;IjO+5K=Hh6sa%ADw5CUa^+9-4lZ>2auuIb z>ZYi|c$cqNUQr-o@44IC{j>DO$C3x@tU+^}*0u>D6vQeVn9HesrU>hav_5m3>bcsX zM6Nz)y)$oBlkIP!Pj+yYWH656XoALhCg^0Rwx%RofT-f2^_@HN*QMMMUcTcvTxKPa zW)y;i<-2_;sFG_P`|zu_kER7{BF0X1kT~mHp7~vo4=A90xLq>@xfL|Mf22IqA?qIM z#Dc%()DOoq;S|oR0!QH(L+@F*oF6;Nn*uf~pohqW=ujq-M>%XAX!&ACrwJ|4JW3u( zaSoqX>gDz+p8SHo`SN&|(qDaS`G5QGX>)zCw(-Uqp}V)~&A{c6#`x1G>B@zqhplT~ zxj(hg9hPAgF`2+I5?53b5AEW@6Fv=|fg{!)-4YkqC_sD6VJL#(DS{z!3MW|}UK**I zf2COn1;r8TP=02g`-X*I8nTQP1=-|ymJ{HmQ3tuMk6NEwAdWx0yj3u_U7|1JXh&|o z)9dTubPKK?PQ@h`dCBlz_u7q92bE&H-R96K=$6YI*+d?-{xE29pZC^>?=OX^q|8yg zC^IVD-(59OCGQ>fK;7iGuR-?~s0ay1FSm~*2D|coUWe?-FBl+rguFSaFMM4(7R{OT z(5VUf-J7CEI*wai`^eL(CH|y^o7p!RU z39!W)g9>y_ldNNqBISGJ9W9@nnrGuJV2a+a;V8~`W}mDhV-(GY7pN+#-es|TZ%Oa#e*rwdvgij1|iJ=BS1b>NQGBaj3jXyR~CLfXn$^cttw#c zeH3B$&spm?z4nRDb?PJcVb$$mNP@>iiI;hnOq$iVeQ6?+qT45vZRr8i^*vt-L#HT; zDH0zs5@QG=$ijB5=IC`&a_A~dwoco+o_&-FZ#$`q0ztA8hSLnpS64%}J;Bzb-GGlX zPS*&Yj{vWp46%1){k5fCO8%1+noS9!%$-9i8Q?Wp#}c1n+F4FKmFIf;S!;=?8+ zDM+y$AN2X^5qC8WM%5CngTv|Hak@_`@F-rdGv5ufsQo_64@iS6O1JIWJO23}XM&0b zpmpaot*D|TlMIisOUC}%e`B4;5>Y4fy>;^O0#rR^t^eORX)NJ#(EMeFZR+=r+quKl z!pk9a^ioI_o9&~?-EW!)q>yIW!MH?Gyu+10QStN_iN!^$>^`qk4rFK#<&@HWZ%Rrq<3C3r4yUAaa|$`|;z7CN zw3r&&u6=dM(}#~B-Q1BB&(I9d5j@FYe6Tyt;acR!HxO>_8rtgL(V}7U>`20SF|x?Y z97~C^L|v)jywWNRWg6M;WIpWu>kbf%Zd%FIdVC&1QXC$h5d>MHSP>VBJQm+tJ@b?W zOzc)D1uz2gN(Sp^N9*KKVOuSx#SjQ zn)emr7c>>18J;A0mcem`z-b(&0&O%bAP_^|)Hb>4>a4*m+M6ax5FB6R@)x5Ro@e0- zR%j*FNI+4t=C*DYvQ$Sh^ z!DG11k&PD&A2$yF`_yQq8?Al!BHFY1vB32X;_W1x!}mk4od6bU-?tp?|L5>cq3aiZ z3#=dD{Q#Zv;a%aEYeF{|lsms#V7Y+Y9o5$N0jNNWCIn2=!U_mFYN4@~@b6)UU}r!C zaQa4b0N$Rf7J5CrkXXWB7NK0ddgkfCFh3Jg15Sp#xLdXS zrrpEedGkc3v>#eGW1#$gD4@pqEXMd(rtydQ;&YJN?;NyV=0N#LP`)RWw?p~sp*#~pJ}fC#RyU0cB!P1*hw)5ML^u0dln$n)EoIf8{|;OMIP7}C+ON{O zX+cpGTH!E^Q}7P&ueh+L?S?AA*?uhzdq68msQd8ma9C5A38|{(|I$|7tV$ktvt&?P z+eXfQEWKsdu)!y2NhN4e;0Y05f83he3>nc>CUIWIF^Lvs_3svg=8ieLxj3Tf{wRB7F^#YhFK^gO ztOPxJArwoc&LQ)t?#^jJlZy~Tg~mGziR}L ztyc*0!BGAnLPHdfTksTq*#7g>eNQWi%_E#4ua>@U4pgSW@S=vqU?&3@`AviqzBt17 zz|0QQhKWT)AB7`l9w z4oVFF9F*5td;WZkheHYF{5uK{s{=MM#^>?c-HMeVQO>8{@6nD&W1Zh4#nWgdhLJk+ zqbG~ihVb{0Ikt7$D#G_7?Y}ufAySDG9>MDn4u%}<`bI$usJzw@_7mm~+e3LvgzH-m z@2f!H0oEE0{_WDCA0*;=7W5s7VBx1lSiUnQe7}Fu=4GI9&pa+r+1E47Jf4Y=o;k}% zzpqp%FM5R<-wX(UG9sJ+m`*f^vY#maq_X3?39%1YBHaJWdwyf(Gb(YSA1u0iqy_Fqey|)po7!YgJ@k_mb48KC~ z3LeMS#u6wOs2OKGkC*;2T0Fk*GFztxlx<%$<}WfiFIIW-YUNuEccp<)%@_g$4A7;M zSIWL*=qau)C@*|nW-w?57;weH1D9EQnT*SOymPM*db$si=SN;)>jL0etoR0OomEsE zQMave3+@oy-3jjQ?jA^RcXxLu(73z1LvV)#cY;ep<1}*l&$&9rt%rK4{Zv)E#;P^v z`sV1tIl}SR6RA`Ah#i3z>=SU8_2~h9F#U9-T|}QR^flHzxZ;>jw3Sj{_%!0*fSa7W z>5HYh-)KEIMJ;M?J1=!Z;O^P3;U%A6Ko>%C5yUFrgXneaE~@1hg_E1f_ON5lL9fAw zFW0;~r5dPy?XYnxbgsLvnhPM&xg`-vQ@ywSwp2pc=<<)oEmlPD2{r7mJEUa>`qbg%ZI1Li_KQP}xv z$9x3*ZW@jsEaxvV9)@a{@_z~)GpD+E27;QdYjwT`eDz3$GekXK{7-c_ki_{v3o-;B zl=I%4bOuUhi{}*kpWEe>?-+wKXX<+z^4=cm^f<#fiWI!z^4kJ7KE3V=%|Wwe3s=|e zLh^!w6Kw~c{dYI))CJU#VafC)IlvFi$Qm#Iyw{=s+3aNWNZu4hR8%UPe;zp! zO((nfc0nmS_rwrJE<@&RjgdAN3~CpD-s2ZeGZ1?|QhgV3eR}1i`}mp`2stTIo@#=6 zNE!~hE$|~xdldJ^OUs-wWf@&Rx_h$PwS^uC9{;j8ypEApfsU-CQN!?)$G*GQ1_)e) z(?YkO;u!CSWEVx;nrh#R4zJD&B;X~I;zjK{q0YddlOB}CIM!{`D6Th&`R#b^U)_4x z!T$<;tWBtce;oDNf_q23nzgf?q&rdSdA;oafp~Xp{fzh_$&3F}CdU1&+Vi z=2##rLI_NNY?93;ymC%Q3l(N!2e$Etw%TNpiLi2;SXac*p^A}usML^rZ+U>!4D8Ltj*tw#VcBu+fb5KKzAWm|iD6*NEisLK$5w#}w^vw>pqh%SGE1@V zsRwU;v4MDG6p~<5U*g6#%k6UdR)psx4| zwHwwOGo@B>0t3`N>L@-t<*4N=M>h!J5=dV{HZ$uk>UpX>SGPC&%tthEOX>~9aem+y z6DX_7t?FDYRM6fi8>D8M)Ov6Dn+>ClGq57W6;HeOKQJ9dSlYm9R(PnlU)~_w%Rm`wf z&MR$3uZM}4M`j7+5NWoV>Zf10(g>L)*}=Z?)yw<~#(0T{RI0S3k*4AK6YmlL`5GK` z?89Gc2bw4()LHIgQxp=fr-Tn9Lu0xrw=KcCrrfs zL6nILXQO#7%)R8{adq3?{~)_+sux!wj~q=OBTwhlz@)g5CvClAEv$k(4X?wu;^AhQ z^Yb`^()>D!cg85LT=E(}cu$Po>wV$6sSH+YB(!bq-`u|_v|*$dCf3xr0T(I>x`qOg z4rPj>lSsRzm8+`8o52R?QFD&a+Z2WdC`h!`L^Sv^8jTY_xq@uiHaugJyB*&TTl5+N z2Ifdm6N4%CEIZd0mAh^(f#n)ggQ5y0Zl9L#HdcV8d1IA741oZ=7O@KGF={}x5Zw;|{*tek#K+1=K4n%{daiT;aG zr^R>4gDxlj-e>4#^OktEk|2~0=XP0f*Lxp)x_-Y0wqX}C+gMunQ12zU*tZ=z6D0_z z*3&M2=DSJ42hcNoPY~-I$t@baciXIdH zZy5K<*Phz-pr0DcxhPW;DKTMtND1scvcrH2Z`YRYz1TLRT!*)DgI@b<3N#)|<4^OB zRh9I+>f1Q6NWZqBDVAjTDZ9$xRGU&@tzXrR1VU@O!u354 z5>dxAB0g&ckwTK>Vk}zUCNmF8sJ_Sn<{Bu0D{{6tueczdzH3M9y$gRMS8o)!E?L2` zVYkujy%HZ8i&9%d%`+m8jfg$$BuKu`K@X~@+4NX^d&c|8s5z*k+UJzlUy?cde2eP1 zI}=Tiw^}qRyzqPHb#Lj_`pe9OZM6uc0AWX4_{SUl1{0(=e%H1gsx0wG1WUaH>j!X& zi}7-lG>ir9{&f&_kKK{L5)quc{fGm5DNn8~PS12MLJlpkSnN|ioBhc-SpP1tL-u2h zi9XH)`G618oEI?aoq|AN^6heT84@OZ^ZU6l#rb?Imi#}u@C>P@6T*8&V18{5V#g!e_f4GbmbW)m z=wnB3v$;kDMYA7(LhrnkJ@e@L#2*n%t9I)S;1f2j5GRcZ9s@Be&5p#G}H)M7A2NHLynauM;qxX$yMX&+WGPy z1miF3^ZZ-N`Ovg-PZzJOmzrPw0e8ulv-tw;UgjKG;nsy2!7=QZO4msA71(FOPv+22 z8maVdeY7oO12FF-Cl7y8=Yd!;nM*7iQScG&0e&zklIYkGjZ~wY$O>jucot@W3QkbB z9aht_0|*ttnY$LIlsY0!@;{MGzfWFMU1Mg+AsFo0$G#onE{%Qr`Qu@DWeX-_=`>7r zN{-A5>R1mp6Tx9{A##WCTcZOM9=T7bK=ST2$W_u>bI&hCdD}1jFZ_W)(Ja{KS(^V+A+D*{)ZgFE z*41_>UZ6K9D6)eiS0=fGQs)|#5ct4%n>Hvsc%0UA?6dW(e`>JEBVo<@FW@fPez$q4 z-nY&}pY1ET0xICQSzDnEZdHYU+r|!?rJFaVb*Y#-ubeu5s0Q{Rrr21GMc4LG=Z;6D zYbQuW#k8J{CX0Qd*m=^imPgSko|WHO#rJl}xSgaXb5Yr{@oa=i6w*DM-<(>5^3b}k z>F;U7!tKTEbzc~^;sBL2E?T}$GIL%jFS39=iF*&mgKPKKo{CBDRQ~b;=-v^{59pUZ@6}H+ zbYIwRT!(8CO3mv%|FAZspX2z-3LpnWeW4ou-ok*uVTbSt+G&+28?5<9p7Sg!Z~S3k z3$voB?^B=)>Jyu=m0t-?A=%|eU3*xmeF=3XUCa$GP-U1=W189%yqMkHsi>_)-M9)2 zWVzjU3^ErY?g1!F*HaH3x+E2!xdR^h5tT3pX>E02%WguvGIcxz!3)d5y087oGTC7o zu58FNyMi<7Ff=Ay75pAkfco<~gY0w!cG3#2j%~^|zaKUBYs&~I1MCSJUMYBN!P;hl zge=VdWyJFO{5IVm!73f}Mv%evxQphHl}@~Js@SXwzqzl{T~i&`k~$ADmEs^rJB%jg zI{?Sr>%i|Q`hk@pTeK!Lw*bFid(<&vA~qX)n{Kx%2?>K$!cU{5CKgjbx8pmPsYxo| zcdzyf_l|JEk&P*#y}$Ji%w6U%Ztw#!g+6~a9@sZrv3zG=oHG+9Gs*CE4}EFGR1SSN zGaBCB)W!_B5pZ`DE;C@nobi?Rkff3t1RcfNz&aZrS9R{^Uw2LcRY^TXF36*kV&x19 zGr^UgLY;~FA!%E!ie3Jz?nZ{yX&qNNc{9vxGA4~iie{iheR0lbVG}X5A4Ye~Jm@y* zk@>hKL7!ikSnHKS?UXS}KvJw15`shch`z^HtO{n2WcgfVhI`W0pEcGzL|Cg(R+-IR%lojg0tE z4Fi)OeoK6=eti;qH+&n*>47Yz-H;%)AR1ESsVJxs`V!d+TcuR+)mt~2EU8cB%-kP@ zTbsDcgm&`;T#WNCa~oIbSn&MWWfAj@$9!Id+bLr-MYvCaZ4uC5L479^T@;9TECyy* z{8|KcSrpNYl519 zLnG_#N{*JOTYPemSE{cgk#Y7t#H5xJ#G;;pK#>I+_$%X2Ki&hghK$h?_-Z$~1ZOX7 zu`8el&U!eJZ=d06|7Df|&7tSyiJqAJ96Yl1T~ip&D7qo;{2I}QV92q8;t#N}zMnVQ z&@p3?-Aar?+=`PTvJC7@|BNp1Q$rGk9Tt*pOM4m51_4yR%$Wp4Orl0ziiyI?b` z=dUI+M@!4%boZF3O#$EF%U&jb!E|qaM1+`pK)UjY^f;sz(i0R@{Zf~Eer<9+?)ul8 ziTFAc7!W<*Qoz=qLc+m#Vg?Pr7)^J6CVI+Lbke?2r? z-#19`sV*WB@C~-;0SsPuE!Y^s{QN(mbSrQ?&01EE5-h#VE8S!#8|m}Ef?oWkGTgnr zhp3&QnQ_QQB20`AS4tzP28h$WgxN|nt)A&P`5d+|bRwOhHhM$d1K1xD_-_fX+K zzF3y^dk}rzV4a_d46R(thyP;s^j8lHh%JXmgj4Py64m!EUNe|8C(4=a=XkjppdP6) zkhzH~`j*0|BnKd`)7{rHvB+DiAW?9NT6Y?XD51RmEx2`yrG@0~J1-q-@sEfD3$=fZ z%zZg{udXw*45sm8gjr10aXT{l0p6LhpsdWF`E9NeJI~@%yXnRy;H6o0#O-7hzXJNE zBjMJWD>Dt1x@w~uB$$!>Sg)1o2E6TNb08V*Vg-J=b<8n-wyATliXkxNT_?MX5anVsM&u{Tz27$|MVdUK>R!@9=JxC@7@Ydf#8l z;TU4YU7wa&AW(H>`-3Onew(79MqXg}&r@i^!4G*{UA;Xz@!dSRfS;h{8P2L83jS>9 zXcz`GhKfMxV&?Jm?Qvy`Ki-EkU`k%ca>yNTaPi!BV3nzYWruK*jF3z)kMiY> zG#z5LQvBs~IaY+1XGA(KTiy_x9wrXUsw}KOT6_GP-EHbune7c4U2)V7%>A34%+3y0 z9H7QFqQrxpYJGlEiRbgXAE2DDr9WQC)@kv8c4;{8c!D=}f`R%rr6fXd?!awvSIyS; zV{gKHhiMa08!EyWk!iFNQ>=l5o}urPL!G}RyooFzSxGmQ>ZJ#Oi24hnQddZA^H{1$ z$4Zz%W~AKXo#JGejS5j5;=ml3vXCTHJg95r(ald6_8n@qY7!nA0waa{=Ni|~9Afv>`!%y3o;?0#b}#znbiwdgX z+727}{`~^D)Bkb$q7s?e@1%an8+@dhp3-e^)YggwoJ7it9D&B_2+shIrnU z^cu-foaY)opb*<{O40;|x|&=l*hAptI&?J^z^Djp?C`O$0`tDWRp;?tm?i_pv9M>= z@f8AjopmH0k$zZ}hwXE?a)4SD`?kzyuNE z%Dw$r@j(9j3DI2JuknxmQX14fAF-qwK}zArQ4if^qQ4N;_6|AKC#_W-8}bD__a_1Y zGxIh${jdKrh+D!In1=Vcq++-&KQ_p#6+$g^PdXLM4;>i~`X2n_fQ5XObq`@OK*!(@ zJP(;YCu3?lr6!2qTytQHas~mPG^q^$KPpT~M z?iCwUy_UbiJ`+45sK%&PJ`UqsA?2`AnrtL^Z_{(j!aMEY*3&cwh|HgE9Fa=*5JJn} zy18$T4~PRKHa<9DR6sB)OI-2nl1a`dsrtcs<76URmxjji@$~LF#h$ekzqS5jp^c0~ z6davkU{zSWVmC>|?{rlZXO^t5lB$4^?p+a{bfI`75n30z{A(-1d6i2$JT-+>kI7Ni z0Kg|&k2e7v3Rl)KDRs^K@SK80B`gf2uJx!VFz=>n) zPoid9oAnGMr~r|*gT~lX(vW4enRA7Ffj82h4@mJGq{;~YY~+}?_|y-)$;%#xMWAnu zS|jGdqaL=LL`SIKA@zAwuy9+B<68pW%HqeKeWAv{jSKJg`#WdBb<00>Zgf^pA4QvI zGF!e>dD>!W`gVHoL&&M*^n-YuRK#{~X)a&E%_5*Uy?|aabqTx}tZ=HgZ-FWaTv&nkk!&uBEsS3xz9) ziSYxi_3`<1sEard;?zOZ>U7poHDr${9)n0(A9=tdA{j{uWFz1UMbh20Ck}>APkrmPd#-9r5wZ#k30F3{>E;s+9`~ zHBG#O*F@?6vmnSN98=Grss7#ioA-5o14my>bH`8>GOc-`Jp=1W$>9Cu!zQO@?l-OZ zBPeW@CBd;qCKV7eF*fuIw>>Lmk`~=V&Iixt`WpN`1zG**0`_Hx)fxXlDsUPr^)Wi>hdbXcG3lJ84+1gbW1>ay-0{GBZKaBky{@=+MP|hR)ob)(5h9RjXYW z(Jcxr+6AqvP0mfZzL@Slw>$RM9{a_=k+6P4jGyu>ElU3###O#>_e;DH_+&S8PHPi!NtwHCQWM&;U(r_5=Nt(+QA zf&`q_)?DeiG!KSMaOF~F??x*@2#jYu^RAdKVY6$7lvI^yr$Be$irJJ_VqVCgk)OF%ycu1#iiWq{qLDcow zs`0n8%JdIm8$v#mE&$(4U9;Y}H)v-M9OCrOl=cBXITsFcbptgQ`q}3CDbbl|(dHv| z>;0MdfHYe}<9~K6c%@Z+SEBo1N;9$HAUJT0`_K}34}TC@(RmI4!xd0hX%9I~rGyr+ zM`p3&2{%1Gvo0(7aTm)W99^8H4VJAp3lp=SM*?Gv=ygLLHc}c$VDny~Mh+O=cA4IV zx+gYH!n7cc##(YoYYor9slXkDR`-m(D>s`sq;_*5Fi-Dt;gQY!Lo&|VTd%@dhCKcGYz(ee zD(e6$OMnFitP_UV<1A%k4nwj=;|Y)7WqzIzUbW68DKh~*A=yj^+xb9raSTlg7)Y@f zsFGrYE3_tTe}QA$13^Mtu=FeBY*LSM7TL)Neveu8D~3yl_1O5#iDO3*3yIQjkdmMQ) zc5NIw4D+lt1vE!rySaokxTq`Pbd0Q`fJ9u?9q!pEG;Uh}#v0d5OTHc3P9un*q2e(U z89*zeE+n5^h()_c70yXSnarJyCfMfae~ulbqS z{KG_Nf^bNCR4m4<|43})EJ zL$4spc~>ERfsMWj&WB+iQj!`qN&dxd|DJv3E;V7QK)^m+LJj{V0OY@r>sVmn7jj*E zMo6a=86eXPb$j7fyDrjZtIC{(1P7xVj1%kIlGAc>&49LEj&5SC+kva7Q%C*B?Ix_~ zyWnUn41L0D)mHFOs>bwg3RVZWT@iWuv4SM(tU&2Y%N@$);CF?BZ+*VTuWBt z3kXxWt~zcz!5%?CG2dN}f=Sr9YFkpll?9@C(wuLVi6+N^0p>FU$E^KkNM73pzG&~g z<)g&ND^@y6_PKEjvU}^l2V4d`cRjC#z0g?D{tW^veD-P0g)DZSW@hoc+)AVoEfJNq ztT1Uq{Xid41vDpTxNG*84r?yML=Ep~MYDNMqwKm9SfBPFu7TA20}N2ibo5)KvJikp z%BvYX^I9f+T0r03MA`kLca-SFrc(G0eEJnyAQhHavlmURD~Eh(iHB^v7Ge@D8022~ z>JUY`jd;i-`fRP57YaI6eMhs(N)UTyEqwKPdJ#%{6niFD4PZ+HmLE3juNYnCcK+Mb zLci)U7UYS&%->Q!9{H0q{KQK)lf(!`Sxpf;Q8Xaz?DQLBOopdyfc;q2f#Xc_$=F-s zC!kN_8Oy-VbDI}byd0lhOzFXa%XRpT~LwVw8WI%s1LM>(Pm#k>?T-cpJ zT}rnY{p&d@*gklsHcX6L$m?c?KXcpqFA3j-Q}^$>x4{s?5Gg;xyfln~uKYVPU3FNj z3te?gs%Tv*Xr`6DCim%_Ga<8T6;#(%1JO|AgEEE5UG2;Za6)D#0qZ?;_bv#)S?We3 z-bwO9hQ*G&5FXEUXbmu$Tzv{+jMuAJzusXP-kbYJ)tZA3cl4?l%+cOsNX}hLZ=s*p z^J;i<7$lh$rn#29DI#X%FvM>{crE6G$K(u??ZYCY?D|H>bFZ-%mW_d5&YZ4Ma$|@y zbskA6#foPp1zs@in~y~|&u!%&&unjXg{LzNUT+?f^JuF&l29Ri1<3ASDNi&*P`AXH z8DmfNzz^oY!0Y>q>$1j@AhVQl1rEw##!l{*bQ1Q+dXQfC@l-CjJQ0$~POJin@7IWy zR0;tqBaUg6?Oqy>e;^kdo^Q8ki4PkW@;hu7g5T`BmQ4;y;LVJJb(qqNwiHzxv+GzH zD&j=6sAqgr84r1Y3gdxi#iJtodqAleB1YhLKs{~kM+XbJVOg3!L`X?Y2n9zR1B9O) zoAZYH_qVPvNh=hZlNzN@qpbxgYv1e27x)+ z6=F}pH&AR~S@7R`#NSpSJq6CcRjlUPt+YBZQj~`8=3%hIgdzHQ>Pl`=+BxyjjN6V` z?-I&u_JEWX*zsl^(~L;LdQElyrGvj*^Hf&0 zyOp!AR+Z3jPZ~TNU(Pz<0V%^7t3+3V;`9-aZ1I3MhRQN+3y@}hdOh&P3^o$eATAZx zqGM)VH)XePTFL2l1+V@Hf(Bq?(!%J`U4PCbr6Y!nz$m8>&-OsA)MZw;>Meh?agq6| z|8SFDK`r{a`=Qhw*?)T!tvCS-dbl^?qiOGf_LiSY<&{DQOf&i=F&kc1JcD>HzI*{# z3c(B<;>mk=F(-6U7{z6kjF(96dOtNd3;CUE40`)ck$zU>l$c7NPO%9+m{{wa-p|;| z)6ZubgO4G8M@1jGB=F~Q63;@4giQ7zjOfR3D{cR4LjWmf0R-duSh>{3MXj`g^YHQeP-%FXpcSLst9W!Mzc-FNyNE9XjSMk>FM*!vU-eW}JnKv6k;r-wusx{W79b@* zSNAy!(SwQctb1zxJq|dM6OifooSczESbp<~<6MMbG06+bTS>&+k-uZeU$1{FI0OvK z%(M8iMVLF|9>`@Ju;oRJ+~TZz|F!uS-!yk&&1cZppv?H04H||y{~QbTLMAo&IPMP+ z{-_*)ug+I|8DuFjB;9)o`nFk6YF|ub^5m;K99UfT?U|pFO-pfaNtpk>Xgl;)Bef1$ zP@AG8|L2mh@7y2Za>pmE3SqSDkfd_nS?c0R4i;x*8q@ujNI%Egt$qk^fl)nHr!Q|0 z{nrd+*TS#4EHd*0nq`j*k*y}HCS4A;xko$82*#bI^+73ua3)cF#n#;OnWB#ykADRC zw>Qz{aOmO=WkX73F^dr~(PE&V*D*dyM^_B(#u=E(>MuxtT8R985{JK(tF#>qU^N)1 zr39X6yLFEhkNe*q-be*g0EGLy5fd|MEQtjgtbK1P_h?TGKi^U^3DPfIzw{7%svp5R z563wP=QnZ;*96-9b>&B#iu-H?=1z*=VfKzfE|tnzz~sayhmQu|6(q>zTp&{XZ2N_5 zH!*&FImcETWs}#!NygI@F42%gfBlLh_-fL;%^_^sf0%q0x+4!GQw>`!egR44?TmJ! zeS!-Ds6WcV=ILmRQG4=sciAL(q+=uNs?-kM69&V5;>q$@FNJow-BP|e4LmOxnBJGe zXMo+}+%9#)T%<9%^yGpCimeNl3m=k<(MoCyK!jrz{cu-&gU^9qW#dis{^T4Kr5${S z?R{_KHZp=>A_;14E-!GFUnUqkwb}PMtKMx}UY$-l zNm|6o>`HumuZ5}r)@lzxK!*5@mr@WSWO?oYSXLs+I2g#A>n$x-Q=TXvcK(!raJ~g| z9!y~uxt)8Nrd)W6Bd8+Kf)*Hl{AsI8mQME+yNyE{v`ADc_~01xr)zEaeFL zMIhtHFe>r|4#JLo?u0u-Mj01x|MubtCML&tPo%>DSu`5|w5VFXGoHP|ot##mwNP@C zbG$NFN9KV>t7#{#cCc8-6O!LESA@#2a|N^TcH4;DcU0z-&e2mLlY^c)jvW~o@46H( z8ecLvMRG~@;5cC#9VlDjJC!4WU7Hu!`sXzoe;Sh1_LV|VbCuaLKtE6hlzDNobq=y> zU6#Pu#QgB*w09>4qd$a!6FTEPqhDC?>L*8gt3e~+IE28+m41REPSN^ozvz`5$eZvS z23d!&{PJ{Hp_`BhHP8wKCaaq7ltP^xdxpGWK{`8(&Y!*9({;_SQ`_X zw|a+ypx_l9$bzRbKAn@UZ=Y^l(R8Fb)?3V}OgKa0`3<}Ag-=y&;O}Q@`}<0PZze7N zYvTU}99|d@{UoG{|7g$Tc=G$~`cP?+@AmEs?&4mUgG3`8Girk z6f}u2i~{{1%<6wbjYR<`*vO`!94ir5V z(i&UQIu7m-n|Bg+hMh6*haAyLl9s4Ok|%*X*#Pz9W1z?e#8HdU1fa_z5t&Q>{W7s#CyA*fDWwr0DS@=lly2&XMK$JaKq}CvS{n+e~GBQs9Zl+ym>Xi z4*1O*w?~jSU!MTU;&o-@kcr^jf)dl(I3y#qu>(bTF(< zB`K-hC(CEQ%9#p8VCA%+)gg$&B-!K!t0HDeTS@zW3J9NR9;hD6Z`K`eo z`10ap3YsGPu+3&?gZ!QNW+HCUD`FF9_rle={_ABB-$=Z~EAEF}`T$+ayc5JK|3IX{N;{I(lpt^D6n_W-BrD zfL=XGSPsq{<+ZUHyEo9wGg;V4G@;n-NxmYWj40GUgsZMy{lq;^<39q7$Kifzajz$e zYu|I8w*}uABt4Lg(jHm4%#ge>JU z20pn73GWX{J~|3LBXRo!Y_M50$+2=X`FF!oO44W0=@;H`rrKFcJf2 zhuSl5rzDa%xJx@8-#-3&nh*xtsvMPL>!{V3QhSZ=P3Bj4b@7E0d457p(|XoALpEwV zUKsi|{rC)=I`xmatTnTC=np(4>pheo%b%Cx3BLXJ<_H4qlf1WSdZe7TuXD5uF7n8yaJ{4d@v2kt;`YCd z{>`5zbr*)jpK^=qG|9Ng`N&eW^qeYNPt_}&F$aY~!PA>zGJ<$nE8L2^a*p0M96gs? zMbq#mzb*Rg$QTHFi7W!|wsZKKzTnP zfGG6tqd)I)=S{cb>Qi}<#CaJRh+eJjyg%%z1)c@|2XcDWrEOA$pG%7zg3Y;_Va%=; z5lP4xowzEMlong$O1j9+Dzj@}UAKcZE?GBAE|*tt*^l}ip+23h^6c<@2gkm#;{l6| zT}izZ*k5nXFLCc9r@G<-Z-+l?$oX()LFLnsjdhx8fWy+SBuy@q|2 zikELOv`Q>l^#N_q;onTDS*7bO#A(=K_6fv?M#}nU{H&ktLpnnr)z**$rHBoT_Qs#NSasnwT4D?-@lIVlDq9<7sFs1bTv( zjC0?teLYXGA;uq-zW9;U53?Pp&IBnaSa3EQ%`pph94nAf%%b_ae&!Yu9pkH0{{Y4R z02|xiWmccLW9+w-rqts!&$_z)*Od_@HV#=g!v5X}aL7V7Y-4_~G%i^|It^wU0rVkR3b_ zQ!PQY8(*fa<~{?5qELjb5>{jKo!09uhAeiXV)-z-W63AMuC5Db54z!MCP7Wdn0WF7UT*jsT7)m+u8ag$3< zmZ|8s`ipCAYaa=Qe9p^OZ2Qn5=Md7US>{f4ss+gEE&${F@`e+TTHrm^`|VVIzEBn- zn?tb&yAsgj_)Ydc0DPg108cju>!myZ^n^3`fJteomS^1f$B)9@h zFP~oCJ}sA{=L6*#w8oqDj^@kvuFLEF*i)`ct86rKL+&l1(S?x)if^yL>*B=e{kz|=J&)1v!Z-EWq%1ch zk76{FGnZQ8g0t-1k<>`emf@Tn;0?;$p1UPu)1Zs3Tif%srb+3W|_%CxFTESoF}s4VE6&_N`z(6 z%|-lHSAWw9&A(qv^;K0&NZ7MjM1Ehm7K^hmIYccC^7DP)vQY2E(O-zL^W|Ch zo8PfY(RKt~Tq(w&U<{ZhtqI&-sbm>)Ui>xIE0tAD2%H+(X?_O` zn`R9&{-f`;7`b$PLGl$$1qVLV2@FK=WcLGB4#)~Bc2`KbFd6~b50)7x&%Ph`{#2nWYRJG z?rU4>ze<^KfYkjK$=3KqK!T6@hbD4mnRfGQWthX+FaPyJyT9NTfszcbUVTj1j1>If zGTrPWi8w^GP19_)Mm=PAmd=;SIFiyT5O< zDK~#p(sP-*N&mFU_GUeQ?EkoQmzngs{ZjGZAy1KpiNb-^Vz^L;%~gL~>{XJA*>=I( zQejk~ZA`wkSW`5~0WV30_=Qye@vX4+GZ^D`?I$ndL_sVy82@n7f7D%MRw*P@35oHL z9Uy9rJl2qns@|^B*><^(qqMt9_&w3XG|^pHJ#(CDn*8Rkrz~CC>e8f01LW?Hv3ORfZ-474}#^kJ8cZy)Wn^aF{ z2Yns|&PAZciM<3s?pe=sgTW>jA-`cS7DF;_6{jF)`YWdDMIknBLsOAz1dR~;$ELvP z+)kr}(7kq{!x2@rpYw+ffJIBLUf0+itb{ctXbn9rd3iNnzS(ZHl$#Dd0M!)ZH zy=np>u{NTrTPfXcGK5m2=IMTCnNJU_)N|!Vxsy>ji&Es;<6w| zwl1@=7K=%AYCk2T&G}f|tgRJn^g`;X!s{FNiC!lEncD*szIW$Wa2AacU@EwBbbSK1 z|NN*w`sjGPbe`;QtEIIH=n$fgxvhMJo7uR<-F$QBN;ACY6He4-xb}^C4VM}q%{oY? zdw%g0vH4j0cY%5YW}ZV*sk-JP+<#Z`qbxk?Srl@1E4RmGk!eBpib$o3Udgy3WX$cQ zDSoxHnBYh}o#o+CV(qAt;PM5*6kIbY5x`AVpt!4S=V7$|?KVEP9esOgqNRLfp_C;o zMc|-qL|*TH{Hb@C4BYf20N6c-mxxsR+OP0X2r8CqIpOZYIg=OA{` zbczs~V#N!A!)y?!Kj~RN?t1kOwSKMh{(P`h|K>Cs@Fen7w=&Mi+Wjs%((anOc6qt= zu|P>mL>3)n=1x`#$5`_EAolQ!Paex{h#;9TfM{39??}L;@B8a8ES7SxxH8=mqpA1c z0lc&vd&huI0ZhxOC@DAA)9n`=f7J2uuEQB?zWHHT%c!}QryZoV`?N^$>|9{aQK#pQ zJ*9L@w+ly-X3UCL_;=Or^JSJnjeD6~b*9n1^x5WH5fkUf=y4e%S*8(JLU>&XM8314 zb4kFSdoUKSatr@(^kXijq}V^|h-3PUeL6_u1H50}4O#oFPFI3J5dwHLu`YSE_VWx* zPq+k4sO?|0y|^s!Q^#8y$7{+qvuXNQCyHOiwALAy_W7(=#s@0#zX161{oBkYNoqQv zdG+RT^7i>C|Gf~5+iMj4b3onTp8d6!`w%}Ups`3XtVT4fK%vmgL_CBp+$J--Pk6k+ z$`<8l!wNRQU{h)K-mr8;OG}Y0KXl8$9X# zf)S480GH!6-2$8!cgui6wAY8;hTi8y6H7I#yXO2c#X5Xg%2v*T-B7gKSp$O&-xJ>FylpK^oxjt4Sv*P0s)>^j zW}7IG+nY(WmJPPR#uGR}DnFy3oR63VdcQpV%k%c*@M)AV;v2Fs-(S;WGSO1;m+2p_ zU*10Sdzw7%#!br)*063}2Es#*oZmj7{DLh#U*tl+@BK*V&z|$6#H(XsQ7^vS@6PpH zPkr%Y$FEcJM7i8wY_-<5uu#H<(f=dB{Pb@!g9nhlj+6S$1>ZTU*cE;nOw6p0<~dX; zv==eMas1rQtbt2&(2GEj!O|-bY%ZB+`3pTVF?Hh**q!Eoj)BKfB@0c%nnJYot+hY&S*LYQ3Irh;% z_v^Q?taz_rD)OlVSOhW1l7f?O8CE;(8&~eL_Rf=Q=W{_9@9k_Cd4&XMbk}goEJrGI zy|jhTD!?88-82Iiv!%!3Fmxg2?eoZhc#CFx4eAlwen$lOlN1qR^RjzCDHud1ehPOzTI&y;O;=YafpD$qk^C(v2vHO%m6 z*%(4F4l--8`I~Nm)2$`o)62rgj%5T=8M==1a%^+by!C&bZW!fjvm^zd;bQnVYbkS6 z#S~FVsSm&Wd=&Yah#&FrTmRVIL?r#=Z;H!Y=6qfd2m|Qs9UCj}Z$IV@U9hTQb?56o z)6DwsBNs`9?|HIE-{zqB2keM5Pa19@3;PSJ5%CJDUoMb+=7%oH-6`6Bd z+zV5w=P30X;5JT1*w**n^^)#jX9C)uE&|g^4wa8;pML6>ZSn0~@kfTA3m-PBr6aZeJd7rrpfiidTC>w(z|q=7j9+x6r&(u6&06?D5UQ z^fnsH;O|9v8oD)dN4Cs__f2-BrzqZ3JLg*v%nXu8BlK7I$|gyxAAYk4qE0C38q9a| zBM0}GGBqNl*_h?SVflCGHWWr`((t_H5!1jt{3D8eE0Qu{*9Spz@dr!psV9W9D3tt| zJ7Is~b?iOQcLNNLd^G{W?}|rZ*^@w9?S{D}ZI_AT_j-HruHs;O#r2K=_Nd^UnKV)mC(y z>Q8&pTs495jK6Y~#~$B}uf*zD@=CZ;ZjH6&XfBN@FJ;8URJ|X;yDv45q5BWLU#W=e z@M;lyL(4s8QB(m4T=|+hP7BqDti}POc5lo_xL0`hUfQ>U)ejiNS9G9jm5F8_}WBB~|X|L(4^@XK}qlcbH=C5-qmn z_B>HXqrex@&YP%)Y6(tJ-}*p#_66ZEs#t>RlPU%I`EQ2(K2K90oeSSBEIFk?D)H)% zL&i?;efP8137bv(5ps2v2-S-O^#nwP+{@gp*fdD9r;^91#hFKkS7BbM3Rk|IR=#*z z?{>!B!nn*Il^d9>!egHUz{J{^550TLTP(3ck^W0o4^*NLOM0oq1L-B%k zq1hJ(;8c{aoPxXyUV285*7+%xJVzI81@myd3HwgF`jQfrkV<7m7lfL7{7kxJQ$r@? z85F0(1k5bX^7}N#sE4_`WM?Uc8}rph$(xp59#}K|)q}?{=ouS9^D_9Fn%Km`0$)v; zlGMdZy7NE=f%^%)U^wk5>xFf`HrbM!Q2NySDQo*4GndZ&uO=n2soc@ne9#MN6cGlw zaUA_zh&18e5F5w2Yo1$;!CsPX@a_hN3-H^a618mG!&D%Czn2vH@zfnG_fzcM;gily z@Z)*iZ5L4kxXw*#(@tVl%>;Dp1b~$7hw%sJ^|PmKOgf^%I^Iac&e+S~zuVWW?#KQ{yxvU*u7E67qc6+zQka!{NK*j;^#{{ z04LIo!9(Z)$|a8RyC(4OkUbc9UgODq*M}aBY<0T)qNU3Xt}MM7zenM_P}W)J>|sMA z^_8Nj8g7ZEtGR~H(xB?73L+0CQDUq1|ln zzvl*vW^qKEJjXe;Kexq?e;rVI=dU-x<`-*b0Cn}CxmZuT=KMF1ga?R@$!>tE563=ez4a?(X(l;$7<$)YGg|Yw(tai!|eY!-)qh>KHyY z6_?CQ*|PelS;-SU&~3Yq@y+FoJrh0UYYGI!0hJOOg)cp&V1=ygCOnk#U!*^u)6v`Y z2>>+>xpVR`ibY;&ERHVfHW~J|M$_vuo7>2hh#bcuSp%Zu#o#gJP#MX{kW6}wPr$?V zt#8AQvG$QaJ3!{rf{3#%U2@TDK}q{--uFJI>B7&O1ubH(9Mn@SOwm15Mcty^zdnSQ z5>nx^yZQnvX^SUVvHX&jf9|fN+=SkV}!f@Jy#)T)e~H2mImaRbVe!jZ@NyI)mw>fX)>u$r%-a$Z^&@sVf4t)i z-e%@ddMq;W^&w^~YDHOHD6Ro*SVG+Fksh}9dmnLy z%B{C&f7|YkPZ#B`ocwj1^#hD)g_M~k3Tio___k-R*lEMAj-dRGw{;W|n6?>LD$1p{Y-*Tf4s!qz?1L~PO12%k3e7D+w72W!y--26 z2n@>aHuUFm76KvG#+n(ZLwoCo%XjFzT+?lhDw`tnG|!#>{n zKTku&L=i1;wu=x6Wm8w$1Q$n#={p`6RchvI5m-oI$JGoV!1f=ObGg*IN7Xa_=)+p0}Iu+!ZN=uA+rp zPM~y8C?RpZxHM0sj#`vinSnpdurk?4&5AESonHpyFsZfCsI30dS%)=j$zXF;$;U34 zI6vgUlWuTe>5YFrDqxk!7{3+e5%++8>m5V1!}9N&@u&4gebxe~kp8#BiHL~>a{IgR zgWz{E#l~lnPtV|j3W(nVO?-s!f;GMg>yh1d$!H*9 z=O1;M9rP>8bi?etXoJi8$B3I)s10_6VBFZT6dq=>Q2HPS7F$6C2NeNui+e6eyRmPd z+~fa!_|z|5jVEwtXvrDH+5T;$K}4cu7gABww^{@a`*}!Cy~_ zTcp&Zygbs>f^4G7>?Akz=<(NZ)gVk4%IDKj?C)R)+_K=QOkc+9fxm4JLB3;Ydt85I z`;}PEsX8Dbkr$a@yA0Ylrg;Vk1U1;Z5%sn| z|1X}8FC?Y#{jP5_Mq$o%(`U5TSovy$csmRliPg^v3-@(Os3qbDKP49SLGkKEi*iie z;+!jUSvHGPKNUc>WR4!mQB|N&6A~EJGZ|VvW1`(Z>B}n=fHzIam$6HyyF;a$q>~^E zyMlQ>Jrv1ye`~T@%BfM%=kM(u-tZ$d;!B%0XFN_yue(k)) zQ>iM8AH|W{BggHWp{yWGr%_<+RIKi`tICRc{TsXF-*04NuQaPJFK`s?8g9&o8iv(u zr`Kch1tkV0+j4K{Xv-Hpn}5Z7wg`B7&#b`R$Z8 z_3f6*ATQMmQGw7pM+Yf{y?c|P^>O;C0piH$4(oaZc-AH;Tz?&I4IK?tg(&8bOhV=b+!_|Nz`ExsWTS-08S`@0B=?!j$;PRa?e8G00OV;?`d{AKY7eE5ua?Ak<3 zvb$T-fm5;a+#Kd?>^^u^Rd6!6tY(bTEp$%~1$%@YcJA&woCA1lrEBHoTdD z*ZCJ1wmlqZtl>TqUKja<+U)|=OjaH&3!%X85pNWrE{t57X@`w0r=;T9CSe}W+jBec z>~-}C_6bs5UuZ@|tSMVI~GmGhX- z^Fcp4#F#mVEK__X=!(d@{`rjX^ZD4eBCC$M{G_1w{1&5zq8Z4RA4h7+EIG2%0rb=F zjd=<|pT~LqcJ8z~3kQ9>t&A^Yg#T934w?z^QA4**^4LI6%1NL06)zZQXx|Hh(f?(H z6n>I#33~ab(ok1`m!#6zV3GNWnNg;4fr}Ftc0Kj{zYLYmkB-N$YtX){sK`?)Ni4vj zbue|^iv7=$qWo-NY~3KjWWFTvibm9K?Ba$Cx`EBIXu(O@;tI&YvVTzF?7pg>s^vtF z(w=g_) z0wg%mqgoFHYYIx%`AE$m+t2w53|y(M03j%hF_xhM?JsPjAh9r|Bxsmb<%U(YtQhp3?p-C!vcHU%RbgG zw?xQ~l056!ToUlCHgaxc2NC8YL8W~?ldG`3+3(PA?(WI_@ONZfb@3h}c>ue6Qot}r$z6g0y==nRUUyVK2rYD{`Y>wUVzN6*eAW`2J0hT!K(Lb zGcTH-?a3YGKaiwq&cwh^y5Kq%kc~}1YuO^x+@-RZ7+NrusRZEZyOM}PUGW{dh`bn@ zDB7EY*?AUsxTT!j0VG6NMC2OD_6Unsf3m{C#jm8$MHK?Ow^B;emUplS+h32BmHu8) z0s7oY?hEj65-%i++xA+h%C+Tp>YCGz^}Bx7%NkuyHTzj1g)FQ_GHN^=44W%XbI42$@>(A!J@mt#Ic#8_}=2yk3EnYS*T6)kQ%uTFP?vCi{F^FV?PU`}AwB zXqO^DBtZW-_AJu_3}fIBI_+zfZSQD8Ry!O9VxAl(6&FFa@S5yeg|%Yxg6$;6lNC8myYQ(a?pn{M8Gbyf3(@{IL)GhN%45^bEi1qSA}R~$X* z#{V>GuF~0lfU1>MDk{pY{*{m{xaW9H<_7UKvkfPwN3QxDh7B$hN0Agbrf zkj}*|j-9kLaQ1_cbtE{YkmkYXe-P%3oL||^y=SghuGXyYf9o_96f!E3Eku|dnXvMm zRiI?>Z{YuPUMJ~I4*m-iP4uCCEv~Osw3wgLs`$Xo`KqFPNXz=&Ph|=Z=`VtF{s2vu z_*ms@-hc2>2YJP(7RXK;M}Op!;ZX6q^hq3~oP}OgJLI~-i0@|z!V#mPQISjtBYQH^ zVg_=FoT7E+o-jg5UD1${k=@aNgJ*&MTY(8sCj2~B#O78h$@Xh*C*0#l`(r0Mf0Cl1 zEo+OcsJ?LRPD4RiY*Bq(^#5lS3=4tRi?60)qAyr8pbBFR=UB8rQN}SpkJ|5LH4TT% z9-g3MfD&rlrHu%_W5j5-EVoepKcs8=!mrRD@YLi{)fns>Ojk`>#*!~f)9w0go->sxAIjhy+;>|T>x(U!Y!iM*@N<8Xm(dkb1oN3N6 z!|BbxV#emRER_lbJvx-HK28d;q;{>e5&@O>ioF)*h>T@}y&awg2_X zeBB9Lj*qGppBX2eVHv(r30VDHpW7^?zP^frSMrB4lxZ2T3Sddes2xZzt9f7R_~aoA zIT_gj&X;SCzw;qm(VGc*?zAW zMTY-18nBvS|EG1$cp0(2o=_yQ1}^N|G@yq4ZqmAI6}2U#Y4T$$yONYgd=_7n{gL5R z^xp8I;aujD;mUN3RUU!w~~q)2yp< zy&yg`6i?1&gz<08Fzm1A`R%y$Y5eGo%zcrz<~fDrk2LH*1_AjTssc&N%rx5DZo~(= zS<}*`i~ag`t+xh;1i_o7xJLV--*(w$rOF2IYUVh^G?4X-)&=|LQHRG5)b5k;W^&hLIC%Zo4Yr05!IN`an|!L1;iExh3qT(Io4d@a0#Jzt8Ok`4m(q3jdO_>{qh>Xozbx_P59!2V!XnJ6A1m~I8|{G)_AG#W4{DAyg=DC; zix`{ik-8-z#)5qhcNcp7=U(?0>$$a;nC8({#^qUA#>J|mv_CPKyVUa0M7Nju?$bPK zr3VvWi#PFxfKl*sgqFh98=J--ZQebP2GlY&L9T|MyuzFPKV{BSW7{_(7% z1Ff^f7lW%ipeW$EVs!Y44f#3c5Fsy!$T`%+H00Ba8R^(+zgyG4)loqou#VPWr8ExU@9Y^AG&Y10XEaXG2V zdnC#g?scyd-gDmoY2i87f3bg_o3aL16o;`!oQ1E;IJCZ4V5svN6lh?SQ(U0EhEB+~ zx(>=(%!3TM1lcNi##Flrsu!Q!zUmD?LLpAzjHU+&aQqgq#OxNEQJZ!iS2htQMktn^ z%_H2hb^pnKs1)PQQF)bjaND;eWF%v4kSgF{Z=jfV9z=3vgE__W4&Dy{Pb|R2mfn?0 zKgKJ2;$3xkH|C_rb$dL*G3|i(?o`5Em&ls6D0_v>e;eA6`wNpR0s~L)2wU>yWbL8H z|Bbf2+H9#M;oiG;KP`xk*7$nOcizhJ=-d>mltXz=9*OprqnaK;@N2}hH}Z9pzy-^& zX+|vDw_10e;?OaTBLHbwcCPd}>Cp2#djnAQqzAtDp|Xq**s$-^dW8$Y(-Bvzo2I1M zS2g6(o%fWx`->6%eV>Y|Q4mkT$FfygbrBId>w{+?1lJ3CZMACA57@*nwXrZ0W}#yI zCj_lsj11|XVoAC9?JhCHnw*u0D1toA->xq>uGI0b*|FPS3L=-;K6g zjGrEZ-lSGn9p2DBcnB$52ei)2Ka0E)uDW%^^?#wdbnq$Q?k=G*d=|$*8#(RPz^zAf zb_?TA9y8bsOV5%&-Yi47cJ*4VZEB5r^)pu58BAy^U9Ibc7PAs%iZx=HUrC(auPrel&lXaxP#E)clv24UEh3tbr};z9on;$fXOYC_CX@qU@{}7<7=g}?2vAHd)c#3O8_`P_~^rC{2uoRR-PwpZF$&gmGgCV zi9idqi9^d@Q8XdnN-Y|YO!kV8(#=FOXFua&k_JRedf9CxxPq-VTlv$Yxthf0XHoU# zt=}eXi5+L!9Og=`pxWtQ$*VNH%C55}iP*0jQ;#F*WAHPPvo|DrM)ihfGA?#w@){qR zZOunD28}4IN1(_Bh*4I0dtUM&&nBpAS2!;zm}hDL((Pd}!0Xh=w?1Q_qQm={UWqS{ zad6zUjX5vO#92F*231?W5SrB2Fm=K_M*bE%?Jb(UKMA;~*mu>~eEC`EjqO>oUM&0L(-Gu&nzaSe1sgx5mi0>bR+utB>MS$r}hY`>jDy{tEX++l;g z-*i;_jp)?>Q23=TVmb1)ivxbc3LU4%f-D=YlGDL>~rOC z;QB7mw1Q^baMr%g_VBZ~zNF;r796evbnwQv^7O0MZXfF@z$WhDsG)FJDyP6s5#?KZ zVY$w9_*$Nuea9CAYN!b`j7pF*h)%vrwT}v>w)3r025u~q_Roxs|4_R*AQRpK>Y0Ad*~RoVIY2=boe&&907}ij{QtEjmH!;` z|7%0!Ye2{f<26S@!*Orqj>AsN1$AWM${6lMJ0GED{4MimA%S7J+#1!8bYQIUbKowj zV!`BzRnV<#L6d=~pcyJcIO_B#DG-pgs${u4RcnPmN9TWS$~jJcR4T0DjKp$YH239w z$`SA2(@Q2F^XZ=_1#vGOjnf;f_nn-9UXCY0zPN4Q*B9C}--A$p!N0o;#T9~SSt0*8 z94S*mt$peVR@S3MbQi0c$6WaqTDH{o8^hZ&rET5F3t6|>?Mk!P|Ch>^Jc8|SjDq%s z!_N zaBIqmWa&0$5pno!5&l7`C=ay{;p1^P9A^jv-5qIsLAXgBTV{6>LGkxwiE zHY7y+fewe?3V)|rMlrC;r`f>GL}Stheem7Cf0HvUDA`Hby1>Xg7Rly7QD#BZ55LKT z&_ff}GmT~id*6pBJgFzC?_sk%j!Fa42=&xTp?-Q+Gzo! zYSRxNBYS`T9FYd7HZ{?-Y+Fi+YhL3XqpJ}#oy)sPN1w}=*f>ye&YzY0njVTx@s}+-3YDd6mM)Q?4YyazTb4NL8qae`tl-aEn(| zX(!ox_~dEbDaI#15=gF=MEc4Nx$7X^dEU1in`1idvFJbWZE2`&8~PQuTLPVkc->Nu z8_k4mN>EOfx2iU+djb~-jklVSm5mf7-bzD~=@Z+18l2}$`i-+VgOt(v8b8bQRrv3R zmJLRp|Dj|4X>n;?=XUyC6w2Q&nOH(>YA4 z%Q0d;8>GTRzemrRAAVCve+0t3+=Ug_1n&<>|Bx@W$YcGgsb36 zkCCUJY#aAHf5};&{kI{KCa9H<1)ZRdUkIkRRHoGLpVy2LoLhiF1n|9Z^*j+}nrA0H zYG4!cUZ#%5`JTXzWDEL?4o0zQryiX$r{$kIZpriFGy%cILA_2F+a(2y_mdow)~)FdNgeJYu~d#)gQpjZ4PnWC%TN?~#9Acp%=@ z`5iuWezrF;dfsnsCH<5j#z)l2y8X}T4O%5CP~TaJz`??Pg)GIK%KgYeB-Iz@Ipw-P zH(o;KtSi(5FX807 zn_%f!Z+gaDC`xT)&M`Y_X?h&FLIV!!iIWL2V)h}KV4`Ysb27?lvyR~IptmC5}x0t%09 zFlU6oIz3@I1DpBUIFmf+?m;sL6Cb+hC4#V;hxV zot-N-VDmbeUnLX+ET}%^x|hhOWhe~787$A6RRIA-(w+Z8k8S^r=c|%ijmQ)i<7rD3 z_&`&|tB>CD+Q4;PPp-ELt3atC-eIvpC>NI@W@ynu_MEf6U%!yKf~y_}ht>k79nmkQ zVDHwuRzH_Nr|8I`2+Rrs%)e`-z|%0T&Ea>V&ZjCR+umPrgt#m`o-dK16_RBk!zLER z)N}^6IQNg$2=!Q1u53*|hDZ0q#ntqP&1d^N_W?*%!WS1z0 zUAU$-(gyBwe{ys$eLI(C6ja6(x)S&tFQXB~1;&`rnb}fUesL8`;{6=e_}4TbmruZjTTGR^14tYe2_qOB*d@xps;aU&1~e ziUqkB(Mt{De4jlouwMQi-P_GuYtQd1MrV!ER}0A9~CSB{0P!iwaBWk`C- znhV}CkQ$t5Q}&$$FZ^T-rP>$x)BY`=_O+lJ%APgdH#%8b0gF%GN`gLqwq=TZ@}uzA z%Us_b5s$zSo;%gxKC_py_WKXWYtbeZwf?M3eiV81_97N+_MiK4mXkEDbuxVUF+EQ( zW$dnkDuu~*?05OUunh8*C%je64%oN~cGcDdU1B=YY4=gp@)hC}9RDD7{|aVOLDowC z^NZX--7Zqz8;inz!1qvkB;lzLf7=darH+2E=H54zFc0$3pNRjS{E#c_B^I!K>+5~?QsGHqu3d{Tc>X9Y! zG9=<|aaYP*GXvU4%|}VoL&Y^{w#vR5Dhw@&y+a9s%hh&w;>r@ zVE0ZuZfFW16(hYy0cl);sa}!X%^eXX^Q(CN_9*LY;8imlvWR&`WUBM)Ua`JH<_QPd z{V*7!`$)*INLj?EY0>#7kx#<|-E0Vg(F%k@EM4Mv-rkb8q!pzdroh;LS;9oS!){Gp zP%6QORp07rH|wtjL{977DgHI^>6Y-#n((9GRUq5L&`7!4U6KG7632!SyGO0!(|sA; z9elmHs~NQI&#%N8>neo~zIYRvm^plc^645T#KY<&5@N&15q^1qE4-SBDm{!hd(7sa z#k{s6P1fNR=H)13+sQPH?|G?Q38q*&e~&iD-ft#fjcn<0q(`yw>sdX$L=UnGK@F%^ zGe&0PQGP;Y zh9`H2QSEe@L_=DjyPEF5yB)O6v_zGgQ;FqBl4CU6+G9l0v7dT(>FDnL+_lt7urWEq_Io9K=DXMkczmaSX(kL_vzE`t6r+ z8@xBJL`Rq12aHMyS2$bYDZlHCc3V2qy(poPVyvMnBvZRLlPnW%p9U{d$?G_OapiGE z@Gtv_ItMLV?ubIoNON42*B#ePL%8d|K-#>A$q_D_Mfv&@v*3Yxn}K~?Zry#0xrMJf z>i@z;FMLfEJL@uH;mN(oxD}P+XW5P$qV$<({(OJ+TF1YV-Ijj^47*;O>_a7B>tyZe zgqqvpT>fJG9X7nM=w0>}3rMa!en{xRpoed&ItkIj%aYKhVEw56hUjCWtLt5xt39T8 zWt{PI^3K8gxEe)JMua#oYOt|Xu`X<=DP(l=s4zwnV#GHpetcwHzXz7RJ?3RD{wuYl zihe-= z43e=CbJa+eC%c!`@;G^kZ~Xuw$xVLYHx`Ff)X`1Hbln_|+TiFKdaPo_w6a)2j&ZO<(Ct;_DjyMsXdkMAQREqSsj;~`Y=IJ3%?$}0~Qzv)7tV_RE z8~!=k%UE<-mqIC%nkE|S4t^=D{1|j~`8ltQY>9#-o0sdC29b%O3UjWY2KL<2fN5n4DK6?6*EmIY4W)hC{%?8+~DPRLCe9|x1h2z18-@5ujjNGt{ zLCH8@?1zwLDU^~fZihRbU3zf`rYT65E@rrMpLSa|kF zE~$=n1uTzF=z`E|d^p2;nLV$AK>xV?9go;sqH~|7alW}i1ETA5rL$`OwxX~W zdp327lNfpNOP;|zVCrpG7rtKKFpK9R7Mf@DA-2}8ie6vvFOU5?o^&s{GM%4j*kxPv zSNDyJ#z|iLc!{UpH!ZZ$7xJ&|1F_Sg#?tX3yuJSEa(|Rr zzmax%Yra^4W4YW9>8kPI2e49(T}v=Nl%8!onUvoJo)%$O_53M5YoVb_n>j-|R)yID z{&_EwZ{V^ko+BdmF<(>@L#-w;3cjdgcKvw@nOf-ZLHi0;w;!^Z2R zeI`;P_gxBkC#PzSrAlTWQ_cM$<Z=q$Cm53OzWd< zXT;9lIhU*DOPZ_bhnZ z6rw(Mc0c+2Fmw^(QmpqM%!);cRiZ4=Fk@>P`^{txXEwo4jp~@FC6m3|=b=5nTJSM) zrXvu)>uH2Z7q8!CU*qwZjTAX7aab`yxm_!Ke_JB2&#WBH!Ka6fr>kj0;3);FMDxzj zOMc7Ww@=u3P#?M4<;!29npEX7lGbCQRnNhc%BJcuTyt;d-R%$nz)qx!9AADgx4FIT za#MAkvOwwCZgOzOtSRKlxpv*9xu{FZ)kARW9fsj&7 z1zg`O4K^F&*Fd*7-&XJg5GllStt zQ+36bz*MjTpbpkhRTTrcOEO>dW;JZ%!1ivsxrStDMl4G3tdvUsv-DbbygD+ke|Fl<<|(QftUttkRq#cx zRVQ$6XqgMY33qRVXZ2+LcP@9j$PCc^SBRHTWGS7llcueI2~{<5%JTG+`>l>}qiy4O z3*CrjN*m%hGyj_`J;ef4wk5W(Qpf&u5b2ZcUK9EXkgs7U3yBV8$cn$S7RV2}8qDK#?*oeg7n++AaDc#gO&{R-93y^g(K zMoZLI&yqTZ8%b+NP2_=&(9no+bCk7Sr{s8mJYaxChDMz}uZTVa21Zdk*V%1a*s+Z+y59CEhuc%z6aw^# zrA^OYKgc&cfE^v(k|6a{^@#P?gAKQlN$fCQe!|Y{M(|oNPe#oz$I~rEieT2pDm*-I4dx6>Ss7PlhN9-F%DZ3{MOn~*3GXnf{D)9td-M9BnU>;0W?uC^|4P4 zfx+b>!Q)1o-Rl}j64VO&LPzEG!u@009xII}09ofA9B`GbWE>yCi{^;rtr|1p`)*0A&W{*43LYGd zL-&vfO>i>@^+}Jq#rc!OUz?@H%fU{)HDR!}5S0^k%HG#scQL4Ah2(qWfgQ(-h5o*T z^me^BEB;tBfVs_iZfLC9!Q4)GZavs24st&Hy!xtP?-&H6&-H zN8+t|WpX-uOcDGqXB!)*d+>KqHIDqJ9w2miS(||9Ww6_kF9QLK9x6h|a8pn5PPb<- zxEuBJj<4Iwu*#$7Nk{B-Db@jve0AT#A8fFTNi}Ifjj)kLzD^!bvwtu3KKV<+qaWEM zP+48ERDJU`L)N^`pPp1mR+qdKkdc3?9d)t9{zTKdeHFMNZp+-*y!!RAviCd{u;E{p=w=WKtH~gfLd`z6=3YKgo{zSJh z-SH-{xBhA4XA@|qB-)z%+dmLiA##Zk9-bCDzQjZ#RO5r$ebw>-&o9-?bH)MTC@|77 z)_+?3sndEBcWkloVMr+-xy!|!0Itw%oS!rtSFo)kI55EB?t~hBlL$XuJ+vk&q+EA- zmVNE82!a1t^vd%`ERWLE58XKwYmEbGX)QkSeM@=5`#arxnZRqtr(aP(3R!se+`q{b z#i!iNtY_<2TGE}@Mji)9J+p&7Nm5C|lnUNw;G$e&1C))CX9Ku51V%fezT{*pu1?OS zF9Td{DFeR0Z%nEVaHN#XJQYll z$q)9N1BygF3VwNkhDMEB>PN685o;?LM%BR%`+ir{T^+E+YA;i1~haZ+q^j~~-Nyic~FMcJo8l0rh!Lx?NbVB<`fC_k=w8!4M4=HCJ_3f}8w@79t?95FJXba_1e7zza^Ut_u+#5k!t2PPsvCZIroO9gI zGe*5nw+Bs2!ehKwvmgUJUk)5w$wTbq6)74ELaLzZd6p;8rxQ;LXf{xC)agLo{10ab zA6t{EDE&&omz3RDO+3hNZA5cR!R-0{VrOBkrEC1-YI;8d^u}S7o2)BdW}^ns(M2mo zqwU_Lo%x5JI!fa0b<|dHluNv5p)ie5{(k_nKuy0(2CE8&jx(9Ic-?_%#;@Ef?>KMJ zy<2h_n&*==@)Y1&1i$bG13HJNLRr&ClM=Qjw1jp+!y5fNF>eU|aM) z=&8I4dwWu>19rdr(|OliB$Xdxv@mR*rLHQxB+Edj`ThGR{yCuCmXxZR4O3!#jxYNG z_tm$&;LO<)an}i@3lys;r8`JVV6oAruiw1vu_`#26{FsqdMdlhSa4hL(_nD7exqkG85%(Jz3ZDfB;k(Wkt8m37<)l?WlWx{+NrTuzjc1|*CvsV8W1R2k$8pIIX2{| zfk2WKo&D{;u!BGMjjwxzl62V1d;^7AVZMj%?`4^#yTh$?p7Z*j;g+*OqD4+-&C53FK+l8&qarXrJ(5g}IM6o47PMo|(Mi z%WPByyP%fE!3!|)3t}wSQ*kQKQ&Dn8yX}N~@mW;X(X%4YhAN||q9QS}z=+kI4Lij6 z96t!s@_t|0R{fw|L@9=*7#_$hr%^x%E5)2C5+@yxhlFfi$d33iMXjCsweOc6o!Jn3vM4+$#ARZUKeiSzQ$d{fun5`5!XJ=13fe=9mygqO8H9JTJSRlViKQ@Ap3c)rRyIq0*y1 z%j|Sf@)RguU&_MsPpvEoG`DJIf45B!?ePS;%)+ zAZ1NKGy(WtH}l7_lFGRYg_xz0x9XeCsyYBjDfb5jh?& z;PR<%{?*-sdOT`lXt%t_ub=pW=f>W6MJ;qC{N&lS;{pYN?UUQB>6|qt2~N#>p0`J2 zeifs!S6;`wb3pr{k<@CDmMKZ#c~NAgkd?Cq=J`dZVcmWEF^q@`)adRAbh{b4 zYeGW$5uKnrV@AH+)-UhVTjviFlwMX#rnF3XoTVqvxC^|2(j`Sj*T=kk*7{G6eVv^e zqbk%C+i<7n&GdErruX%c>`)BFmkAAizcR;JSTtuuVPO&4N5GPYv3Y^!XDwZKIpq6A z;m!R9FR7Z&atuwK+a>wBxd%3uyPdsb5mt9fW%HgK+PROtNe6rh3wTNL6e<2dvCBIR z6bEK>Tlnz32ZUr;#5>IP_}gAHv%bS73@?P9HKc1d0nR8+I#S?kWewlSb>?qKgYv}JkU9X(*;@bXevAGdhtZ0v#d5FA@2WZ zu{p}`JaL|PJW<z(|_@xdQADH*VXV+~Q^Kdu3FkLZ9DM@AoN);uM(iW}Q`}*>2Z>Hfch59Zv zIK=%QD{$Z49w$8!O$R?B&&->ew(b0JC(=W;d0q1`%)1w5A1u9+1a*5!c2tjr+iipM z9(ug#%13rbK2?)u4T;wjS`;Kyxk`56l0G+Y=N)IHt{NXPX$4RUFVYIjN+OzcCmU`7 zCNJ;YkyoD1Dv)B9G!Ls2N`Pfpnc{Ro8vXn~`mT2dJ37|$+mJiQX)+&%q=T*&I?Zsb zjezun&Gw;Lur6kKkCJ|l2`|{{M?XVTWMk{+OmeeZgP>oCJL*Zh*7SI5XyX~zudU-e zextphmaby@RrLY35?^C&2Q&e0Gx)0Im;M&q? zq}_WU4GaIr(Qgk&Z1j$M|I(Dm>A-LnCf!M9IiV2|?(;~E@_mTDaiZ(Jw(#lKc6v?X zJWgwSeyVA;H$B11Lr{jwXJ@CG9aVV=rVU13uwll1s>M8Jwe1;=OGvyo67Oa)FQdo7 zH?*jioj~ z*aHuk&Wx>A1;=67a#e7M)+jIU^zUyy|Fz-Gk)A+KMP?=Gy0NK`Rrbh-E6G;A8xGs> zyrhn8s6i|=0gg^W^C;aa!7?mh6q?~^#eMA51^aBjr5aUL%*0_l7#xg4yv|(rrr>bGyja!`)9HM~Fo6F%#VYiLXYjGCy z=j%%uWF81za^xt_NX6$Y)PX)0D0;ESTU;7C6`$_al>qqn?+Ix`Zul_;UC!NTa{gUr zq1T!53}i&pCI>G}3m(D#UqH8 zT1b^+Xu(i;jRz-AoEWue&FB!gj-qr;1ro#l z`qsE#Tkd{1Io2!0k}(r=tl!7$G-XKh%`FrkE5V_@0j+1(O2S%JSOH$m%TjN+MlXo8 zu}_p`RZulSV+{@6*w+&iqSUD7-Sv(8e`Cf$f4M;^A+%UP%YsKM%+Ci}U~qAP9K)~Fp@Ny6?9x3VTKo*HUATt=4rF+PnhE(*(~E3=JVNRD>gU3=S~l1 zD&@`r_Rz+*Rr1ChZ>D>(neH!7mxZq-1xxL)Dc?t^|9Uh1b{WAeVyGF1)4^7BT7_qN zBF`ZKOhBaW!$`gEL_DwiAXc)JYW72-wm<4{4kw-0%pWfPpRSK z==%`RC3Zi?4168tfB5|Q8eXlc(BO=`uSWKM)_HC@^9>a?DxaglQo2S-w4xchcH3=N ze|pKW7t6Q3pNawWGB2cM6x{qC4IkQ_+Vymg$HZ5;{ zA7B4gouRJ!SlE+*8Aay=Sx_03`F73LQOBBfNUiD;Ky}Z4Ns03Rlv*xd9iM3vrfa9? zUD12vsaG9Ku1yLp#MVB@0@)avp8(>5$5I(Q8kfJI@;H-qxEGtXF=8qdn+{2nXrp6J&aP_>D{A1X?o43$=2gj-@ejU7H>O0>}HwAF`N}tOja>@ z%ZYlw&%SwdCRhsZ*TR1s)4rUdsjWvrlViGuU(g=hVF^};{h;yQEDaw9J*-^=yAf*C zO@|=X4eesHHNk5%w1G;~IB8EU$iiVQ-|gD_6RX+973vcCi94sA*fIKwjEBqOn-o~j z%qbqfJ0qeIszN9;upP;IWgFSX1IKZ5%tN68g#vM!|G%WQJxLCgcj~f)=Q}XIsASK z#xYgE=~)Z?+t+=5ucB3{&QuWopQSed^822^cPf55uD+eZGUPT**^VHm2W2t07Xe}vZ~ zDL&tTVpoCRVBI^^I?( z?zucth0Ab~C@HckkPHEEY(-g z2*U*|u@>+8fPUg8AlrNx##I*1Q)gw8Qk#Wg#f75c9V!%!^SbWwUGuo{(#?mB4P$b3T98QH$+#j0L z!|jPE?);#2;I*?#I`2gmQL5*pS)SlHk|lADfQz6sgN*BtT80(YRqyYhRv(JS8#uvJ z982*W!LvNd8#w6i8+FE^s?fTM@1xn)8tcdv4gVy5+}~^i3^?_zrN3P8I2%CmXM}#l;~gwW`A(6L2#TXfUR248+r9h3@mWU_U_oO0mErYs zsONeR?RVX;s@wtqe}`h|38ojV)Ptvv+!e3RY52-9?CS7}DyAJ!8l1xXGGrRgnn-FQV==+XU&0b?~xjHLM z{ebsEk`h=(#%P%m1=xEbgTN+4TF)IgQ}*Ax);AmU>-X9L;A=XyQ5fwj#sM?nfu&-? zP{p6`k%t!hh0^$pK0I;iE7v-Xn6je0eTcf?wBHgw4Vyip{1C(M@3%xQzi}1mT+ekV zpBVW&y$fG}pL8<^V*tY;p)cN-!RJHi9$I_!ZVAQ8Ml4W!Kzdk0`2UAglY8>Y_8&au z;d)04sAgr>usinm&G8vu@ZM0J`ZuoTdZ#DE*58ShtY2C}`0w?O=>eT=Mb$sbev9|z z@2}D$KaZDvB+4Z08FOsyd)ge`{H!dW(gS$hhg9=Ct?YO|UB!l*5#ysoneXIkGOUd? zTcwsbr%F9X<9NQo={WUA8^rUJD%Jz6H>ZqoGr#}?45(4Lr?Jku3GWB63Nl4@RQ1|z zb-%s53wV7-o#10jsS-cpNFx^pb5e`)#Z09KHuolFmXfFcaO~(_;FgS&I+1W zksz8^GCy;S8?_4!%BkT4>s=rB>}_w78cJ|-p%bTJx_WaXDyNUFMIQ}5)ppM%6(*Sp zee+eOC6QA_7FUFju(d07tNn>J^R;_lp!2h%#W}UqwCJV0 zSLO|VsCyzD-m+(MFs}aY|J3R`x~S9qao`MN5~>|ITV_l?Fkg`-E8bFT@={CL5ne@h zNrGP)uI9S(+=9Q@tDIQ-$%~ewr|ZMfu;9bxq)J!(Gwa}Ae=K;Bj1V0G!x@^QC>o=&#jw>_L{vd5oruZ^9;seD#>FE4I9HXh`B}ktohsgBewTJhdN?e zhM;+j%INf z*BWNcPD#|}AF$j6_s;Y*wz0l%LcG^7%Ku|6Z+R67GG6tE>szmq^{Z@uS64f#tA9;8 z<)YdF+aC5x2)`3=g23>t5(%-Q->G4+T3rNT`1?!^gQNNS(EC8}fZf7^?_SjK3#Rb- z%(H~g4<47f(DOosros~AdvN_ z3aAD3<|w}}M6@3@E(RD74mKjhnlG$Gw_Xbh$3(u#OJbOc2{J}0oCpj2tC0fJ0jp(1 z(RU3$i5v-MM0tnIO~s)Cf46tPe0qB_=f*-LPfud)fN?5lu1IS6}(BuqMhjFWgfB4`u14Mi@Zp2lOhRHCPURE?{G}HuA=d5m}W`IF%O^yu0Z`zM`cl z3Oeij@75~WG0tRP-pCWyk?rPBllGvm7O*^lGt^lR0}M%o1S)m(3E+p3rbs_qPwn|R z^PM3P!mSb<&yoa25G;rB7+l1~T3$s+HZeGxgQw#K!drfJ!*Z9E45Ka{zj_hdnZb-_L-Kv<^YNbvKlr4W&I$dL)## zL+LJ1x+9eS))YAn-=F>hN`I`AejQ5hgwk-BmsN=U0Dy;}^n*}(S=m_pX5D(I>%2B+ zazZtZ75yae6vm4bBQZEXpL>HTA zdU0o&E_EUT13-CQI%Tyk`CtFE_W>Uab3?w6q&TO`H%JlW(LTwp6cj1mL5fr`>!Bg9 zJ<;jO%&Zz(&imAm+*eKlXRL4C@yo^DlfhXUE_Br#{9;8(mZp}=*6QmXBOO;qzk-n+A~X%BsK8~D!R{$8ug6^)0oUP{m1(eSW{#OU z(`~nI+?I7r1eW!)&@LPlfu>qCB*; zgz2+$0ZonmyP*1a;d$1-b*=xaYrf>@v>)|eQ;06$|DBcW-wsjI*=>n6+g}|nu*g{* zj7VV-0ku2Kuw8L$BiDD2T!NOSgUuI2iQq&^#d-0wy*-;%+zA5*9=&zt+WNL%n%%c& z)&{gniQ#YtCm9TKeq!AS+ds`hH`_kHkk`GKkp)3v zIaOA8szuL^!%EefKzlmO9E^Y0o?orgvIe$osr@hg^8nh9jl}Q_MToe}2pvBdJG0{U zAQ`bDp}Vo6?Y&nTOh2|h;&zXu8JfXKQX(ZP>~`;Mb7UOaw~cHc4tjLn!C}CzIE*X6 z(k;nkrNme2+*A}eilh`?HQ%%R^LMg`m_bW(Op$rI2^u@NCXH+(cMZR>;X7q_+mj;F zu1e4{OVFZB@X8n6zkcFl`#kWHJK`sCFpVjW)L3I%(=YVRM9IESzIl zlEp}dWeFCCWsZ}V1s24jdf|&`Z0o=1vNpR%MfUK_Fsh>PG^SD{Iha~UlJ8}hn#8%$ ziHLNf&U?{zW@FochI1b+XxcVZwyJQ&o_4x?{Y7takw4cj_?1$%=%O10R;VCn{i12h zmuOwvS-TP5@RDF0D*80Bb$w~d!1rbX&1ex?Gb*txffF=O$Sk|gbi>d+e-x#_2e;?i z-XtT+bY-K-#oMqqIl}HDSQFaJRyfytat0GYTVF_Uv{P_P`5tenk5Kr4?cOQTQDrmR z7i8}Q>q$^j!^Cj&Pv9w%mv~AL1S+B6`aj3Lr8bxe<@k0r>0tekXx}_aos=oY_fRL2 z(F(i3i1_a)`vGO020Yq;=?o+ERKLX!+w<72#B{4kxJQd@-TcU+sTUO=@Yrw|(f& zw(O{!a7v!SB3FUp9X{2q$W~W@GRoue`%Gir6?RqF-LjUd)+Wm~W-mE+%Pru^J>|8L z)yYm`6?yg3xW&byz z=2e^(IfB{L+V-w}Ymw(#pqKj)Rt9oBgUO;q;-rGtpa15D{e|{ausZ$uWiSQ+9H4{Q z_oi0%vs&4!lY*t=o|>57MtiSa>)=&yhxYs?jf;c3X=RI^W~m2)I(~xRO86#!3)V_t3CD_Ch7+SvCC0e%C(bj~Slif8N%!~0AdRtRVb}uF+ zToMI}5II)3I%m%}FCJZ;2G)T$wY-$Xs&ddyHonV^U61(MhgL=NQU)YY=V%|BtN45_ zk9&mIljju*VV7lQsDsUx=*clyCcf16*!TBkwknf}CxyF;%J5FVd_%6^;}!Ch`*!yC z_PhP5^v=~hg{U}xr$(7CRZaEM%Lh=(rM5#!Npm;71vKgRv=1{P9Oj%N6r7DNABbo$ z^U0Ne<|HjJ&r39z@3{aVMuU0uK#c#RpaASwG){h9Qsn373*Zp0EpX9Q7ZzRDH>UOl z&CgKqZw=W7mOywJqTH>O%zw#db5!M*>46{x`6rEQrXNuC{J#0QsZVtQ&>}xo&E>%K zt4GgIRdah^KuCi%1+mWO7pl2FaCHPtu9YGAc9SLYI+a<==sUe$u>7`66A1s*`HpWM zeqF8)wG)No4RC$Zp_9mOQ-g85Ku)Y29a*$~UQ$c>-VCjN(;)+-&s)T2oHA=^yUn#! z-oH4?_Xe1&_W!w-=Po|S^@^9hLA>@UThFuoPrT;+vTE*+$KtHN*c#*Wb1hGu>v8=H zI=9?h#eTau%6i|X;XZYIK5acm^|`H<_t$Nv^9x?6B;QI! zuPyKo{c zj_ESq{g8Q8aJ#CjpPR)wznze!K}#+2xU8j5j*m_*wQTdf#UoMVIewmFyoBY((%`n7>!FEUUQ3 zF`K&Dntamvoy-JqGbe1=P+1~)LZuiACs^P}4>ooKU5^?9__7 zfV#A>*}q)EXk1$F%>i)AH+cN1%brwOa|D z4+dViRpV{ISmQg*3FV$~0b@^1s5U+HNdParCXm4)?Yk!s(IY`XnRTFE+ID!pR zZ~8Wn|3`Qtl!Vl>pi{_pI<2);t~f61qIwww@mB!1!?*@!DPS%0N0q~ z`(ACWzX9jAzv5l)`8Mv=-JZJEU&g$?u;z1ZuRnM{UhQsbjK_HewZ%ax*cSCf&_g{P zY(k`~3n)ZA6EE;_Jl8V?I_cU< z4~9HayTI^SU_eF4s^)q9UoGXQRfUsKJ=z$g6xVS(S;OOKfB^;=U_g8!Ui%c3O@8_D zeoq^A^;>UGm;~&b8iqQdEcFeo76|^KLXlIZ6@|wcNfIQw`{NI7VO{NA`A*qUdi4w&iz=8E@(5>sioZ(bmI zj^qi3mD|5D<@O#w_fH#!AlMtB^dvy0kYJ?uT~)tFeP@dD`_@sGYmV}}J3$XaX%qDP z9_aZD4Ffx~OsIV(=>Hi|z4}A-Lcy3i#&FBy_4Kv9)nVYN-<`mKwt`h~xxEukIlYSH z@ydfr+*c|}kwCxh6V^{g+A>NQ zPgA@oP@;gt!IOZin&c=8Gn+2AJ^E1g;y34kCl#zR{jwA1U?a?iNvNFq+mddX-Rtyc z5!|aXG)*!xsc;<1z3ORmv=tvzV3u=k+ge2|mRaSp8K_W++0ISzfwl z;9nWflieFMs3i4%dv$zg?OJnioHp3Gbl2g*={=Bjd3Wf2*hn2VhKG&n+e7JgI@@;G z=Riv+ouiY^4zm(RO2d}t&2`c+SAy@B0&Cz`7wmne)q#B??8*(k3IbLwt8EUkYu;1d z#-X!NBSx3o<#!Sslb64E!@vrY4mIP;Dcx>+ab-73=#CCmLy#Cp@dQrM1kJG|Jg+l! zCX?NE-Uby-)kWNBOC4!}#Lkb2Ly8M2{d)6LybiUU!b*xZOtXy{du7Q`H8J)4F zl9Ya=iZi9E-yf>t_XnzZ{caZH^LXW7-QUNn+!Avz9l&WA!2gGI{y%_tuGbE@UNF}C zUhlhRrpl$8RTKSG_c)*H{A*4=ci1+soO|Y*Z;H`~ zi?S%nyeRO04!Z(ZdrwIMYi?SUJ=qW;OFXXCKD$wTN6c&*Y5S|U?UAv&nuTs23S_U7 zq@0Cn{)emmS*BilHb8nxQ#T;U)Hl{q6gg>NDXv+BRmB5Cb|C)Wd}1wVQERGRE}i{L6vB7+FtSHZ?eA10G9jD zY3`t|&@|cKn8T~m0RF}t+gRk=I<>jH%jiZxWfsQILg_iA&r_&4RaIscj%Fm4lV@Jn z_ZE8Z_4KmD6`@3Eb2AyxtPh%!a-mN+?rlt^5rRYs!j zTz=@)r60bYf^yjP>9K?$jQT%TKz+Asb9{t1I3IM^xowMjEcz9H01#7_8BI(AD`PY* zE1W3MTdtViG#d*^MzP z^YBLQB_vn$+L|s^b|3g0EH;;$BW{8jjKx@mAXt{d7OZH0$s6+BmiADkQ+1@-%Gy_) zFVbFl=lg8mahKe@$pf?;J3X)J1Aksv9}H#UBGXrFIW zflN01683g3lusr_ zqG?)S8JWSQ@1N}d*2(k(WwC%q<;B{nS+kra1?!u03HVz1z|S!X`Y zv-SPzvMXunP6lbR|t_~U{*0X5jCXqZP##Dd8V7QPz)BQNsMC& zoTV6^!EuJqd_rd{RV-LLfMiRB)D%NvzN7{Yw5?ZL0sc z_UU(!D3KOt%CGJlX&`?%EZt?vu}L`a>s7tE(YA&;j2>r)tpLE13>&(E@$X=$vZNU@iwQ;MXcS}Gfa-<1+TTF>eAu>q(L&RdXIh2oZYtcv69koIIGm7p!YPRa ztieH>COl#*`7LkT?p0xw$z_UG8au0*6G}!YKL1%8csPFxGXK@K&S%#Co{Rw{K~Xr7 zU6pZxmQ{wABoRbr$eH@;a$@89ghWW2k*K5P;2>2TT(txQe=?ia*m9J5zyEkA=!&pG zBuG^vXqBWSjK|OJE_Mt#(D_*F8JR!_KH-z-u*noYjXnx}%(mi-m3?Qbkq_=DnP+K2 zWf_VKIk@{Z0X0k;ZRF{L($V*k))S)2iI~hO3dxA@U9Mv{1`6O3q9bXG=}Ft#$D7m} zy9C@z4WkOyNROnQXDw3(7bt~_+n?(f{EE*h$g;Z~) z4lEFSz7bxJ-y?Ynd`?&C?>w(ic=ox&?ehz6N$GIgySz24C>^N3gewE8(!Q?-PmvZ4 z1L;%s6XVLLET&DiZo4)<@zrjmHkijThUGYx=Xi=|30NDfr^eo7yJ}DS&ME~az}I0z zs9=;J$P%VVti(;t-};K<>k}#9NEE&$)UiS>P0m}ppGQb+T@p6O&y2xle*=d(q_ zo`m2Lz@X;|5Hnm4VL%y|K;-~cY10_rt3FlK)3z)AN-N%2c38=UERTW1N|u0cVCvmK zEidC)+xDD$u`ySHHEcxaTrEAgn(C7M`A(c66@pg@K~aS1dzSbQANAD(!~NBe@I`&E zEgm31+bQG~x#w&bw`+Xg_TP{v+BnG)499T{OOrH5hg^~CS}$^IpR=t!J$vWvMWC&d z!YzUVN2oMK^R$G?sP!6kt_i30A}A%4{kPfn*T$TAt1pl80x%`2BFz$l!chcl$@s9= z+(Zkiqqo>P9G!CIM>ep?+EM%&=XmFU0*^F#K(VVpR=iHh22RMEwi zoqStu{l1#e=hBJUk@T?=ta+_`H zmA>zC=8)Qxw~^~+1tS;lSpxx|)vNgO2YLz$6v^-MxXpd14>3Xk~S)-o(EIfuw+HO12|NU=X zKA9FOfy#wWoCt9qP!CBuuh|w{weg3iUu_pUzX0WE^NVrjDusgE?~-y$3Pn%BoK+jX z`=x@(FuQo=S#rAd+HPmtv$J=Ay8ih*P+F@&35afc-8S<#-&-qBqVJ#KB+oJ=j^PZ0 zvm^^M(-R4iN9J|gNyjK>hbJOS?5sj@94(0?LCJ6jzJ)w`f6F$)es95qLcpl!gt3qz zcr3sY2f2Kb*H!3p3vU1J5vv~Dd}u~0xCH$tgaM$A$A_DPtMZ42f70+Uh_S6hkm?9HZkY=d_UaKGKIY(YzixS*99sEIhu(jrT(-r-x|b9GG@ zbqa%H@Y!|LxZBTBr+kSJOF(_kXz7jhgQbFlwQO)3K>a(Zma5ICMY;X0tdoN$pmYIb zdvh52e{{7^ws&o|aYxBMqss0Ahuu6Xq|6dDp-2QNeA52JhXbS`nXRBa;Z31lLa~7 z7!2ME01rC#5jb>8@3%E;IDPc8Rbk|}aw?-~g~Jt$BxF9s4^(?yu5r*d`ID?RIbTKg zARsa-#tQ;2i4u2t;#ynl3TlREm0M=hA=^#I626>~5y4uWWk{AMXbdMvnhw#QZPrBM zImjXo+pd$hv}-j1n8J++f19RIC~_(?L1P#$sv^fOyYATV7n(0kMD^E!)X3?00jiv; zmOm{{%3wGjv@ZWgTh7V{-#YRv4!%UIe{tuiVz=K_s0=SE-L(n@Lp+jD;B(4Kf#O$2 z9NyI4XZL1+4fB_figL8JyX{;r7`NBxvm3P2?v@_yf_lQXWzywamXC?NxuGSIp)gq_ zDVc^hH;Wsi^8VSDe9Nr7g2iDpZsp8`WqDQ+Sef8(0i}hqWJNb}=#l!vwxLVm_)a^@ z43HyVX(B~dQe{SDRE3z|?X6imR}IMM1XN#;Iuup`Nzjc#AGCOUroBHyBeS(B!8@S^ zP=3I9-9UtfBU<74U{dW^jLLWJCvAhauRW2rwu~*OhTT{bGN!VkNXZQQ$7jc0H`yMq z{{hfgX9lYy4=02_$uuF}H<=>8Z-O-7U}!qT_C6ciPz;S?SPGCk>10ZDUNsN{?9mn^x7| zX|pP(o5q+13WJxfSXbzO`I93+sdLhHX8Ur^KO^#02cDuCR**P}z{6fSn7=PF4wZmU z*_OU@=j@h0MdL(6;2D7@S&3y78s$U-9A;gCcpfikR>k+xH!089Tr<|6nK=`j7;P%= zk$}UM(DNd?zdEf6$R)&I}fhL68K zhrJ~`bZ1pT=YXj)E%E|IQX%GkCG}8bWwB4aYB%xUcfjy!64IG8$vXqCtQ~GynKr;v z?3R5_pCq_7A3>5y`dEGEM3NP1gbxzP20;IV9j~H|#$p5mLjkboyaY<4?rY$P=L4Yc z@fZmgM{EErCIccta@0HTH2a3`1-(YEippyaL6us*urf_cxSrRX&KimSX^#AV%*s;4 zcW=f?2@L0h?z(z*zoq9JZ#;>##N#NABq)X>2$trE5KSUkPfz=kY40?r@2*{2$4`=2XaBUa@$3sn#K{6b#$Vg^$s zPQ?g;kkq?xtDo|B`hgTcc!|q{@h(TO7G;?|a`6eKhW2Eid-T|~6j~{a2o5mNTTDuLUNe3Tl+<4OiQxLYa1knS--;RvErpWHq}sh)eU;>G1c+*#0t$XP%*lx zvEK*_b$x!%7X*FPRyo#y*dXN`m77q<$~|A*uRL&$ z{WF+#_*rU<`<1#Pk6#=meK`y{^t|Hpmh*gk>e{~4*ykDTt`1me_?fz(V0;6k56%Su zGmxD|R~TSG%y7Dn*S{#ngDc=wVYjh9pVM`+NV`C>wU&&-`L>RnHb|{b?*nTqjMZ?x zb5wp?je!F{ro%Ms)$py_!947h2AISF{Sk270^hen`QmC7Wxwb_R*e0pr!N`?(Q{?% zuUFs7)-zRS98(eR!R}@m2q!}Aha<>>|Jy?toOdKa`N8h4nCkw=FflP0kE&$70p#dRG`)NQ9f2$L z$u-OuVf|UG=O7r=->C?CgXakdR9o48x9HG=UjL^+?T0<9M!U`@=(Jm}->Q1h({O&U{Nkyl2t*E>``lS?9Z?Dz>+_0dBf50XOPhC4OJeWI$csw^@vFHoyP_3^2d| z0}L?000Rs#z<_fOz}^Ey=z{e=KyQjVe?kE`H$g>qY-Ar|?S1%%iNFU=g}93qU9eLU zg^M!FU?S7v-z^Ql&H5?>XbM_ss)eT@4Z@i!szAzXm{hpqwn!7>zv!(M(CS%NVyG{rIu!C)+g!3FeSBa?u| z)XcvBjkLR;y&2sGlH&=6WH_ATFp6a0A_m+RzzkDYwk-T2(Bp1%bR=ytwXk!RKc_wS+y`hm~^ElsCD)+yY zW54pLJX#zCdhUA!l@|#9p+b?9kQiK%RSe^KDdp8AH?IF_Pzq}@pgQ1hbIs?t>HNyYx zfdC%eFMxqhy7ZX=JS#g;^cBwsaGqwAGG7yI{?!T^su^>DB_H^_T>{yNw$%=ayJ+>8 zsd=pX_@e;sqdAM8Hz4rBM_S7P&G&Q!GmsHly+tkG2egU1x<{}KITu=|aqYc9&_sVr z!&*mXFz4hV*>^U!%3-ISnt?Fz9wDK9T8X&De?6j0W!98YqbB6!>u7ALgKT zn3KZG#62|5xK{grp5~O7pB(u911&04qfT7{4030noKJrbSd-Zs26qVgSfIv-v;q80 zbBfp46UeWHE4UudX+rs;&cV+&Y3+Z{5-me)XTRt8tl_4&fEC!1aw(%UFd{}eMUPyv zt-I-^|8_S_gRklwzbJA!*Cj^xJl1r%*wWs9jl+F#^vyv0niINP(7fyNm~&u($E{R! z=;&}4xZFyqH^ZfaruFUf;N*IlI=h&7F)5V4!1nexw-h|qt~>A+m=%73I_o_d;{;41 zF_LCfcFPshdwj6`-jr4l2Z~p{tIO*^yu7WTxVgpD!QQb!w-N201Kd16{C-jqRbFO9 zUZuFp*7m&P!Qm6rQXpDLjBY_3XjQ z=T2vPQpcZ%KF5Pe944kxAx#m^Lcu#(@eUIT75TUO#upy{x__Bu2=HxCbdioa7zL>J zV$G+v0~mzP_6ZO4x$LpqTZV4(O80mg>!L~>(GDySe7>QIUx4>|lbQt!I&Bj4n0|LV zJ8IPC1`C0I;>{?+BXH+vA3M(FRh&|R$EPeM{yFn^pKT3*zwdwJ#18*8S8q3)O{Bg5 z^0lMu^^X|jjZ_(t5;%zxFa-|sHU`RijBb&XebJyo`?fbD`jnLji6Cf&Cd2rYot1*h znzmcV?%UJle}JcbA67!87>?vffyQN8dj7s8dtdD^GIda#`F1jdQO~QIe|4|N08p{} zKz%Y}RJvIp9b{dDO%?jMRQffWO%{_mbU4gaOj~s1BRyyJ<If|9IwNpFYv~_gXOfbJbh?A=kluW|f3r^p5CG!Ee!4N4z2FBqOK@)<^P_k1J zaa5FycY)#$@jPB|Llxgg$wI9C?hJ?TgbJ3V3SPnUB-N`Z6#|2gDUDh;O38Ei{N9qr zZATvcWMhXEKrpAxv8ff%uIM@Y;Uk6nzf0>1#It&#;t@%30f}sMNs%JwDt>=~A`kQw ziY~X{_jrFlGPd3Rl75-}5tWEmlC15%dki0P7iK>vUZCwV=)HD~Yqa@WV$4Txr=J*O zK9zm1uJmtZ`|Et0SBVC{Fdz<^L9?lseaZa~mYnz-$itJu=_@1^XBl3WC5fPraipW% zKy`+yg4&>&O?~aXR``XE<8wkq^$Hs-8B>^2)q_kD)QgR8x#q|#FPKrP$%IOGQ$IVg zXAW4i!*wqR<~qK7?eqrX(t0V9 z;T4)vXhr1TxbK?%@}kGnz#`teO=48IY!Oc`pLC`FWMyRqxybnzk`CbXx@t}ro#Ql9 z&FOy=Bd3l2H~Qb`f203vy8q24yS>ZItpANrJ5<<;hHfZHLd7^tWHFLg&OVb}UPbc5 zhvq{rN3C}bdnec7H;V~y9{+QBF+Oce-MyG0NlxH7k&$@yB6ZjPdN++JyQr~sY%q>i zyI5C5Aw>OkSR7BVJr1M6Ay|+g!QI_GxVw9BCpawb53``ot~;w=fv4X2Y%Z)2NV=we)`5LceX=dP(Uu3-J!QhUfFw;%MUfu%{ROv z@K3)aNFwfPKI6+;k@;oWcziyrHYU=y;M)^9V&5%oBW zOQy|)%)#zV*OvV055$LvK^_t;FCHO5GffV-FbC_yzieetGJQWmL0;)cHS1SfY-c%T zVRca=1ztT z*VF%HhTjh43m9xQ zrL#_uLwtU>Nn|@wZsFSkK!Csf%@PQ)>&EP2G<_m9tubxXY~bm)kt^n=(icR>A%A2| zeUmfs^z#Xb)Geqr(!sWT_LrrAwltzT9OUJ=@>cnW*D;nr+V)ZpTQ}>y(B}9B!*eo< zj-FUyR-kqvIyA5x@E)j>wX9J8r5yyzycQrY5pVIxqucPOl*?t< zSS{a-U%IYP6Wqro2wMb-8R$Xln!bue{i{_l!4B@1!p*S-G!G^!-sHe;{2@IkVN+De zMcve1^WAc(=nhEF+Qwr%YB%sgezoF^9mNy!9?5!(q#7_zoMJJrB%#Y=1%AoYb$j)= zjetH)emi+P@pHHT8F&m)(2&>mXV?a^&7eE0W*-8df;^UP68Lzibu|0R?Wfyw&y60Y zZ#BEWgBC?SB@nH2jqH6-Jkd<*ZK^cO5MmEdGIsf4`!-pB-YYOQVENP?!M`8EVwZbp z@MC{Xmx&#+j-VoHEAiM)p-tkR&_g-M3j49YAs+YJUha3N!E2-JFuC3LKFMKHoX~?V z^7-NRn1!`pp365g3-cbW2j7b_oIl9VI#3YRpsC5xAb`@yRI!|0Fc*(DxY2XC=p=+h z)qpldAYDlyPuL5az75H#J*1=t?;+S)mTFhqK*WU)DG9A67iR0Us$|b_>)jkNG+4(K zP`kCmS-%b0(YRD_A_#X7O13uWGJoGW`tmNO2JpDLttq{-dkTcvnY2rxc?u13~qQ{k6^B#+GDxfpv9*DEWJgh%(Jm+7DrttES4G(4m+HoSTjPEB=A=Im053OwQBXAx37}>U04af zD|Gd=9U)NODro(ik=o|;N*czANg(P>;@-YQb0!-zBP8^WF$k-=;E&sU-w-e^dJW;x zMg+NJ^jFiJl^!{fT6-(ddn4|B0?nU*f(O7)tDeS~8XFvCq!03zGJ^~D2UfE!Fgf?V zYfbZ!=VTld@&L-(1yMpj(kD{+#B@fU_qSPODO)=4O1{>a@bNymf6!~LDEg$6`@fOc zNMEZF){X->M0(@An-F+qz;RQcF{2(sy$fm0_1l?``|8mgoPi}2-}l~RIekLgau<@> zwL*mqSHh6LORx7rSSCPA7kQ#Xx~Zz z82|Im7%guWuo!g9@mer%GxxMzVi%H(9FmEW8xjZ_*&u7_I#;aTc+6jGncNQA@|=R- z7{pI4emS7*@EQp(^O4bJ52Zs~+H#W_>e*!aE1tu>Uo~moK+s7e@&>V1>+jjUZ((B4 z*Poaj!At!mP&)lekTORB%B46FrWaAC3uH;lhbc1$T3%f`(x>%FLL+$dVc+K)a2sI- zPo^xD9=V9#UGDnlAYUP$|KGt;7v$`$G}Uw7kCA9q2U~@WPex_Kb3U-hlbn9bkZucJ z!8?g#3q!#awM^)&ROqx+=(u#_q%`5#`l=nR5yDcw^q_de)919(YK!(I_lr-;fqaU! zTuRYD_$H&jIA-ue8uqR4E1B-55L2;_uC(#>E5YhczpOR$aZ9XXDFxEC*js_UOqfY$ zLHtFCTCZacadWa|_$&_Rk0d_5BV2waEXawW^_Obi2<+?CBOhL)Te3v1?2czm2~}PB z75A7`cF~82sJgfW2%|nVPUK%HU53ryV+Y3IwkW7|{E-(P930h=SFg)CkP&r&+<069 zv!te&3neEi7qj?Pe+X02Gs<8q3zC5vUpy7oj0qi?YDWaL>bL|Ga#n8OIfdpG^>drBixK#B~Xj@^|}$b3lRUl4|TJZuV3eC;|_X{{5)V= z5t)EI`cYU}9#w`Zx9QZ9(gl2DgDXKFr3$69vWk7{scP_4^-*l#0wU!T*yk15D_wwS z)0vQJ-8bzZ?h4&Dyo%VW?{-8-zC@P#+4w{GFJjJ#@ls>wPOk_(Cp%&vWCT9TrI*>| zrnqKl(1{B5%vBpY&mc5Y5V={QWl9usT_Biblp0|`NJwm{m$*Y8N{u2& zO>O1>_rU|Jsoi_SW93cn?!C2ZI$O?1|Ft81yXm3ty}#ACzvJDR-P9%`vGh|z1a;o< zw8^Yn=dJsD=rN<~vtq8Manr8Oz6RgHrJI;{=a~d0t|vyDuo;G(*R0L;9Ae1(bgv7- z_~8r2gv80CA7YiUV;xCq+fT3!x$BCHp$6xA@kG;)FYFaFY;vhlaVrcdz*7xWs{+pF z8SN3YHhlwWeXEdZh;rd0&X@BlisWO!gBURX`fn*y@~Ee<>W_{gY|5FYD;@)J?h2e@ zYqZz%zr^mZ$GqmTx6cthmm_6JoZ(+^39Dm$pW|$pt8{;6Aphm>&C(OyG+k?a*G&bq z$6Zb{$1$YUaF$AQpq@^jznrhW2tbL+5M5HC(U9AZFVe0zYl1f)-$*CFo-+BDtQ@~W z6(VkHaF<=lG>IfWQe$SiRfXE*VL0q-^0qTMmHl#-(oBUNjaU$2-__r?Jfh&XO10)PiEo2m#LRZx^oWR zhPng$)Qy1O?LLXHu^m_kSOWiT^_OjNIZYI(*;P4PwCJ8;2hH2g=;aIJwN4n8624#5=O zL}ur&)o9tDjm@BdO0DJqm;Z2;H)j!o+faCA&G1(M(SIih-Rmflphpy$gl55wK)!*U zod3|5OJ=m;dwFcZ>PuRXzK?aaYHG6U0?aDUo>8vfFC2t)n?%{Q+KpwkL8-?}_0Zxs z&qX_3?FE=7&(YfQ0`H`hL?t(3o8Id>qy*Jr`{Hif*I!k)mF4#)347cid(`4uBoeNr z;B^c?74a$uK(5|jPXnxCSzJmCu@(RRrc1B;oBGq+$>ox!XQBu84@To}O5qG9#_-P~vo-~U8Mciti& zKGxNUn8=7hVkFMyKo6Q8qo6OWF@({nRBl-W0&rvcu$aG562);o62e4ZUv=mC`dq0t zw*`LzEDYh(?^1>>bcl159;e|K0M8iQCXq0?-;1UmzW=hDwKrJPUcxELWc(_{o&#=a ze|yaX0eUzrkfVm1b|azU zLRVIsu$&iLy>z7u$v3U~e%}%~sD~v^3T`6*gY72*AmP4zbxoiW(EoyrjqCRHWa(u5 z$p+LM8UBN7Wws{TTL6DdgrYJx^})OT-R#N%d-QN#^+*){lM3fTo>AI%;gFYAyldWs8l*Tsq&xkE%>Qc+4 z$})u3_~co{nCicP31poiQBE*${@Yl}!LKEl{#IT+xBYZ+;PYA7t)mF;X+dil#*aM; zc9n{q{e1NuYHqKAXPIsES^WL7VWY^HriPdmQ#m<1wzDO-_fdU_smW*yMsP>D3Pn?5 z<6KKDD5CwfdRLQBwunE|2GF-a+Z3)zN|tizv8WAq)~?&8^VvpL(`~Dm9h7tn`dqXB z|2yd5I{~p$FE_p5Tx0z$>EA1vFYfb?zlxk_d|9k!)SxJa%H5gAaST;--!mG>@BKq{ z&ZX1sL4)15-m(-RG{gcVvFyy?e~hA0d3TKQfGl#0=-D$EXL}z1#<9s-#;E`|Yq^=o zy!O(l+^#^w^c`1)%81+9L>VAjA(j3?qZo{SW=||~<_O#tt`!!%=Nl0)cBmNeC{5V8 z0BFekEMnqgigjDzzQ#2&m)~Fc_lquO7yr`5zIJ-CmC^tQgS5>~cI>3kR%7pbd@|d5 zNKHF+E}`n4%97?6(~}3K?@4am!p^2~drUSn-M;oX#|4SA*;-q#kFi43inFU${L62g zrS~-bt*F`(X=tb1CushD4bzXV#66F+#U+D~PNpFUq>L9}BX#R0^qX8bd=_P7Hp=N87v!pwnqbLUq$rtijJZPu=hqnbf|M=l^`Iqd7)cd{#jRx-1gWPchO# zu(F%Dnx-hraE)DIcv9G$rynsr|DJ@88k_#_6w1$v4`_69SVxNH9X6yXghLQ)?fam= zh|=)8vZRucI~4h+bPoY2?8#QOZhD| zlJazn9Hl|yP)R60Kk`Wuirr@6;U>R3zAzf*--pXQSQTpYUiqivY9E#*`Kd#4{^SNj zL#wD`K&(NW{(%9yt>8oNc`*~2?WNjA9xWQN5<2w;`29{9)-M`)dNVojql;1Jp3xu~ zxuzbr$oOC^i&dpIlvoeH(%lB)pi&rjBLQFf#623cV&Q*a-1JL{pzKezM9hNf6;bQY zpD;2r1|PsWxIcO6DG>+|vjuugbX8D&HkU`Iz$W?2!6`fBTkk+B3W_7?rNb~t+wxF)mHfXCO~ZCldB;k;kfCwKc~v;B?nvcw2@|pPCgTeMvBYIfwW;~Edk== zom;0V1=h)$+B?E}((75F*@0;Np`Vtp|-bgc_MMVe3ec;7cfVhE=9CAs5^7vn?R z>(-=VO9M!y-m_l37ZFF!gv*2%w&KjOeqsc$rm65DSw;y2yzd0ujqsb$8gU&E2F&dEe*kh7wq1#s`TL<> zL%NBB5U&H`A|HvQbV^>D*Wv7zasl(c>n_*!D>>0nUf%SBqkZ2p`@XbtRk}ON3N=*zANjuzr$`c4b>$UPn(-%I z3lFB+6il$zQ$Ba&9rn4Z_xF~|Cg>C3?-LrEMSmeMgB)sc^mmEY9b_;WlUm5D>>3+R z8CCuqYBa-h;)W>|YQ#ae}!QFqhrc@c%&iEtL5`AZRtU{39ncf`!z^u53FrEsP}3R9(xNVp&k zcDDm!pw=usw4m)=@_o1}5`oRF z=CM%Ag!yopKmCRu^q7m1)qkPE6#`-fr|=zAJL3J-BIOyORMwEv)_?8^S z8A*Z3yq+Letdfn8jnI*J`)z8pPLE4MUKETy) zUp53)dCqwg*ee#SYk$G-8t5wKbWG|42wesbXE$1)CDa9RB376S!mifc4_Afnys;^V zZdViZk<;1o8aT8B_2A#=V%|JUMBR(QEWt;n88mZWW7ajx^lrH~K3M4pOjYj~no{UR z6tQch;8oNQaD6X7NcGBeHqAusx;8)_c6Eveoj5HqmzyNo0ED)7-baEnE$)Y|H6qoJ z^|hsAMjHD)#MY^Qq}Yk)Y6oYj_pNZM9L$M0M1HmJGKT)a=Nw5n!CLkanTg*IODEKO z&ixZ&!44SzS@GS_X<_PmfDXRzgd2?>o-wl1ok=s#B%fAK$xL5e?LcfjAzSFAfaS!@ zG@wJ>F(OO3mo$;@Ca7;0!^*8Am?`DU6{X@2S2q|HS<>YL_o&;yOy9VLV0rmn=)xIY zk#j{pA5rUG19D!Ei4Wj~TRRf5oO}Mr7#i)Q{>HKFsyeWux^XJH1$UOGu8%JMDF?s=knm3WI0J)`U_yne^KPMvp4H43)fOO9m6bZoRNA z)BoT;sS6+8G0K>XR6tYz!VThA&o!)N;$$p7P_X!Kw-#yYz>Iv-l22v@U)2)|6=cv> zwB2AJPS66z+CondKh`>&o`PhLPssy`Z5Odl<2%n1pz(nXy8`@9AtXE}i`JMJ$Dp$3 zURSs+z{odZB~JasB18zq-YbFnB|I^oPL>3pw`(UV>{EB%#LpK@ivnC!kyY6kw2k5~ z+oZvw*c^brf^ml06h*Q3vJy@%NwEK3ftTzT#v;@rOp+vu^^^h?T~>fT%`)4JAwBs6 z#9b)rM|7rsg`0~)LB7MN$&;Uq$WJ-Wi(8L!_~mFvJ5o)H2+CN((h3)>jF|z+2^|mW z*c;r#c;WB%mTi~wSRBEg4k2p?GRc8Mo@2T7uLa9AIMlB1ACm}`?_KglIVoV*>8;Lq zUDEb4r)0@}oCN6s2DZ-;{71Q`H%Cbyp=zsD9W*qvjvA~OBg}J@ip;kD2QMsRshZ~9 z=Nk({Gu?Vv)w81smLyJnM+7WA+!m|OA>}d#8p!%uZv2jq+_f{?^Ye<#gD11BpU;l3 z(lHC1N<%VF^K?xhw#clUbI`AJdve#_CI?4H1pVQ zJpI6my^DjtIqEaPwewLKu7)%rHm>M4h@qqDP9>P2x3jkD`BCEeP}rl0F0+(9(K-nF zf&5O@w*bAmkYrBTSvc8LlBRzjnR z{g4Sgo`67dv6V)#!qKs=YC&IDZ@P*jQY-H}iNI~>6e1L9A!55(=6(-NUwHSN4`f5` zO?L5v*CP3!hXitm_~A+jGrs8IMD+%=&YdgbB)=KCF~2@NggF7EP6uU-(zIfB?J!Hs zMSRel@VAJ00*?gzJ_Wu=wUZrxx<&|_qn4QGAHn9vXpf$ACY$;CDH+6{dOcwO&&uE! zl?Iv}K}ilV`Rb(-vbP0b^LuI%Sew11TXjoY&^^6TVn~qoHAOp|zQFXxX~?l}ud_!V zw7B7qeNiFqee_YWjndY`!#7Hij`?95scj~u8mB@;HkL{S2sjA2^TH-(z!RuQ6EwLO z;0PHMFru*1r~LcK40+2tr}#FzzWnj);K+F6t&mQ@{B3h-0hIzln&fP0%?<+1$16Qi zh|E(Bu97AR4Lm4_`&Dc<`Pp1ZL(2zqPhUdIRsfh3#}x(m`HN0l`fLM-XQqOGbUNte z#UatA#6d?cwRC}RO`E%Cwcf|SZ}qhx{_hkHYy_bA9}@`OYQuP#{4YvUNm(C5VopKO zNf%%|x+H`PZlXc>7qZ5rd&@_r?L-4 zxy|a1#IX~(&Z~xQU@5>NKtAj#AS!mko*kTR-Ybkny|{e<`*j+!BRU&iDB zUoyBXXg6KF&$JIa3$t8^ST9M`=WAjMKGXLVBdsnhrd9#5*XR;?9WDI$;s_0RPhm** z*i{>WszP|X#Rb-=EJLixR~x}g_wIr|nXxfyRZTcH_hNBNW~Ff?4fXJVI7`>p>aM?y zZPG`^a^xnNlDP`Ib#^ehVPDP73ka*;`=a90(axeP&UeT)-snK^AHhr3y5G(e zPQ}egt-o(Q*Z&HblV~6i0XdQgvOG*0?|Ti_hiebPkJNrW>^lGtodx_hxV|NbxX-Sl@y*HvebQpU$7r*;1}!7 zipS-1nRgVDGG6mz4zus~h^GuwK0ZUnEVasjrt(dZ{ZedzBz|X|uDj!|Di2$ru3n#h zg>Glx>VY;ibZW|=wF$;1E0}^p@^d_eFWGIXspR$kx#{g&ofhLfGw z1d^^0qiL;`PK}&yE!8X1pP$IMuP$5PGb{#lHI}y1#*@=8bW5>t8eIoj=K z7VsB;_g?+u`X0B%nzW(M`29eDh6qZsg6os54H4{m)&dv4-v_N!g#kW4-BsM}OsRdj z(EcOg^I|zYP?*P!_|HUD1%2aW=Xoch{L7ooRE$K^=K(H5l^S&#bXx*jBp5YSwR(ve zq;X|xI8ggG?v6vf9bKHMy}EKne~t1~1$_7HV0JY7H2Kpdtukqw%IB?u+QFQ*ByO)x zHK9&lTaVyj7*f9+_z?H}#mTM0iw|4GS%D>G1tH~o{cK3L<_g1cEoQ%nW|yj@X73Hf z5BtR@E6EH5JX)$v0Je0Ug&kq*{-*Xs%(L6|d{PT?!6JT=J7Ej!ejKK+LA724Aj`j+ z8z>jA*bqb$SE%ke6nx|7eg!+ZN1Ru^?y>X40$C7;Cjf6#*apB}W_hPeM13G#<|hqx zLwxeEe-kKza)ec)us>}@On_iu`jAHPcUb0s8b~BT_dK{}SG{Pdppa{Xrg32UZL#&yYlw)#Nz-zY{0=M!@T1EvyD6sJi+w5>Ko>j{-15| zai9Zc(>b+P<$uSkcZT{x27}AG;ro9ccl|dU%w}Wat;kO@4E93WfgS(vai9<|l241h z5ZlTB=dv!}6WOtMFl1&sb&O9vprF)Sk-;6P_7S1;K@{eI{)@7PxHBGxe_IGIgFNaY zE;ha}w%SKPfVzYXCRWb?n#)HCaT#?9aftym8EN%JAe~i(6Rpr%DFNcc``pUf`SkPQ z-yA-dZGW3t{39NbY%@ux;EBgmULgEChU-x`8=7JP$kQ$qr4{yw7$T(mL>3E#1?Slg zK*Ys{-?#Pl!U#}}|LZicb_h5U1WH{C4XnTd!ww|zAn@;KS1~-Bi1JCaKh-{~={|3F zPJ>{8iRqw_AVi;W%S}WXz<*~xvK3U@fxX(Eh+>llVV;Cz`v>2M%8;b5MSh44C}y}4 zaR1L0LMM<)ZU=py$#)#S27J7u8`yIBr(_W?SwOqU9PE2tw1(f>1mO{UtYkXs4K9EM z6M=l$m>)xe;P~4Y_x&4Zi%@8R)ynM8CigcYvtlr8+0Hk;KF6~Nsh}m=p8@rLPh{w{ zz&I5bs7pDnMe7{H0d-v$SrTHfZ&%eV@{Sz;4Y2q>Eua6VWfG<`dlB#8zW>tHBR%p_ z&Rf04oh@{tP-r35WMHHh#2sTaWw0*3ANOGRIzlQ$I{^{`hItC}hZ^(#w`E)sWYFwJ z3|EQ$85l7H5r2gh6&~9M@chr5<(S>{KkbX9LbA+4Cm^hN;7r(mBGA$QZ^)Bdv00U1 zK*!QPp!NTH97B6GEB-g<(A$7EU=23SvHssn3_`^HVamb0JX+sl$o}*HpX45(kQC<8 z|Mx!h$AET43xkBbzzh5Tnt+^A{?B?Cf;xsN?(+=EdIK31E8;3uD)R%MzEsi;b)L`p?+g2LE{aXqwi5e4 z$KZ)0CH~KM-$GGr;FUyehSm!CaG?gX8v)$u54~AtLugrBH%0$0m zP9mb)#6h+kn#58sPsN`tM4+>(_WFjEW)mv!ZPUD|yM1Df8H!@9W(c)eLPriG6Zef; zPy*XXQZI5t;Gc7#!xo~cO|R10L6z}>!?Sk*V@m!yxE6)o+0$bE$rBJI_Q=yiz!&I` z869zH&j|dc{AZwu_BZCz2*ikXElUlhtNQY7d4&&UF8rye>J6l?`Nma*>Zxn|T$&xS zr(6lAl>zB>NXbK468F}A4^m5!kEYck-8}$&d*Z;YO1)%!H3t0Wb;3s`2s|juO!E7@ zaSuqWWvmC_EO{S>4bl8-y%J7b+N}qq661OC?@jRsB7RI#q0hkBui7F5g8d!bYn(sxic;ix3K zv6zTKHEuFM{<}0kSfUdHK z{b-s{UhO1Bb>!?++t3oZrKYR&jQuvhj8Cnbfn%vdZpsqAK;hI^w;Z^Y;N`!qbVk0w zrvL?USAQeXPh%)4x^f{IYc62YK(sut$~(eEFDx>N{=7$LC&ZiE`zZZ_<)0^v8*+cU zoZBy9njfLjnuJNcoNu>rndd%yWi-Vs70pr=xWi4jC15hP!oD0I%X=fD&lkzoQ|0Bc zt#h9>r5nJ~J>CSr|~ns)km?HxPL@4d zTXdstVC(wpRx#|w|Kp)C`tJL!6Uok$USXd{D?r8et=rPlNx=b81iAdnoyhUDYT?n* zBtKLI^v_a*;;Y0Gswdsp!)xBFw@0avomqciAuapDGYz3GkOh9e+?c*=5|rN^gH|$$Bfu*e?Hj ze7Jt=-7;xX&hc|(to1$W0i1?4tRMS%2&RtPwq&F50%o2`!0a9>_0bfIV6Igk;|3M- z@J0a5XcHnJ9J+>_J=>Hy-v8aqC0Z@x49alU8Hfn9o=D^;%~kp$31U^5hfG1BI4 zULrX@FrHBOwL)|ALdU!Nt-suva)*a^e{RBBV%hX+& zfT!z=F;q5JUgIsO-sJ}+KB_3i&VwKL71VE8P$@F+UI)p}A=Fpndy zTGt|zN6i^o4M(+uM`zyozW&AB^J>q?lVN1>BUB_ z3`=C29DK6E7IM$I@H^x?3(ojah~HN>s%J7J6It$>-~XQ1Pk!+-TD9mYR}nUpj@a_z zYg1=)N43&=f_|EtU88Fwe_)-pi~nIP4e3UVkw2)SdHZbXk5bMkJ?3LIZMoCFlK|0m zV6Q>G4G-y7Pu)X5_TAS-HYj!9XuA~4y`pJlPha#S4#o@Gy?-yiFX>w#tmGt9CB;cS zNZ-U}#_Rc*=6+#Q7RRs-5TX>fIXIqzM(5*G;Es^BM9>*0+V6OkO*gRZ7bW9eQQRfs zUMH*bt;1Er9Pw*0y~b}^mVL2;=LnwWMp&9rAu6?MXKR}bmP765sZHHetmci(*7)-^ z)N0P!6p~R+VO2%u%_aHqy6sdZz>4qA9!Pd3hsrIo+!tF{250(pZARS}G!xkHZcL{d z`1zgP(84#q2qk;;Dth=FSBI+Vm3C$6YU}W^ZwbHhz6QArb)w_z>spZsV1VWskD@UC z%J()1?~Qh`D~{lzVml$PJ?Xk&%=7 z+E7#r_V@|fMAL2%q3oY=(DxGt!dPn_hW~!n{n8bQ#r3ZRx$Lv#2`$51E553f(5uJ~ zxX-+A#IP85h1A^5zmQiX;~+Z6-f1Vqs2L9TgjgcvE^`1(PR6}s$lk*~=yNMLBoo?<$TAg zGH*FYWKc)C+B$i(%)q2h4EgKqSe2OG7hq0H@2aNM@GXRz|y9i|=q+_f7xU(BVEH zD#bi#(in0JzYkHNNbvB9!ShK><(DZm-^H6mz%8 zUA>G0U-U5it$MqR(q~>R>3cI(L$4RADBKMNPh$S5xw%rg*k$ zyUHe|7*G#_hmkTr!M{A%rB^b06Kcwv>&MvwG5zQp)3A)xv7$0`Fez(&(*-}UC?KI_ z{ZV=Jlc=!Z=XLhTOA~y?P*b;O#3&W)v&ajNvhxf7Sb3t~3Hhjq zKM#SHcpg$LAhsjLGoK1Uv1SMo^KiAUa8$!wzCFuF7wB5%2PGNi9RHR;^e()h}|jjbsYetM#_tkG(6P3&Kw8IU(|oZuzN%>4*466Kr}dv=u{# zBbFFr$y%l}`E3W8vUFl|*z?p#uPRi2PM32X4&mK;0v;$$zd;Xvhs>rPPb*8xlhbgA z_MVR{R&c^2uBS`wY%Ld`xrz~2B}A&bDe&Av(-`t`T_ZMm4j$k4{Kyo7@f&VsrESLF zdEyqC1@5*tlPP-ODC|{Wgo}UV01rtx41=My@e6&BwWTd&~lO5>#(UH7Y1jP;(JItA% z`JLPdEdB|Czy}g<2~~03*=xPqH5Mzm`Oy?zEfo)7i7{O?4IB)cWK*2j4DShbbhatp zJ(}hdO+HT0;hy3RJAA8uv@-CH6Jbe_!keUFg^QX=Ca}ABGy4$~iITDW;Cv&qG7$!& zx#1*HEhmtjiJ(IxAOG7WJK&l5CGPq}xv+{i7`m?N7^!ux1JktT*F_=UlzP#72x7Eu?C3f@eKhYS5%rADH7u(c8)^RY#qI0;(1;W>I z8J&GR?ev+&y6eHj{hn%wJdCthyDxtv?0rveb4#;J=ehojW!@ZT3q2?0AQbMY)jtA(s+5iE!_WJJ!rbR)P<6GN}p;XjX9%-M{Zoo0tj2U2$< ztz{-s;;1t)ay)CMzHgmvIE?>bp3`Z5re59k?=LSHGlZ-M<=a+o+;bHadR&10! zc>~jUd;%_mnABEK()axj?sv}fRKV`t&Ey?g3#Juh+{0Y~3VocGaz#Ht{gNnM>|~dz z_`u`}B*zynODN_vjOTLKe?#nHD^OS+S}I? zk}Et8!b=TAZ}S+{XV1L@Z$N*aTUN$$mQ%@jl!575Z z6|JlkJ$ZEw`2d%Hzk6@q=oSXn-dpc2hGDYlGO?6g3$yuL$jT?80!4;Ss;ZW%v+KO` zEB=D{UV-O;W{883Q}h1nV}Ib^Z8{f8p{7!fj2)uLBDdVZU&yi)NE)M-u@Z05w7hkL<$hhgPK|hr;m5oi&xyL_l5F-v;>56}$Q|Z2`vmK=c$*4j{G+K}Q=;hgQxKO`i+si-ZFE=)ycgEd4 zF5S&qk6_|?_rHe~NY6^v2Ik$? zEOnkSl}Z=&AlIyrrCf~cp?Shd_!CRLk6ut*pp?9WH|bueNV zkR0>~kKQ*E*O*`VptbUP<8#KCN(V0qr z5X<0zaAX^&0$qBMhaZCjR4F^2qqwhrMe5d?&E4S7`N)=vCE#;G*H+p{{84Pv60u7Qy%5PBPU1Z zK1ybl54CkEXRGuzlDo*|K-MbRRbUyy@b2jPi7J`(if63-o)TA0Xd?~-8YL1)8k4G- z%mvg5>8)Nz8b~EN(4UfF4Yez;JI1{e{jia(dYMKTsSX2iPGhZKlNJIWLD{>(sMMp_ z_>Xcnxur4a62DR?QQ*90obEMMtj`r0xNsp8#F0pLqMt!Q3&gx;$D;kC+h&aZcej4F zrh*8HmV~3`2!sC+RE7*&z*~|}c<#;nP5rCSRhN9dXuNiSQ#I^q+`*m6FcDbOUz+sy z83@~#;H%4WMb)xGR{CL`XX8TBc-y!Op~c6uqK6(Aw6TFxNDzG>|5x50kt8@1L0u@2 z<&3s3V|-MvXYsF$^)3xx@H@10zTv0fQGeBGQ+;u_UJ<3!*kK~MV4Og;c(1d|?Jub} zVt8PoOT@cmyX7t%b9aUFct#7iu(q;|6Z;K+o2`gG9f<#$NV>rVw+skXF0`BZ(}Z1 zd`>6iKD%bWiydl$#H?8;tBSTaMW1wDkvKo{=zwIAqTo7%SblHCCkeU;O8f^TTJYeNA)k>75$FG z@RF_c{0_~bcxq3%=qlek5O8H}nYmcy1T49?5!u(shaUN46r;rNyONNd09A>w7ZM(x zg>V&;>i`Tg!Y45NS@7^mDgnE5v(G&t|&ei{5 zr|n_pC)JM5uMdF|HoYb@B@p=6;xP*bxM{vx7T3OnF^sP{!`7*l*_#*Hi7U8V#($Y=X zo8;yXXYysS*=M#je1oc6N=xN71HaNYc8!l( zIGN@kytQnF58-1MR%oiSpE?JE!t-uSR*a9~jdzt?1>%3xF+k%W|qGv>&CB#uS87$G7`YaGYyD^* zibDI-HKQnh4H25FkmQ;2=bajUFhN}54ZNK^{?-9EI7#yN7cQ+ZxJTE zz){?cfNi%4pZR?CK_M<(m_}_U-6aQ1-d1;%hNrf+87z7za)gPPS(Bz^4Hvvg=b3Tw_QcahmdoOMJ`3Z#@G1t?33Nhsn-?^pT zH+eC-!om?hk|=y119KS-d;#rDnf7>W!;dFLv#Y~ivii~M@brA3bz4)Q-Q9zzuOJeR zbr)K`J3g|+-)mQVUP;?;{2bdYM@OjsrcVeL_L*hU=~e>zRtP_}w`n5mQPH&piAOC1 zR<>h^yevFyJ`y;&Eq2&ak0htjYt)3IF=TqGLL62fm|d4FP4R099+pxt_M8nJ`$28m zto{AFT1q2+{+IQHVGnD}ITyPkkL~tLtbUuVI|+0VvBava8V=SlC6ZyH+YWE922`yZ0hU*=ZeZYH9tG6OwMM~`Nyeb)Gq2kjm63TKM5z49y zpmE*vx&F0>PwA58bljr!>-TY0<4sfhno#Gq*xNu?W#%uAtTdlca{9- zG@POTMAG?ZzB`B8cQsdcNE}c0B_%yP5<;UIk+DI|6MT0a;JrHc_uV!}hCPoNj{L{A zFY_7vfO>|*bk{-kDV5hel-i)P)CEEJ9Id(#x6b#Hz_JoJ$sMJ0=BMnp?;b;LRt(+e zF4SB7HyIdToZAy8ys(w$Sla<=zhvp%6)m?W@6y8#0oMw17bgSu-6wJX)<6NG!hr@{ zy9gN{RUH=BibsXq+TRn$2m~R~l3+%*U5D4w*HTs%o7O04 z)m~ShSn(;sC^Q@<_j+{5+b)fIO@F-;dE)3IHFD?1=`*u1P=C`V_aZ<6;ViY z4c#Y-EJX~N7g5ONqZLdfX#|}&3k6;8&3<5+OF76|AT~JUNZxac2>pH~Mkp98;%IY{ z)KBK8u7&25{{!Ek91jOqIxXS>u~0pYK^(v@0L zA#!Zk(eKCXa>$`qG zkFHxX+f`9tS=0LL+R7Jo%3!tpX3yy|;PwBV0Fwj}Ni=kGh7);;Bm@~}2`q5kklXY^ zi7vH;WB7S>LA?3F@1t+ToV35ZZ`8=$7o#U9&NC!WFbu;{6wOCWod$FW&qmPoN&BcL zy7`B9$wo83@>_Q@MTs=Wc zpJ}I1lp~HVDDg}g;`Qyks>Q0d8CNC%X28TOVEki$l>6_gKA!_dzeAVNlQb;}D#v0f z&R@EBiM)NzkVK#>4$KV4_4329q1q-t9RIF{@Gi|C!>fHl7?|>}kCCvZwup6+{m)MJ zoBP3Y9U4ZCcbU%39dO|+0V_}TX|wAeqzi^bhjaF;^RFHP6ndL=?p)J26*9vgr1;#^ z6gem0^$qmOrAvOiZxG*VU;QyvoQlt!<3{|T{wN%4JL7%d2p&66`@5>+1J44RCCRa? z^JAl@6&u{}J|ib!6eTKxEDOA-_G4o%Zpf?+6>U*^L)eJp+bqeB*|WZq+cb-AC@PZ} zBTJ-;QL+phVwJYk&pX+%_P-CWIn)bCbr||SH%G}7fny}c3QVB*lDSX4JaXuU?i{(fmKN(nU9Qd_7%yeYqN~ z>s4AJXq6!Zk`dvqKar%5BTH)LSo_&4O3t!Q=*=5&oRjO3l^HIIlth{&8BV0gNelVU zXMSPLMA@jltl=ebcErnw=T}#9>RcV;vS_t5cl?Lnp0M+cs0u|%rBt3KRf529ltOW) zEgV}c`e`GDV!1(c3&%J7({0`^1+*{%wa>>nS5AQ@ z=X-=eiBIwW?cep>n;UM=1Z?HWZ^CEHtGV$~ZCeM+LPk z(=vF>!6+)Oa+1t(%4M&*zq|c!HVcep^Wsp;G%qJ**n&t$)}fo@n*MJ31Ao+eF$_-; zyvzy`Nh+`qa7QvK>+X&*N!!*$i3gRJgGxOiOPT?`BLBeBL2kd~a~HWif+vu4YBgSu>1WH0Q9P|I zu4(bvgt`e8IZ#5_wxO4!x&OU&H@tl@TGt=4{_+SupLe=TA{BxZ2~y?ch(o_Oo{v7) zdpR=y{;Sm;%cHstVsL^9>e&fWRA9G3leKf>7gO|cOTG8n;eG)a;XA!nLVA^aUunNBzki~k$+^+P&B zeT5Tanh{h^q&SAA<}6=x_}2=y=Tlw}*(0|3G_>NZUKc9-n&)j{=}4^^<+!9b$?y9d zdFVt1Eio8|WeJ+&b-YRyQqlX5aAk+|npPRn9!ZNDOCH zT#^}1bx9(=@|iZPQ;;w zdqe;I)C#4!WF1XXZ|wKqlSs9f#1f<+Ni0pU1g!S@L!*J8Gh(A>){#H=xfLsJj5=8b zTv7!|l0+I;;mPXP{&`O8C&Ctb(#5v@NhB6!T@2it%Vf=?(?0*cSy zrQ3%$@$>&K)1l6RaJ(?bAdPxHz78|~aC+_S_^$o;O;_JorUp;d5B4S%Vr5IT$v;boc_26R1F4-Dab8KIoIzZAkZX*XdSYu|xw z)xsXOux$izM0^!A*+0{4uEEBBthTm2GCzbjz7@)$=LW4lT0;Qsbkynz7L~uWTgzj# z4D+=1w$jc^0ds8z5%$wy)q+$)#Kk8<5w8BaG5os^G`z8zgf7^$94MXkYD=Dd zeyBqGSB27}yIaHB-^Z#i1eV0<@HMlF?YJq%yRGo{v$~9rKT6dbLJLD0~jsifq;J7d3pQV$|Ok*YcWuu3la)7v%al z@;kiwShm^;S{lOcW#MGV=x4BJ+Gy_x!R_-8RSJt0-&cb=btj+tsor&v*b=W%Np%^a zTqwpYH62W|c=(RxvoI1yJQmZa2(Ufi3ek0VPO;z(I@|EGNq#Sq7WqA5XQ1cm1) zsyEl?0lhdD>2-ua(-GAh$j6wfg<&)^(Q(+rJA#4PEO z9*!f&+UeNy_v{S|Rs$VWg(H=KPg~kq&gCodV7^LHuW=1*Q=y=~P8&4tCZY)MnJd3d^tthbCEg{b0 zY?d*ON0)uvp!a*Jkv%LIx#);66z?K(8siwz_pbaECqV~QxH(ZMm$-2JgNx?)a%q4ZOM5Wcpb*1?J4xTYmsFd$P8U(ImU7b9jP74P1}Ux~nCmw}lpZRThJ>F-VHS-cB7ak3Qi| zN{ewj5U9^;N%aeREw z-sc`q?HDQ80?m%5>}Fh}{p<*zH{T}|ozd?!KG=~An%gH^5NkYwO`Jw z_J4rNJd3#00}PmH4ktNI5IIaClAgQo`9TF~@IlTaXNk9xHvKf}ISTe$;`<$>D=Rk- zuHtw5W7hOcaWuL4;T2o|4-9sLq=><;=(LRzd5l*`mQn<%-IoW~HIoJ;!h!I7S_y-o z=gAOZHbSgls#U*HcR*?bn8@$=mvmcv@g9YWp>=5zKa}tGrrs0 zuml`LR6HpO9h7KylNi@1rAY7u+)~+1B>2?ii^C!N%KJVF;XWT_AWEI)=rL{Y|E}hN zjMl)0l+h%bJw=_h?zV7jA; zIAF|axf`^=8u-vrirj%wfucar(+cdkXW$8=IaBIL-#0zG`4q59&#R9plqPA{0H04N z9b9COgQx3&hNIJ{K_hX?OXu0K^wnwS9loo!)|hL>a5ZB-Kk{&A7P*0`4e zT_qT7RpB(e-mZcw@QeXJ6QML5(26q{Z&(!q^F7!ys-r==s^i(*ARX`c!s&)_j`!ox z3-jUi1WLp57VHL+Hkfa4x~oC@5`%PS19`Mn700Wo!F*~MXa8pzjK@q%^sP8Z|Mu|a zj8#kCU6W`qKO^`rRH22??~|bPD1-5Z(>)B*9bnZTlx}A*-m1#-*_K^4reUN? zD9j})M&C9vd1XZf5V&Xik2U%h`Z~uV-;~cmqm$WaEAPJwfB8jk2|fsZQ;u#e6dpi(@rbwKW#lrY!cqx z%QQY&@N5Wwe@NSAYy82k?Hk8v1r<)Uh7C6$lU0F^!kT%W zhu89qL%t@_K#v-Qkzz7WRwTpQo2LUr}q z0qyya6hH^sIJQ1FRPA;B#+TW@X8%`ee(wo!;BGgB1o$3nH9kazk7OqFdt2!H`!BJD z{a&V~(w>jh82aQnY-IxS9^ZsSgjAHcdDfYuWrUjTUl%oDJ_g7pThbAaB88|beuKrWdF$pJt&S#m#}3MkZXY41mq(?KVTgNaukpYeu3np zskU(Wsf?ae!~5qhOKfxs+CQ(7`~;SdV7U_XBcKCNUN)rvfaOeBt_1z{g+*VkM9C$n zcERd_LWCSM0FuijXb%kN9MBh-Pj^A{{C11J+zRGDphrNTfF1!K0P_O056n|AKLFjq z@;qnfsacFFyJ zUK-WMJ;};E#)*W$MA)h=;HbGecKNlH|Yexu4rs5&@`*^;+bpyMbxr@ z+IL#gMU~u&pib=x8n+hjC=kpDEA6_v=&RF7AFu^R~M;jGI{I z^F#MKtxR(iD-g0mu>6x93f@|B?B&d{HPjXU)SB(;dEiLG;Jte13F!i618x=o@ujMM z=F{pS(plmC=S7$_OV-%(ttlvg7Ax31CI_lFd|V742WKFe_JfYU8t=PeVLoz7%QD9* z1gnxV#dh94_NIy0fzr_>#f8eKVxJ<& z1A4#nVEWBt(woKDfw6jU0WIlV#e6EyQy+17|2i@tcRE24`IiDdx(ve$H|I+?Q|ey){bU)*{m7_76W}0xl1pQS4Lvez(^H zd$oE>bKwMVWKqvMln!HxM>IEz8A?RN3_X#eKfTN4+Ls&&e@tEW;sjtWrxPtP(tXPr zUxGu?WFD^JiT;vf^Y+8H$)Cb*uPY8XMY66yX|Xa=5u^gepHnIoDxIcXn|L_s;lvbZ z<*)2Q#6c^I4N+g1L0T;@J5C&Z0+Sj5)2a2SvBpW)0DoYJyHIgSARds{m({KL-mYI~ zW&lf>woil?1kx}@L7OLP_Nb(M0>sh;Tzjx9_wvb;9hB%ql6D~_oHr%hQoo8?VMN!w$~@HuGh7e^30Ns$=K z;5>I2E}c1;CD4Hd;p8;F%%~_T;(W|6){oQW9YNl z(Qzbi-0WDe@K|2<@Th*;fv?DduQ5hkNvT{ zK5Tng*~pGng{u+it7EL+P=ox3R2l}z2!-@LWeEVh8nWxes zBeA%M$+)UJ^AZejBvF3u*z=MwcIQ`sD33(fuv3gEvK%QYjKHj?HW%$J8k!0wZBN9S zW@L*GV6?&SJq;wm1cNnow80vJ<_%vtil??cWNibao(8&#TbiT@f*~oIkZDdhdc_C7 zY|p&bVnSVp8L_) zz@O_0_}m`9TY4t(@Fi&%4amIL^uBMxIRxfSZ*x3NFyTU>Ztj$Cpc|T; zZnSSb?D#)CZPO=n%lzHygodVAK4b+ju+S^!di(*wBPm|>>E1WAD1EnW8qnztML1kV zIm@W7=d}&#$jr58uiRe5-ChH(|S{eHJ6zsz5t*QfZ$`a7OZZ+AQmHLH%t7=8t< z`aR}&t=qv8;uN?8qjPHO6(_q21pn~q!DuVEDB?6nW4H`EPwZ{j6Tdh2Sk z?9e+sRXGg9NSYCOmIl^YEvLWrFtK)Z0#KUHvW8=eML%tHPzpP4gB{Jlj!7CD(72H! znanDt>lvi0>X-yp7^WDcVX}<%pxJ8q)v^6&@z;B2Mlm}-W5MJ04=X7W72l9TA>Sp5 z_+JN>^kIr0sRz5PNkohgP*sA-HzdaIP~Uffeh2lx4oXji-a}apCL;2yDxfF)amO4h z>JW0-505O7aZLN5Fz#qV#JedWOx4tEj~sboZ}?P(KyAiG=s4iNx&zd-3-=Jo8?c(Eaiv0hWoOk@L`JLUEbn=+v^bu zgNAftg~AerZ6Ek7sriW1R7j)%`07eZwIIJg9UsmrzI^bsPDUeA?uU7pS5}4!9=T9~ zhh*kc;f0)BS=k}^+i_rAkB1f>1x)Ob@@mPFiy$eN=#@*C4{Lq$(`~a7&-R(XJIGb% zwBz2-Qf_ZEGRBi{nI;Gdqh*5Q*nD9{Q~b%Sh?DQm)m-`N%C7?=-NE3!nrc^KjQLg< z@iah`haz&D zb<*btsOLry>vvfP=?w|de~%YX-eQ&HhNVP zVp~hB&Y$PZeE{nly(FB#1exL$O5&vci7T&YSHY&VBZSclYJ$^QI<(c~*-3>dPXw<;fPDg7KtH58RHAdomPO-U zI@mI@hzk9LNemqf8^@3Y59f{EXC^rxJG5!}sKYMtB+qaNTPvRI?{FF8qK-uAe|sy9~*oAcS6ymd@3_qam>eNL~{X zt5?@u-*R@Ld?1sZV#yVczuX!4E7b|uRzx!NnQI2}NX1`L7)UuaieA@qN=hXow;GxS z#ru7{>FTP7QRgOD(wytXgLdalEzvu`U}#!g#aWfcNr9vHzO%UN!1l9Jf%LLQqeL*z zrfWI0G-y*Jh)1tT3@1Fm)^iZ~e2WlQ3bYSFScQEWej_P-PqQ{Pd{6i)f}>RT-Y?Br z@aDtJwyB1f55mGj3nlplfuJi+`0~LcF%ET_(;`p3%`yyl5l(HXzwbJu!yK+{zD5NN zZ}LNXe`5BR&HKLBCgZX=<$z1iab`o!E_KBS!5kM8OfbO& z6V7Eh-Gp<4o=juuaQ%oWzt+uuwAHb3N{f>}+s-jMOfy-+dDS20c$#2>2_~GM`@!-9 z*>varkJmToKP9?DFP2eBRVEZkqVPrEa^&?#)|cfmt(t_%@;YTYd%kvb`twhN6JH%K z(LP0TO?S(I0vFB8EGzOjPvUg<5AI1U$~{%)wzZ&+U5+VlVgu(3bM24p-Ukd6b=-8E zF_Ln7lfy=>LXT*25eAE3ka~3_jx2;m|p)Zg$ zcV4q$`I{YYiRDTT!-)bdb0RNs_*>U}`JL;-er3+&f1RaI38?3>LRG(qfp1zp-=TZ7 zwR85%L)+cG6c8S!Ya<}YLUF()s~a?s7dGnrbFUTuq?BD6xG2V}Nq<7e z=de9WK9n8_rMnoU;b^&VbZR*IsgaRC%E@HBX`7{u^SxV7CiFRpb`pqF1jW-l!;v^a zvKVZH{bt8d$Gw-XU&S`g37tRc{Ke-TBl&j)!!DjwNS5J985hgl^TJm^9#0C#7K?tm z3pASW6lhrXTRZ2m5mQIEy+1ozXIdQ-Bq`k#)RL)ylnvws#(!|I*Jl z{fl(NMq(nPQXEMVJOjIm2k=*nCWMKWZ0B2FKGWraZ(2qcSJ8-&3%Upku4Wv!@XL`$ ze*Eh;Yhs1w#Iwd^J0JJ%n(+96G-%F*Y|U{d!q4yv^-vj2Je{66r(+A9o+=wekrQZ^ zloXbu`5gGI{clT)=8+sL7ylh)fJhNlQ;*fZ<#xB_>flH)D$1Qg!@DR_rN@qg2G%A}i~lGBGE z7+R{qzB(WAys~iu7C`BT4Bmez#(e2Y)(6ht$c@%8gYO@TvHlmuSg(gHbpwhkO!OeF zSgqDDK6Y&JzC}IuHUA2%yQiqvzuorX;aMO(cKugdTm8Ic6i5&I^`CpJ-_P9!(rbTm zym8Z4SqsZ$gD%bkB|olf`+l^@FA|}$9EG!-NJT_`0lv3k3{!p=XYcmcHJy70SgPmf zyIfO9j*%o5CkfFd2_D%k3jt+_*SBeLr@6_$ev(*JLmj`NhVq41j9z`Czu&!%DCSyK z@;jsU#oAw5d^pQ+Fp=ZDV2}loABI>y`)c_f*M8rn8KMqC^*`R3+c|@3>P(9zFcVCw}_q}v{_2GcMNt6xpE%&lm${GunE#q}>r)UAWf z!aD!Bam%izcOth!6h*Tn!EiiIU>L{X@K)$!Z8D5UEHSLJ#XiT1=eB@e7@@CI38{@3 zM#d?TQwdDOuHU&haOkK%9gx)ugxUB_8eW(-LT+-=0egDVJ zzkaiABrTRtbd04j5+iwzm-#ajz45`0PU_t# z`sFoN#3dSMRRzbGYc3);Hmh(=?%^0$hCFA_3G1Ht_83srb%~;?qeMZ)xS;=UTzxHI zU+_oQEt%jbdkTVVt~4Mg@ZFRe()s4sQog9g!)6)gJTz;;+`mugFwhDr1Xdyig%eat zrr>xvWkLsH2!}a;Zo+ZF&(2T zoPl)1aRN)IGAmIu)uqptYo42OeO8+~eiqcWhVns6J)@kvmT6%}*?MQ~h2 zH&C!%swr}?MmvdzhR(@7-Wjb_3oW%wFyPNPw@*0}^hfc?J>4WRksPqHVr+$xm$wMR z#-i{T=Z!aAx5Kpr8DfCrS(3$Rnqg>~ZZw;&!WE=pANfHLd71Ts+oa& zn^d*;(f(0f&KJd(?)$zk+GP!nu>_7&1VhjyO-Hz_S)yrvzeGla6P%we`0j}@J)5D5 zU^$-PS&qgrnq?#QAcM3G_h*O#1m~_z2c93_ts{C5QZf7jhKY(2Ji5kItogew2Q6Ry zb}CrR790uhx9N8##x>Eb7SZ|miODZ5`vTDZBYL|K9CL}@V3a$*-S73}dWuT|{vNqp z!2?52r^f0;q=mMx{88(b8?>VYOzXC_+IwGXzdxh31a&??+n{L)7RfoOV8o;knJ7+1 zD)uuiQlu(~3NG@($)5a-;g_^eYgxaE9U;6c3&NVGAOS<}kAJ zn|HXAKi!KC_Jm=qA~2Z1sS)91fmfGXHT;y}&bGjLUwWfyNiU+0e_5WPNDSwBil*sE zep94HA$kT;M1gbRTMLpO92`x3uhJYz;R+pegaWJYDXkespnqxQ-YIKd==8*EC#*4xAx)f!gXUZ8*w1C*bTm zquZ>tzX0QtF3Bi0B#paGc-7B%!cSQC`_TXmg@>YvRim6HG9{gm4%GVHhr} zp>mto@&2f*V03oTA4;biq|5V~=FC2@a%39^QplrmLZxL6qZL7dRSfmDT~~{kSGDQR z$@_<2d+?Z!_MDNZVTlFplYWMidTCBd;SltWm?IdPB{7^Nf@+|M zFqW4mg$gM}o>i4P?_Pdr!_?2BO6P(i$dn|B4A1Z=>D(5_gwdDI?{My4lJ}wTTa4OQ zNunf$VK{=}k=oaTh_hn&`N8Dcp?XhR&E$jDzRq>_?YwwcyUU{=cZrNhkc_PGI6=V2 zU89zDHkbnUI@{fM&xx1o0l}`gW2D4R5>&_`)foqUIpvYp$4Xo8`D2(nWimp)VRTut zTMZ3WH_Q16R%m|!0Jhh>&;ZphR>XlvywB@eJ8KJK^*)(lmTSUUpm*?lWhH>>mK^GO ze&;$rI@{%%^Uo~T1QSd!`I*^H6HG9{gz5*Nzw3nHXPuxl&@gJnpeZVc| zwutt0s^xx8hH$Ly2lZ@+z%W#oKFz3pE#mw>B~JYUDn76kf#YqLwT9@hrsM}x&M?6Q z6HG9{1QSd!!Gx*V@BDir+wJS7;H&R^gz&J8-&nkE$bbAI6ZuRppX4P1kI zzg;*sg7Rw^3!D$m{MI+I6tweAy{MBk88T1G@fH;efsxA8Q30PK6b-KsyS#VKr|s4@ z>Ri9NZq)tIB2*E#b~8)6K3(Q$!i4GpKnV++KR(!$JbDNi3F?QD#axB%Y05}N^7`cA z#s1y}1$VS)baNUABKvWi*o4Z#Gf2j=)cNg;!>pIR4J;ZPq(uy)>T$`UAgUP8@`}pO zfAp;csp+otR@J0yXjRL7(aI!;a|$}wk$Mb4Jg=#Ee_bp2{*YSk7a+uRM9i~f==YA$ za|_~o)O>AK#rG^w`N;<3np{O}sU+PbAo5ABp?W14yl0e-wV$iYgwBk!X+mv4`wGV@ zdS=E}%QELpm*-z6bp_5xM%0fKg(;c{ZHWeZ{lV>zC`eOsiVpnH`jr)y_v4hL%!ooJ zm|%hlCYVrnp{kdX(rW3unvy3jR5>LLkxn!F#{?5hsAecXoO{%{=I+;inA8}AzR>Yf z==KD>;J}b1Tym*LC~`|f+)AN5D&X}g{;yuj6qA?MPXP`K8&csI1;B?$O9)qYD<5@k zysdwi#xFl3?yoQBxW zo&B?J+xkR9Fgc$^IO-}C0(nBQOOOPH;$)H$2%JcI?!M;-6{KNM=6n7;JB{k`r+yms z90jY_+2Z;gq@Sr|y+5*6?K`+WXhG#==iCm%+V9+n_Cd#U96^v6!;%jXbR<%YEW)+jOAbG&^U@Pl@Qi{ zuQ`3+jXl2kaj+HIp}!Rh#d}~GfglA*CKdHW;pA;sZJCsSiuG47#+stuYZzxfsP8Q? zZhc;JTAn#E{+s)N%s3}ZsALEHbYOfi_&_F<9TsDCuqvj@xzgLtJsAfVNj|+0%0(`m z!eBq1@1&!$de6C_bllE|+USHh3vRlBPjlC3KO2lw1Mj;5oQ^T3n$_~Y^ZDFm7vKL! zTBKG*mRtl$FCW(Wsw1)HtTk(=LoO#C-$2rNp)Szc8|LXbH)1lDIuLX0w3i{>)y`w;F8r6Y^}$SFSe zRsWY~_R*dcL^fA*j!u%UVL~uuhuf1sDj_P!RJJwX z8|EC{n;D_RhnRaxjP^|_NYDB^oNex%xhvzjGAE-}y|I#6oT3$przDnQmk5iVCPyZ= zuO>ROA^38<_-sQ(u9*_nzManB?w$JDpfZI~1$I>4#T?D^48@C*C`YiT4t?~8Z>RHm ztowhnOBzOMCo9H!q=W)Y=a0k2K7F_?QXPqj5=l$6Dv}Zo z`)WQBcX7L&T;D4^bakXMP4ShwR7MnKhQLTsq6a#^04U?CE?DcP)wq9Q4 zve(*2Tm5K`aOHSC!GNEEoB~CfqWDnDU|P)M{hf1%x6MO+dV%q7fY_)8V@9_w-Z+GnE1 zE*4dhp(K%ESX@7L@m+UB$C2e8bGF#@{KOw-wT#q3R#;j{h7u)7pcs*q7TzYlfVFr# z39!$&ux?EqDNWomrg8t?nD!u%xtH0n7F#}VXVbnWnXLaeOzq_e>#1&xaHaWE!U zq^QTcLgzBM9$A?&&|Bh>{jO<3VTm$CkOKbwC%&RPKk&4r4MLe=fs_@t^K3I;J~RJt zrZ+yczHwrdNSO@BaSYCo6oWBbM4*JEw9p{jY(o`s%9%BM>6|&6J4NnW%WsiVxXY*H zyZr&hmlM3#n(J`~h7=0iBcLyIQKAPW%7@w3e8 z>Vd?(ClA#Nyz_h1O`f7q1Ws@S&WJd?$=mx?s6RgG5+auL=WV*_$fA?-o@%{&a3&FT z5mgz9QYfBKMOo2ZL|0p*Z{c@W=3-eL(Xv=rcvBWr1WHv@1rrMn{{G(wEu7XA zgnqlS)7fTeoHz9J;yX4b#bRQ@vmA$$f+Px(Xk=oN7^en25%FETnu~bfN68tD^X?k@ zKJnF4K*KgEF><}4F)ZYNVSvBXBjpAZpAhi+MidI3#3Ma+p1l4*`kZr2BuGBX`Be65 zWp&l3rrt}grhcjFdq!7Ft2&OA&A+-+xc^ODM_Nvvysh#qZTA0i`wd_(U5vJAmnc%AC7K{Hfm>y{E^p`W#VK8&g4Ukp=!$Up zsI<|0D0nxkdg}}4huZAgEN$}^J#wUT`wx&BU=rsU5+`W_XK9+@B2G-|L((8D9k$JD zm%!iJY{wH&bYd}ET0i>>@~f9BLs^{j*-=(O#H0|`N6*%Ex}+|+U4E(`rz+6 z;9!EzK~GRs5@e3TSe25wcXRM{r-p6M0zG!Pnr^lljm!UMLLKS#n%ghf^;FpJ3s^^zOwHwn9H5H97U=KN>dOEAQlu+ zq}WhFks>w}6|exJVgY}GD2Skdf_z`Nw|AFKHZQrxp_zX+`|{q_+1Z)d*%{x}J6f)s z0{13NyzpXc9#RCN%BYekD{N-w@iorv*CoMo)3-1ax3PkY;H^qtRQ)RHJN#lKtsQsI zoAmUfaFd%KdXrOFk)RYH5`_5S(eK~6Chxs;xP5Nb(<78QWWIzYWDVe*>#^so=G*^?Fj75BiM+^@fEQI37pcAn zre8fGe6O-$jJayc!Q&ohy~>rfbItBnVW_lJ;JgitFLa6oOVgAhN(>9k^Ox-XpbVKF zt^}qi?9gBQ9eZ(Buqv1T_?YSJ<#+nC}}Imq27&*k$UV*2v?1E~P@lzqEkgs@$$ zgWmZ>z2h4ERbYmtd66O+iYG{l3c6wQyKV_NtQM|{Zq|l_r+zg!4b2~ zgG&#Rz;OgEsIj zf}$DozQxqnT9kj?lo`kH(g8sWydZLfNYEU|1kuJd`ewcxV;s}hx_i_$FaO>=oJ}Xk z5duNdl*rL4YSRg?I&?wO;BoF}o%+-BbHDM1=t}9-vW!gdKqVASl~8`Wp5atltzR~} z?xS0Oz#4azwedlXyBbF+d7Djk>j$2WNy~opcnI8M|Q8VKu|ZFzp{za}+QU2Ul}QasZ7)hXLec^$%G1P?fh0U`?&BFIA8 zVxZtIt!b3?zLb=oKL4~4jxew!Necuk&@9hTK}v(~^b1*^U4cV6$$Ht6HLJfI1liHO zrjR`Th5MR>D6pia^1MQElSf}Wjs0?1Y6GO{4Up7?$yXHWSMYtDmQk`U8$Ei)+Vrqy zlOjhlBE_nrCh(})WL>1tI+%R$Tr1X_hB>zW=7aj19$}R5JgcaH6j+7U_P=y)c)PxA zJZh=l@**v>mY96Ya<;F$5m8%2tXFnfvoap|?un^k?M49!fF=-72DkaZCJ{uX77#+{_nwoGunVp$fRfDryhT zKGRY|`hTg;UkqXpgBZjh1~G_13@#4;XhGy_RPMH5#+>f zJbNnMY>=iIULgI(DkQ@T0(#EdT=y$I3@N(w>~f+t{m#O)x!=H>5M?bOOU~(ejujN3 zQ36AdLZ#La+eKUT2y(7(_PG+Y$l)^++2*66RBs18c-`~uM3rJ1Ka|nJ9+htF8(XgzI zW(Vphend=R9!m?%4@UCYNPa33?rbuWACKf2B!3KX8}bQ~-;U%9k$eu4?||g@Bc7ag zA^A^`{AMJ75HZo2gybnCKMSE@oyo@>#$c^K6>lbc&8r>L(&96lf1~$ zA{(S+JshtGfWRsT=UKn$kmBsjMB-?iS2#tK1X-pC!pPA$>aEtuGORY{J5z+;mG^yo zK$H2_y2%5dZiLrx(;PvtBrgaI!-@n=p@TnC4}-fNdwKdn>qOt{iEb&({1O6B`-4}o z1j(^PkdF*~UeR!srBy7pI(L50JaUfLpJ1J+aRlMCNj}#Y&}SS_iev5dl9XJayHc|k z`3j1B83(+{Me{aV;Kej}&lpWjrpTUGHIxp-66?DUf8B5B?O}osv5Y_qf*>#~#Zf^Q z(=@I)y&OB8k6Bl|(KYoz*D%hX{g>fIKnbL#(lj8>ShD{7rRBt0i9s7}bb!a=$E}yO z`SFK_2k}@;)3iu31SgU#ONT%)b8&$~u^uFrTA!OFq-D-kDogZrOeMO#qt! zq#@Ktn2fmzk-c9(Lcf1?qx27mzK_4l`;67QYsgNbGraway+_Fqv@<*3>l@&8x_#&Y zVG3qRKVw~fNBR?)Q?Q;VR9azFlHz1mlu*wT=~0uYXRQ-=HGauE1xCCI#=KFuiD<~t=I1^At3W+2c}<{qiw_r>nE8b+JGkqngVyPV6U5;=p5v~HVVAX zu`XY3R*}c&$^*qA%q91QNKr%mQN1p_EiXh1{!OnZJmiP0OPuIRG!VyYC_wWc^ zJ4A|gZS?XU)<7^I%y}8ZKjU+?{n2#@M-*eWK zQQh;-H44AOi3|`KfnWg1YV(KRo%E=E6dIk^)JgZ8_i}D=n)6V3ces_-jc=M~{(S{> zdRkzL0AVlOcfQ~i(|_qXabfm*j~(LT5n0X@|DjH2KCEnRGTr&}(V=1Eppvvqv%E+Wsw$!sa%V>9qAjKut#1MYUS%mLZ zHl$ZkId~QTc|eB0X|45oe&+d&EAZ?+!&59t`UN(YrMMtZ+3)G>{Y)&M`-*kTq@{Ol znFoC~{ALcZJ=2WSRhSJVuafJKyi#7_h4@pl8{9tf$aB5d%86Z}bcl3qk-_G7AM!j7 z^E_Jk93djt>G`vI9>#WG4FA{DNA>h!q#Y!tUG*!ey)_+5@#R%(zwSw$p7<#OFZ%*3 zup&@oh2gPY_GhJD+>W93VJjtiz zW`V*Yx6dZ!0kxkuzo?+luC3qv&nM4+C&jfwbX|)KT6n*n&NetMO1#9E!Y1qDH#cNz z+ZqJZX1D5fih_S+f!8IuU3UP~KVY2UI@@ghWntTQo+!k7nk8A0XGu=v37)3-AVuAR zPQ??R(h66_7Hfw|zg~G)PGoUE1yZ8`2^dADSv2nF){Ye$(~5o{DNIO>?EeX=dr zPiQ1bD?nn1Dw?QiS=Nv~%&b)lJ$PKa!fa20zz&q{wm*4cq)__GP{!&bP^D$d#(XrHk)OhrESnb!Z z8={_XjN5TVMqmk^q&R_LgwR*dH)D7Q1 zh}U0Y{YnH^s|A_Q5X{+_5MU|T!qx`?L z>0eoAjoonDfves7AOBF*DeWm#hwMPfxD#t**XYd?Dy z>D-{qTT(d{*|5zAtnst1I<%y9_&ACLEdfTQ8I1wlx;h6}|CF#K9<4bJS>qEU6c%uT zuEq4DwdmWc-kp3ZB11iiqbVNntV93?HPpM`9=ht8%?}wt_(yBE zFQ44fcv(p#KD?rrlVt@E1VsRfe`NG8_SYINj&Fyl-e-`nEWp)M(fYepQv14@B30!6 zWc}ftC1PJ|YeWGXsK^zaz@xbH3tgU^zC|9zZ?3SbEj^&|M~~Nnr<`puY3m5nifU{j zS^3F2>b(!H_~C}S!BU~#AD74FbFw7S`q_uwS>f%n%e3J?Z_m-4Ugc~TFA}D|(8G&uNqTitx`Zr61q5q!t>UHb=+@Xhj z7>!VhEvE9lzfpfMATE$;>y#YX3`YBXUH$z-`X1TD96Dc7sOl)(M1=2MYrqPem>oVJ zgUI0YWz{>6kpgNQIlPkleX&C)P?+bj$Qg zt`)&OTxTAZeioP-(LuOgJbJzWY1g-ti_?4c0!}xG?EfhVfoVy72tL@&RXcCFm9#K2 z%K6=z3f9{%u44UYonVSV6$0L3^o+Ipi;ioo*Fvtof3Un(Ht3y35sWDEf+Voikc54^ z>y4RjLWAgLBE%l$t#BSrezpWMswEobxX_y*T26h;^EgaBCQSR1k@qqq+`KYPq1$gP zPW)wM&%9dnVNv5?ji5J2(YydSy__m?tgML~tq^rv-+1`*HJ2yBRr5t$V2Z*Qagn0K zS?k`hYoEKiUMo}J-=XfWC^E%LG9wDKEWcsj)9#U-?<6(F^tBa6du=j!ZUn6i#C|qW+?psHw|1@EQ zpk!oq!jssmBcu*8n2F6^jrwuT|LZU&x@N_BO_POIx zS55|;*+ua|IIUkNplPQ~@_O^fx;#1AK1p`DUB2;7B|opA$Omq6slWr&gCC{3ri@}U z=3(Zuk->a7!i?u2y@LY{o<|F{B4wW5DsJgyJ%#y(Iw`nTKN8K*5%6Jy^8QW?Od&`} z`bc76IsnlTK4s9K4hlr6b1EH zUGO@jmzj_hm=CTp$+nj1vgF0P;N}tHSWgdnNs`VipaC!ND4>s~0mWD7EbxLt;2i@B zcg)SW^Pi70Q{dU$Yx*X-;i15E(ag#n`_hhN*gjtL^T{n??vk^-AkZXFF#^p~Y*766 z#_>i~WZM2(_Qjdi&BHpsm1v1zWd_hdBXQ1e&tkRhb!|PiEPh@40-isuFoooMT^_|< zq=G^xP4hfS0a9dW;(vc|1+BW%X)wI8V^?TI?EaZK;QyzP_u&69#lgpQOi}$`+RLW4 z7?g> zy9I9s#!)m&k|f8I6io;$ia6Wm7N$-z&>V%f{UwA7_0A7I;Iga2+PUHk3$j`xb z2&oCKqO!Bd_aqjV{#wV-sS$k8 zI1pXFZ4Y{-rN!{W|5V+EBwZ z@*)#|*ek-un5N%7ZU-Fy`60(&28uvXEFlRh{oSfg+n?#Wt;9UykfnNBj6bTSY-{AW zC)Ov9J>Il!*%8s21Oi?kb|`!hp}gVJ%l#@2oFRB_pe zaJPLmP2do7Yix<9?0X+@g{y~TIG)!yL8dqst!GUiOUN{M zFuU1?%R@Ka(F%)*lBTjMqX;Z30u&L?MeQcyT3c4%_iRV5fTv3Y&P@cxGtNQoK6x&m z|7sM-bNRABjw|0Yz~l3}JcTahzVEMDKa^>0X%i(CY#f5tlodscoJz-I^63Woc+CIR zMg1+N9=7#u<~;b~vrX~q#&5Gg$TTU_k|0n@;;s?X8?RcD5oe$VLF?*a`)63v=-*OW zhgXdjn~n*r#L^lN{ZRyFE&1WC#bI=#sYr_ZKUzpFr@kvvNQhWB+S5jVb<%2Cc3?#*l9e&5mL!RmNtKj%<@m~17EtZxrQZmNWPD&6gBa8d)b4G2YwxL^ zC!fRXSVWPhSb-B*iVx*}(X+OgEUIg8EK?ubr*Ct!u4oV0?=sevKCh$z$m(*=Y>+2; zd@jX*?XJJ%QQdxVWMrN66BbzZ8zcJ$q^2@^bJ-07CBZEaCs=(t!FT)H`VQGWdF#6H z_mf#5Ya}NMG*E&r5qHK8?hUr_ou4?eaYvXd^9)bWB*C&Q&xkw|6ul|VX#NJ;lCS)q zGVJjd!Frcc1s0>IgvMwB$FYL5(49ZF{=v4i|L!8((OM!Y3M~!s$Yxu1*TT0(FNAM) z2tLorBWc>{z2Mg&+nw*r_T_uQ2OrPRdi1?J67NI`?o&gZF=SLzJ#Aw?%`X5t3~?1? zTv4c3N#$IZ5{S3B`I{RhPxqJQzdiq@1m1#~Uwv=0U|L$LVRX6*FSv~9Gp-=t z>nlX{OpP%zZD}pTZ7DDP(zJI$csFYj$*K%3ON=O^o56H!(N0^_B=@tqHoQ)W6)BG3 zIZhz`TSAaw|K;h$H1*UnTotNqV#dlPOTX)i-^-vXmT_M29N7zAQ*-5edb{H}Mx z-Ca9p#!1VXZJlHH9mgh#_yd4YH|qzvJfNpX?UgHeCB+B4cFmDr=<+uw>p;6Pn+AAF zZepiZ5QQ{bOJT*yt)I8YBM_={b9(&9)GOEJRv+A#C0x5QrGya~wxFef`e3xpKFvlC zB3gvd6qW4EfTv}VAViIor3*Xr;hSggYqV-fiQW+B`S}cHB1YTVW&ZNTkt<3f2ZXAX zW=W1DRf1Oq6&E?cj-)0`ei6cXp=M1D40h1k2GT3)?Gx3h;Dac5Y;m7&PHK?_hhlH2 zAPNkxO0uAcKm!UK3e*NJZ*yRZ0{Yx2n8Mu~0`sVeFKV@gUb4L&$)hIu=)HT?wEt>@ zd`E*kszO99<&4}WqSiL3Rbw54yb%ha`Sg;xD6owj{nd$m&0z~>mRTLevWS<}SXK}u zn)5p-r)M7Cziw-Zn)t-A&?$sr$jwCyf}Db>e0j5}$ksI_{ju+c|f&H2ro8wSn_FBCDn!fO=8QM@Rj8-*EL_T9GD-*qzI@@ZJQ$ZE9T zQJ*A`f*g`Ajxk7s>z!@8r}^=X1>Iqv4u$NKOz~0K@(qE{agd%Ey8MK=!2Urm zIMAs>Jklb5PNlDx1-8x~ zwyksdfv{qNCTRX>&nl3)ATgmwb6n96+Lmnm^Ua%Yhw~@|yZgywIj-2XcDDa;FK3Wc z=o{cs!8p6tOLDtE|K`lkn{s)Jf;7J&1|v{T3O!6h^KEo1@LBrq2?;q&n<4Kv!<18A zh%aZpvY*Ez<7zUoXItjwHm2EGdL6Y7EV56dGb>8D?i+X0YSK z|Cd$KG0`1y)l)9s8|Y%&__4k}j{i*~9IkN`0f#p~&s7Mlz?Yk^`pFz>m6ALw(rI2| z`*804%EQCLd9vjQk|9No5D1pyQB91~pbCrWQQO}ger%QddQ&_mX&xu1Q9zXiiPM0x z{>-*xe?0J0GOAYS4h8w7;4zK_8b|oNaSdCQxFi2ZZR++TDP!M-LZ~CGk6>Qn2?da{ z$|$1y`D4u1z8kP+U`BqSt4k#xZ95VxS$v3#a3;*ACu|Srw&aJ;YKLFVc{D2)FJS+g zl_ll2p57e4b6nI`CNW|vz0_9wo^IMtZ@`r@6YmFz6nRdNNm3wqI-#L=T%)6NlHhS2 zXAnn*OR1dcjJ6#3KKfm><56;r5_4wgF3SFn@3h zR9x*eg`P=0$d5)*8l$NsD~fEFf#CSmFU_@Zcj}KMgnxddoW_nbEfsqoza771oA>t@ z@87h)EuOBHwS}ZLk(Nb)rhv$?`M=Nh8-&0<;Vv|HG9;#JZCjVqC(bT_yH%MuVXETf z8LIG|EN5YVZ+c}N;cqh=HR%=GdEd^i_0rnnwb{jDo!2)IjCU%c#E2TrtC}2S>(c>S z$s4w`IR|Dod=6GJE|gkZk_ny_R9PTo5uP?V)!7R>Y)NhTdx;a)imtv62!@z3M8#~{ zU^{Yn;q-@IYKq4gzi(-c0Su=JfR?0Jx-Wm(ns#@I@#;z4GW|CyGJ;hqB5krMe=;*} zsvGuZBv2ThP=TZgDv92VJUgg3?WN~WnF8}@m78o=?s)O3FCK3a+}Y9!RasP2iexyB zVP`H|{nPPP4bos=6ZDH{f9sB);e0L549o{3$6L0Sb2giIKLh=lgs@W9&G=cuIoJyT zG~^%al?s}i+4l9FUreY4?UdVK^ia?pHF_TtVq!Y7Pg0ojTA&(cgS}`t@*awnI8R?) zko=XH|C0^m9rPx`MGoL}5%pJFZ9PXmlKsO8xSU&Mgj&v&N^mMesf55ue|LOvOP`_Z z6ZRuC0_U{}Zk7j4fhdoXnBMy2_cRBp;P)U%^kGuy?{RYoPcY$~W_Y@kjRXc5fcgI4 z)~|Zzz_kX;L66wKNAlr*IMfB zZ)G48jxkqH`Ij0VT`c^$)bQp~r4N@180l)NdVV8?s=lfgsc!s)o<0Y~9lgKf;v@Ut z2%u)1BA1x1sTafe&lH%qy0l{Z8(hsy9u>Lcc^>7QO9;5fp zVBR+&^B#}k*WwYGt`Vl3=E#1w7Foz}e}ni3_cL@Kdk(Xoq4eei(yxjvvH%f^OaDE{t)dkg%rA>bSw)rtDKO5V&$ z<^6YT`EM>gaCfs1R>6fnDW^-3sHn2c6C6piVovVDi3?KmQs9(4*)A}>Sx4#qEpgSo zXY01@Ovc)y*yw#EMYFu$hcC^sBo|~h@u_Z@4zOO8w%J-7zO(Vx-=G3xSV(=O26$Fe zWFU!xykYab1?^u?F43L3$LkB*9azWhNRXLbgN+9F1KXwp3wGb%E3A#5M3NfE145N) z5!Dsr)q$VbTHeriPfk+FC5=!w6BL$KNt&iOTG4QqG;aMSFg2M%=Z$V8Y%$037b9;r z?XVqK&CH)pz+;2oLZ3~*11v#O1&volKyq@6Hrro2I`w~XjScjpM$e5P((lSEGFc*~ z#XD`|hDqI;Zw`MgfoDX(ksQZ~6p60M=dh$_m+iX0T77Zn#FFa)A;WCa3Q^$x1o<-KLuTar7+bchn^!+g6px4(CZg#xc@y5z4vVQlso2sNwUo9iAN8Znd> z(_Y)#?~Z!RwGrYSkJa!+a4dhp2+lIFD0@B4P-^&C_+)&?-)Ay_f-wj&{Jzm#N+>+ZBW7Ny&K(l zvu%Rm2>-=RN~8%w2#OxwPnUN8#8mN}?Wfk)|1s|wh@7{EL{5^`2!dBNf+XozroYgC z!lCu0fdq9nZnK{DMI58QrRV$Cja0<@-u72HW(X zKUwlX5~`6gVe-+!!7AD%%$4*V-umwcTlb6~pM76~)7&^Q)HF*PB?zjZ6ok zhT(#(D;uBq)!JbLzB4CR(k2(o9~`hvdn#o?+Fz}Mn^4y5)E`KTrDcZ2suCAk0#lPI zbl%9g6uxgU{baj!ook=>negG|d4&>ai4!Hj$(}E@cMBeUvn1riz~zDXUw>X;+86o1 zB>qo1hw~@fj~iDGynlAGaQFSh@!UXggWHJn#& z6^JVBzg|I+LEgVRw+0umGaX6Mntri0ZTELUy^{Mshmu0|fsz=R(+H9fSn;0GPYpZS zWVPi~6%oAZiaEJ3#T0`O5NUj^%u1^7lcdURf7~TH?kj7ki)nL||38YXt0PH)^Ac%4 z?Mad0iMfjV9rOGhgMTyNj|_jcO?^3K)U-!Sbb29=^+Zk4XoVJNfs}{1r(Ai2Br@Q2 zn#VE&70cIPq+2MQjYNb~$a_6s&hns^cSeNeD|ITjo`hXbJoteh1%& z<-vO3cWAx1@8JLF^EowcP5x%<^=X5|#JZ7Pzm`dk1Pm=}3c+HpUt=^r@fAE1j;~@f z@cHKO&(Z&}?;F%}d$jF}crK6IrjYME+c_PcTlT=YO!E4igvwKbtSYLc^8OIt2dD3z zb9v@(8SPB}F0fZ|`K8*QleRf8B)^leC47h?Qj$4|mlahdl$_j!=}rGS3%wWy87e1W z>-y7{F=gq(K@W$uhhqempa_}O6f(%?-GGhpA&ZVPownVY`{ePXCRTqEB6^_igHmwz z$_2`8z}r_+Bo+9hm1@AJAW@?-|>U45&(4Q9)kb==*5Phwn%F zozZ*MZN&n@kh-$e|Fo^ysV}cuIS+f-O_8*$2nr=ol7b#~L&bXvMjscv+D!JhhBWwW zodQ=|Zm8A9s{$n`swh&Dc>agQi~O-aVHMx&ZZuogE3Rxp-QqO$Xhn&R#lD;NjqNx; zqG)9@$w-pqkGBd0qiAJ^ItQk>|Dy%mcb)534ho^=;CIV7`~GQ@v?2F|wcKC{R?|qC zRRjrywA^r|1g0p!=UozTQy6b|-o|g5l@XCu0j-ggKr(_N03Nj}K)3FSZsk?Q3Zz%k zcX+2(3HD#MzB?~_S6H{!8X!oHqezLOsE}^08G{s^N{cDcJ~Hp@Ezfs}Fz_<3aWq44 z1Vaf78h9DKs1R+q%0zp1YvRo}KMp$t97#(o>36Fr(jkXn=Ck3`;324GAAWxQ?p0Ud zV=>bLMQ{Ww3M|8tOwh%I$@=hqhOrK=We2mHUVGKy$OION;aHaN$M6ub{-S=x4CSvC z#PeUPc(+__@O*WI?`Ij*(-*C)l94-Fdnr z?}S7gajk1lPMurtClLNfO;%Wy2DC~`G=Xx;^{{(yeS4E#!!E1;@)i1%G8bfW6wM1@ zFf~eGo!J*+xDv#h(@+RSNIuod1d+r_A~WgIzhDzD;pwPzn#`PFjqD8`&pei7oNkv7cqMl>@c4T>2Kc1`hvb#=3MZWUW%$8y<1--d z7VT}@E|}*=<)l^8s>2U^EBlR;KDzUoH^MqmA_+;MNmk=njzyg)wYEgI0hdlkYkTJw zFE#zTHr_^<5?D?YIg%GeQlNrtgrQ&-kFBDUy~&0rkM>*&-6ajfx=W&*gA4hr0;QMR z1w1~dBDviMKfJerf3o2sTK{8UTGL z&#GE5($1bI!V_Bbf1~&REucJLu9k9fZ2Xn>GaI&%Nj>0Jli4JAt6>B^_(HGzyn_59 zk2+Lx7lFa~gC%bc@ExD?LhIajKTK{>&LjNRRdn=^GN6!qIa9+NowTT-rVkBjSi7pK zIY!RWH&xOCZ4_ZS*%6ipEqW~=|`_|`Nzu~iJ^FDkVEm-GYjao3u|%gI+F>3N;t{9%bqzBH2a z;xO{kJIKNtt>?#7aDP5r#r$eqHSrCW2rZxvm$cXGDm(ur`8>ZvDW{yTj=${I$LT8{+nn}54Lgsx z!4hjn3}O(27{tJMzl#+58^~#Jw~REHS$Xw-sDX2j*j4dLmH+2gQM)eHX)>3yKNkxx z`~OSuIJKPknruKXOc=U#vDf3jg^b14OKg3_)<+Ct5Q7-hJbBM-y4v3Up7wi=+#b%$ zp-70LED@Zn2|ViM@VBJUDO!`oMA+}!y|*XUoLRH^+hog3b+FB^|u)gG37ah4ET zFU;ef8$ri%?n&r(_{+P3eOG~bxHd1WHxQ9fSw&?1H+Yqh-ay{%7?`2}pC74@Ljci! zEBjZ@pbubBl3-sm7eZXJBtN|SSHJrGnM21TKjB> ze)F3loHohRD_;d_7Le3Jr|j~mE>BLjJKxtjMOx$!$qCt)J9Y8AeSUFzRwsFGD%$p4 zYj5}DyJwzBZH4TNL4MEjfKy>LN>vz{5(MyueNVeb!uY_+Fla$WIY&05lFKtH2aokV z?6-W`rfK5UaO8HHLXMo~@~BQuCN)x#B#G8I_SYsq%^JVmjJxsEE4iah@M0P zjUyh3UiQx#q|8{fsw1L13%=$q`mee7EO0rC3RKAlvOQ8kVQ#*!;mvQ1+C0FMkWfxG zx^x5YZ9kHu@E@+Y9KQ$n-K5HjKbDigsf2Xlndi166NsswkMcq`?2<2LhJ+jHlWiwmUQ-#+-_M?ZtJ6IK=BpA z2n0?Q4d^gB3VaQXpMC9rPJjK?LkqC3QYczvc$ODLo}_t}LmfT1bgKG4EET=MKI4Yk zA7yfpIxnJWf@c^}Bm}<6^8^03?(owoi4k(f#w7hld$*Ij+oq&73EpxF^?B%0L~yJ| zP&_F}nmWAct^@uiK>TTo6di58v?)XEi$`}l*hdYgQ(#$+6eU8^NSQ%(3av3|a7{z) zaaW~$-D`bV2O_dW0$SpLrVteBKxAP;U}`dj&X-nslf8$<{n4wpU}Y1I69|bSL`D>X zWRsKE={vWMs?Q`FP3tgwmSf~Iw~h!mmZZq=fZ}+SV^nl3_v_OuTa0SD*}m=553jj* zIdnI-*Nma(BuzO7cziVF=TQp%$X(?1WJ#VJklOyOvkyOAKM^umqvArR<%MzPK*tB@ zHQQ~L@Y{NA1HDALK|TdDc({C0l=(!1d_t7@c!RCWNDpK#`A)Xq)9_@$OUZDD9T!5P zqXo+8$@hUF9^d!^pl18LK+5au&L4Ya&q*_8KT6d?iCb0{tD$aedW-^ACsJ$yWN zh2SKhNQ@*AbiMXR=bgEwX*wLpRR%S!if}iQ?Hh&RE$VNC(&nw^5b{6G6no|v1ac&0 z{1C6(zgcd#w;%e)i5_VkB2;OaF;k8HU*4M${}z>6Dc#fTYhFC^)xwwXfjJq96IoKE zIFj{q3Miv6K_@fKv7ttD>`U1D(-TM439h27h{_14Ug>CBXbH8n=v@25L*stj&=b2I zi3+9B9AE{8(f(+YHRsm9Uk_51or_Ra7H7)0YfY}4*yNY+E}J#Kzb=BMWQx-E$6K~D zWj3V2ba8qxK73vI_OyfJr=~0q;fKzxdYvT4JM$zT@VX?o>ki<|l?qXxY%lJ^l{~%E z3s>*S4#6q;OJ=!%f^&ec?dRtD>&tLT&*`(mCWTI61B&8}aIe6gD&~Il;!Ut~dqYc; zE|20aQbD1U=LnHkC`G1ev5SkQTGsj5eB-zM6JSWeeV7S^&rf6iKW&gdg<18R4G^bA zxmTsvKKI3sriu={pEiM{1fJqZhGjU02=a<~EY7HkLVLplK4Skp;XNG!fd&Ghv3?)G zD2AnD%Pz7jN3Y%0rctZlWm&2L6$vNEbNL2%d>{vSd%5#HpkDi<&aw<+ou~(HjD6Cu zyW84JP-1==g5dK?ir=FiM+zD(h&;j5?Z3Zk{fRn4?RaG0h%?wnh9P^6#S$HBfsg4n z*6w+Ab{HBj`AzNYoIcU~5@T3|70_Y8K-ay9rc26?T_OuD4y3^MuOIG};e?z!GT&vUQ~R zfwB~r53XDhq*j#gFErSZC)tzNy?N`REl_umTq^pF9_yqj;n}5^_m;n(lnT#=;kh-M z))Y8n;B`4#9Z#tme~SIrX}6r+b~~QuqX|yn8JZPDmZB*N<@vf>irex&MqHX=pS5q5 zmAD&LP$ryOsg4v#96@l5Ml&SWe(|)lSH8J26`mNooBE2^6sr$ zw}f4VtReuB6EsyIIdm1mny)~ryaJ2Mi|p;@(enqjX^q!(dV%5`>r#EWPKf}LASeK+ zlJ?-fti8FZ^=m;s$g?og<@2b0*b@t9JwGjc1R9p-1%aalg;GOCpn1?B4X*bI`|Wp) z{7s8*i)jBaS_%Saf=UU16lmhi;mjq^UGaRa%P^hGGN8DTT8{peRMR%DC++?Fyz*PW z8x6FFG9j`uOEVh5pxVPz*fZv3_SqAd*G_DQxYr@naS=~C3k#&No-9z<*V9v#3j8|j z^je8i*tHq0D@$1am+`fwL;SS;wq2Qrw%->v;tR(SlEP3FFH-_KdmGF+G3FWj$-5q! z#HL0RVU8mxp5_G#sH|cXVJ;C#asNjPxbG^TlWShjn5}=YUa#~0qR@E=za&NWKboJb=U>p%x;jRUXj5F!LLCF1($lLj zeXfhaL|8uVkPaRue2ZWjY|p|7G4K}yF#+w5$)H^*pzpn{mp2ykKb(hOA{1Y`N6E>J zMe2#P-4Jpy&rv*kaCLO!R9!GOwue**%5vff0i5hh_S)nAcbn$}C?BmT4Psb&R6-LZ zEfEUG0aiY~@|6WtyLst1>L1McCNUt5K@4h!=>M|)vk6xnJ)RW)x-ZZ11VJepEAn!} zOlzmI=ZtBFb*#0&_I90&ZJ<*bQ4RQ}V6 zXRa=D`7fc;`oGTJ>PL2KQD1z(X^ImCo+eqEV>pHkGOZh{pWWBTTBWYHfAwXT6$R5S zLpam`y)oe&E4goTctO5b1>RA^);~Mxn3w_;F+cYyzAE=@Cb;nN!2C}V%jQvl&(oc^ zU&u2z?vE?`9R2RbUY3AVENl=^-pzYVA@lJ4Uvr1XmCd8?L)iGT7Eo_o|Im9i?I?au z=%aXlv&xa)Rv>pKP~RC(*WDSw3@!g8@Je*6+`x8RmmQct{CglTwR~-0zTm#V{L;OF z{eSPh!2JDN1M{D}9++SGd|>`oB!X<+#ew-LlLPaca{}|bh6Uy`5qpwJ*1-INL4o-R z$b9~OeQ`cNSlKPd(YT*WOR-R&DByL{R9&V zv;J%8D4$ffoD|+-dec6&9Z}HqS*U@=n3Wf6pe2%`NQqEcPGNm_^qs+eIV`mSMt(55 z16WaLUBUNpn&mg`XPVVp((gxT#I~(;K>S|ZN%n#l_fy-~KlS6cbu4J_k7b5$#V6*9 zImQ)PIio76-B*%(;uqD+tb0FUmjANVawTESY`sXbW*_ZYY zMs|v1FQ~YC<$HYIe0QI51>Rf!uAMi|ye;D|y`+75jnK#eYFW~CtA%Ji4k`vLn$ar9 zVIYk73h(bjmkWpW=bog07nQA5&hM_MqW0hpibo)bi9%Wp8`tuB7#&v4^9si;Bg*+e zBO@nN)3{PmuJcBi|C?1aKGd`Wb||qu`X9H`9}(p`scCSjc`_bU&Gn^Ma(R}286Cg@ zQOR^Rb{**A(5;#dRZYJJ#Zcv#QXpiDSo}+dvohxZ0yDZB0 zaYpv3Ra9PWq~HHlXp7)WS~&^c*GPV{B{V;51e5>e^a2NP-OWmg?z`&7-&&PCKPb9Y z)cy*33NB0zJx)gUb;$QI8SiMJQIz`_h?3t!``^M`zW$e2o{e@~3Xy)6SB{O;p3YLideHZ9 z?>`vf{8W@a)l@sF_(~c_lK~ww;;W3lt8BeVt!f_UxCrs&QiZfjq@9*Z!n=xoSJil1 z(=HvOuJ2wq#UR{%-mjeaRoQxj%A%85dO8$Yhqf4}x(hyE5rJ7)uW0rK>PM4>);VQ#pZF z1eKB|kG^(VC?{PwRY+y4NGuh6A0OuDE&I~L-~8TqZ3G_hC{`9&L8KXiVo?uxQ!B_! z8hsya`SATnzcZ}An%DT$FcTQ)0dK2)bC$S$O=~!T^YIhN6S6|GG9hV%*neubr@g(C z)32;1p`fY)o5V#(+$w8jnW}6hzHM)^sm+1beZxc`D)k^pGcqFpP2~wyTGa20I;T2* zSgWN$mm&>0P*p>6Sv8f5Un|?~2WovfKG6+#n*OEazckvHKdzX@Y6Ptk+{|UGe>%RZ zK^in^J9|TM+D-p*i(Y>Bq`*A7^H@wD*>9Y3(+z81>=3-k1x6SoX=l$upDkYnPB+ke z+3tK_7Z*+a@Xz2o+0?gdL%eSPUWFbx1qC2@axsgN8 z%yN>ACI3g-ivXp>dzbyEe#iE`(hBRZmf{J4qY0AX1ezjQ z5%pJ_Zcv5A^r`*(Hx}*ba}Ij_4-r*Y^Ef4zq69$zoG1~k`uslOu|r%udgTR$+Lbg6 zpW2_?mY%gdGbOl%s^a8Xk!||zpFgj!UvdY>WD310o-j1x!x zxT#;c|e4c01g_O zW5`#`o_%G{UzC_hK8FuVBnTWKFswi`1WC~Z$^m9}EFR0<7!AQ!_5+T0=XYi!ipoqY zl1QqY!U=?66qWfVJso23f3$%6u0th_eTRsZf&1;A9p`Hu_&x%)JVO(_03^Vuf`+Q) zlPd{X%KJa={p5>Ozp0YP1FvfQ!G8CvyFc+fkBxUKGm=PfG|7-8g~mI5Sg)sJ#2d+v z_Pzu59CS~CORY>CoKnrEtcrk;WtA0J&8dPypErMe)3I+Yq1LR5gH>lCsR@(EqwXQQ zquY$jhdu~Vw*Zg2GN}NL0jvUK{-b?g_3F9&>m+oISZfH~v__b>Ru@Xbc3L(kf ziDSoX8sJI5-8h7plNd4m{N0}R$ZqS$W83LZcA7#GEzRXooixV*5pX0W2(n-O@yeom zKV7-^ff5}z3in3&JktN8g?l5W41e0UuB>C*FdBv>B+Qu6aQlGQ321@mC`sZ}ia*k3 z*D~AIFB34t!8FTY$AP| zw%;*y^6#q}lrRdlN{DTE&08s=pSCYMF?1I>2a9@^i?aCC3CBor5 zV;9tA!+UmuJj$)XH56|*tp5zxT9{KHDYs<-Nd;bKk;~)Der?5K%i(jiVR>iEIR&nqbM z0sjNFcE;mBU#?8cK(98PIuLk1_&K6w*sTx3a06vQ9X*{G<#A&NL~ek;p9VA2e|S0l zeAAMVNN^1GEbunv>jzJ%pMzG%dz`%UHZa~P=Z`~Yazr9-e3Kk2_gyyc^aqjH!6~w; zFsw+>EZz?8#a_CysaNRdl?5LrRP22`8BTRPvc|V}V~djbo#qe%6fcpi$nv~Eb0q>) z-PSiA{(Q~lNwB74Z7cz4)6W8N2tF^-yO{%MDi0#=AxD( z?WqTz?@RiXRWnoWhSqK;$0$Jv6J zYm2AV7nsF-Zy!m?b#g2z2tW`NRTX#Kx^G3tYfdJi)*a2usiTebyAx6Vk0%>-9pfMT zb$tHrrorU7%$BIKK;}gSNP`RhaB}Bjg(CtFZ-oQxZRwP300#=n&w0vExIFP z!4zHJL65#q{i`XQv+u&oUNWoFf~s%|C$YEAnep+?{dZ-cbgKXKV#4%9qvA@h*;xGi zj-kc*gI$XAz3wb#T2JefPPZuDYfo=0Zr+~7+=cY2)Qfc})4-8=bM9RqKNNmWDOKP! zQj{1LXz0LC9*!%!vE!M{t*6>`fH9lOS|IwolH#{Nq$rw|SRhE8%F;6r?_amI#OiNS zW?=e_IZ}OOOUL$|Pk(SMA1<|EBl`MBR*q#2zjx<_^J2%Hu;q%4I|XF$4A(rXyy0#3cioq8ag>nj5>ZI z>l&y+31R!>=ikRj?jlg=lsQ>q36^Jhl76B2trfNEDv*iA<!~@ZBxK*TirHuL_K;aDt51 zBt2}2kc?mDc>J%v3B-ah25t-`a()Jjq$z@6QO2i}u3dQ(%UjWo0gFa{fBY7Mok9cx zrxF4XIe_jI=?S6BrX6plJur0T?X80c%D*MMJTTn5NI0`|r2>!za$JQz?|9Ts+}G0_ zG7TOt(NWO;m)?ncv5F{-BUo9{B#~7(R1wulAGEXPNEK1t99LxD_|}?aI6W_xnm!cj z0#}zJRw5;q7HNV5QXV<7>7%p6=G4cS{M(2haHcN56 zdWNDIR^S+xC0T+KD0I$e*DF53owYAHWEBG(PyW1P=^PuJhhd@T90-7rX@ymIVUc-M z$K&UA2HjEEg5h$8x1XdJjGDt&`IlTDSLtSx_>OjY3 zpYUJ5|2bYmLX*5elLAXqEFtm?TE&vTbrmo68q*+0uMfVsYhWS~yzybLX5|2%a~v%> zU3mrhUSBV_R8$C(lZGvIjVMfld*VmW2d2gZ!{&&;zwLFN$p!}V46BMXr%)0jX}Raq zhuN-=nC(586xn;YIk!1XS?4oiCk?Y)8W(;)(T( z!?VO9OS6JRv5FuDojk1KnyW6#uA=V;@@D#SEK1ByVfoInI|+g5C|hdHiZ`}tL) ze+b`fNs|?o1~d&65-ocQR>?cuk!I^&G-O5_MB^U%G@W5&n&2cqTL9+#J>qt8!QbXK z2nIA8QoieL<^2u^-y!!iVIKD!h0Pe(w(l8`GxR(B3OxN~SR75)H4Fy}uE9OHyF0-l zcyJ4@!QGwU4#C}B2e-l99R_!I9X`(MyPtP{^|7a`YL)c#RPDXi#@!#befIijy$4o* z3HX99ItvXfM{0xIVRaM+C;f7m?{b^FRD7@up)vfCRi&KN_YHyEl0l~9f9yyW$r@@G z=nO-YkN^G_GIb{;Nj;+{(l69?%lNsj^howbPFJ+t3i=jDZ^1{$V@g1Yo?MumC4CvP z{-%Js3dY#u7=p=IIgIBxcJ}b*a4`g&PvW3l31q%0HzsB`Qii zG9(}keOyqqBDk2HN>zzi(;(P(G?>jmo+%6iXO-H;8JzxkQ(YoDIk`};3S|c36(Aqd?YnTtst$uCZT`ukc=WFfk;+_StM%e4n>i} zg@`r2(^t*cionE#>%8i|9>f*9i;l+DX7=5dGpHs>R|{TxlvfVVtJ|5#Q4GYMic6goo|Aju z=B+r{(gXyXWHKo-UA1|KEPe9-gi=csv&RmhHubx_FEx(uhnOwv`vVDbU|!^Dbe9YA zj^lTFkcQ4P4v6q)zG)BE4CxHgS4vt$<%V-e+1YhT`A{2cu!UpVn9y#PMZ~wOCS9sE zc5)6F{PFw;gYJIh8M~VuTXWoM%d;nh6M~`X9$r8Zl7K0Kz-@aZGy+a6;VM*|3H^l^ z$}wOBKh>oDGydI z3EeX`m%b65qZ>jLV3g+YdjkZbcMEcHRU1-H*zo_12%Tr>2%H1XQ zL=@jW{{5gFgH7pq{Ed)fx1eefsfViSizXJp0mmqeC1dE($#ROTVIWwTIr-H+L%a7` zR_cHryRF0ug4#*vCn|5IPN?9{AVC>BwA>XW{WQfC8wbH&KST*#fDTA31`NyJ5kKAE zW1mFv4_@#DJV$mu*m8s{MdCVcxOi#z+}L}J_`L#OqLhJ}zVzZzr}9_b^tDkttVVW@ zKEEcY@PJn)+jaE$POOpT|AJjPG32rI@$q_)LsC1g*h_Q zi1vf2KsDM*^Q`54DXe6Xy99bFrksaXTb!Dl9$sUfJZ4V9>9X~C$Qet-q}}5Cn*E%p`-E z-v!MC_c(-52|bcw^%0;Lkjlao+!Q2#5t^Q>#yTtUSMqGg9&6vf0;&!YKOyR$d!L1I ze>J@{P~tCw1`)ex`UFu9HPkn4JZF1jCUW8v zVOjBw)m6?q|3X%Zvj_LlBlx`c#K5LRxASgD(^DQ%8?F~$eIRD!i{*8XmT^4O?*y%u z5TahR-b{!-Hp2y2QI?$eW77UW;}vnJ{Ev0`a5d5J`1}X;JHq-bop;2JZl-10?sRFe zpn)4A@C*&Q7Xw;e*|(yBH1UG54dVtCNt@ZJ<)h`}j*+x71fH=j>F1}Q-Y9h2J(iZ0 zA%cQ?MXf{7iy--+_Zv5 z9;0x(G}B$Hw}fvuO^~R5fX+>TC=09s?@|WqiIw zpb>nGNcfB?Z9xB#o|w^HrZFFnJ!s$Lsp6xNZWtl26mrP^XyJqZTl9(h%#7*gcg2f? zK~v}RS~=c`1DKxUB29%nJT+rzEpg_ril4ml8gh2735??#uK`w@fr+IqA>xZak%3+W zTh(fb6p|Pmfu?_xI2#ThJu2nAB`N=mxzI<)`Yg;m`Hmr9R)rk)(a#3)eR5h=VIufZ z-JSdsmBdxHB_zL5l3wT;_>HEC(LW|Pf6PV6((g|H4Lp00!wiF z5Ap`1GkCd3{wLa|RHj6CQ0c^RmVGxBqz5cl&|JLyjq$f0At|mr@$Ko#(!R5XKVq=+ zNfT70WY)p?s}q6w8)lVEVR`Zsz_GkEa;pt@1$Dwr1e%onz^CCDSt_gK(Z{J=ZH-M< z>6b+BbffCXBj!PS0M?V^ts4DsVgL@O2XVGdj6Kz+3xoU4mkl#}I4p@g#nEFGioe0q ze={YO^HUPr?RNs_J1}{^p0m<+wf%|N%ec{BXN5VD(+HkUl`StU?HE)N2=Vmw5J$-h zolShff_M0P6!}%sI(Sc6F8ebzcZQICqo+7^->cu}cw$o+36g25yp^!YC zB91uHUApa$(d~1@Il?=-gyQ)_6Z-|jYeXX#PjiarHYAk`bIG35GZg2IGWvF4%~WdY zHR=&2z!=Wn8>--mknuEKN6aPp(6$5PKC&^;Dql?|NlPEyQo>kS-kB~1!)NEod~$`^ zKm`aQ)xeVY?XHzVqYrNU)KeYWbQ$7Rb3Uh7b32xnN+*@ydN2}2mxybYK|`CiAJgTE zlTV=-tM{cjqDP2`z=HX87V#2M>W`*P&R&<2OCDM=L-EDe#W=oZ}Zo<9xJKIm; z;9#@Nd@_^Q$;@niN&azv8(Vd%Q4R1<=T^fzE!%Ghhr#C2BGuhV?(eXNUe zi|2GpZg+k{%ot7IWS#|^PeyTSR*Xx%@_Vp7a zB`FUBhiB*oa+}=ys?xw=MhCe5S^FV=e1PM;?Q5&HM-}zXRvf)F?IUb=i5rTST^Cm3sEB-1wn!A zc0Qr(MbWfk6jr5RJy~kVlNDTn)x74qAGXQQeV9yp?aD&WQK8DfILaoL$N$ew@cge# zL7J@_IA?C^!|h6wAyzeMTqa5!k-;=qgP+om_w?~uu8aP{VM5q}Q4*mBudR42Vw`}L zQL>8B!nw`s*3Y-%_7;(ANIo^?S2hk-vN{<$B9iK>7lO6?KlCb!8Weye-eiVFiqRqq{YF)AjHh^sq476fFQu?B({5HR~_vcm! zz%4jJmJx%Gjhf%K$!7_FdhK^;?$Y^g2SVb^i;s$yWlpht*exSEmLK5VM1uX@W6 zehWN5@k7{M$LnE(oKPa1a%7|bjd}+Pm*24muQBTh-d;dmqjJw9Woc-p?K@++P-Z?Q zTM~2t8{5;Ve`b0)n(*))7d#76b#Uje^v+$W*8Nb(jpenZ70r#MXM}u~RicG9L~~BX zE{8C%cq8BSb#DjKcu}c{EiCa8O$Gsw+uo}d9AN3P=1gVHw3G{$|3P83>A(QIiX3xg zo%FZTUWPu}28F8>&GCBR%gf6+UQJ&EChNbke*ftBqwv6$wml=w(;n)Qcrf18U~Ks~ za%I-x!ppiESv-1}WE?sACWMvKK76>)^OuE2_hqwGHOGgOKd5=BQg{*TgnnbUN_WUw zF#G6SR;m5`pSs+6y1G1AMvg7J5py6zedwL4D*HxML@B2yr$6OFckVeQg`ftQVW=fG z;}MbQ9xYjg!cEn3*s20GWnYZ-_9ii=;B%!O_9Nw+!0O%fk-z>~MN1W#Fr4=QpJvx~ z80sd-S+rr2w*23jq>+71Wv|U4kh?X9F7JBD=u@MF5RsFsjSIJiBwE0%fvzw1KWf9N$ zddaIr_!x?rrD`fWYnlG7Cvr^KxBAwu%j~E(&CjTRB8L-G%x|$C_f^>I?eOSB{c=gY z%U-V1vKeI(#@H;|SIz8A2azLNl|u_#jr=8IDoP>zS|0YW15fPbCo$ww^W(CS(*&yl ztO9f!lDPZ=tw(OEX;+8s*fIBd3ll&LYVM}EPZ>0uMR-1ugt2nOQ?6=l(;B) z#6n&y)Z_Q|4@AlBUPFuS3ctTrL8+;^$i^ej!`pPRb491D31(SS`JLE0@Pcz~!&50k z4QV8cEwZC;qWnhZr3LVg?i#6Mjm%!RfC1WX*Nq)_+WOjP#?Kad+;%(1fIqHH} zSFQrV!Gask`77dMZN+Bw^)_e9hi@RkIB$iEe*7wthdaXx#ZcHx>LeJTMI)nb4p>?^ z8Q=fs9R+xr3>dfgnt|s#@V1)`eL9n!`{hscJYsa2fqU<)$X@xrX|de;cM5~0(fI97 zGk#kx)JsWjWZmC0)p~JvO%&#V(9p)V&&PP$6Xpn_z&&_u!9X#!%XKFWZtsO@e08JZgHW*Q zP}_e0Hap=Dgonsg%GysQjpg({o(8OC-l^wM#^onU83@gQ^N8 z?fwv#%Q|YTMkm?Kv*)2F-4bnK1xKZ?^+UVCpE}w*W-zaWD{Mui!ZHQ<0B|c=Jr&zt2yi@|Hsj|SMbc3v`oSM`` z%G)i)X=q}w3P&G~`$|~bL&kD=y+^oK^H4J&MN?fJdb?-T+|JN*a~?Wdx|Wwa0RwHG znl4t8XU*dXROe>13>ymwcfrW-aYqo%LkJw2)Fp(M9q_mXpEucF2Jzx8{34>8BiZTj zLh>0^Z`*->I=YL^GQ)w(uCah2Rx89!ONG|@s+&rDS^aFf`Dl4$T8Jc>%feWpp%w1? zXrX_Myu9|=OD=pXu%jW{Hg&LCI z2SF)&g;Ol#N5K?xv?DW70|J*xTaQ@`JE8&YC_)?gawvUKp%!)+pQ}LnjpjTs9M*RU zB2iH%iYiv^@|+P2Aa7WI`1f&+-C)u_U?aFDsYEnhtpXTwpIqm9AM;P0`mlK`Ki%!VgGt!)}-?%vku0bU7d1iPcD1p_8!GryNbRVy9FoVA|@tl)6h zX~zYb3%fQvbxS3vEq11>Z3r6DreBVqvmQ^+OTB!*5x#iV4A3IQy*t^`-9k)Se+ZGo z?_e8(sPOxt*gh~Uytz_-z8pC@c^b1MFkXRAySR%24J03p*_*{QIQ{7CvigIyZ_J+1 z56!-xTXe%Z@I3RbQI$FKmAF7)Enoz4LN*&wjGP0&t?TMpqJl~H5a1LPVL*RW=i)s% z<@VD#ievXkZS1|IhhYPN3-L7$<*H?%2ZoUIH7B?0*P~q4g|**XR2t@PbA^&-8dt4m z35_1Rc&9%IeEMLN`kM3QTvrFT$Pju({Jw9$=8EpS9 zxdbFjgcoaJ3yysXi?%xuacy|GD>fuVMs82Lm^b3<4YT~jGcFT~BSA_ko80vG8y7C9 z*3x`y6r3LRTA(1)#25EbBs)(|m=Sg3x+c;Wcv^d^|DmFWY>)IQs2DgJ!Ex4N%p<1_ zSi6UX+GYBS$BoJ%nd~70BeGIwxx)wr1~U<5-|b6Tl*^vi$~!4&cU^Yy24uTSG{2pY zc4HuYl@(~T#f2Q3<||M=dM@-~P{}y(Hr!gLUpxy%uu1;(7!aP)9V40BGn6!BO2OvY zvnZ|Bne4J1j10}q#?$&yBPIp}BO#ZXh4T4xR=F>oj$7TwfQ#8L^sWV{#~`bu>j$q` zFC#89&~qdkMx76NzYW%Ws0UWgk}+ae*z=jgA<3~vjW#WlPr3>}Xz&RX>$1h3g@ke1 za2^IBu)Wi>(*6ql3`FwS7GRGp$soyu2{V|pzsoFJY#Ar)(!td#KAmvZfZM;5zoqhL zDoLHoK<_-}WY1n8H_DKizT0xb4gK)rrDDsGAAdt-0lk(P@3|fR^NC_3cTU^iG+i9x z#E}>b+F`nFZ#A2GzSYA`Jz-Y5q`Wp$RBLUSUBZ<|Z~V{u#B>EN*QUymeX(N zNlj%n+T?;SjI5wPE44evte0vusOPL-w@)C2x6@B`U7kJ;IL?V4*Ii*)GDRCAitE05 z+P?BXCNrPk;p&3-lo{F0cs#e>Vs8r;a9oH}mpCP7;~yXEqRfMZ$JuNRNC|(iZ`r8m z=N}Yn!?vrNH)uK4$!@w#mU)GQSNI-dY$zm~nnQ~j;^~$kuDe+10M@|`AzxI_dx3Fl<5j94sKqzFfTt5JAZAtS6uGKo5myfC!Zo9WG@C~ zDbz+mSyF17n%8;LWqLtsG}X!1EINL>bcwenm!A|0b}b{kzfY6xY{005*cX+A*yZb~ zV%7rm6#`?3qnC4Eg(ZcTM+4Sk-@9O%ifT!Z(GjMN4lBM5&}*PX)%Bs>1z{*Ep&^AE zW5pZt+|N(!);!%`b9t~t&=%W5)%?u<-J0vraJ!)Rnb@*;EN^2sbKF9mp`D?H1{V&t z(cezG_9_%0M`d28$j8NbrN0sZSIh@-p%D4ApF(}^iJxM5lE`Y9nh0O(-A($&!v{*1 zD^niYCnn>eS)R2UW1fi)Hft@*qvT=y!0AQ34!msi>hB&J+-D5Qcl8r$TUlS&K5UHX zSfZwVOpfQ6a`Keq_ELc=Qn%nK*SA*?(!Z^DD!~=D z-l9o6~o1cWZCbwj!1UJhhUO-C6Zlc)%!!s8Hwhms0}`rmm5ylTtZ+>UT}XFA<} zsurggiCwSfyvG7e>NuYPP=97fHi;se(9&bva{kJXp zb5Gc{STw+t1AH(z?8swoK3gIO|M>L(5l-V}8)-u6NxyHWE<1cI(ei)Xx>zGTfSvNQ zrVw}~uPk6hGFTa$m8Vl{f)Rg|aAmd06LfT}cyK29pIRRQ9H9<8aM)TnuF&n0Z+*6t zt(gfCmWCsZ#-A+H@7={NcJlK@Sdi|%_vWg2cA;Wk#D#sZ8g98+PAg+pdn#}oTT*=k z!i1^*6!p!ZtshKp9%ro$;w9@2yUeSus5L(XL`1o{=@){F8Y9MW%p-r+_;Fe@BetRo zCdNcP%CK*o{se7Sj2})ok)Hxnehbm*oLU|pr3QWm$T6!2k6}nCV~9B=QAFw{#d|dH z?yrQ)Z{wLy!EoPuKJ)PyJ%k^GIp&0u!qLwOAW%DP{gcs2>3XtCHT3gw#9r0SD%YBr zh*xH~Y&mwG;^h}w_1Fe=AxKu|qew6u)h3y1vTUJ3bqnDCi#l%Wj(4^ct3e1 zyhQwVnZsB>`D*}$!ETuxZVn+ufVDBgBCxuq6p^8_>wZ$+1E+h90f5Rj+^-2;b&_qz z7A|GCu+jRsOYLRaOV`RXGQ!n0)p20sBD0+$MOoTD(J+)L9o|-Il|F2Ze-0ml2<)sO z60%_W`03OFB1HspFTjkZ@cs*Y5bJk#+w=KbjeTA4;U=|no}POTjL|%{VXd4gLkDJL z`Y|G$Z(If#Z=5x@7!!R)9OPYP_C%8X1244Pk=res7nT;>%U*km8;g}I%DHIvD-tf? zQ-$idt^zXc#)n~=0~}lzcu>DBwHS(LtwnjV_;7h1?(^0lugSDPcB;R-rys3}tDAMi-zW9mshJ{M^3O#$J~nF6}v<%VZ)}xn;8So(JI+wcH&7xXrtQ;R;*yc8uNPwR}Bpc(F5eIw- zaT?AY^8Mpz=q*Y*$5vFzV1C6Gh7hR6dp-Bm=W)9*vH7~6rMko5YG3)w%mo3q6Kq+~ zVTkt2yP$*p3*E+Ix{RRt?94F_%Ezq7{`RX7{=LmQ9u-gqgV8=+G#t}4)N+Wj4%jN+ zc>(BL5+%5c&@_jtOV2xD8yJSn&EcV@KfjKBRKI%x7Ld_Gh`u8PWlaJvWtiKm1N(kE z*ckR$=Z}xDnO~h8kWkeYOs!^ZRHjhbkE9ruILYNyqB9x@Q)(MWH4;h(n(InC!$t&Y zrw zLtTu!@q^=29{&V7nPdJrEgh};*{FV4HNSXQQ>S1IkMG)f7+)USLmO}h=Ml81I7jhn zF2Baf-|IRG48BdUv4bt{fNtXNU;87>e`)61IeZpn;^_v?0A;(w*Q#i}xK44xf;U z>+0>AipXfz1n>t{=GqJTf1i=4oEDc_pUF!%)X!WO!b$*rEaUs|k$y6E#n2%(_VSs`-=Op4cYqPHI&kokFTP>v3JHF3gg20PRW%*BBunw$NEXAYY~;# z&BRvF2mD;SSW)Sx#``r?T~h&rRtuZLd4v8N|7&yW1>?(Q^0(|M(SA|s+oSZb?G-=? z$FnK$c8NnGYh9@9x*xQC!v1)%COp;GCVopO#Y#vyn(%Yr-y<+`vbw)G?!q&izt*om z!yd*&pMxIHHPmbV#3G-a_Ni3o2h>~fW|q6t$?MDOUdEp6``pbu5oEhZ8@oi9LQBT-@z%qd zv8#Ps>IQkDW+H6mubV&MB2QX{PSRJ7JGMnU?)lubNqe3)4pVG9{pCN8*A3^3Vjhk~ zy8Kbu0voppAbLSieZ0~< zK(aHDKB@Q@u$_bshnt@Bhrtr;;=l%#)nES^Q!OP9?a0>0+-{iYi+e`BkCkh+Rm4+% zsS=%oDf5?Pxg_&{OEpiS9s!D|dDOZ+8+74zuomZSzFQ(z8mnKt4MYf0gR$+(KIpH=`6`fr7AA7 zmhyLXh~|38c6he*kk3|<4Ywg^GL}bG)ygzF=}qrWmV|bEDH@Gf);1XU%nqGPlVAcz zr%Ks3yscQ}a#}bf-{d5P{nqL7MbkyFKM$~w2O2Jp-EiVLP zU*cJz)l8i6ssJqNlWq<~K8Gg>3l&!t)CoQHY-4QRD=Vfyl73__QTd*_Iq2Qbx5(vy zvAM?QH3fByu_X3k0%p}ZRn`kKG8f6_L*aF3Nz$-B?p>=~^^7kc$vPs8#UhVJsE3lX zmMflBkbqVkOa}T{E{>XFfz8m=?5o1-VDzX?>|C9CfBLTByZpI`XN8Ze%Nn^J( zC$Zvr1D5kGXRH6mDKv<{SJEZ|$dfqxsYRW=(BV5LwFYwlJ4?{gCIe`5JVn$g>=^vM z;+VO>(e&KZmjJQwI$6F><7*wid`mU-%!5{4;4CCv3&u}hf31oq+*%Dr8u{-R1HC8f z@vQdV;T$>h7hTF0qwvt85pT@Ccs6$a($s?y*ZYq#MUo|J;OaiAHY#T|om|l073HfI z(Yzg0uO_xR*%cJqZWJ>ppTETcN~LFbO#*jI?S4~#wQHaD>QBxY%Q*-iT(VjnW|!q* z+jpF_q@8rrI(Y>c&^V^Nk$ik&IeZ@^j65Zy+NxyQm;b!$%B#PtuJ2Vnm9UrBkIn?< zUsaOA9z>^W+&d381|#~VTlZG1lsrzv0(vvKzL>I9qF@Gy0e<(L1}oub&3;+Z12vJ< zV_vLOTJDSPE9 zsD#SQ76gmj`C5=sbdpAgIRK$&3Y{DAHgDkd^Yw8qwt;^!0nKJf1Vt^td$2A0++pg( zgK=l=mQ&^cI(oq$zX|1L!Gq^3w%+N7zRT_=iLvxO9d}h3A0;h*xqjh$aq6^Ny_epYb9h(Ka`9Ee4&Pg{OcN*{zyIG zB`f9^cRh49QJjqKU7m`|Y!UpVO3=S>WUg;oQMegzW(WGrj3ua5+?2N3drsZQypPSt zIu9#bpArLzE1ZnHBjv4)utYC9*1P((gLsu>Bjvpj2{E>l!b>*E>_5$3r^mlAJZ)IE zY<|&A-Mlw?DEz~63`ifC2IKX9V~Ik8Yxj4!jP8z_AlbLPQBDx*Qa{-k-rv9bJK%Lj zus|?C3hHZjWp3{ycH3kV1TK=IVW<)qh*^h;FWk|_xlHUDN~Vnh5fV|oCZw52hOA!~ z)qV2*rK<8jU#Jf~TyWRhL_?Nn+_sZPH~I$BMwqK{c8~Q37vW$0;hhzEmMh8;!P}D%z>z=~5bBo-Hnw-lNVDQ1ND+mzgy3b)>l^{q9 zOlRJbLdlU}{K;%YbsK68{9^hXh2r4mSXxesfWSNwYSX_y!a5TuIki{P+CtF#(i>|S zYnI}Y&1x~SrHPy?#jZnSOqi9qbpJfuD;VixpI1t%&T1*aEv}rPDVv(7!`~KWxiKq^ z5cm12)JZmrI9&JjkcjuL{>Z-DLS?p%Ft0XqPc!!~o~|FvKRB6uSjMbE$~m3z5_Ef} zt4@4nFE)(8Mgrzqs{O)!2w(-=A?nSLb$%nmG;~7xG1~YAKEqhA&-}96W|rhU{+dxF z%4Ug7vhCOTw;Gd-9~upK-Jr*-REJb^TrC`2Q4KXQVmM(1|5QqDdG^3B?ty&fU*Zi%Q6_Peua`L(6DEJi zE^;uks@JZWwNKukeK=jEH;hxO7Nq6HmAEU0bL(!=J%n|J{I*eECb*&(0$|fX4s9wy zOqFsJNC*D(;z-W!xXmp3&yyOYklKomG#9_pHSRLnRU)PT2+_enCCs;0r>+BhF}0Py zrtL-dwR;j3LIdyR*kzb3Dpjj-onlpop%7c}7k#@+*FsxU%vYklIbt&&#wdXZ!sY|$ zcmWNJxwyj!*tqAX5iIY0>aT&1-`*!rI%`g!D9|_rGapjT+GkyQ0Q<4TsQsEoNU-$c z1wC}-B66X!@JQufc2Xp3oX;~qoX2bp5%b1W@~hz>p*Zd@Cha=yGI{9>d^qLwbg0_W z2X(Z_Rac_juua_lmS9BYGeAnryAG9(rql_5u1Iis-)- zi^eSshiWs;@vqwd^S%>nD4pf7GN6rnyzgj8f>)W$jT1~m=)}gX78hiYO}ggUMhq*}aZ{Q`PZYYQ`~5*n1BlTlM@3QwTDHZQzVQ)+#1zIV4><|LWCj2r;) zekhw5oisJS`V~ub!Ks)l%El`Fs&U8y>{rI-p?bc`sr(w;|NiLC2VUusLK z@hG1YgIVT9l%P#Qp;1V6!Om)M22h$YlxB)D+h@IR^K0ybFvwf~=aN`PbC+a={%v0= z^#5EEv#gM8$iO(3R+#GOsVrWD?jiygYX8b{zDSK?^%+x~eA9&24L*sFFDoZp$I z@}M)x!@ln5(wIQ_bm zL(*k3-VK}G=wNE#$p^)`-V}Z@8+QZS-Ym=@B zR8W2<{ir1fEzVZy;B9<`7u2Q}#nqP`VXM(ZSTJc!?dk9==?n$*#MTL1&+8|v zo+qf}UwO9!_PmUir;d!J_Mx4>JUT_Il|V5P(jCHttKg*^h-=RQ_FZ0VH=-Z+^I(rR zJY9LupMO6GZ^>2S7k z00tVgXsq_xeMl!b&oqM&)9*>Q#^CX#B${)&eMNCfrjqZ%zUllG;?)H3)P7C_W8YB- zrBy89Tmr;O#4rtM*DVn`+kEtNs_gqQ1_^}6q;w<%)+!S)`He;t&d@SAxeR;EuI;dD zrZck2kw<10k0eMt321tm03&|qD5e9SaVBf*BgmVN-RsGHabfQ|P2TaY(+3@wj%ao2 zv5};YYIz5gFeOvngScp%g=Qhv;vQQayM*Yy_bZmG8j$21Fd5;<^qSv;_NP+>I7%|R z{IhP>FSR{PmQLkU2D`(**++fBmE5&Rk>YkV)xOz?_yWJer%)&jV(RypqPYDl)R3>o z``o74rEkYs)ZVyNjPwki=j?*V%`9GORg70ReH>OuJOsTZMr1pviI*$9L^lY0`Bn$U zx_(6{`Vy`BOZ0G{6nSE;Zf;zydQ8FZNarvh-eR7@IgbN7K>A}>&c#{eEDSSjRF zWgI$rq}ny>A;?V&-p|n)=l)m}bs{4B2PIOV{=i`;l6#|wVIFtX3%Nk#^pQ!n%P|r7 z=6od58Ykgb)z6sYq#`|G{+*-_^An$X>JVDqBOZQErvu8XpO8?nAJ zOtuEEJcP2>Jv$>qL+!Y=-XET|p&I$%$7F?pd)qvl%VTIeG^E=Xf9letD5wVDdG!mm z(qe1Fw??QE>fz4hx4Zn)RNe{KZckV+0z|giXsBMAxkq@aMB|LpxN&#}pqKJGqQWVx zCi~^~zD39evcM8@3QE*4U@xEdab(k29ehr#hIbf5z}q} zMOClJB1IW9qNEs$A#JvPq!*zRhFdu;`VJ|nfDqfzixcR&)?Pg{d8ME5NKo0pZ!E*2 zo`jW^ha9d^7N-AXZ!HJn;pfX-4^{dUX-+@MTQl?mHEm@84BTtYGx@*KWXHqA#JkIw z}5|8}ZLs_->o zMe34ON{4*~!Cd!m8LD{bw#S#qpJr>p9m24x2CDHadl$S^%qSeuoQY_~Az|>ST)@r= z7l=}y4jaB~UoJrArkxPTt{%wfyvK9*%)jr`a%-l z94S61h?H=_~3W#71m&y5!8bG&~plgQp?`1i-( z?V@d$yJue~Vxgg3AjlqL>{?;h5LF&J&m?>QpGoo41V0-uu@W8ee=QOjING8ASc0u5 zpcMWeu62H)z0EYZcSZ6>N7^NZO>s;0|0ep?`hM2~i@d;({MVy^P`XF__KuA7 z!gaB!qR~#~_uw62$iOy{ddKSKeI1{X2Jyo(rODkr0=gVKIhBjvr|(2gb}#S~sh@@6 z$zXTKdQJ9oCQ|+p2H(RUm$(0=!i*J$x5BA4h&%u5!v67x;glFn$(O2qGxEfzHkS%1uO{v9jr8$;3p-Mu_@U$d%HsWM^+`MLVaYzM z`u^)H{O1ML2iJG!TYa(*mH&1B)0B|tNAvfMoRswDQas+z3GIaHrbhjL6C%22;P^rk z?*|Pn4n}{LQp$Lf7Ewwiy_q5gWX2J%i|rZc-H`teO!pJ%O^xsW3OVGD2_xjyeBLi2 zvIqLC|F;Ke=aviE2R_+ zl=vk2JZv{Q+C1#vO!87xU-HlI(RihlNZuqxlt@S&V{(O_qWyD)lt7Ls#ftVuH+o_+ zAJri!vozE!CvxBo#q#VcJt*mqSeVbA%y+&89iyXlRZKZIFp=NH=3=-|eoQ;V!GRW} zJO_Z`Z{$4%%}cy+AD?Z0WQmohkV14-oIiPxeZXOel<$*7#rl(?cU4rmm*^_I8Ngvk zmG6NZRg$3>w%UIU0Sr+-2VCCQ|7qeBaoGnM^|?aR?zNxzcE7Rp z{e>=rS}_-&E%CXN?-UN{}1qb>B3epmss*a1z&^xRmyjn_(b>qkpi9L=xP)3e5=K^{m zb;H^RGAQNet0XzE;ZzF>zYBu0ZM#~jdPRwgTUR(ox+)f4DD@_;#HD^kgk+9;bok~h zp@u>S1td5@c^-Q;fa(R?-y7Ev$EVoPtU~UzUxbOo!cXREAy#H=U#F9O7G%1S#>4cs z8vf~td~?n^pa@XqId}w5DS(d$wN}o@xwG)%8{0%W{b!!4uX58sdcZ`?5dd-|1 zr@fC;t2pZ=Ju2zP2^M^kC*T6JF=~jJaQ8X;o`nXwT~xx7^=>jPuln)_Z1T@*CyHWy zd@gBGmQmaWZD3)J@{8f4q;I_)lqWma5W~#5xk2MrECTnf)|MBI34nCB{-uKL$D?$r z>90~UZG)XFcN5XN7uFNMJ10bUvV*X;4O$oX-t12ognJv!hJKEr@n{vQ!Q)1F1FJ(c zQgtbc#kg8%M;v8`Mm1><=gMyeA9i| zr2n*#ery|mAbu?=V<5L=gUZHxy9=x;0smVR8u*hoBjP?{_&P}GXFq#40qBEBte09y z$3XH#NG6MwR^<>Q2$gOydi|(ccFgf~o3oYfmpW-7m15Z@mpLeDlSEKH$#rd~bZ1ab(t1x6|*YE~PK8 zoQ)cUlwbESSx@%HDsw{&l`Lr-<2+es&U89(Wy!fK`&l;n+t8j2(IVQAr_9y;bglP7Pdt?pSn?#!XvDtSXRijvc+)WarB5c#2%^V0CuHdj38(iR`j zmyeq5?R!2l=Y;qki^{kL`o1F+hHw98$UM9``KMQ-$tNIKSfY=FYzC&@C35sF(B#f)ZLlR67qw88W}Lw*LPgD)y5_0s&9`=xvE#jJ{qJ%Y=C44-(O|9Kk9(UynMPsGvb^Xg zj6|Uzfv^F)m}itx+8y6$ z4c4pn_4f-u!W^}$vb0)Wt$MxSr1t(o+ym;JEq6O`0V-$h(lxgX%n{V`*~7i{Rj$a9sD>px^fd zq5d)|qF4c`F@AV>gP7n9;~hIwWoAJY3ebUCgCBK-zO*2c7}(DE+9wioA8H@ufPH!U z&$Y?;iF5^=U8>uZa99iex|ooOV`b@WarBbLC?ixwODH2`7ZgvE3Z$=N5ne4e^jT3(?@6yl?5bnjL#@i}*=KBSplM5w~PPkcjw?fr>L?!-2B14${D zPoo@F;PK&bD8CwyDCeX7;mF5G?~3uNmpfIP({h^|Vk;NLw{6*=a1#_@DOzM-st6x4 z=$}A^>cf8csHHc%)`xGKs(GFJb=QJ)=tg6e{=;6Lw&fQIKRlC1=C|`Cs8j)G`LCg+ z!(;>SZj6L)=P_HZL#_XyiBEBbb=P`|!p3l9PYJ>Z(q2i&nYI0%C^Loiy1o70J%`Ng z>s$VRRDDx)CehYycWm1>I<{@wwmLRDM#r{oJL%ZAoiDb(oAaN4jC=2TsH%s0*kjbH zvDcn!t~tp=Ik4m!a}4VRF6hA+rtl&D=s_ZMy(Ei~(wgf6GgL-^EIfzdO5ccYInE__ zXV#ZBLweP8E9Jx1Sm_Xu;LHD#?`V&53gJ<=@=iILrBs!%G5*?)`C!L5?t^f@<6_i@ z3n|$y{Z(Ld3D$x@M9vGc#4|@Y6zxkGA4eB0sO#q`D9)HLwOAdm?fkM)ejoF6Fq z1;k9h^PYMhR!_W)BdYji%K?R74<+weACPO3@rj}#Lz@dDA5e9sIE4^?Q+`5NBe`Fa=ggsz@?h}nJs@V^cf9=#BsdH+!9 z7MN6XQn4cT5&xI7ac}4el=~|~Te*J4k27u0d^5=J6F0uIWts=Ri6t;0k28 zJ#g&+-GHf09I3Z!b&JV1Ch8g^+W~@zrmEvoaiakeU6S=wJwDcSU6&x~UaAvDi;;UhE1 z=(95BK#VKbY`+fU17@0GK{Eu5lKEk;|8nNOxBV@4>U5Mq2+oM*!r|IdnPI%b9*yz$ zf6E(T!*CKaXgZz2khMJT%P`L?uI=>|YN)K*f3+ge;hvVO>Nost_om#|Ni(f@Nf3DJ z)F;oitSh)qGPYVcSo_j3-bQ4*X-BHEwkZ^*Sh~x-GoWKWC>yx;RyRS?`+;WjDw95! z9AT#dQxBaMDH0wrYwI^HLe9rFnX@eOf^9o!pbF`v7c%Bu8kw{RinyIWtv(^A?sft^ zOm%>Eu7>#?LCm?jYKNR;xsDSzHZ~ zjngrjpAyAz>>%Up7ku5NCz~XuL68M9N8QPZeU}l(BJM7t7B~xh^}v-ha6VkrUiyBQ zs=6%#Z{zj=ni;{l>Cs=hyJ0g%-hFz+*V?zNy!sT%F5Sk~=T^A@^&KVrf#rRZQGGh@{U5aO&r{T`01Ack8CflvVLF z)(BoC<$GQSsBao4HAiuI`t`V7JXPYqJ%?ch8keQ++yxRKYtupX>+~udj;_(=H(?r* zvOw1lFXf|=y5Kvy6KasY=ha9SZd;$PjL0oo`$T)zm{*Onu?B06=MfHw6n!@o7?nHv<R8z(YU7GUhcVKnZ9pH(k$6OmepdA%SrH?#xjYY{1y}xi<(+S@uC46Q zjtjZgzqL31N!nKDOW0cY$iwY9S2dlG?mW#7Lpga(nr5n8@+#ng>Q=$`A<4U}NMG&v zonM_6+l{oQtVH2~nan8fz|iQ`>q2K)iJ)6`4uR%UlB{!k(w47de~W|Se6pLQZ*i7? zH*mC~TBQQNPI&lbQ4n+-p3|pmdkowZMl1m?$@+B4S)!F0|6pjgz65al*?{3wM%BD# zUNh5dXNQ3i=(Vly^`#t6y;CDKWH<#hQeF9T4Ge{}5wky1G9C4voCIEuih(|D4LT24 z>P$hANtB9cPZ(Z|Dg)aAz_j~>?;X~H=j+n}0IMBbL&$pFDY$JKj z}OAvv_QKb?}4SCQsQw<>CihS03=C(N5DAy45A_}!$ zv|q$(+`F9kHz+p0&ef*juT?LN)QclA$b-DMj%P4Fd!QJrEz|eKB27#q54aelwU06& zb+S&vvqN?Lp@#nCEMT%?i+WN`29U+xYHOGgIlOMO3cY@phCw^U(%T2? z#bbOCN0b|;ZS9q)-ay*M9}~Y(Cc+FcPIa9hZ-dA-oTT4JT~xMe@g6s8cXu?>u<_>j zTe3X8tPai*M`U=G+w{(si0kA9(!I{sT;vFY>!tZ$3FseSk*mwnM6F4OIh&Xc|(4V)`Rsov_P zHl0ra&ogJXYfoPLj>X4!Tw^eq?$ z^YXu-jKn=Q$5fOEDtvMk#vk?8pLxS4*W%jnS(L0);XArPrXg;=U17*}mFbG!3bN6n zaMXzK{Lp3m^X1q5>!`U(A<8W`c+6is!N-|aJWl2*w9_2=DOfh@gX_ESmV3$we%n7u zRY39+2ONm*oz%M)VdhZXqw4w=5tbe`TKkttlvI4;M(Mj3tmERs?x?$e*i&7u)AheO z#6>5bLC2oN;7}aS#-(*SPn_5Mk((sm&67V~kcN@&3)^ z&vEWZ!1s=!0*vx>$|aX=@T0*249X`T$SaY6`)n6cg7{bY%Sm@dPdyc`n3@C1bwtPO zIdh-cD*x&+q+k?T3oY@fH`men(n&G=p7ph3K)iILj07k1KDX;wlLym&Mc zrQTMv@NAG`i}Y)F_Otzqf5*7N6$g>t=i2iYN_jt#FD>(u+?WW5CNVEh3}ETG-y_v)xXX`KgWlGmla2KTK#|T( z`HnEdic2<~l7E7zLy=csPKMW5(oe*h?x4}X%b_y&>$XfQ&R{J8K!}7$x+gJjUFx4FDe@*} zFfR}b_?pYlc^lEd>iijNP-zn;spg|@X=JlW^vU4oFXQ5;vhfKU#;)#$k;tD(;tL@5 zzVJ)F@KD+Ei;+ovDE;@Q;`>Od6Lg?f@fJ@y_GWD`=%;Dm17>74qo?hg7j+pLx@aZH8|g*UHN%$wnbgl5@aj@@Qdw}sc%o*mbfhUapHwIjclA!??lMHZ>Tl9{bNu?%(lz#{`tFiKhg&o9Lq=2^#OYc812{yW+oqs`FsIB=~OpgzTIFtEnIN{mPAvQ>;fu@^O%9;824%c1wMS32^xuy1 ziEg#oUJcT%9Y6uonn60@;RR)tE41dA4?^GV0n=~6`7Ys|cLTRW?>NIJVBg21m@1%9 zD}JZ&dI?RUvEo^;;8oJq&xSTPVw}=NYup+a-yUy}9`bN8p4T!c9<*HlX-w$wQrY`5 z8Wfb-r1kwaG~-(~EQ|a@@}-_#(W-gP*{U~q9v;7d-Z&Y8Np3|{j)f{R1$>bxB_ihK zK^>C0V{?Z*hb!7lQ)xkU5Xo~2^$uM6N#AD+^OC?|8Lt5-g~7TG$1%V4n5;z$x}ea% z@h|doJ75R(``5|{!|;&)k&qWj*Ze-EwZ)-cAAYn{XOG{abY;WN-&=Dk)ds~NrYfv? z`^F~}KL?&+tOY^!F->|*G1&*AusF>jpjSgY51?r`dHvYp53WE$8?6OA{g3u=&YygaSy(D z-N~`O7R!$lc?xAz?B$Eh##t6IZ-W0bq0%sf@GU_Ee4ZK!^S%30JDYv7&mgudi|IAP ze`f8ptIG6xXqv^&9qeJ}RZlfE6#fg1~mKUXA^&dw|}UPpVrx+gILl3(M%%J@c1-J!9IHg zhy)20yF9seTU7Ee%(!kqLD9@Pc?}YjU$!`1H7$W>x8g-`WI#pIMJ9}n2NCBNjvZm) z@Y(Af`Z=AK+^+2m$v|V#R8|Piq7fBR>qX{&+gZ0;AWm}u$5}mApK>WBTu+7N(fE1< zp`r9u*=-T;L2~(tPvL{1TV1SJo_e28y3*7;AA!)fh9uu=DE~J&uath@MP&O`IQp0V zmcF3=AO8P;FY0Bw*#y^5TVC_Nb_!t#q^NQ@mxN>qnyQ(?yGv{Z;;mIs7{6j!EvM~$m}>r-g&u&w>}&c~#VGD~Rv z8~@tN-g|_f#iM>D_fx;b!r--d?tTlbPrpTGKH?F~Bs3^L`8R5N_uXh1=T-dHUTa0f zP57Gh1m^;c{|8x8h6IWl?O+^qD<`YeQ-P}xhf}fvn&|hArSm%BZ*VSeb9GNORCkh# z+IZp&QL0EsZe1XBsaW~56Dd8*-4rZZP+?3v`hR*eF7MfjE0?P_AFI2HNRdvdBU^s}k#BXyqEK?jXk-|FSNJYTJhDLGVA+O3gH`(yDYO>Q=sy;2 z@Tj6unj2ZplJ?UBZ;)`bU6C>KBzV&#l+lIk3IEF5Bd1@)j&vt@MOb%{ow;k9cLo^C zIAPTX=kE|&`_6m^TwjJLY3F%lXn(qcalb~NW5`$A>gJL&;Z#Cl75l~gVS4@Pq~Oi| zfF$r)F`A&4av`O?_PUm4$sQ+hQMiQLh%(K?;UyuRrxwM|MlgmX&OXZo4N*#%gonmYyEv(|WYRq9=oR@W_-{ZR_bN~GB zp4`l8I*%>%z>|19^?{?28`zBWwy^a@+0)xq1ihKBiY7vIAAJaoiTT>cq;LbR&!_tf z?=8Yd}h@Aw*032Txp$8sZF@&;ljN?{=V38^p-eK zG&70~`DIi(rp8nm^Kj4YcRDMNsQCqGfM~qpQI<;E00d7sC3SE;bk=0!%cxY}C$}&q zH>^g1&aA?oPad~5gtWU)#tUSjpYA(s_g9{0-LZ4o)sg37<=ln1RWPDb8R~V15&3Ez zIszf$rAmiiZ;xA3++7g4bTzUfE4(1@+f|JGcED{uxy&V}35DzA*s)vL=fPUvwC1zd z#Ofpw^*dcGoc#7~BP8Lqemnc7k2)K&7({X6$efH!yWL0cK?3;_%IwFZ z8B_@$o3O@Px$cQGBX4V-;4KU{M6Aado#%%$6h7SALs*d%>b~%%QS1gvGzPdrmb=Qj z+ZiYSUzVpgS3tqxOq|49(?C>9?L;vxR0IFB%3uN4Y?XRUdrb3#GlnSm@)yrh^s8!T zbG^^&QLrko4hEGbVP`bM7&(!%CADfW@yJbJ^?tcM_YKeHbK8mk(gHJA2E(@Wua(O) z4jcJtcGDHTD#PkZLcyaSibOfJ3d4?J5;#VYV)3r<==j{e0DjV zQY-8``geT#F{Y*w`Jz&Xq7f)%WotKmB1cbidY~6rKb@1`K|h07Qa@_dn&4__RIy_- zug86wDt5OQczYD9WlehCIck;f#v8Z(M!*d?qt{7ARpB$5h1DaRbP2ei9PlvKs0 zMdBG^+?z^#029HlIx(oZO(0_+XMU`-t-u~Q@Y?a?-RrDYo`bP6dM*a&uLE|^!7p30 ztpPzrDpROZwhF}b9S92p0A;$f<)vR+(y^V(-R=;b30@e#r?Z*D#oYlNU}fU8*lawK5b-`GmE{X9{8#?9wn{VKv*@XMgy5$67HKL8H? zMP$#g9s>bvBp?AdY9+eYaooM#8Sr+~h&7NA@V~M9GJfQYwcyacGB|qV(Xq@2*KJ!=6BZvbk)YErY5pm^FrIPa-!wOBIMX~!ZyOMWrvVL7* zSocvjlVjPoDm1g0&Cxj5fazYRs^wO_eQl9HuWOLWVS#7xWEhiWg?;W=y5KwE=i|6a zujryV@5r+Osdhfim_m+XV+gbR^In~gN~mZrd%-Rwo+O6hm>^$Ia}a`eR=IrpAh&EdL653SySs=CX2c_{TaC10ll(U zh{mZuL_F2gJJKGgfb~*)(rWKs_>gYngp%L7&2@#;k)ARHzCS)TL9Ltka&_0~99B$R z&=k{n5AKD3^l~Y0jD^m$4k2vVA-3mwy*xN?0|~d~gp0(|i^0^5!2*v8Hp#T_j~+0p zKzF5|p9?V=lzO7P*SV2A1Lw(dmd?sba6)6=pr1FRcxnjRh`vG@HnEAiSj= zzP46i=7EOgVI`h_=3N65P0PiiKoz{U`FJYp@l)n`#dh|#nwC2>x?LYiD0%wqU{|*i zqIFy71d2uhb2O|bT1^IoJZa~ zB_EV9-#UD$n}H+D{@hgX4GlkT{v+acYzPrVfN#l94&{Ogg$Wsl&m1@n#LSLQi#@FC zzDQXQ&4qc4zv;+aAw&?hbb`Bf`UwgaXaG9GAPwbdjuC8Fz5&Rs-wk$eQCm7zkC};; zN^DN*zVLH8qt7+43iG}W9myi1>QF{|!fN^l$f)=YC+FSuN33VEI#GbY&BF+zD(CZ? z3%LPMbMNiZ0j7UQNp!W>TzI*s7>kNw1dL00N9psMQ2VPB*z>S^fuo%hbGBsgLLQ9u z7oEud%ISib52Mj~a($kBU*28U zql%f50Y2LVX=K({F-TVtmDBBRIGlTIgU0n{z_TpO8(%bzPi9_y%VM}VEu()HHiF=X z%Z+K8C*tF0s25qX=nBroL64en$hgw2wMBd0-Li7RZK;_!eWtu$FN+0FZ%450&Vo!d zCQUjOkV!@N6+qyzuUOB&wY=+_0LkwW+~bt4U}mh+Rlv1&S@laqb*-)i%ivLmQ=jx& zpMJlQLgvU@JHq^N&DH4c!&F4yu~aMzBI4%kK08ez;_=({vw7Fa0uTC%)~X7tX%Vf%_&`)eN9_`K;Pk3K`$K7fyh*9|LCI}Q+weAw=i`3)lM zVYZ`3l$s?KTM&*fB&vvnrWzR@ZrERoh7PWz6ha6>0!2bqW+HmN+%R zCOG-QvX=VXGCe&#IXzu6u!R^V@0#tp9uRep6%s5hi7q?p{h1)#%VBgUaj%8K0=k~~ zXlOR_#$|6?_ag_*Bf}6n786VxO@_Po$&{<|NdD`Ea5}tb&|UYHXIR z!$^WLo80Bp+s%}M_N1((`Xoh+GW!@RLM}-=nFEC)uRa^C#yI3sF8@N$7PJD0ZVK&C z_=I$f`9e=sX{-ig+Lw&&24_`YIgA2L)@Z~jyL{@oS*40rZ?ATqb>~DPZnOY=FUObhpL!+h2$4OQM5@R zZu0*xCRhzO(K;ZgaBArn5>7&~ju+SWIR<*07^3UgMsE)t~8U;wI&``$H;GB{V)nN;mx^B5YF!+f!I*N6! z#;eOKiisttC73x?jB3kJv8R)iIwM1bMXF51KdZpI$g)t_v418lJ%sh*=gver9yMXM zsrsVa1Qn)EtXU*1?A#u4aAP7Wro|?u1(XXZS$riVceV{xz4vzXE2bMuN|*<3Tu*`y z-hnEalCK!Xk+l0DNU5=$FZ1c@?oZ;OYj4ZwIUVIrS9BhJxw}0C5tz`D-q5F*;(Ja@ z$-4`-U7~A7LA0^W{Ic_El%w7uC^w=$Q~GV@_u%q6(`C@u$1V}}+E_5dn~)0D2g6!? zOUHdrgPr0;QMr`@+kWkfp!d{qp!X!It|Ap{LYBN&$#?&?9uGh$&A;U=jqU2Pg`r0* zahh>b)Za5H_!A7>ERi+k^;@x*BoA=`cY*!5O&0j7W z;!StCDSG6LxKd|-0#jD*EtSSFP@OVHNcT%32=F&&KpGpDMUQmusq!(>B z#MCn>5tx7Ciwx*+Ca#nzCD{KM2v^-AJ(>FZ5y&?1__Gyf^wcm%9JxGl{EZUOvh%j( zO@vLmGe<_r3`TxKrtmu&3q!f?b4TcdeOYJJbgTJxS_D84!?05hGtm$!j?5}jc(=70 zGU}OG^?caFeLu5tE~<$MVxOlU3n_RUNK{$1>6PkxUdy;lt&4_Yk~9_dMl+`E-nacq zNy!?asJjOOzeE{&?Hto(k(yX`YLc2xdrMBWQ`PgPcI9_ie$s$Wb^s?lD%A;j0jK{br+j|s@M zb#y^F9Cs22;rHkn*;WU$=cnA_4iiK=!oPFyNGfDJ8T?cJjq300ULQ*HM zpdVG_AUKcz*M*Ht4_u0p=xjmKyH{-9fb`5)(hD=e4_vA@x+qs-9yb0V@sEkpmRE;6 zW^q6J=|z@KG+9v1KmEW1Ev*zXxp~c$9KXAX$7>96==XjUY!Rf%eDNk#(brK??h3Ai z9)j{0R|q#o)?>!=xm?SKD3G`hD2ShFB-_5+KLT}W$+N=61 zmRtQyfQC8)%UCXu`ledQR4`-KAE~wacxw%enc?RMwHLk26Uf>OvIjZ1{R>>hm5^`v z_U&a3Q+!`*FV}IH9d>AQVKR^0lFE*UQhvLx(hMh;ees|lklbV^3hz-0VW#{ec#$k9 z!VAL<+pg*gOI7dYkD;!d77=7k)(vFL(La8akA7Ag!1DkftJTESW~uZUaPKr#@WxTe ziVVoXM%IBE8*OM(SJ=xvxR4lSy%Ml8Gm3CxH+cPt$RBh$S(s#3_h<1@qjl`(b8d4s z91^qlK7Q0Ep&;=`tQlY+^r$~4&XPuGPKpP}ucl3)iC4=H)>qqiHo2W)x%{U+sqhHc z#7y7&MJPciD_|fs2^5uQgf3(Gc&xrPVJuf5;akuML>;Sc1gXRB0r^}{IJySi?DLO$NI=jxi0)F zOl_B|s|V5m`k4s`d=~mr16bZ7TX37;45?(Df(De1<(~lpHbY_FnAg5@4J{$vaV>Z! zCaX(Cct%wNh+bWY87u@rCI4{dF!KV#4WN_MR0b}d%!0KbfG0$sCd{ieLPDX^k;Rf) zaD7oViVO>23p0wWKe%%sljX^L5W5Hu%k0xXxajXZnBKfEp)}eWt4%wg7w@N0RwtFz&>$PM0;U6xmMQ zy-sdTxJn+8DL*JtBSj@o(Xpr_;+;ICI5eNX8ZMrEdGc~(L?bp8exWcrqfjms2=q=x zV{T8eTVP>P(!kUV7S+<4`AP%!;;|ZE7RR)`ZNPzW6DTMLV^VEAOB#y`9Rd-{ zg86bj&Sq-9t{$RXEBd5((fg6svE_~X&+YGC7dbMAeoFs6Jl4Tj?~OIun;^*r1J!R)LVI7h2H0c}*lLQHT`$1Z89#_A90cUUMmI zR+=IJz4EMZZlen*D;p^8L<^WXd1wps=Ht?*W;7&T@_%xZ=CY5QxmNWlIL94uP?}vl-=eqc1V{fP+*s{zx*13@jdlnqiVB zzr1E*ynt5B;h{{0~Od4S9MIr$is0*zZa=%Ct7q%T@Xs0zhJ;G}lF&qe3HH zeq?cZ))*q23_be|#P#pws;m2Ps?$L!=frl)3~)mUu9f>%?&H6=O0GlrZ*kuAjPf~V$RTWGykmrG>gH_>vF2*|i0@d(hsxXE50Gqqw zCRtS0;=eGv*lYQtMNQ>1PilqkC51_LXh%>xGU|Yhn4C5yrfT9I)FpCTy>B zh9u<)7yiZBldvY2G}L(0`2h%oVWmEBhaaczy$dSxfA#n$4IHX9lZhp&C%8m<^9vT9 z<{t0@a;QDUyyVHZ`Jmi<oC{om z3X9S!tWud4;1e{wHO7>wBcuH`9!Ei&`h#C^4|ncdE+XK?Pi~A{Sk*|OtnjQCzLH3l zb7GZ1EG1;>$uwzI8EjM(tWyoZdGgq;-g300(27KNvO?4cp`nMNn^iUY(4*Dw9o*!y z`}=hl;gye-44PrcipOc92+MN5-O?_8>W&xsJAaU#*Lnws`WotPGc;+XAPHv;|5y>_ zEZ}vTid}L6i~bk+AI(Dff}d4;;03HDy$jN#fleva;7m50CRGlPZ*Gm80sXkrcaj77 zo?X6V;VUb=uw3s0{K}8W#H(jiVo*deaHL zpR94VetS|&b5c`hje>s9SS{od=yrNUn}(WC^fMuh?=mBN?K1muv_-$N_XtmD9biqR zm@jD3%3NhERyyrLr-V-jBuQJN5!>L2Ia**8rSYF~@w{qBouz9|Uw>-khZ{*P2kWh0 zZtfu9iu^&RN;!s(E%waWAEaacniFa*UY8*utctJd72vyp(gaPPbKzx~m(=>%C0rjO z%2CtmxBQylIlm|UD6GAPWBcGMbsu&;@|Hd4jmg+3?>&cydtaZ1Ryq4r$~P7#?&^c8 zrVd^t=uCr(fp5ec`h;ea+j)I!>(6FAj6z=#RKXlXRbe@7ld&Hnw#4oE2>Oi1`-j|` z0f{-g5G>x#o55QZV3vk48VwJ1jyEUtczuS=IdT zCUr&d!rN7#UuQ|8(hL)lU^1Atu~>5AoHN|WIYj5(r3w0!*{~LefZXKEfm&A(5faIa z5}bsis-Re(UD3q|8q>6wm&ss+yLO%|>sCk^Bw4>CjtEi!QTh^Nt=1{vS}u4$hl35R zFp{`DPH78E1Jgq!OsjP9b(I*;LCwVp;kKa>!sgNbAm7E^n8A7L95=$DczcorU+f zy^JR<1X80nRk>1Kh){30SW>#!wuZpMASJ)RIx?W zvBYx`--_8x@AV`7uvI&7@4X`$-o~7N{!xX{ummiMiVbHASD1D%D?%sMsE)rSKh5W_ zGGccOzHQU1b>mS)15dO%c%JhX|@lutH*IlInP}((-63(df`FD-{Dp&dq$ zZ^`93osWZJaZzXBOcqgoDRyj+hv%n48>lV`F{Th>>K&Rblk5Cnn z+NVPtCeDfpgE`UysAPfQ z%zw>stU5cXEEpaCrs^srZzj7_UxT$U?18L1n_1c)+6m3Q zjV#WxMs`-id=egmF0>K+$|R^~fA6v@yhi`1330TdOwETv3wXFL~oy z#)qZMZuGqUsvwzw<{fil9YQ8UcDsMY;--Savq=u1-dC#RZI)SJv@wn{l$1vTk1v+Y zK*Q#@bvR1ONe#U$dtFxWyPnXo7AYOtbX308@2D8rdqjH)3?>Cj&kvAQENWUfBEVlQ zmoQmgp|c2L&&;Xg@9g|cJ0#Pb4-;#~%a)*52r-`@Lp4c zqQ`fdi%-?LPw}4N>%dOYvu&inLREr`h)AP-B^Z^}*YfE+=I}K^f16hyj0XI!bUj|i zWmy(c`bt*hAFUG-e;He7=SvajE^-uq5PP9%`unhGGX2%f6D~n~Jvf;DjxkUIVf3hy z{|aA)`KF&X&N3DLp{}X0$$j{$a5cgrABoASPKKR>0?IU_l)|D9%r|OP%WZABYjamda{fzp_LuMcaBCZi(*CjX07i#Rf1Wa%tKkL-41H6K?s%m)00&Q z>;t)IEY%GQ#GUu6XMbt?vbRdRxM)O+@f>`mk9Q}uuE4rHRR_m&=}m40zmVC!KM{>o znM`3$ch^rU5Z>9vnL<S7<8=4=zxdr{Gq zy;zK{O*UUiruWAq{4JpD*ljdx+?lt39IlXnx2tgc_RXNAn56O;ct&?Z@B9karwu6W zicYV&tZILYGEa&ER|u1<J3Y$MJ%79Ij1*}4Tw}uIZ zFsyo+#lU~I-{%KG&IJKK0D42Oc&8V$bCSq;}yNptbArn%!?0 z-~B;wJ&pjH!^K37JZUo6KQBQCU^)DM&$-jx$(AoADj>f405_ai^% z!tstlP|}Oc#?r;D2=oDNUWNFr8PBcyUY@k+vki>7d7|yZQNt53^h>4L=zo zW-+8y4Rq`V!?vH4YLtYu1;0Q>Se<3fW{ZFH#nlD$u(|R>c)#bCwDAJado;qDiRzS18_x|E?+8FTy z38oM!K^8h|JRj0cJN0jaU)`HQ`CEsx#A2`_iI7vm_um1jDiv~CeAr4Vo`b3`?jHzijgM(g_-i|(=S5N0Q@{JBTHBu%|rY&cI1;l2BP z_LV(rkz=`%(l~B{rVNi@-sE!T^c_5O61+u3Nb4ih$dwAT%ng*BHj<|+kB9v>4PX-7 z_8+vH^Ms^rq@k@tDYYcYaJB|vIB^TZJf3dFgy?3K(0<_xOUgyuv_^E07b0+c_Z=j! zO@jRB+qj*Lj5DyjS5{*US=z*P#m$Iy7uHz871_z$o^+8DiVx5kbYrl4vy1g}^ltK> zdzJJ+uPCa(+XYqkRu--mHcKEm7y#!dUL?>VTr(H{o&yCh&BXeKzf3yng*ud=7c2vI zA&|k!8j21+{mtHbLobizzMT@^A=JCfL^BSlwLsve^7b$PqD&C7*)3>fOZ`2Jrix5V zy1x0D$BjjlKvEt~CQ_E$`|%-uLS?Y`dhK#(z58d?<^ZrR@AELn=X&0c5QDTq7Kbbr zwhx{__~Iqw=LpF7Inm63nvnOVaMpEbSDIsGZ^|pN^j@d@`b(^_Jm)eHe2SpQn}3m1 z{von%6RBzto%3KF7fd7#%_I9NYg^E1OY_rm z>LeLvaQQ|+-Ck^|a@(xtq*+;CBlz`CroWy^Rw^fzL+a)PKGD84v}F7TV`k~?@7l}X z0NrCReZbKY%O^)(!5jL?EK;TJs{#1EKV7efzz8t{kl*r!(9T_K0z8G&YKXX}=HIJv z*3J+yg3}jv80Ww5d$Y@j&`D>ZC zoV8tlyq--(Wp5{`cLC(Y7OXCf^^cg-4XPdK;w%ocKbnYz`j?Niw7P&xT3Y{z$InW+ znvV5hHvL?G4d4Bb{qtR(+qX}Sb>v))m0^$J5vFN@{l}iC<;4HHcu2I!{gB00-`OIG z=A^JfT{VhdtV2p&f{u8kb(VCkVekj39Ch6{`)|$J?`#N+B#w_LBVn6eMILu|S$?cV zj91ce1B}Y>K&<|jl?ZL#b9`T%%pXY0U4nLvdf%<|b(UlUukf@u31%1B-hXfZ>q81Qvl1+F7j04pD=zf-N^a(p^4(FcrAKPIEwsuOu2$0Lr*{;Ysk<8`Xp50EJKz#Q2V_>o*w?}`8aWz7d}z;Zpt zb)?LXXH=Zw&H~{)FRnR2FSv5;?UNq0pPG8Wh*YJ1@nx6tN^}}+cnvKMPRiY9Xlnar zWN49%ZO_Q%9*`lt)962m_=SDl`=5`hflD^jNrZ6gO1u5VnN$v>N&b}^>D60w8NMKf z_Z6c!=a$^2i}v4Tyl|Y~N-_21q8;2d(|TqQlFQoc{e6sjAq08q zfzr+u59JW*hp-8H1PNF2@3Ov=XDhl%XiW)a8$WLo?}RC~=a~rfe7c`(Z0Jqt${FSO0ZHY0J_OS4+G1cJ|(0e+?`l zD|wHynjk?{k}%AdV!SXqN#+Sf-u$wUX}b36$5s_94kE2MHmWx+%}112-RHyEhlyH> z)CiQ&v6bN*(K*E$uE8hg>oDYs4$MLgj9C_^_P)I%w>s#kmTA90 zdpv#nyksvd+6*^1H*3GVr$=eeyhn!jMFWE>mDP22Ciadp)%hoiN4W`P8Js_<+E_eM zsCe3eI=4K0_LZm}^k?|m)74q2FtF8;AsFul>PT^t1L)El!?w5c|H2{WI?{^FH|%kWs=iwUklp;X&5d4oQrr9itzRGJi02-W$&Kv$^_4j z5`20xcP={2cMK#J65=DKH4C5bSY_={{vvza%Qe(Q)gRC-^g`IU9JmneIRru zNyQR7(~scrV!GQF-7#=>`d(}&lOn;1G)qz>O;Q{k*2#2bqn7lvS%=;^WV;Ca=$1%q zg$Eo@P?Ri49DssN&1N^B{B%vLN|5-`D-u!Y7Dd)FhsavJZ3n)|>iGHr?Apasv;;Vr zP$WV^xp~z>ei5CkrxgC**S2oU$?fakfb%{-;=Jc5nI#yRrwN5z`pfaR=JmgjQW@#9 zTPkQV?`zw0_Yd>G{S(_E0-{XJ?tbd`eZ-bbIr*fRt9E#F1*L};Fzr780^ZHT;qY)r!O;ooxr7Ld!2!B znU8I1H(~pl%CMq1JQ%?cdLsC01H3viz?M0C#POPWaAlqpVP$4SA{f1MxEJ_?d$uh4^bgm;q$>J1d2@ppa!u#lilJhQX_#$yucaAHUuqc6(2LD95zrhEB%sKG zc-%Kdsk&idBCOl=r-Cchr<=?XacS!|%y#|7zV+%A1}Vx7M1|5U*NkZU-e(q(m|*O=)7S z)~6iB1%QvLlvJ3z)pqKqp*w%Szgf7{49oNa#WB&T`g0u`;0anIfJzC(yn~qsax2%a z3Z>_IF>}JvwjLW+etz_!MtEH#k0lw0)7{?*ypmVRom^{uzXgGILV4?EAvE*nYXxyl zOZ37=+bT_aw(`$L_o>mFS=GDC-m z$vMD}PNlzh;D`~}X0)lD$)proY zmeF0uc7yNB3YRNioRncPxon%ZrCn&yC925*5R@bW00;_DsL25rh8!mtx@?V~d~o#l z1_A;tD;g^WE(##!(H*z7Pkcv6s)RAGqG0qte&xxxHGg#f3Co-H!?}bvFjbsxhgYNk zFR~(`$$jIt(n6{MWp~%6E&XU z0nKtEN&!DSip%<8+u~{TIL8T?<%!5_6p%E>36h`*vO~!Q%J{(^Pkv92I=Rofv!CDe z$v8uK#t25}yz=Jyp=sQEXc~QfU5)79m7J;1vVAAs`ySZ^-h%OoIZ{tas+8|{0G0%V zs%aA78HY13-&L#isZPr;R!V?0(71nWet}PzL#u%VyR+X-4G``g!VEN8Q%Fvv89-A5=B=J}=CeNv&7Bu6v&Erpv8E&xn^)}g9UHCJ zXBA3-X45maLpSXkc6&n9opXVt2$|tnN&yUR=loBsz_i4+?&!-4e$5r}3Y@+Z$T_S& zez@0@Xt$c23R)M;#vT!RFxWb}gVpMQS)pDR5oDH19n zGb$y?63ydC@dT!b7i^OB!k7o|Y!HEe6c)hpk|Z&Fz;%L}{_N`SP8U<^cbpG4uD;Ha z-}uoIbu^0zZ?)~i%v+CsG(D=WCaN3@WKB^Nkwta2L%OiK99b>UbWxEn*_Pk+(X27= zVFe~xlvq|~2}#x%RA8!Ct7JA4v*}gaQ#rPqx9@3z-!&+1xDaKQmRM3GIi5UF>CGxn zbvj=enp;x!fxI2jN70SLRS3(05|^fvU@MFOg60pugYTpHqWZ9g$gHqlwU{pz<6#~^-e@}>v!%Ny=%NK2bf3OvrRi@)j=$s;G9IXP9CFJ*d zk!#Nn-Id-!|7>0bEm-r)_}#n`=G*ov*+Xg@^PbWhHm=#}8=q?g=T}PP0n|L+K9Z8_ z;8mlU3?RzBb1O=fS}X4DyV`~4jw@I;?q z%DJIHrb%AqNJ;^Unv=U=%45Zxg3K6b+Ae~4SJOr^NUfLjQPEa%BQSzBn?wOPICLemU_6Zy>4DJ{Dwi%_6P|? zmK6Y_NGdz;V5ZP*RjS2eQ1PNQWSeciuj=6zk4Lq!5-Klh8binejjmQtEf3;P+ZvVh zq3x5O);!rS3-99~hyo|jEYAocLo*Dz_p#V)G$lT=y)v)UuW!$Y>JzE)vLLXULW-&w z(I=8Lz-YN2+1l2uzjwtw(LFImQlVu+RC!v8SnB5nIJ`r*+g|$S%x6Q-WZ-qMz|!vn zKy@&j%F(3AF)~kn{_^u1ouhn};1B|&1T(h8owk~brrdJ#FD;^!ND~gt>v8*mTMg=Z zy`4G0=STIs?{787C{KN2YhQ14otaxXylGA6yA12N+yROkINU(H~roM#b4l+ zTvo}O1N@J5uGjdxFCVQ2sc`-sp@QG&L&17o+!4gz=~(zUh^N05#Qevh^?zAkuhvvE zT~R2S47+VZ)jwUik2S;(mgaUas;m*3sIe@|-rXi~T+=EyC%`dUCxjBjOG4zbcH5Q} zv^utaWkz%j{F3=wRw$W~IEJDa+L7J!$yEpDns5qu4Fs1JYLxJKtoh&-@V&MRwlA-1 ze;=Ob5v(Y198YqBNHJ^}&wE(c*;-*OF`wF&zEJ1bE$Q$guLzfY=g}hN(12gb9VU+h zieKM483OWT;2kRE0rg(?x6?i6S0XVD9;V~5NZMrZJPzeMsm-Q+wsT8Yy?a23x`n~X zlqLx>Pw@nS+EF%l#AW@Z?KP=ezx9=)UMj8-G*2rG!^tEa@gDC(UzkHM3b^M*^*CZ{ zGW;3HGMR_zWIQ+R@AgNg{kKY%hk+G4saOLUbYuo?c|ET@?uVQpLQAmMg2uV@^FQY8zklCoP6Sk)seD}t$ zGazX_fm@*|GH~;H@5yclm?SCwON(_cDc@J%0)1EI4150WZ&ILj-Flh(7O$lLZ2MyW z-_qMFBA767t&=?-*Dwv8dcCn7_>0YZ-CwJj(ERF(rWO^}?*3g<1EzyH{D zN})PgTEE&45o)etpb=I!NT*`jXu{aY6AdiM76aL|Xvj|63T!VgZGA^n&KNkez*7>C z0UdV2L&a46w5@m38Vi10A2nmZuri=1iB{RLU1LvE+_?W{)87C4uNUURS^9}3>MRvF znj<-yqzRcAB8}|7ZNc{RlDaHCi}WaD3|lepKd$3FG3Gxu#tSmm#W=tF$GB<2A^$f- z@_#-t^gRgaF}ACRIePg})6UZ0(+qZZo{F*DBN(3yON{+zG#qgJ2N}F)E2qsX{3>$J zmYP}V=k1013K3CX(PWlo$iP+(Euhv8(y)vRwqJ(Sdw;A2@5@Y)Bu|SX!!VRc31RNE zHTWPtiIvCyv7PwK|I%|O;H2-4H_G#(LNc1bagwNf`Rc`yE&H;G&5^o`S4fl)F=?@s z^}DW=^|&r(UCU&ze$YCv)nurE7duuJIMOGG4%wql{%y^1_lA?pQ;jxjudfw~jrDD_ z-SY6$%TDx+$)}lQIa(B11+a>U`ZQOIb;{WH%KLghw5<)oDla(<2IX~VkY*h{yk2Q? z9~a02Zhw|v@&g~b5h@%G_Q@ez5JR{3o;vO0-QZK~$w|ZMhW+6(wmG6%hz=f&$@X~1 z1Md*vmsH6wwY}~79|w0mo`Rk?u3Ad+scK-6u4(Xo%@{4>MZ;d%o_z52%`Mts&j3&? zL2#5viabqGLfCmhO3&&iObM0k_pf>Pw0}=@jgOUOR^cRqR#{a1z#leFv)6fVW&JZ2 zXt!x=j%K$J1WhC*l4I1j`~&9}cjqqcVXlfk40gHl#pw-p#(Ww zAt)@((5j{i?3(j8RG$0uyd+PpndnexkpnC# z3NjTIi$dvSv?SHFFWgg`IJmkYV#Yx|C@`cC3dJa#(QyYXfi$rfyzt9|3BTfk{jf|7 z!G4J3P#i9oG_|L%s;oXUC`zBw93ip{#ZoN63t^!RPPGc^K9Am#9I=R|_EWPy9Mh&@ z;a+BuwpGj_iB%|8<0y&dWa$3xQ1=Vu4A0RrD=8$evKI#2xuHohKc38Yf>Zv-=Y#m46IC!1qPe~EV{iZX zaG|D3;EmU$q*8)R%ai~Fe}0=8FT9&=&OqAGBxD?zrnyLG67pMIchyXI(qf@}%>pK|i(Lm{Pdq@SXmnse#M)HXUEU8s1yk$BkQ^*uL=aWP~_sd6d8eR_y+g zFPFbCaMAb;)?{cy_^2nQAVKl)vG1>4#0uhqU^&&O(p+dD#n z?=a+nHNy4vi@C$X-G+4JgxsOEI54ut?3FVGT6E85+U z`rnS`JzCvhfyKRFy_78}?fbYzg0k;fG`8M_B)Ffr#1uhxWHeG$BwiyaO@p#y!--oK zQEOf-JS6{FLMS$wBByhailFRi5i%7~7cDRHKFajl6y`11Yt}WtLp&G7Etcj4f)y!} zV|jriP;PzKA4Wn`XZsL#akGb9QMb)#iD3yDFpR8(Mf<>e!S}HDexT;SQNO`Sc}t~8 zlTsFFn$Sd+k~Om0Il`^9ezeMWNS+6Dyi3Qa2sUBTrV{>N(QvibFAi~tLmc98Z9vgl z+{^y8Sa9H#r09`DWr8I%MN&DM;bLFsd)rmtFW+=H-6~wnFCLYYW_dyZDo0S%rDv+s z3zV?)ZUvUunv`G~Vt;Zo_hPqau?|H7M{*=h5Hv0D3>79Dy`bB7Z^EdJL+p1SpZa3w zM`LhMWU0WF6>K299)WXEJZ!l#G=P>1)41nGaIuu8lZ!D1(j8*=eE*wuaZZy6N@GxG zEV??QSFYrAU%EL1RUNBha5(i2wYPZlLPi%S3~3lC_6yDM4qv{@>CXhW6*#>>KNvPl z8=wNW-#zHm#o|$5Uat{~W1pp&_I@{*pZfD8biVE%PI5(_M?){|Fge{hS$;`&x}5&W z0sDS_fgf1C1wKDe^P1h@dhONIsc>GN(RI@&5rt)xITWJ+`*acV{Fq+xxApH*lS6Uc zKlepX`W`{)?mn|QTw&n$bvpzFJKX&;??N(v=Q6iKRp;8E^**n-R3YM=3Z zgDPuZrSPVUsAHZdP3{S@{E`c#oNm&!%3ZcXlLjnuy?(#Jqlm`-CmqC_bUW8)I$i9y zgr+O1UoECv>~9XMI_Is?kU}GEq)~wbbbd*8flIEr!==D<_XDr*r!%iL-Ck@r%`zm2 zIi_^gxy3$4SU8~ar!?N!tk;MSoL}mb=kx~x{ewKGf7nDfPA}Nh+yw zfqkk)kJ*Q2o@`$gcmGo9$6d8A{I1uc@7Bi>`4!Nzps`ki=)8dGkbsW45)afm}4u3Ed;c>8|p+bQdP(KS1c3tUaZ1*F2TKq22^^S;eK!BV~9fo(C| z+@y*kFN(6Nsj`f_xrxxOY^R~+h~j0(6x-^Uk9;Um+vmHLHxa*FT75-2SeRO=I{vC{)2FGa?qP->R*147ALZ8Du*gkr{pVrfv2zmX8v66j{Xb#YR z`HhZm(_cKKm)}Mdyu1M?8jMu3hY0&c&p#0Q89$& zr4>#SREiNunQL5c{jB0)d6$*Z<;LjylI9QJkM%pF_aO~N zM*WM-NZNv_Zb<S&|Y?6%0!SjbmAUDF?tf)4xzZueYx1W~p|y#iolxOpp}sf;e0TdPyqb zdpK;0@qY`3K^uK%R3A$PwLhtZ?% zJWB~2MG=A^2w~^TXX&vI@4z^?-EVJx=dWL!`Z!udE|y{if~Gm1Wx^cjCL{++lIhUMMlP#j*wL-rHD{n~f-Kq&Ut zPl(iQDg_u#6i7)D$j1E{(~8%Av^YvhmGOolFvcb+cBZ5jV5D4HD$ z?B7h8GjaTT&`Io*aH=f2+cgkml~Gw)p#^f+*n_u>+&(q|$N1|Hj*X^p@BNfivV3Cw z9xpa8w7+=#&f#CYiIr-3f}mKD5=em;IWFubnWlPn4`4cbq5Z&&MW*cK9fy%Qhp*4UBIL%Z2e2AP+}_2pa`2ZV{us9Suc2 z(BS>{2DFL(t(Z#oKC1JftDcdkWcq)cujBgXjKOy!khE&hkLb7%=9201eUS9W2K7tB z*#022HbG=(dKGh|n;D}EOiS#oFWz=j zy~1O35o`c?B+Ui3`3HgE@s5B8Gim}@nHY-Ef|l5ojQR8LXcXbX(P9S+1c~Ko1@HnZ z;-Wyr8u1SAPPEki;gcUsoc2Z3GY*mj2p|DLCK(w$<4{stZBb`0xBuVFBWp|##S$Zg zRvC%oWC5_WU=$-H(STBM@0WDH+ET{%af7nlo;R}N(ml%p5pY11RiL1Be}hiw z9{quk6q*j7YAfx>8<^&@QREPcB}7_aIFYAmDlFdpySf9=DePdsU>BQAK5*{27!Ig% zf(BTIk`09jUqVty`FmA2xE_xB|b7!l}Ebm`YI6dB*N#P(eh=6fjm*%Qi^PZQ+- zE|$c zcMh8n3%jOho&tbTcuoUG>^jYWedFFwD`l^h_Ia z&EB13w=0_tB_I8m9;}83?aOs648qE&n-)H|UQ2Kk8$cm>il8pFO+n zOGPl~@zm%c_e6;&cwXQ{i9vS|#$&naetR}^N7CS~Fqp}SIuTX~jtFjy49gEVLQ1YI zzt`!`acYw@Js$rre(=VKZU-$JOE^U;Ds-I@_kE+3`7SA^`c%QJs76q?l;4&2N?hLK z5r=p`m&9gztwO?otlT)n;lD`t@%a{qI9$5`^_n+zyi~_Ggy~+Pkc?mm)eOEXsl1d> z&hlbS%=79MEYGEs6Mw~pgpV~;e4L+OUF&DUWiBrcafm}4u2m?KI}X^15ufIZFUJs` zN+TIsU{s2y1O^wLs>Ky0mwaGI|gE+WR(~#}U(& zwh|`Am`*BXy^|`qzIb9fYQHOk**GQv?zEimak}H^I``*Y>48i}Kz{=zMi9JnuY)6CBDoU!O_&98~gg#q(DEYHu~=4%X<2*8KH!L{tpjs(1>3J++T!o+m`9IU8=`-gByFT z^fdHM8r~%|pS`+<{ds1n!o6A#@bnF$z}6fT7Xr$cxW2dN{x!Ja>U=ogyGNYwJVR(C z$FhnfFtu8ra{T&Yohr~T?MR8s{Lwz)?JnCsW25e&bArk;G%vA02n*Z!4L0jj_ASf7 z(Fu#|gl8SNQ(K^mU#X2|`(tffw&(1kcKwYb33$8ThY>gzOEQ6olYXZ%1o(43YTp3g zQh;*;@HTR;_K&>l-cZw*_nrVgERmr#Vsi_cps@(@JVi7LrD87i1KodkpVP#Gvo?o{8BVzxD=BCJ;M$ak03wqI` z+KHI736n-415+OEJyQwuPb?+#wwV61uX~bM{qCz+XF*03MV1l-f}$9T6j5ivH+3Cj zGp2;U>BCTRTOE8;Z#M_9L~Ia7rFqfSeb+=fKfZyw204li&^d0?oN2Yh|LIxYnS)}}EIYsMZaBt7D#u_0-qQFciBk8Z&$K@Q}E zx-r$M9Qxin9ZeD6v(^uN{>!?`A?bE=gA#R5eX2v;PLJbqQ19_qkajdP95QqE-g8f% z>2aui1T`ikfF;edj^0jRejuu6meUV>IDMuQwydP=Q0 zB&fIi1LywHZhKWYdN*KN)!blIZ!q}2tZ=#V#mAMXls)gQ7iPV+4I5L36ljL!Nr596 zR-iZ<&*^NGWq-y2)FzZ($WzCQb zPXbC16p8QDt&-Aq_BeA*gVrU)_`JO0qb6XQn4aDLkG*#-f7_t%1eyo3BC2dy?9-R5 zxZql~vinT_D)oS?PPp3Tb%TixuT1ldN=dXpGSu1TnKQ@!wXsm+B4VVbqM@pvT`(l) zmiaHhmW(YnM|GBaLxIARD!~FtQe<*P@`~FxZ=6;YW^sc#6hl~0O4+4;_O$2jN}u{E zoR~y-l&m~jU>rcxfa34#1>o?yfgPA*-7T`|@_>;ydRbNPf18SNrA-x<61E!>Y8vt# z8qXrJoLTYJ(_ambHc`j^*p(^bkBaf(Qz>?uvEKA*81@`jZWxg9Ohyv6_$ z0F~@{VXe6Q=_Mu@mvpv{pVXIxwgwx0PbOY2PTn}%J>{vb+Xlkp(8F;?J5q4;$_2`J z;O#3Zk_sF-z#q6yGRv9cmiz@?;QPzB@6~`{2Zy^&_t1UjdwQ3wa*eaI-|al(w!KlJ z#OBcsr|;5f=H5VYNnR;%at#d;GkeDaAs8(u!E{~r4RsTr8-5O}XHulVkp#z6EYGk^ zxV3AmK~t{Fe!A&vgNOVB&rwId6GbCwSz~2^QAydM1Ufs*pDcrP<9xfJ$EKBelpgB=lzPSC{BfWQ4x(f;|h$-b}eLoaB zR=uQONx5Bjk;vEWoQw6!?OhwhbM!c?|NAb8AJc2wPLC(O0ET_lM;}^P;ojdu{r{o5 zyIgJkf{DV-?UkYB?$j}ijL|{QcReyn)ux1|3vXZ3zkAz&8qXRiTd1z}vq65S8gm}G zeWF#>&@|efzDT+|QUM?GKd2XdcCA3-i{3XZH0?*;r>g$9SEko6weYeIh`Zl#FBWLVHG|4kNmXq zwa>b}p9(FO6&0c^4so~|*L$<6UH1RJeB3qvN61?u37*Q;j0 z82b^NHuFfHbWHRH?rd=n_9&9em%Mw_{ecLl2{EcNMe~lxKK^8nw8aykTjAIU#Zdz1 z$nz-U0|6>;Tl+s}QvKm6DW>T5%+hlLGW*=`I0g| zaAE{_QOAiV3~Y|!YB81leXG+8x-Ny@g(o6)g~&KG;8wseZ*Q+BKNy*95SYAb>gO|R zO_*bX3QJN6>E++TsHyB*AD`Ia>(8UQ$q=$cvIHr!RJe=Lwl{DY??^rNWi_cuG>wlI zSymx6h1XOq;GsP64t#Da)$-d8ixbuQ0QA`S#;(L0$eIzB< z!K$>(${Hh(GS#H{$Lmhr{cQqzF~Fag8U1G|XL_vPjV`6AJ32<|i`mpYyT>n2ytBVf zMikB}Nk_F z3onrod57i%F0~)<_nDOM{qDs7R&4OEto9f_W4$*4Xim@J*kn}u^ZSGh(ZMM&^UbgSsT`N}xUS_aergB_m{Jwj&vVD*8 z3uh_w!Le~KlJ1Ck@n_W7_#dag>=HgNE0k5BPx_aY+xs$)XGN9QbW>~+e0ggG6%GFt zx+pDVq+A{2RMzz&jGniK;wuGE?;CWL_Gek4&ZZKM$EK^J-dBn*TxwdyQ@n;+2Tr{Hk96-MSt&cuV={!Bd-+O^-n9QC|8V%r{o~uuv~dE?K<_-%F1C zeVE>)|Gyd|(09}i>)eGY?|FE2i~$|s{>QC5J2B78dd#J~kI7^s?b{Qhp~4(vzG(gs z57fpu$V6)bPz4z?QgGn5BYN=aX_rYjmQH$A1q zC{M>q2RJ$MYJn?NpIG}B_g?zHte<0r8rPa)agB{n9O4j%e;-(ugsvy(x&o6uvNv9I zbXzeg>Mp%N5QIV#JSA~5x=X(l8)vm=c9(}4*NEP5hcKg7Vb;YD8)_8I5`bd~mHcbu zz|Z%ry)EI^3X1$t{|ZHvsCk)k7WbXlHWd z`r+EQ>|c7TQfd3KqU8(N&(+N3+t175w_lZNe2n@HH!rH0OO{{d_)wIopV0nJiqC^` zX@9K!8izPsjnKB(hINf7@0m?~vOBgs+W5dExE*v<$Cu)YJ1CY2M2A#zcg$dpelT@F z;VBo+KtY55qwnFqi}k&kF@9fC!-3h=GuTXni~GJyuX zfdg#5)9HOT&w2chuz1-P16(u)k)K~}P}EiZvrm8Y`qpO}!4^k2>v-uTmZoZekYr6D zpX+>^ddGyrb)i_2aB$;D1zV$Bq)Z*7KL6(>h^I%%Dn>|V_Z9l0a zWU%4!^zw9Ij<3;pvY!@H_R(jdL^da)M5b^gBk+=-Nt!zA=55wpZ?{Z? zq5X|QU6=GK0UY3umCM=hPhY>K?}8@bBUL0sh@2#;3d75!L|qbIY9DVw&lzztYd!&f{JK@ngn!Qqj|WdgRVl%q%rNZjns-UV{Wk>qHiq9THT4OB!acB+6PMNtq7 z78DQ!1Ph7<6a^F&1@!xc?A%=@nIw0Yqka4Snapl>%9}TD-n@D9o?%qLJ$7b{B`F0% zEGd}1LD{ENj`^ETo8NoBU$t-|4Dq2wp}vN{*S^4qzjc}qT)(f~77v!b8Cqa@j$%2I zBSeZIQTqN%>*v?0xGKh)hkSk^|Ni|^jY@chaR@ZwAXqspz-~GQF-eB;=J}f+&-i0+ zO+=cy*m^0aLjmAe)xq)-DsJxbSN44oRnn(anIixY zRhd*#Nnb=#114YO{K0F^a8Fs|nuR4VTHq;~VMK=JMV99ol)R?j9ZbKh7P$(q`CUh^ z2lw3GBz)^w)F(z(2$fLAalB^|fy-TMISe&ZX$`5i@wc97@v_w=~^lG6>m z0S_BpRy}2Uwa1c-${O)ciIbL+DwmJ^@W3T<@>dzbZ8J9V|IU zeh7CF4qv8Igm~%6qz_SC5byM?n4oe>3Syne^Y0PuhT?+c$b4dbM&$oNzFJ;_RmwNYYkosKO}WbE*G^3y?4coMdV<LLh|pK zBGYXwxZJ*tarI{yE=@mh{9q^MT}0%89X4S>3RIWKKrhWYSa(3NN-g>;ty)USS(Ld;*!__Fq*D9xaK1cY@r2_pNK?fZYx4xmE zMYS|!C1^cPzRKq>l;>*dq?1s{Ea&nY>nPTt6i^XEMAve~pYNrvH>qUvd#ye-E-5rFr+JwYCp}ziuEqkqM34&e&a^iNM?6?FU}>2+83BgTX{ntnvEO>ryMWM1*)~ z%lLcE$ijJPh4gDxxG%crJH?!rv~OJRrtsKsw;^mWRJR?l4qkS!6i?9HnH$z^%-V0O zoG1f@^V;w~q7C8$6)rUnZZi)Vdi>4)jUWzYM#O=GRU9I#keou3-+yoUvt!-uDW!G( zQ?Z24acwslrkY#Ncm7mmaaJ)l7~Xc4BnUxtC?u^ALw25cJuj(c8d+FI=v*46rBMGf zNGlNq1@)Odj_<(Fa0& zjL@G`%?DrqqnbAZ8YFHCA6d!kwdYCsc2;G129T=C(LyKx`C0iXXVX!B+_J~zWjSd5 zFps8}8pf7$xoE%e?j3G7-|@Mhe0C3%T#C7vDHbC~afr0SN-y8>3Q=Skji zgFH%s3-ot8{e3Pv+Z{CInIV<#yg9x4CGO3aB_xL;v2B|9pVyjy^u(`laX(lkthYsu zX=P7det{n-c5>{NcdG38pmW-18V>*dyL!ZQ(f6SnA>99P?@@^LzNG&v?f);=cl!7G z_m|@P3je3|Kliigsv5^@jUMBDey!s}FGnv&FDHI}aiz3NAHT~>2bcPL9j^ZEe{qR# znt90S1#PM|Z=vOjIor*VRWAV0!#2&^gYLEaxI7bk26o55hdSK-8&Rx}EJ34;A-bNs z&09>Ja=5PWr1ETPxK4s%nTt+vv%Q`?rw?>@xkeP^yTDM#@%G2Qo0EK4AM~pN+UO|I z@o^1~OZ9TDKi>A#zQX8Kr|-l9)EwPyeqL?1v_lsrd?iOhVsZB2AWw4popRs=Fc1;T z?{&I!20Hz~E4c#Ch^KsifAr6|uq#myCYP;^M#FS-i>%w8xTO>Hi5wO^K%fLtWC)%V zMNwdAGBYVG8{_WZeP(O+ z^)vF`EVK$Sh1DK7OI8&CctWHVN*-$HO7}lkTNWjn>i^v=p{=rPkOM zRfDOjy%?iC->QIBYla{5madjf_by(y!BqStD#`B}6(#ErK9^yJ`i0s!fjh+6Q+V zL;Eoo`5u-F!_7gnKBM;C(^^cF`%*&j`NEoDIp z*n)-HpWJWu4y-riycFFGP9h{i5&@&~KrX8pTyhDY;~Cx@^L6{) z5mZJ{NkTyb8uf}<#XNKF{z_|xSEw64V#Tn-k_sgf3@NJgr_JjP{3_#QrIaWmQM!?O z)cn}8nvae4Lgx8CZgHVl+CFSjU*NX;0~WNYgr#9h2dc;?N@^%msfau;sgQ|&R@OUq zr%kxQc+7nGRJCsJuf~S|p#+g+X@cZ70ILEorcxk~%)BXwacN=%%bY zuqy@r($b?^irzypVfb01=n9r+yh_HuMj88wOA}2eeJUoV-LRaanD-;g$zxRG^w6}t zpmqA?p`)P{6Fc5VuabV37DRpN34vOdX#7lutBXIE6yojIZtah!jFIOr4g{T4bKYcUx~I|ho&HLEFxoK<9pyU@J8ZMBW$TUcutlBGm} z;~0_PID#b6v$T#{nuh85LUUT@%-pq~zR4pX{CZ`M zy&>L~NDwK8;{}=(7?L7T?EP6GRt-;^!I3J{{APG~I1}#)L}3VrOo|H0iH`cUH_rWg z@UF~u+GknzvTY$^eP7b@;s4|PF4q4tte2J%p!MO5+l_|j%*VK+n`JR8d;%0BLtao7 zMP^xtLaIYw?|k9QVzvX%m9TAi+kqF%Yi`>4#ehRu#n?-DTU1))1mNH}n&j_ie|hh= zW19*I5}NSrK?(O`gJ&J-(WuY#=$JhM-A~9&m&pJ?s52l zcLMMY_e=6PpX2+VziQCxuk^a*%!S}xw46&EO`!-s8_mQ+9~`{DX{T^hhuR9VjA-u# zRN(ajrO3gKQ*t?{0B^tvO`+v1Iye`MxXL$~Hw`~4+;yhVrCFuI%8NNClx3L!JjK%_ zy8Zcu7Z>;tGf)(iCO_qvVi7QQehk<(=4l`oZ7icT0J#ffhk1K z<5ljTm>0eMQ+mJg@Y-FsVwiM;iFPl;DYB?AoT#euA5DkNzV+PZO0fEWj3uS&J?0 zD_&}k=gvIK5duR~g21!95M~!p#}urj8g>Er%DhlYZTfg8Y!x&sP_#hvyhw{I$A>xM zpwnX^=ZJaqYwJJT+_!o-^VM+UbUXcaiX#rL7OoGt+kl)Gsx>bd;~j`^&8`2rOaA$H z=r2qN=e!rk!0rWpDaTF;BqNBVq{sxB^w-aIe|}<6nw%i&uxsh?7}C?K29ckqYQ$6x z^WCX2q4^E#BLD9;i1r&w*N+}vqt0Rz2t7)v(W`>oQ2+Pl(jD-c;(UMqv3MulQSrWo)BV~Y)Oj0Lh^b2zleR(D0iN<#~y3T>+pZv%cbzr42l zH%-oA#XA)Uw9JzQf`0D{N2Fto^khV}6MrkQk&h10%iv`fpSu!$Ct`gQeDo!dj8BCTn#1kHA1%7+RFbvN!Okj6L zap5~ar-sA*m>rYa@^^#A~Y$w!GCPwi|Y-TP%;zX~OFh=eMk4W;}Z zCq6@iHpEUTEMNLvezL{>UC#rp?}CffrU<)yn&E&XXR|KywA z&8u#IpkpR%Q4P&zugX`!^h%3$Th#M3%lkWS`Z>u48O+%Tp15d%&;kbM1>F4&0)A;2 z@OfMlKu^i#8qiwWQl~`&BO3Jh=eoh7oSJ0!%#vU_Lf{#spCjfvA!aCvEW$nDdWZVinabkG9&+}o4K9W= z7%905K%tjXs%QP57S?^z1Pe?_`247Cnf_YU^#`AY1|bU~ldtM@1$+}LG^dEXC=i0e zq_t|F{?FiDnK00MhcY&?M)HLoxO?UTc^vTC zbAW$vASiFZ`!=c&^TtV2wJoC_+&uq=z74~-Qvr1ZbWCa=;p2yU3cNDtDapA2RZKuQ zAGh+oj%8%OpH@71XFQBpP*h%FWX3^@w9bg*qbsAKu4TyM;E9w!;K*i2QlQBK%`p-n z35loeK9^@=jqjS#4~@Ez1SRj$#?TZ6_`Cylq_ZsF4(yu!^hP*h-SGDzIZ0+%j*v-* zqI|XPf~|QUHW{^jyIeM|Ntk?6Dce^bdXi8>hI*Esi?(b(u3d8Q zDoLs|D-!(tL&FZ{rq{?wic?ioG}iSkKYe_Vx_K$Q#CkA->>p6`wIb? z-yzB4dU-whzQ50IY4@DJS!K=RZ{b}YW>K&?`=1Zy=OPObT-X|78hIMo8bZj#FoaCZ z`_hiz|Leh65pguMZ2avWW_zD_?u`PPRe4T!s5Ggf_uXJnf-yKXg`tJz!88Aj=IcgK zvbz+oon!2Ik{@`3w`IPl^+m0}D}7yfZtyy0qeQRrT^D$+u^nD7bR9N$Z`7o zz}ut1>GC_>TI`C85^TJ;gIZdq-!S-=KlWD-XC}oD=1GydcUbDO;ztO7r9>_{ygRKd z|I8ltQth?%!n*?{5t1hCz%6?eFt{KP5CCm{#(~I%x3Uaa`uwcimQ^F1gUEI`Ok;hG zYt{O)xEQs)QLCUf@2^gWfbvIJEY(+Z@o3BnLRaq%f)oIR%@frgRFkX4ME-SyRQX>r!!v! z54YOB6C9IO?*#vUpxR48{Lc&`mi#M7r$cv*yx8$6TL0EtgV*p^eymxjwW@HjL9d)4 zFzC3!a_56XTFl&y9btx{DV7mfisnV0At-c&hZ&<2k%zWS$axQ9VNXh` zNYW}r$@1~G!)LX5b47)UF(g$(obL^Bv&8O})*lO9A1MsSWJ!uJ_c%%tWEOCWKuC`< zTW)-JZj~xI+6RqKH4I60&_RciZ-Tm8R*$;7_D9C3>T#Y{NReP=8t@D%Bec2;$B%kg znl+lSe)KVD%^Kmr@uF(j9RkA?K;;nay`Fm@<+o5f+@~{OfBU95SD94x&>nN@5?$LO@@A! z4{mvOhI>{!yr|}M`#q?-jFJ_Bm3T_!SzyX+w?6Bi^C?g`>$Te$SLOXIn|AN()$#!- zLF|qwK~M~#sv;p%yn}e}>}ww$**+p2UfzJgYH+E+5IKLT0k5PUXnD4M<9Y8auN|(b zQHw=I19|!W$-{wv;*l|bo-L-U>6RIaYG?*bm~ba(gDf9kcwIWOGwL=(QB;v46_%kz z8r_DZAZckyZ;<8HXSVdn%xi<+bA=m1D^4kAxYIpukmMbQs>M=Y3*zE4Y=t*mH|fbo zm(|3sJ$a-++1&+s1D$?!-7)mU@yEfIp%ts-Z|5Nu=SSA+iL@v%39#B?SjxfDBn3!?K>J@CLr~ck?@;>2skWHM zcg0815mdts_j`eq*WayrhN$TAoirplPOa+AmgAP~&h{_CzHLg0)HhM$Scc&lk&%V& zUG_ZDZOg_acn4&$rf;)oi(u=QgXs|M{f*j=ISkb|A{WfWbzS zL)Y3n4hLU!iZPDUE2M@{76kMPTP;mdxk(syHRi)g=?jzlh{LNFI8wv?24Kiop-2A$c09-w!vnh?_X5#(PC8AN3`R zqtlLvXxdv#QOjRQhwvoy(RD$DpM5!){?Z^wh%ZzQ&h8EWMW#dd13`pu8W>FHX!O@q zV;)tLLlt6+wfsL?euc&=7WjiLzK|A7-}Xmj^@eQ32_l^4d$e(X1DRkA7Hi|6y{ zdbq|n(l}I?l<0H@dFpTke@{U=&{DVdArgtsPz5ty&@t`rn4?-$6<@uaq@oF06x$2W z8{^dDSS%w))Y!pKY#T1TAjTrJy}Qrf$LRug-$kp9z|X-_9vJlU!9yR^9cYB4uV(DK zm}Y4a!3XB-4avv5##$`Wcb#`mH#La1v32kY1yn_3D3T25+tEeVg}r$?+R`=o)wh;k zhe7MYTizUB0rPd{2m1`sQh*3C)LzG!n zBEi5r$Nf-e=i-cb$pwvkxh76A=YukC>0lRcv84W8d-kSQ_=|9aB=DRP(8Y`B%`{^P zD>E4!mbaSryM7geH{T;DhGj^W;&@RM!_D{dXAQpiv&lV?&G#IZZZ&L`zPKeq1%X3f zkuQGeYtNN@Xa#$RY&va z4eRkrOULrv*#DI!0T&5y#-4m!AZQt&%W&hJKPLx$HY=koBv z$Yjf!22b04&ENz$mKup1S%npNUXUq~5HI8|{_wFsyy@@%pPmBLGU#wA@!TK={^Qk( znlR;*_IpDqRfx3`RE`chTubm!v9&2+TwyX9jght}v93|Wq}!)|Qy4*IRHQ{( zkXeT;3A{*g>eC(WHoev4^9uQubi#@R)r%2YBzazk@&O&sI_Ple_?r$_CS>aU)IkRw zbkIQu9dyt^-*1##d6`#uc{=F(V10aaxLP1dr&GP3dO!7k>Yzgjpq`F`D-y5u;w-1) zW#V7eOW2R7dh_4m#-crh^VwC&UV@Ufjob@jEU?!&FQ1 zGimES`4#&3jvN<;rvWF+tb^cGrlxz_twi1JDM$c3!=ehMYkN_Ql-n&|zS@JCI|_20 z@OZ9Hx6^ND3GU!(;rco&pUi~5&i~Omb-@^KU3Xh7xA%Qi+6^%m(apNzAPA09fFQ}L z-RJU5tnpnl`k_%5l3*0WG*f7b0(@@6w8Lb$$8sTQOWRjJ#74HF1d5?(o)TG#API^@ z15Z`i@WaJ)$>d5AtGLHLlHJnTZZl}j>_X59^V^figf1&87KZ10@^Gxl_=u(6Qxl)+w67h$wxY=O9>-WMay=tDD2J$! zvdGA22iNU1#;A0jCI7d2?ToH!;guF&-+3M#9k{Gtz{dD|)KZ<{ywud_QB!z<06ZyB z6hY&nrp!cA1166r$;U10hBf*5^dBwpd@he72)oNU0rZ;ezBw?c!+_d7K%>BWVd%~o zKMj4nGK>qn6GQzNHu7(!DVpzo7)Av!AcnhXmp`x`x=JpDF|{Jkvl`lUKue&Dqe zA_+)NBt(fO`Zt+5`tg5PX23B`N{wD8`aahG%NuSzT??4n-9Of+SHs_27-cRLsQjsU?;VW)B>-WgQfPy5WT&nvfZV zV`!F^L6ybtzHHm@W#y(w&xRW>EhV7!!94Cc4#p>_J>%o#$|l27%g=u`Juf^Dg_R1% zNP>bQ0!0!zf#6lCpX<*Zn>+N&tQTYdawC$K*WSLo_fI$-!BWfFrv{AXTXoR#w}y)f zd5lQeWiOEYU>F!*;PirQuP4VV@Y!j%?aYBhRmT*GsAN)RX-?*3fnh&7 z)5Z4H0g#b_l-Wz$ybnPFrwvJPAhqHFnv)_py!i#u>U2cjw~3K2X~1PPz;dhr%BZ8b zSOJvL{?#gDzGjR(8lksZjC|EXT6)g%Np_=escq2}af~WR07#@mCdFHX@dJh(Z<~>Z zOxk~v%Q}^oS*|;AzRfxjA8wuISwbW^n&%jiplEb2@J8jx8@URFO!egazs zLlZ1R6AU2;yeLo{T19eFI)rg-l4vuhe3_%FY znCwmKZtpm;3jE+Nu3|`ft%%_(EX`Ma{`pVqqT-xnRf44$o^WtsIM2co{VL0(V_%=y zRihzZosq|qjNKIo3EtBs`FuV79&g*l(#&m}XJn)zQU@d=TXMKG5`FTmwv0Wsws!I# zu#5d7tz)F5s5~zSG@*zypI!Ci>6JAr!HA+4Rs?@-n;vY$riH=hvz{}AbhG$YH-OMM$x)*Bwo)c$y<6L7X%20kf1;JaZ80$mz z80yk0Wz3`NcI6oPN+ryvBl(ILGATS@h>IleL;D&_oRG}*YE2t%fRQ`@a`wdya(x2xH6xKJ$_Q3%*^#5pjotAFZzF%A;y0Ex2VMFtQ<)fjU?!Co}KgAOXlH@p!;CX=| z2o|OB7U{9nPaj#{*!N(!em_8oc~baZL(?sijD4W1*JQUnzrgRCdvq4lBB@1XIEzn} zu#bW%bi+`sT6BRo^D57$mocy7Lz}x6qMpLCDWoNO%2KSJ3Vg6)!my@ zkO$oUzyEvj{f>L;RLLx%dJE2`oCKYny%?ty!^Bx$Nwsxl{Xfj#6l2h?7BCrf&_SoC z@&;75;?(%*VN)pHaQHK0@N7bM?WP2eQ%T3Oa)R+@)Jf->gd#!zW6O7UeC;{JX|Xquw|FOdoZ*gu*Mn|-5A4u-=Q;fWOMwFSL~r1iySvLN z`L>@T`P2S0nKHssYGC#k28_bT6oaLsV8O7m6y*6HB;O=Xp4Uf9>tPgksG{ysOgZVr z_M(v#XtyVLi9p*44}>~s4L)BvG-VNu-E|s%=4tr?tpQIV4LO2oAG9KZ45wLgsG{!` z3?6XvFdxC6p@ejTLdqp(#Z(ebL2}6Qq4|?F?;gO5b(D>zma zk|S4f$YQ&dYu)pOD7J=qq)6F8p3^_LAkP83gQWa^k`FysN{z`94_kUoZuVaGP*_u) z$W|ggPad#ylE|_Sp64k^+&tj6j=P?mm(~C?IAtBI@)~SYg1*DssvfcYw)4zhnTOi5 zY`onK$CkDR7-4y}7d#mm#zGnTcr znp&3)jcOL+01BnD1g8)Tk8T`mbunQG=&Yr+<^8?O3VRyC{81DA1bfe1AddsyK9Vdc z!0wRza_(S{0(=+$Kr@o0Fr-9F41L$(UuqY(hMc7F@Sl?+rzNNR3zlxD{%|~(jW^+9 z1d3%yf}|Ny6v;46#p~KK_4x)|6$b12e@#!%m=P7}65td;A~;GFNfhbgqLhe5CmGVL zWACaqcM^zdi7h%9nPNmn0X&CVVmFI{pfu}+3OoCV`|5^|YEcD%Cl!(ctf~q^qwcGP zfUzCA{U2IFiXDn=(B2Jo9;v+rp1(a*Y~=@blBeu>0dlJU!_Z!5U58StV2T%VgEMeW z^H8oeP&n`D48EYUbB)4hj+HH>)Z#pm~;NMKyKL zsObUs8*q)B6!)6c#Jcgo+v53oP_ua~!kWZVRPefSPfz~j9>6~l0QZ#1pPVBOyl+G& z>kY_xNc%Fb>TQtpQm>1 zVAj(5XM?4Ee^qNo803Hq7O?LpC>Dq`BNHI7w;5fn^6!P`2Y19V!;7zVEv;2=dhpJd z@2r9ExXp|^Tn^U86#ZzT8*sx{gC&)kbE~J-w9uE?P$%IHaKI%cSGf@9H@gLc+ur~a}0p>0jVb^Va=yg-Jo zB}l-LBrj340#f&kTEF95?LwpFlu~k2qoKWZsg(Q1s8evgnHg!lA$hyx^8v3PC?h84 zgMiAWNsnWX*4eqZ@SbL6Oeb+^;uM%2tgpV-y$v&^4qlPN69Uh(gg}b|DWDv>r8b|c zV{7PW_0+EO#C`w3%jMmoIyRtqo(>#8FuXv|J~wK-_{cdUdM&-QZt@fDUOxkq&xr9q zoGfgJz+L0{4u2d*SoeN6cigZk(Qheow4|sc%?c75=IfJtJ@5+Xs<9M6h$m>%=r3ZZE@et7HH)$9B3I34}wts=>SN&(iPFx2_#1?K{n{-B8R zAk-pih2oCC95KgAu-5vr{dVJ1vBrue(6Ks`7JaOY2GKgUddj>}57rE4$3DN5)1iPD zSV5F1l2S>o_6;ou2c#UxdOIU2ZnX=q_6F<2@4b9($$;qU14`xqLvSp~ssgG$_^l4E z?5@`2ZpVJi*oP-ShGGexAz6{4Ie`h2UteqxtfbvWTov7|Z@+xvpA%=B;i&_ve+2tL zmsxU+172U??v1>x*XfPLJtdAxPSm2%!@AfuqVvA*<73$lha~f=LtsP-%d*#&lJ~An zFwbtB5jl^0jzSIw$J1ljn4sU`J&E+Rwz@mzT6TKtWo=Pq`5mu~ z%L+f8DDLXbAu_-ru(T-22WQRvt@5UY$yH)Vr}08*y`U8Jp||z)`>O_c7vfLED2k*6 zo}@&I;|V709_ogQkp=FeD*9L}A3Z0(@n#{bi>S>MSW#vrR-k!~w1U*HElcGLDTX9e z2|3q#SxfsDl4YoT`=7?Q|1pJbTAoA7os2oKjpcUael3OOgzGdtfKjUtEf&4E4ehXO zh+d{;8QBMJrvUNO&Qvl<57ire0#{qDobwuQ7-))5zXZ)-halxP(7s=+<%es#yeirs z1|zGwLtDfrRtSBMvMg8+EVrpCbo?MdZFMRfMIOOI86C_QSdZjgYCc z3G2DdI&bkJ$^KIlWX^@`VHwU2CdsY>1^8}uD?oL+fs*_C9rhKQn56U=+a6QGnrgSM z@BcxU$^}^UrKHj#BLRsOC=pd(zN2X#{xQZYEd%THf3y1UUkhnrH&!;IIeT`2!{zkl zLIKSS@?Fy8e!!Ivy#00^>|bNTLxrY46-tSK%MC@CQLQ&S&+n|^fXij%%loV((1Ep1 zBqYKvJIMJrB>i}fS(O2o$QGvPDGuU&U(#|Dc?OacvO45gXASAO;DrOt@H*1sh6RE| zG7KY=v_whMS|#6K+!uL|N})^2A!GEx%CUBSqT$w|=@Gn92h5R`$!@zq*e6J?0$_J| zCZU_7mRK6dv;O=XF=_plIBcFp!10Vjp;$%bW7#~DG4hE5Ts>v2zeR%D*CK8u&v